fabrica

hanna/fabrica

3033 bytes
Raw
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! The `fabrica serve` wiring.
6//!
7//! This is the one place the top-level server crates are tied together — kept in
8//! the binary so that no library crate depends on `web` (or, later, `ssh`/`api`).
9//! `cli` parses the command and hands us a [`cli::ServeRequest`]; we load config,
10//! open the store, and run the web server.
11
12use std::net::SocketAddr;
13use std::process::ExitCode;
14use std::sync::Arc;
15
16use cli::ServeRequest;
17
18/// Handle `fabrica serve`.
19///
20/// # Errors
21///
22/// Returns an error if configuration is invalid, the store cannot be opened, the
23/// bind address cannot be parsed, or the server fails to run.
24pub fn run(req: ServeRequest) -> anyhow::Result<ExitCode> {
25 let runtime = tokio::runtime::Builder::new_multi_thread()
26 .enable_all()
27 .build()?;
28 runtime.block_on(async move {
29 if req.detach {
30 anyhow::bail!(
31 "--detach is not yet implemented; run in the foreground \
32 (systemd and Docker manage the process lifecycle)"
33 );
34 }
35
36 let loaded = config::load(req.config_path.as_deref())?;
37 for warning in &loaded.warnings {
38 eprintln!("warning: {warning}");
39 }
40
41 let store = store::Store::connect(
42 &loaded.config.database.url,
43 loaded.config.database.max_connections,
44 )
45 .await?;
46 if !req.no_migrate {
47 store.migrate().await?;
48 }
49
50 let secret = cli::resolve_secret(&loaded.config)?;
51 let addr: SocketAddr = format!(
52 "{}:{}",
53 loaded.config.server.address, loaded.config.server.port
54 )
55 .parse()?;
56
57 let config = Arc::new(loaded.config);
58
59 // Start the SSH server alongside the web server. Its lifetime is tied to
60 // the process: when web's graceful shutdown returns, we abort it.
61 let ssh_config = Arc::clone(&config);
62 let ssh_store = store.clone();
63 let ssh_config_path = req.config_path.as_ref().map(|p| p.display().to_string());
64 let ssh_secret = secret.clone();
65 let ssh_task = tokio::spawn(async move {
66 if let Err(err) = ssh::serve(ssh_config, ssh_store, ssh_config_path, ssh_secret).await {
67 eprintln!("ssh server error: {err}");
68 }
69 });
70
71 // The mirror scheduler syncs due push/pull mirrors in the background.
72 let mirror_task = tokio::spawn(mirror::run_scheduler(store.clone(), Arc::clone(&config)));
73
74 let state = web::AppState::build(config, store, secret)?;
75 // Load admin config overrides from the database before serving.
76 state.reload_settings().await;
77 let result = web::serve(state, addr).await;
78 ssh_task.abort();
79 mirror_task.abort();
80 result?;
81 Ok(ExitCode::SUCCESS)
82 })
83}