Date: 2026-10-10 · Status: approved in conversation (owner) · Scope: small, additive. Schema changes are forward-only migrations from here on (0002_…): 0.1.0 shipped 0001_init.sql and sqlx refuses to boot when an applied migration's checksum changes.
Intent
Close the gaps found while deploying 0.1.0 and reviewing it, so the public install can run the sample manifests and early users do not hit the rough edges we already know about. Everything here is tracked as issues #2–#10 on the repository; this document fixes the behaviour.
Items
- Raw single-binary releases (shipped on main after 0.1.0): a manifest target may say
format = "binary"; the helper places the verified file asbin/<version>/<exec>at 0755. The release must include it. - Delete operator (#2):
DELETE /api/v1/operators/{id}, owner only; refuses yourself and the last owner; destroys the operator's sessions; auditedoperator.delete. UI: Delete in Settings → Operators with a confirmation naming the email. Rulings: R149 (operator deletion serialised by an advisory lock; the last-owner check and the delete are one decision). - Live usage in the sites list (#3): the list carries each site's latest metric sample (one query); the Memory and Disk columns show
used / limitwith the Limits tab's progress style. - Admin link (#4):
SiteDetailOut.admin_url=https://<domain><app.admin_path>or null; rendered next to the domain on the site overview. - Cheaper heartbeats (#5):
data_bytesper site cached for ten minutes in the agent; memory and CPU refreshed every beat; a unit test proves two beats inside the window issue onedu. - Delete-job noise (#6): migration
0002makesjobs.site_idON DELETE SET NULL, so the delete job keeps its row and log; the runner no longer logsnot_foundafter a delete, and a delete job reclaimed after its site row is gone counts as complete. Rulings: R142 (migration0002, not a pre-1.0 edit of0001), R147 (adelete_sitejob with NULLsite_idis complete). - Prompts per version (#7):
GET /api/v1/apps/{id}/versions/{v}/ promptsreturns[ { name, label, secret } ]; the update dialog renders one field per prompt instead of free-form lines. Rulings: R143 (the endpoint answers{ "prompts": [...] }like every list endpoint). - Server token rotation (#8):
POST /api/v1/servers/{id}/rotate- token(owner) issues a new join token and marks the serverrejoining; the old server token stays valid until the agent presents the new one throughsetu agent join(readsSETU_JOIN_TOKEN, keepsagent.tomlotherwise intact); after the join the old token is invalid. OPERATIONS.md replaces the SQL procedure. Rulings: R144 (rejoiningis computed, never stored), R148 (re-join keeps agent.toml;--control-planedefaults to it). - Control-plane self-backup (#9): the scheduler runs
pg_dump -Fcof Setu's own database nightly into<data_dir>/backups/setu/, keeps seven, and Settings → Panel shows the last successful one. Rulings: R145 (the self-backup lives in the api crate; the due rule and the hourly retry are in the ledger). - Lints (#10):
clippy::pedantic,unwrap_used,expect_usedas warnings in the workspace, CI fails on warnings; fakes may allow per module with a comment. Rulings: R146 (pedantic lints allowed workspace-wide, with the listed exceptions); R150 (the per-item allows marked "reviewed: kept as-is"). - Installer:
/var/lib/caddyownership fix (shipped); re-runninginstall.shrestarts the local agent when the binary changed (shipped). Documented upgrade path:install.sh --version <v>on a panel box,setu agent installre-run is not needed.
Out of scope: anything under 0.2.
Acceptance
Every item has a failing-first test named in the plan; the e2e and the installer test stay green; v0.1.1 is tagged and the sample manifests provision from the public release in a fresh container.