Setu

Your binaries. Your servers. One control panel.

Setu 0.1.1: follow-ups from the first release

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

  1. Raw single-binary releases (shipped on main after 0.1.0): a manifest target may say format = "binary"; the helper places the verified file as bin/<version>/<exec> at 0755. The release must include it.
  2. Delete operator (#2): DELETE /api/v1/operators/{id}, owner only; refuses yourself and the last owner; destroys the operator's sessions; audited operator.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).
  3. 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 / limit with the Limits tab's progress style.
  4. Admin link (#4): SiteDetailOut.admin_url = https://<domain><app.admin_path> or null; rendered next to the domain on the site overview.
  5. Cheaper heartbeats (#5): data_bytes per site cached for ten minutes in the agent; memory and CPU refreshed every beat; a unit test proves two beats inside the window issue one du.
  6. Delete-job noise (#6): migration 0002 makes jobs.site_id ON DELETE SET NULL, so the delete job keeps its row and log; the runner no longer logs not_found after a delete, and a delete job reclaimed after its site row is gone counts as complete. Rulings: R142 (migration 0002, not a pre-1.0 edit of 0001), R147 (a delete_site job with NULL site_id is complete).
  7. Prompts per version (#7): GET /api/v1/apps/{id}/versions/{v}/ prompts returns [ { 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).
  8. Server token rotation (#8): POST /api/v1/servers/{id}/rotate- token (owner) issues a new join token and marks the server rejoining; the old server token stays valid until the agent presents the new one through setu agent join (reads SETU_JOIN_TOKEN, keeps agent.toml otherwise intact); after the join the old token is invalid. OPERATIONS.md replaces the SQL procedure. Rulings: R144 (rejoining is computed, never stored), R148 (re-join keeps agent.toml; --control-plane defaults to it).
  9. Control-plane self-backup (#9): the scheduler runs pg_dump -Fc of 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).
  10. Lints (#10): clippy::pedantic, unwrap_used, expect_used as 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").
  11. Installer: /var/lib/caddy ownership fix (shipped); re-running install.sh restarts the local agent when the binary changed (shipped). Documented upgrade path: install.sh --version <v> on a panel box, setu agent install re-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.