Reset and Removal#
What Is It?#
Reset is PSW’s answer to “I want to undo something.” That covers a range: uninstalling a single app, wiping a whole test project clean, rebuilding a broken VPS, or nuking everything so you can start over. PSW gives you one command per size, each with its own safety rails.
Think of the scale like this:
| Size of undo | Command | What dies |
|---|---|---|
| One app, clean uninstall | psw app remove <name> | That app’s containers, data, database, convention files |
| One target no longer needed | psw target delete <name> | The entry in network.yml (actual LXC survives until a full reset) |
| The whole bootstrap & retry | psw deploy bootstrap --clean | Just the bootstrap progress file, so bootstrap starts fresh |
| The whole setup, rebuild from scratch | psw deploy reset --confirm | Every managed LXC, every ZFS dataset, every PSW-managed DNS + DHCP record, every generated project file |
| The remote VPS, rebuilt in place | psw remote rotate-vps | The VPS’s OS reinstalled; certs preserved |
All of these are declarative-first — you don’t destroy anything directly. You change a file (or run a command that changes a file), commit, and convergence or an explicit deploy step does the actual teardown in a safe, predictable order.
Removing a Single App#
psw app remove sonarrCLI definition lives in psw/src/psw/commands/remove/cli.py
. What the command actually does on your workstation:
- Marks the app’s
service.ymlwithstate: absent - Records your chosen
teardown_optionsalongside it - Re-renders the project (convention files, generated config)
The app is still running at this point — nothing on the server has changed yet. The next convergence
run (or psw deploy apply <app>) picks up the state: absent marker during Phase 4: Teardown and performs the real removal, in this order:
- Reconciler teardown — unwires the app from every other live app (removes its Prowlarr registration, deletes its OIDC client from Authelia, etc.)
- Stop containers — main container + every sidecar (exporter, backup helper)
- Database drop — dumps first if
backup.database: trueis set elsewhere, then drops the PostgreSQL database - Volume cleanup — deletes local Podman volumes and bind-mounted data dirs
- Secret cleanup — removes the app’s entries from
secrets/apps.yml - Re-render — writes the now-app-free convention files
The teardown logic lives in psw-deploy/src/psw_deploy/commands/converge/teardown.py
.
Flags that change what gets deleted#
| Flag | Effect |
|---|---|
--keep-data | Step 4 (volume cleanup) is skipped — your data stays on disk. Re-add the app later and it picks up where it left off |
--keep-secrets | Step 5 is skipped — passwords and API keys stay in secrets/apps.yml. Re-add without regenerating credentials |
--cascade | Also removes any app that declared requires: [this-app]. Use when you’re ripping out a shared dependency |
--reset | Recovery-only: clears a stuck state: absent marker when a teardown never finished. Doesn’t remove anything else |
Bring an app back from a --keep-data --keep-secrets removal with the normal add command:
psw app add sonarr --target media
git commit && git pushConvergence redeploys it against the preserved data and secrets. No re-onboarding.
Two App-Level Metadata Hooks#
Each app’s meta.yml
can declare two reset-related contracts that PSW honours automatically during a full reset:
| Field | What it does |
|---|---|
reset_invalidates_secrets | A list of secret keys that are only valid for one lifetime of the app — when the app is reset, these keys are removed from secrets/apps.yml. Example: Forgejo
’s forgejo_runner_token — pointless to preserve since the database it’s tied to was wiped |
preserve_on_reset | A list of files/volumes to copy off the target before destruction and restore after. Example: Traefik
’s acme.json — preserving it avoids hitting Let’s Encrypt rate limits on every reset |
The reset service saves preserved volumes to ~/.local/share/psw-cache/<app>/ via SCP before it starts wiping, then restores them on the next deploy. See psw-deploy/src/psw_deploy/commands/reset/
.
Deleting a Target#
psw target delete mediaDefined in psw/src/psw/targets/cli.py
. Refuses if any app is still assigned to the target. On success it only removes the target’s entry from network.yml — the actual Proxmox LXC stays put. Destroying the LXC on Proxmox is left to the next psw deploy reset, which is the one command that touches the hypervisor. Flag: --force to skip the confirm prompt.
Retrying a Failed Bootstrap#
Bootstrap tracks which of its seven steps
have completed in .psw/bootstrap/progress.yml. Re-running psw deploy bootstrap --target core resumes at the first incomplete step. If you want a fully-fresh bootstrap (because the earlier failure left half-created state you don’t trust), add --clean:
psw deploy bootstrap --target core --cleanAll --clean does is delete .psw/bootstrap/progress.yml before starting; it doesn’t touch the target, the LXC, or any data. For that, you need the next tool.
Full Reset#
psw deploy reset --confirmDefined in psw-deploy/src/psw_deploy/cli.py
. This is the one that actually destroys infrastructure. It’s structured in phases:
- Save preserved volumes — runs through every app that declared
preserve_on_resetand SCPs the listed paths into~/.local/share/psw-cache/ - Destroy every managed LXC —
pct stop+pct destroy --purgeon every managed target on every Proxmox node - Destroy ZFS datasets — wipes every app storage dataset the planner created (unless
--keep-zfsis passed, in which case the datasets survive but their contents are emptied) - Clean the network — removes every DHCP reservation and DNS override PSW created in OPNsense
, and every PSW-managed record from your Cloudflare
zone. Reservations, overrides, and DNS records you added manually are left alone — PSW only touches entries carrying the
Managed by pswmarker - Clean stale secrets — iterates every deployed app’s
reset_invalidates_secretsand removes those keys fromsecrets/apps.yml - Clean the project folder — deletes generated files (
services/,roles/,.psw/converge/state.yml,.psw/bootstrap/progress.yml, etc.) and strips target entries fromnetwork.yml
After all that, psw deploy bootstrap --target <name> rebuilds from a clean slate.
Reset flags#
| Flag | What it does |
|---|---|
--confirm | Required. Without it the command just prints a warning and exits |
--keep-zfs | Pools and dataset structures stay; only contents are wiped. Useful when you want to keep the exact layout the AI planner produced |
--include-bare | Also reinstall bare targets (like your remote-access VPS) via the Hostinger API. Full OS wipe + reprovision |
What reset does NOT touch#
- Proxmox nodes themselves — PSW resets its own targets and project files, not your hypervisor
- Manually-created provider entries — only PSW-marked reservations, overrides, and DNS records are removed (PSW identifies its own entries on OPNsense and Cloudflare alike via the
Managed by pswmarker ). Records you added by hand stay put - Provider-side VPS settings —
--include-barereinstalls the OS, but billing, SSH keys in the provider portal, and anything else outside the Hostinger reinstall API stays - Your age key and SOPS setup —
.sops.yamlandsecrets/.age-keyare untouched; you’d have to delete them by hand
Cleaning Up After an Old Project#
psw deploy reset needs a project on disk to know what to remove. If the project folder is already gone — you nuked it, you cloned a fresh checkout, you switched machines — the DHCP reservations and DNS records that project created are stuck on OPNsense / Cloudflare with nothing to remove them.
That’s what psw provider clean --orphans
is for. It scans your providers for entries carrying the Managed by psw marker, finds the ones with no living project to claim them, and deletes them. No project required. Dry-run by default — pass --confirm to delete for real. Use this whenever:
- You wiped a project folder before running
psw deploy reset - A reset crashed mid-way and left half its work behind
- You’re cleaning up after a long-dead test deployment
Rotating the Remote-Access VPS#
A different flavour of reset that belongs in the same conversation:
psw remote rotate-vpsUsed when your remote-access VPS is misbehaving. Instead of a clean-slate destroy, it:
- Snapshots the live
acme.jsonfrom the VPS intosecrets/acme.jsonso certificates survive the rebuild - Calls
psw vps reinstall --confirm, which triggers an OS reinstall through the Hostinger API - Re-runs the Pangolin deploy (the role’s pre-start hook restores
acme.jsonbefore Pangolin boots)
You end up with a freshly-installed VPS, same IP, same certificates, no Let’s Encrypt rate-limit drama.
Verifying a Clean Teardown#
After a reset, PSW’s convention (CLAUDE.md
) is to confirm the teardown finished:
pct liston each Proxmox node — should show no managed LXCs- OPNsense UI → DHCP reservations — should show only your own entries, no PSW-marked ones
- OPNsense UI → Unbound host overrides — same
- Cloudflare dashboard → DNS → your zone — no records with the
Managed by pswcomment - Your project folder — no
services/,roles/,.psw/converge/state.yml,.psw/bootstrap/progress.yml
If something survived the reset, that’s a bug to fix, not a state to work around.
Key Ideas#
- Remove is declarative first —
psw app removeonly marksstate: absent; convergence does the real teardown during Phase 4 --keep-data+--keep-secretsare the “bring it back later” switches — re-add the app and it picks up where it left offpsw target deleteis light — just removes the entry; the LXC waits for apsw deploy resetpsw deploy resetis the big one — every LXC, every dataset, every PSW-managed network entry, every generated file- Two metadata contracts ride along —
reset_invalidates_secretsdrops stale credentials;preserve_on_resetkeeps certs and anything else worth rescuing - Verify every teardown —
pct list, OPNsense, project folder. No “probably cleaned up”