refactor: split long functions per review feedback; remove all #[allow] attributes
This commit is contained in:
parent
bbe2112dc9
commit
748536203b
5 changed files with 429 additions and 432 deletions
|
|
@ -100,7 +100,6 @@ enum Cmd {
|
|||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[allow(clippy::too_many_lines)] // top-level dispatch; hard to split without losing readability
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
|
|
@ -117,121 +116,7 @@ async fn main() -> Result<()> {
|
|||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
} => {
|
||||
let cwt: std::collections::HashMap<String, u64> =
|
||||
serde_json::from_str(&context_window_tokens)
|
||||
.context("--context-window-tokens: invalid JSON")?;
|
||||
let coord = Arc::new(Coordinator::open(
|
||||
&db,
|
||||
hyperhive_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
cwt,
|
||||
)?);
|
||||
manager_server::start(coord.clone())?;
|
||||
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
||||
// repos, ensure proposed repos carry the `applied`
|
||||
// remote, bootstrap the meta repo, repoint containers at
|
||||
// `meta#<name>` (one-shot, guarded by a marker file).
|
||||
// Runs before manager auto-spawn so the new manager is
|
||||
// built against meta from the first attempt.
|
||||
if let Err(e) = migrate::run(&coord).await {
|
||||
tracing::warn!(error = ?e, "startup migration failed");
|
||||
}
|
||||
// Auto-create the manager container if it isn't there yet. Block
|
||||
// on this — without hm1nd the system has no manager harness.
|
||||
// Failures are logged but allowed: a broken auto-spawn shouldn't
|
||||
// make the dashboard unreachable for debugging.
|
||||
if let Err(e) = auto_update::ensure_manager(&coord).await {
|
||||
tracing::warn!(error = ?e, "auto-spawn manager failed");
|
||||
}
|
||||
// Auto-update in the background — don't block service start.
|
||||
// Sub-agent rebuilds can take tens of seconds; we want the admin
|
||||
// socket up immediately.
|
||||
let update_coord = coord.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = auto_update::run(update_coord).await {
|
||||
tracing::warn!(error = ?e, "auto-update task failed");
|
||||
}
|
||||
});
|
||||
// Forge user sweep: ensure every existing container has a
|
||||
// forgejo user + access token. No-op when the hive-forge
|
||||
// container isn't running. Backgrounded — touches the
|
||||
// forge state dir via `nixos-container run` which is slow.
|
||||
tokio::spawn(async move {
|
||||
forge::ensure_all().await;
|
||||
});
|
||||
// Periodic broker vacuum: drop fully-acked messages older
|
||||
// than 30 days. Delivered-but-unacked rows (recoverable via
|
||||
// requeue_inflight) and undelivered rows are always kept.
|
||||
// Runs hourly; first sweep happens immediately.
|
||||
let vacuum_coord = coord.clone();
|
||||
let mut vacuum_shutdown = coord.shutdown_rx();
|
||||
tokio::spawn(async move {
|
||||
let interval = std::time::Duration::from_secs(3600);
|
||||
let keep_secs: i64 = 30 * 24 * 3600;
|
||||
loop {
|
||||
match vacuum_coord.broker.vacuum_delivered(keep_secs) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => tracing::info!(removed = n, "broker vacuum"),
|
||||
Err(e) => tracing::warn!(error = ?e, "broker vacuum failed"),
|
||||
}
|
||||
tokio::select! {
|
||||
() = tokio::time::sleep(interval) => {}
|
||||
_ = vacuum_shutdown.changed() => {
|
||||
tracing::info!("broker vacuum: shutdown signal received");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Per-agent events.sqlite vacuum: host-side so the harness
|
||||
// doesn't need any retention wiring of its own.
|
||||
events_vacuum::spawn(&coord);
|
||||
// Per-agent turn-stats.sqlite vacuum: same pattern, 90-day
|
||||
// retention so trend analysis has enough history.
|
||||
stats_vacuum::spawn(&coord);
|
||||
// Container crash watcher: emits HelperEvent::ContainerCrash
|
||||
// when a previously-running container goes away without an
|
||||
// operator-initiated transient state.
|
||||
crash_watch::spawn(coord.clone());
|
||||
// Reminder scheduler: drains due reminders + handles
|
||||
// file_path payload persistence. See reminder_scheduler.rs.
|
||||
reminder_scheduler::spawn(coord.clone());
|
||||
// Forward every broker event onto the unified dashboard
|
||||
// channel with a freshly-stamped seq, so the dashboard SSE
|
||||
// sees broker messages + future mutation events on one
|
||||
// stream with one monotonic seq. The broker's intra-process
|
||||
// channel (used by `recv_blocking_batch`) stays untouched.
|
||||
spawn_broker_to_dashboard_forwarder(coord.clone());
|
||||
let dash_coord = coord.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = dashboard::serve(dashboard_port, dash_coord).await {
|
||||
tracing::error!(error = ?e, "dashboard failed");
|
||||
}
|
||||
});
|
||||
// Run the admin socket until a signal arrives; then signal
|
||||
// all background tasks so they exit cleanly before the
|
||||
// process terminates.
|
||||
let coord_sig = coord.clone();
|
||||
tokio::select! {
|
||||
res = server::serve(&cli.socket, coord) => { res? }
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
tracing::info!("SIGINT received — requesting shutdown");
|
||||
coord_sig.request_shutdown();
|
||||
}
|
||||
() = async {
|
||||
let mut sig = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate()
|
||||
).expect("failed to install SIGTERM handler");
|
||||
sig.recv().await;
|
||||
} => {
|
||||
tracing::info!("SIGTERM received — requesting shutdown");
|
||||
coord_sig.request_shutdown();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
} => cmd_serve(hyperhive_flake, db, dashboard_port, operator_pronouns, context_window_tokens, &cli.socket).await,
|
||||
Cmd::Spawn { name } => {
|
||||
render(client::request(&cli.socket, HostRequest::Spawn { name }).await?)
|
||||
}
|
||||
|
|
@ -256,6 +141,132 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Start the coordinator daemon: open the broker, run migrations, spawn
|
||||
/// background tasks (auto-update, vacuums, crash-watcher, reminder-scheduler,
|
||||
/// dashboard), then serve the admin socket until a signal arrives.
|
||||
async fn cmd_serve(
|
||||
hyperhive_flake: String,
|
||||
db: std::path::PathBuf,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: String,
|
||||
context_window_tokens: String,
|
||||
socket: &std::path::Path,
|
||||
) -> Result<()> {
|
||||
let cwt: std::collections::HashMap<String, u64> =
|
||||
serde_json::from_str(&context_window_tokens)
|
||||
.context("--context-window-tokens: invalid JSON")?;
|
||||
let coord = Arc::new(Coordinator::open(
|
||||
&db,
|
||||
hyperhive_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
cwt,
|
||||
)?);
|
||||
manager_server::start(coord.clone())?;
|
||||
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
||||
// repos, ensure proposed repos carry the `applied`
|
||||
// remote, bootstrap the meta repo, repoint containers at
|
||||
// `meta#<name>` (one-shot, guarded by a marker file).
|
||||
// Runs before manager auto-spawn so the new manager is
|
||||
// built against meta from the first attempt.
|
||||
if let Err(e) = migrate::run(&coord).await {
|
||||
tracing::warn!(error = ?e, "startup migration failed");
|
||||
}
|
||||
// Auto-create the manager container if it isn't there yet. Block
|
||||
// on this — without hm1nd the system has no manager harness.
|
||||
// Failures are logged but allowed: a broken auto-spawn shouldn't
|
||||
// make the dashboard unreachable for debugging.
|
||||
if let Err(e) = auto_update::ensure_manager(&coord).await {
|
||||
tracing::warn!(error = ?e, "auto-spawn manager failed");
|
||||
}
|
||||
// Auto-update in the background — don't block service start.
|
||||
// Sub-agent rebuilds can take tens of seconds; we want the admin
|
||||
// socket up immediately.
|
||||
let update_coord = coord.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = auto_update::run(update_coord).await {
|
||||
tracing::warn!(error = ?e, "auto-update task failed");
|
||||
}
|
||||
});
|
||||
// Forge user sweep: ensure every existing container has a
|
||||
// forgejo user + access token. No-op when the hive-forge
|
||||
// container isn't running. Backgrounded — touches the
|
||||
// forge state dir via `nixos-container run` which is slow.
|
||||
tokio::spawn(async move {
|
||||
forge::ensure_all().await;
|
||||
});
|
||||
// Periodic broker vacuum: drop fully-acked messages older
|
||||
// than 30 days. Delivered-but-unacked rows (recoverable via
|
||||
// requeue_inflight) and undelivered rows are always kept.
|
||||
// Runs hourly; first sweep happens immediately.
|
||||
let vacuum_coord = coord.clone();
|
||||
let mut vacuum_shutdown = coord.shutdown_rx();
|
||||
tokio::spawn(async move {
|
||||
let interval = std::time::Duration::from_secs(3600);
|
||||
let keep_secs: i64 = 30 * 24 * 3600;
|
||||
loop {
|
||||
match vacuum_coord.broker.vacuum_delivered(keep_secs) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => tracing::info!(removed = n, "broker vacuum"),
|
||||
Err(e) => tracing::warn!(error = ?e, "broker vacuum failed"),
|
||||
}
|
||||
tokio::select! {
|
||||
() = tokio::time::sleep(interval) => {}
|
||||
_ = vacuum_shutdown.changed() => {
|
||||
tracing::info!("broker vacuum: shutdown signal received");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Per-agent events.sqlite vacuum: host-side so the harness
|
||||
// doesn't need any retention wiring of its own.
|
||||
events_vacuum::spawn(&coord);
|
||||
// Per-agent turn-stats.sqlite vacuum: same pattern, 90-day
|
||||
// retention so trend analysis has enough history.
|
||||
stats_vacuum::spawn(&coord);
|
||||
// Container crash watcher: emits HelperEvent::ContainerCrash
|
||||
// when a previously-running container goes away without an
|
||||
// operator-initiated transient state.
|
||||
crash_watch::spawn(coord.clone());
|
||||
// Reminder scheduler: drains due reminders + handles
|
||||
// file_path payload persistence. See reminder_scheduler.rs.
|
||||
reminder_scheduler::spawn(coord.clone());
|
||||
// Forward every broker event onto the unified dashboard
|
||||
// channel with a freshly-stamped seq, so the dashboard SSE
|
||||
// sees broker messages + future mutation events on one
|
||||
// stream with one monotonic seq. The broker's intra-process
|
||||
// channel (used by `recv_blocking_batch`) stays untouched.
|
||||
spawn_broker_to_dashboard_forwarder(coord.clone());
|
||||
let dash_coord = coord.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = dashboard::serve(dashboard_port, dash_coord).await {
|
||||
tracing::error!(error = ?e, "dashboard failed");
|
||||
}
|
||||
});
|
||||
// Run the admin socket until a signal arrives; then signal
|
||||
// all background tasks so they exit cleanly before the
|
||||
// process terminates.
|
||||
let coord_sig = coord.clone();
|
||||
tokio::select! {
|
||||
res = server::serve(socket, coord) => { res? }
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
tracing::info!("SIGINT received — requesting shutdown");
|
||||
coord_sig.request_shutdown();
|
||||
}
|
||||
() = async {
|
||||
let mut sig = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate()
|
||||
).expect("failed to install SIGTERM handler");
|
||||
sig.recv().await;
|
||||
} => {
|
||||
tracing::info!("SIGTERM received — requesting shutdown");
|
||||
coord_sig.request_shutdown();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Re-emit every broker `MessageEvent` onto the dashboard channel as
|
||||
/// a `DashboardEvent::Sent` / `Delivered` with a freshly-stamped seq.
|
||||
/// Background task; runs for the life of the process. On a lagged
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue