Skip to main content

max / makenotwork

5.5 KB · 140 lines History Blame Raw
1 use anyhow::Result;
2 use bento_daemon::{config, db, events, metrics, ota, routes, state, topology};
3 use std::collections::HashMap;
4 use std::net::SocketAddr;
5 use std::sync::Arc;
6
7 /// MNW base URL the `tauri-mnw` OTA backend publishes to. Overridable so a
8 /// staging host can point elsewhere; defaults to production.
9 fn mnw_base_url() -> String {
10 std::env::var("BENTO_MNW_BASE_URL").unwrap_or_else(|_| "https://makenot.work".into())
11 }
12
13 #[tokio::main]
14 async fn main() -> Result<()> {
15 // `--check-config`: load the daemon config + topology (which reads every
16 // app's in-repo manifest) and exit 0, or fail with the parse error and a
17 // non-zero code. Run before swapping in a freshly built bentod so a schema
18 // change that can't parse the live config fails the install instead of
19 // bricking the daemon — a per-app-config move once left it down for 20h.
20 // Mirrors `sandod --check-config`.
21 if std::env::args().skip(1).any(|a| a == "--check-config") {
22 let cfg = config::Config::load()?;
23 let topo = topology::Topology::load(&cfg.topology_path)?;
24 println!(
25 "bentod config OK: {} host(s), {} app(s) from {}",
26 topo.hosts.len(),
27 topo.app.len(),
28 cfg.topology_path.display()
29 );
30 return Ok(());
31 }
32
33 tracing_subscriber::fmt()
34 .with_writer(std::io::stderr)
35 .with_env_filter(
36 tracing_subscriber::EnvFilter::try_from_default_env()
37 .unwrap_or_else(|_| "bento_daemon=info,bentod=info,tower_http=info".into()),
38 )
39 .init();
40
41 let cfg = Arc::new(config::Config::load()?);
42 let topo = Arc::new(topology::Topology::load(&cfg.topology_path)?);
43 tokio::fs::create_dir_all(&cfg.dist_root).await?;
44 tokio::fs::create_dir_all(&cfg.logs_root).await?;
45 let pool = db::open(&cfg.db_path).await?;
46 // Reconcile any builds left `running` by a previous process (the finalizer
47 // and target tasks don't survive a restart) so they don't orphan forever.
48 match db::recover_orphaned_running(&pool).await {
49 Ok(0) => {}
50 Ok(n) => tracing::warn!(
51 reconciled = n,
52 "marked orphaned `running` builds failed after restart"
53 ),
54 Err(e) => tracing::error!(error = %e, "failed to reconcile orphaned running builds"),
55 }
56 tracing::info!(
57 hosts = topo.hosts.len(),
58 apps = topo.app.len(),
59 "topology loaded"
60 );
61
62 let prom = metrics::init();
63 let addr: SocketAddr = cfg.listen.parse()?;
64
65 // Build-API auth (CF2). Token from BENTO_API_TOKEN (systemd EnvironmentFile);
66 // refuse to expose build triggers unauthenticated on a non-loopback bind.
67 let api_token: Option<Arc<str>> = std::env::var("BENTO_API_TOKEN")
68 .ok()
69 .map(|s| s.trim().to_string())
70 .filter(|s| !s.is_empty())
71 .map(|s| Arc::from(s.as_str()));
72 if api_token.is_none() && !addr.ip().is_loopback() {
73 anyhow::bail!(
74 "BENTO_API_TOKEN is unset but listen={addr} is not loopback; refusing to expose build \
75 triggers unauthenticated. Set BENTO_API_TOKEN (via EnvironmentFile) or bind 127.0.0.1."
76 );
77 }
78 match &api_token {
79 Some(_) => tracing::info!("build endpoints require a bearer token"),
80 None => {
81 tracing::warn!(%addr, "BENTO_API_TOKEN unset; build endpoints are UNAUTHENTICATED (loopback bind)");
82 }
83 }
84
85 let executors = Arc::new(state::build_executors(&topo));
86 let syncs = Arc::new(state::build_syncs(&topo));
87 let host_locks = state::build_host_locks(&topo);
88 let app_state = state::AppState {
89 pool,
90 topo,
91 cfg,
92 prom,
93 events: events::channel(),
94 ota: Arc::new(ota::OtaRegistry::standard(mnw_base_url())),
95 executors,
96 syncs,
97 active: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
98 api_token,
99 host_locks,
100 };
101 // Disk retention: prune logs_root + dist_root to the newest N versions per
102 // app, at startup and every 6h, so neither root grows without bound. Skips
103 // any (app, version) that must survive: one an in-flight build is writing
104 // to, or one a published release still points at (so a slow-adopting
105 // release that has fallen outside the keep window is never deleted out from
106 // under clients still fetching it).
107 {
108 let cfg = app_state.cfg.clone();
109 let pool = app_state.pool.clone();
110 tokio::spawn(async move {
111 let mut tick = tokio::time::interval(std::time::Duration::from_hours(6));
112 loop {
113 tick.tick().await; // fires immediately, then every 6h
114 let protected: bento_daemon::retention::ProtectedVersions =
115 sqlx::query_as::<_, (String, String)>(
116 "SELECT DISTINCT app, version FROM target_runs WHERE status = 'running' \
117 UNION \
118 SELECT DISTINCT app, version FROM releases",
119 )
120 .fetch_all(&pool)
121 .await
122 .unwrap_or_default()
123 .into_iter()
124 .collect();
125 let cfg = cfg.clone();
126 let _ = tokio::task::spawn_blocking(move || {
127 bento_daemon::retention::prune_once(&cfg, &protected);
128 })
129 .await;
130 }
131 });
132 }
133
134 let app = routes::router(app_state);
135 tracing::info!(%addr, "bento daemon listening");
136 let listener = tokio::net::TcpListener::bind(addr).await?;
137 axum::serve(listener, app).await?;
138 Ok(())
139 }
140