| 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 |
|
| 8 |
|
| 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 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 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 |
|
| 47 |
|
| 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 |
|
| 66 |
|
| 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 |
|
| 102 |
|
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
|
| 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; |
| 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 |
|