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 undoCommandWhat dies
One app, clean uninstallpsw app remove <name>That app’s containers, data, database, convention files
One target no longer neededpsw target delete <name>The entry in network.yml (actual LXC survives until a full reset)
The whole bootstrap & retrypsw deploy bootstrap --cleanJust the bootstrap progress file, so bootstrap starts fresh
The whole setup, rebuild from scratchpsw deploy reset --confirmEvery managed LXC, every ZFS dataset, every PSW-managed DNS + DHCP record, every generated project file
The remote VPS, rebuilt in placepsw remote rotate-vpsThe 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 sonarr

CLI definition lives in psw/src/psw/commands/remove/cli.py . What the command actually does on your workstation:

  1. Marks the app’s service.yml with state: absent
  2. Records your chosen teardown_options alongside it
  3. 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:

  1. Reconciler teardown — unwires the app from every other live app (removes its Prowlarr registration, deletes its OIDC client from Authelia, etc.)
  2. Stop containers — main container + every sidecar (exporter, backup helper)
  3. Database drop — dumps first if backup.database: true is set elsewhere, then drops the PostgreSQL database
  4. Volume cleanup — deletes local Podman volumes and bind-mounted data dirs
  5. Secret cleanup — removes the app’s entries from secrets/apps.yml
  6. 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#

FlagEffect
--keep-dataStep 4 (volume cleanup) is skipped — your data stays on disk. Re-add the app later and it picks up where it left off
--keep-secretsStep 5 is skipped — passwords and API keys stay in secrets/apps.yml. Re-add without regenerating credentials
--cascadeAlso removes any app that declared requires: [this-app]. Use when you’re ripping out a shared dependency
--resetRecovery-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 push

Convergence 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:

FieldWhat it does
reset_invalidates_secretsA 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_resetA 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 media

Defined 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 --clean

All --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 --confirm

Defined in psw-deploy/src/psw_deploy/cli.py . This is the one that actually destroys infrastructure. It’s structured in phases:

  1. Save preserved volumes — runs through every app that declared preserve_on_reset and SCPs the listed paths into ~/.local/share/psw-cache/
  2. Destroy every managed LXCpct stop + pct destroy --purge on every managed target on every Proxmox node
  3. Destroy ZFS datasets — wipes every app storage dataset the planner created (unless --keep-zfs is passed, in which case the datasets survive but their contents are emptied)
  4. 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 psw marker
  5. Clean stale secrets — iterates every deployed app’s reset_invalidates_secrets and removes those keys from secrets/apps.yml
  6. Clean the project folder — deletes generated files (services/, roles/, .psw/converge/state.yml, .psw/bootstrap/progress.yml, etc.) and strips target entries from network.yml

After all that, psw deploy bootstrap --target <name> rebuilds from a clean slate.

Reset flags#

FlagWhat it does
--confirmRequired. Without it the command just prints a warning and exits
--keep-zfsPools and dataset structures stay; only contents are wiped. Useful when you want to keep the exact layout the AI planner produced
--include-bareAlso 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 psw marker ). Records you added by hand stay put
  • Provider-side VPS settings--include-bare reinstalls 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.yaml and secrets/.age-key are 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-vps

Used when your remote-access VPS is misbehaving. Instead of a clean-slate destroy, it:

  1. Snapshots the live acme.json from the VPS into secrets/acme.json so certificates survive the rebuild
  2. Calls psw vps reinstall --confirm, which triggers an OS reinstall through the Hostinger API
  3. Re-runs the Pangolin deploy (the role’s pre-start hook restores acme.json before 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:

  1. pct list on each Proxmox node — should show no managed LXCs
  2. OPNsense UI → DHCP reservations — should show only your own entries, no PSW-marked ones
  3. OPNsense UI → Unbound host overrides — same
  4. Cloudflare dashboard → DNS → your zone — no records with the Managed by psw comment
  5. 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 firstpsw app remove only marks state: absent; convergence does the real teardown during Phase 4
  • --keep-data + --keep-secrets are the “bring it back later” switches — re-add the app and it picks up where it left off
  • psw target delete is light — just removes the entry; the LXC waits for a psw deploy reset
  • psw deploy reset is the big one — every LXC, every dataset, every PSW-managed network entry, every generated file
  • Two metadata contracts ride alongreset_invalidates_secrets drops stale credentials; preserve_on_reset keeps certs and anything else worth rescuing
  • Verify every teardownpct list, OPNsense, project folder. No “probably cleaned up”