Jump to…
snowinitial commitqoxwzsukwmkx1mo
Matt W1//! Keeping the pre-receive hook installed (spec §4, §9).
Matt W2//!
Matt W3//! > Hooks are the compiled `dogfood-hook` binary, installed into each repo's
Matt W4//! > `hooks/` directory at creation.
Matt W5//!
Matt W6//! Installing at creation is necessary but not sufficient. A repository created
Matt W7//! before the hook existed, restored from a backup taken before it, or created
Matt W8//! by a code path that forgot the call, has no hook — and a *missing* hook fails
Matt W9//! open. Nothing errors; pushes simply stop being validated. Protected bookmarks
Matt W10//! silently become unprotected and the ref-name allowlist silently stops
Matt W11//! applying.
Matt W12//!
Matt W13//! That is exactly the shape of bug this sweep exists to make impossible: on
Matt W14//! every boot, every repository gets the hook reinstalled. The write is
Matt W15//! idempotent and costs one small file per repository, which is nothing next to
Matt W16//! a validation control that is off without anybody noticing.
Matt W17
Matt W18use std::sync::Arc;
Matt W19
Matt W20use df_db::PgPool;
Matt W21use df_store::{RepoId, RepoStore};
Matt W22use uuid::Uuid;
Matt W23
Matt W24/// Install the hook into one repository.
Matt W25///
Matt W26/// Called at creation, and by the boot sweep. Errors are the caller's to
Matt W27/// report: at creation a failure should be loud, during the sweep it should not
Matt W28/// stop the process from starting.
Matt W29pub async fn install(store: &dyn RepoStore, repo_id: Uuid, hook_binary: &str) -> anyhow::Result<()> {
Matt W30 store
Matt W31 .configure_receive_validation(RepoId(repo_id), hook_binary)
Matt W32 .await
Matt W33 .map_err(|e| anyhow::anyhow!("installing the pre-receive hook: {e}"))
Matt W34}
Matt W35
Matt W36/// Reinstall the hook on every repository, at startup.
Matt W37///
Matt W38/// Runs in the background so a slow filesystem cannot delay serving, and never
Matt W39/// fails the process — an instance that cannot install hooks should still come
Matt W40/// up, loudly complaining, rather than refuse to start.
Matt W41pub fn sweep_in_background(db: PgPool, store: Arc<dyn RepoStore>, hook_binary: String) {
Matt W42 tokio::spawn(async move {
Matt W43 let repos: Vec<(Uuid,)> = match sqlx::query_as("SELECT id FROM repos").fetch_all(&db).await {
Matt W44 Ok(r) => r,
Matt W45 Err(e) => {
Matt W46 tracing::error!("could not list repositories to install hooks: {e}");
Matt W47 return;
Matt W48 }
Matt W49 };
Matt W50
Matt W51 let total = repos.len();
Matt W52 let mut failed = 0usize;
Matt W53
Matt W54 for (id,) in repos {
Matt W55 // A repository row whose storage is missing is a real state after a
Matt W56 // partial restore (spec §10). It is reported by the reindex path,
Matt W57 // not here, so this only counts hook failures.
Matt W58 if !store.exists(RepoId(id)).await {
Matt W59 continue;
Matt W60 }
Matt W61 if let Err(e) = install(store.as_ref(), id, &hook_binary).await {
Matt W62 failed += 1;
Matt W63 tracing::error!(repo = %id, "{e:#}");
Matt W64 }
Matt W65 }
Matt W66
Matt W67 if failed > 0 {
Matt W68 tracing::error!(
Matt W69 total,
Matt W70 failed,
Matt W71 "some repositories are accepting pushes WITHOUT validation"
Matt W72 );
Matt W73 } else {
Matt W74 tracing::info!(total, "pre-receive hooks verified on every repository");
Matt W75 }
Matt W76 });
Matt W77}

77 lines · Rust