feat(agent-fetch): signed binary fetcher + hot-swap (VS-20) #29
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/agent-binary-fetcher-vs-20"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements VS-20. The server now pulls signed agent binaries from a remote source (Forgejo Generic Package registry or a plain HTTP host like Dufs) on boot, verifies each with minisign, and atomically promotes verified bytes into the on-disk catalog while hot-swapping the in-memory
AgentBinaryCatalogso in-flight/meshagents/:idrequests are never interrupted. A periodic refresh task and an admin-gated force-refresh endpoint reuse the same path.Config (under
settings.agentbinaryfetcher)type:"http"or"forgejo"baseurl, optional nestedauth: { authtype: "bearer"|"basic", ... }baseurl,owner,package,version, optionaltokenpublickey(minisign pubkey, bare or two-line block), optionalagentids(defaults to the known AgentId set),refreshintervalsecs(0= boot-only)Binaries land in
settings.agentbinariespath(VS-19) when set; otherwise<datapath>/agents. Filename conventionmeshagent-<id>matches VS-19's catalog loader, so the existing on-disk read path picks them up.Failure model
warn!and disable the fetcher; server still boots.warn!and keep the previous on-disk catalog.<file>.tmp.<pid>plusrename(2)in the same directory, so a crash mid-fetch leaves either the old binary or the new one - never a torn write.Test plan
cargo build --workspacecleancargo clippy --workspace --all-targets -- -D warningscleancargo test --workspace-> 892 passing, 0 failing (including 17 new unit tests inmeshcentral-agent-fetchand 5 new boot-level tests)meshagent-6, point a dev server at it, confirm/meshagents/6serves the fetched bytes after bootrefreshintervalsecs: 60, drop a new binary into the source, confirm hot-swap on next tickPOST /agentbinaries/refreshas a site admin: 200 with{installed, failed}; as non-admin: 403; with no fetcher configured: 404Deferred (not in scope for this PR)
- New `meshcentral-agent-fetch` crate: `BinarySource` trait with `HttpSource` (Dufs / plain HTTP / S3 / nginx autoindex) and `ForgejoSource` (Generic Package registry) impls, `MinisignVerifier` for mandatory ed25519 signature checking, and a `BinaryFetcher` orchestrator that writes via `<file>.tmp.<pid>` + atomic rename so readers never see a half-written `meshagent-<id>`. - `AgentBinaryCatalog` now lives behind a thin `AgentBinaryHandle` (`Arc<ArcSwap<...>>`) so the boot-time fetch, the periodic refresh task, and the admin force-refresh trigger all hot-swap the in-memory catalog without interrupting in-flight `/meshagents/:id` requests. - `AgentBinaryRefresher` (in `meshcentral-web`) wires the fetcher to the handle: each cycle runs `fetch_into` then `AgentBinaryCatalog::load_dir` against the target dir and swaps the result into the live handle. - Boot path in `meshcentral::boot` parses `settings.agentbinaryfetcher`, ensures the target dir exists (defaults to `settings.agentbinariespath` from VS-19, else `<datapath>/agents`), runs an initial fetch (failures logged, server still starts), and schedules a periodic refresh via the existing `TaskManager` when `refreshintervalsecs > 0`. A bad pubkey, malformed config, or unwritable target dir disables the fetcher cleanly with a `warn!`. - New `POST /agentbinaries/refresh` route: site-admin-gated (mirrors the `pluginadmin.ashx` auth pattern), returns `{installed, failed}` JSON. 404s when no fetcher is configured. - Tests cover: URL layout for both sources (HTTP + Forgejo), trailing-slash handling, config (de)serialization for both source types, signature parsing happy/error paths, atomic-replace + temp-file cleanup, partial-failure aggregation, target-missing error path, and three boot-time disabling paths (no config, malformed block, bad pubkey). #VS-20 State Done Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>