// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. //! The `fabrica serve` wiring. //! //! This is the one place the top-level server crates are tied together — kept in //! the binary so that no library crate depends on `web` (or, later, `ssh`/`api`). //! `cli` parses the command and hands us a [`cli::ServeRequest`]; we load config, //! open the store, and run the web server. use std::net::SocketAddr; use std::process::ExitCode; use std::sync::Arc; use cli::ServeRequest; /// Handle `fabrica serve`. /// /// # Errors /// /// Returns an error if configuration is invalid, the store cannot be opened, the /// bind address cannot be parsed, or the server fails to run. pub fn run(req: ServeRequest) -> anyhow::Result { let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build()?; runtime.block_on(async move { if req.detach { anyhow::bail!( "--detach is not yet implemented; run in the foreground \ (systemd and Docker manage the process lifecycle)" ); } let loaded = config::load(req.config_path.as_deref())?; for warning in &loaded.warnings { eprintln!("warning: {warning}"); } let store = store::Store::connect( &loaded.config.database.url, loaded.config.database.max_connections, ) .await?; if !req.no_migrate { store.migrate().await?; } let secret = cli::resolve_secret(&loaded.config)?; let addr: SocketAddr = format!( "{}:{}", loaded.config.server.address, loaded.config.server.port ) .parse()?; let config = Arc::new(loaded.config); // Start the SSH server alongside the web server. Its lifetime is tied to // the process: when web's graceful shutdown returns, we abort it. let ssh_config = Arc::clone(&config); let ssh_store = store.clone(); let ssh_config_path = req.config_path.as_ref().map(|p| p.display().to_string()); let ssh_secret = secret.clone(); let ssh_task = tokio::spawn(async move { if let Err(err) = ssh::serve(ssh_config, ssh_store, ssh_config_path, ssh_secret).await { eprintln!("ssh server error: {err}"); } }); // The mirror scheduler syncs due push/pull mirrors in the background. let mirror_task = tokio::spawn(mirror::run_scheduler(store.clone(), Arc::clone(&config))); let state = web::AppState::build(config, store, secret)?; // Load admin config overrides from the database before serving. state.reload_settings().await; let result = web::serve(state, addr).await; ssh_task.abort(); mirror_task.abort(); result?; Ok(ExitCode::SUCCESS) }) }