Repository navigation
feat: Slice 6 — server audit, route remove, releases/server prune - #67
Merged
Merged
Conversation
…version selection Unset keep_versions now means DefaultKeepVersions (5), pruned by deploys only after a 7-day notice recorded server-side (.prune-notice) so the deploy output and `server audit` can warn before anything goes; `teploy init` writes 3; an explicit 0 keeps everything and records keep_all_versions in the applied manifest (unset and N record what they always did, so existing digests hold; confighome seeding maps it back). docker.PrunableVersions is extracted from PruneVersions so the audit and `releases prune` can reason about the selection without running it: the `keep` most recent versions by newest container creation time, minus the protected set, accessories never candidates, undatable versions sorted newest (cleanup never deletes what it cannot date). Deploy-triggered prunes are recorded as releases.prune events.
…nd remedies
`teploy server audit <server> --json` reports what is left over on a
server: stale and empty managed routes, managed routes with dead
upstreams, orphan state, hand-written blocks (dead upstream, non-local
address), versions beyond keep_versions, reclaimable build cache and
dangling images, large writable layers, unmanaged containers, dangling
volumes, a Caddyfile with no Caddy, scheduled jobs with no cron daemon,
and disk pressure with attribution.
Every finding carries kind:subject (stable), severity, evidence, and a
remedy {argv, destructive, confirm} — argv null where nothing the CLI
does is safe (hand-written config, other people's containers, volumes).
Remedies are offered only where a command exists: route remove for a
proven-dead route, redeploy (confirm) for an app's own dead upstream,
remove -y (confirm) for orphan state, releases prune (confirm) for
versions beyond policy, server prune for build cache/dangling images.
The audit only reads: every remote command is read-only (pinned by a
test over a recording executor: no mutating verb, no docker subcommand
outside the read set, no upload). A source that could not be read shows
as unknown in checks[] and yields no finding — never a clean bill; with
--json the exit code is 0 whenever the envelope was produced, read state.
caddy.ManagedRegions/RegionHasWebhook/RegionRoots expose the managed
region view InspectRoutes already had; domains.ResolveNames is the
extracted in-Caddy-network resolver, shared with domains check; doctor's
disk remediations now point at server audit instead of a releases prune
command that did not exist.
Removes one app's managed Caddy block, reloads Caddy, records it as a route.remove event (removed/absent; refused/failed in the detail). An absent block is success. Only a block proven dead goes. Refuses: a live app (state plus running non-accessory containers, or a live static app), any container still carrying the app's name, an upstream that still resolves (asked inside Caddy's network) or cannot be proven dead, files the block still serves, state or containers that cannot be read, and a block carrying the autodeploy webhook route. The commit runs under the app's fence guard and an exact-block compare-and-swap on the region that was judged, so a deploy that rewrites the block in between is refused (ErrRouteCAS) with no partial edit — the staged file is cleaned up and the successor's block stays byte-exact.
releases prune removes a container app's old versions beyond --keep: containers and images of everything outside the keep window, never the pinned, current, previous or running versions whatever --keep says. Every read happens under the app's lock and each failure refuses the prune (acting without knowing current, previous or the pins could delete them); removals run fenced. Recorded as a releases.prune event with keep/pruned/protected. --keep 0 prunes everything the protections do not name — deliberately the opposite of keep_versions: 0, documented in the command help; the audit never emits it. server prune --build-cache --dangling reclaims docker's unused build cache and dangling images and nothing else — no tagged image, no container, no volume. Recorded as a server.prune event under _fleet. remove: an app whose directory is already gone (an earlier removal finished) now succeeds instead of failing its state deletion — the audit's one-click remove remedy must be idempotent against a second click. The test now models the directory and lock a real remove sees.
New artifact server-audit-envelope: the `teploy server audit <server>
--json` envelope — findings with stable kind:subject ids, severity,
evidence and remedy {argv (null where nothing the CLI does is safe),
destructive, confirm}, per-source checks where unknown means the
findings depending on it are missing rather than clean, and disk
attribution. Schema derived from the real serverAuditEnvelope DTO;
fixture GENERATED from the real auditServer over a mock executor (one of
each finding including null-remedy classes, one unknown check, partial
state).
version-handshake advertises server-audit-v1 (mi2.json regenerated; the
capability registry golden updated to force the addition). MANIFEST
bumped to rev 19 with the artifact row. No MI bump.
README: a Fleet cleanup & audit section (server audit, route remove, releases prune, server prune, the finding/remedy table, the never-reported-clean rule) and the keep_versions config comment now states the D3 semantics. CHANGELOG unreleased entry. AUDIT_OPEN records what the audit deliberately leaves report-only (hand-written blocks, writable layers, dangling volumes, unmanaged containers, live/webhook/ file-serving routes, foreign upstreams), the deferred surfaces (backup output sizes stay in the backup engine, attribution beyond docker + /deployments), and the decisions worth reviewing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan:
_internal/DASH_AUTOCONFIG_PLAN_2026-11-03.md§9 (P5 cleanup/audit, owner-approved). Dash auto-config Slice 6, CLI side. Contracts corpus rev 19; no MI bump.teploy server audit <server> --json(read-only)Every finding from the plan table, each with evidence and a remedy
{argv|null, destructive, confirm}:teploy route remove --app X(CAS)teploy redeploy(confirm); report-only when the upstream isn't this app's (caddy_extraproxy) or the app is staticteploy remove --app X --host H -y(keeps volumes; confirm)teploy releases prune --keep N(confirm)teploy server prune --build-cache --danglingreleases pruneDesign principles held: report before remedy; evidence on every finding; destructive ⇒ confirm; hand-written config report-only; every remote command the audit issues only reads — pinned by a test (no mutating verb, docker subcommand outside the read set, or upload); unreadable sources land in
checks[]asunknownand yield no finding — never a clean bill.--jsonexits 0 whenever the envelope was produced; readstate.Also flags scheduled jobs on a host whose cron daemon is absent (the deploy-ovh lesson); implausibly-small backup outputs were already refused at write time by the Slice 2 engine.
teploy route remove --app XRemoves a managed Caddy block proven dead: refuses a live app, a container still carrying the name, an upstream that still resolves (asked inside Caddy's network) or is unknowable, files the block still serves, unreadable state/containers, and webhook-carrying blocks. Commit is an exact-block CAS on the region judged + the app's fence guard — a concurrent rewrite is refused with no partial edit. Recorded as
route.remove.releases prune --keep N/server prune --build-cache --danglingPinned, current, previous and running versions are never pruned whatever
--keepsays; unreadable pins/state/containers refuse the prune; removals run fenced under the app lock.server prunetouches only unused build cache and dangling images — no tagged image, container or volume. Eventsreleases.prune,server.prune(under_fleet).keep_versions (D3)
*int: unset → 5, pruned by deploys only after a recorded 7-day notice (deploy output and audit warn first);initwrites 3;0keeps everything (keep_all_versionsin the applied manifest — unset and N record what they always did, so existing digests hold).Contracts (rev 19)
server-audit-envelopeschema derived from the real DTOs + fixture generated from the realauditServer(one of each finding incl. null-remedy, one unknown check); version-handshake advertisesserver-audit-v1(mi2.json regenerated, capability golden updated). Dash CI should re-pinci/contracts.pinto rev 19.Tests
Hermetic (mock SSH executors, the
domains checkdiscipline): every finding kind from fixture data with remedy/destructive/confirm flags; unreadable → check-not-finding; the read-only pin over every command the audit issues; route remove success, all refusal paths, and the concurrent-rewrite CAS refusal; prune selections (pins/current/previous/running survive,--keep 0, refusals); the notice-period state machine; manifest/D3 semantics;initwrites 3.go build,go vet,GOOS=linux go vet,go test ./...and-raceon the touched packages all clean; every commit builds standalone. Docs: README fleet-cleanup section, CHANGELOG unreleased, AUDIT_OPEN entry (report-only surface + deferrals recorded).