feat(#3255): register the swarm-wide forge hooks against the controller
The endpoint landed inert: nothing pointed at it, so the only way to see it work was to mint an HMAC by hand. Register the two swarm-wide hooks at startup so a real forge event produces a journal line. Registered ALONGSIDE the per-hive hooks, not instead of them. Every hive keeps receiving and acting on its own deliveries; the controller gets a copy and logs it. Moving the registration is a later step and has to be: fan-out swarm->hive does not exist yet, so a hook moved now would point at a receiver that forwards nowhere, silently on both sides. Deliberately no stale-hook deletion arm, unlike the two per-hive registrars this otherwise mirrors: theirs delete hooks matching their own path with a foreign base, and the hives' hooks are not stale. The route prefix is what keeps this safe. Both hive-side registrars delete any hook ending in /webhook/knowledge or /webhook/config-pr with a different base, so a swarm hook under those paths would be deleted by every hive on every boot. Serving them under /webhook/forge/ avoids it, and a test pins it -- there is nothing else that can. SWARM_CONTROLLER_PUBLIC_URL is set only where the swarm vhost is served, because a hook whose target_url nothing answers is worse than no hook.
This commit is contained in:
parent
a8bc084262
commit
4573865745
6 changed files with 325 additions and 13 deletions
|
|
@ -661,6 +661,47 @@ async fn get_jobq_rollup(State(state): State<AppState>) -> Json<Vec<hive_jobq_wi
|
|||
Json(hive_jobq_wire::state_rollup(graph, roots))
|
||||
}
|
||||
|
||||
/// The swarm's own public base URL, as the forge must address it.
|
||||
///
|
||||
/// Set by `swarm-controller.nix` **only when this host actually serves the
|
||||
/// swarm vhost** that carries the `/webhook/forge/` location. Absent means
|
||||
/// "the endpoint is not reachable from outside", and the right response to
|
||||
/// that is to register nothing: a hook pointing at a URL nothing answers is
|
||||
/// worse than no hook, because forgejo records failed deliveries against a
|
||||
/// registration that looks configured.
|
||||
const PUBLIC_URL_ENV: &str = "SWARM_CONTROLLER_PUBLIC_URL";
|
||||
|
||||
/// Register the swarm-wide forge hooks against this controller, in the
|
||||
/// background.
|
||||
///
|
||||
/// Detached rather than awaited, and never fatal: the forge may be slow or
|
||||
/// briefly down at boot, and none of the daemon's other routes depend on a
|
||||
/// hook existing. Registration is idempotent, so the next restart retries.
|
||||
///
|
||||
/// Silently does nothing when any of the three preconditions is missing —
|
||||
/// each is a legitimate deployment shape (no forge here, no state directory
|
||||
/// to hold a secret, no public vhost), and each is already logged where it
|
||||
/// is discovered.
|
||||
fn register_swarm_webhooks(forge: Option<Arc<forge::Client>>, secret: Option<Arc<str>>) {
|
||||
let Some(forge) = forge else { return };
|
||||
let Some(secret) = secret else { return };
|
||||
let Ok(public_url) = std::env::var(PUBLIC_URL_ENV) else {
|
||||
tracing::info!(
|
||||
"{PUBLIC_URL_ENV} unset; not registering swarm-wide forge webhooks (the \
|
||||
endpoint is not published on this host)"
|
||||
);
|
||||
return;
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = forge.ensure_swarm_webhooks(&public_url, &secret).await {
|
||||
tracing::warn!(
|
||||
error = %format!("{e:#}"),
|
||||
"registering swarm-wide forge webhooks failed; retrying on next start"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
|
|
@ -763,7 +804,7 @@ async fn main() -> Result<()> {
|
|||
};
|
||||
let deps = WorkerDeps {
|
||||
auth,
|
||||
forge: forge_client,
|
||||
forge: forge_client.clone(),
|
||||
};
|
||||
|
||||
let jobq = Arc::new(Mutex::new(hive_jobq::scheduler::Scheduler::new(
|
||||
|
|
@ -787,6 +828,8 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
register_swarm_webhooks(forge_client, webhook_secret.clone());
|
||||
|
||||
let state = AppState {
|
||||
hives: Arc::new(load_hives()),
|
||||
links: Arc::new(load_links()),
|
||||
|
|
|
|||
Loading…
Reference in a new issue