The Setup Wizard#
What Is It?#
The Setup Wizard is the guided, browser-based experience that walks you from bare hardware in a box to a running, production-grade self-hosted solution . It’s the front door to PSW — the one thing you open the first time.
You don’t memorize commands, edit YAML by hand, or SSH into anything. You open a web page, answer questions, click buttons, and by the end you have a full platform: a Proxmox hypervisor, the seven core apps , HTTPS with valid certificates, Single Sign-On , and automatic convergence — all driven from your laptop.
Think of the wizard as a recipe that also does the cooking for you. It knows every step, in the right order, and it refuses to let you skip one that matters.
Why Does It Exist?#
Setting up a self-hosted stack from scratch involves dozens of independent steps — each with its own pitfalls, gotchas, and ways to fail silently. You’d normally:
- Build a bootable installer USB
- Run the Proxmox installer and partition disks correctly for ZFS (a modern filesystem with snapshots and data integrity)
- Configure the network, gateway, and DNS servers
- Connect to your router (OPNsense ) and generate API keys
- Plan storage pools for the apps you expect to run
- Define targets (containers where apps will live)
- Configure providers for DNS, DHCP (automatic IP assignment), and ACME (automatic TLS — the encryption behind HTTPS)
- Initialize the user project , generate secrets , set up SOPS (a tool for encrypting secrets in git)
- Back up the master encryption key somewhere safe
- Run bootstrap and hope you didn’t skip anything
Doing that by hand is hours of work, easy to get wrong, and nearly impossible to reproduce. The wizard encodes the whole procedure in a linear flow, runs each step against the real tooling, and checks that the previous step actually worked before unlocking the next. It’s the opposite of “ten paragraphs of install instructions in a README.”
How It Works (In One Sentence)#
The wizard is a series of screens backed by phase detection: each screen produces real artifacts (files, configs, a USB stick, a running container), and the next screen unlocks only when those artifacts exist on disk.
There are no hidden flags or “completed” checkmarks stored somewhere. If nodes/small/hardware.yml exists, the hardware step is done. If .psw/converge/state.yml shows last_result: success, the whole wizard is done. This means you can quit in the middle, come back days later, and pick up exactly where you left off. Delete a file and that step unlocks again.
Starting the Wizard#
You launch the wizard with:
psw wizardThen open the printed URL (defaults to http://127.0.0.1:8111) in your browser. With no arguments, you get a project launcher
where you can create a new project or open an existing one. Pick “New Project”, choose a folder, and the wizard starts. The project folder you pick here is locked in — the wizard shows it as read-only from that point on.
If you already know the project path, you can skip the launcher:
psw wizard --project ~/homelab| Flag | What it does |
|---|---|
--project | Open a specific user project directly (creates the directory if it doesn’t exist) |
--port | Port for the server (default 8111) |
--host | Bind address (default 127.0.0.1) |
--reload / --no-reload | Auto-restart the wizard when PSW source changes (default on — the wizard is operator-facing dev tooling, so a fresh git pull takes effect immediately). Pass --no-reload for a stable single-process run when you don’t want hot-reload churn. |
psw wizardvspsw dashboard. They’re two separate CLIs for two separate jobs:psw wizardis the installer UI (project launcher + pre-bootstrap screens, ending in the Launch step).psw dashboardis the operations UI — it only opens a project that’s already been bootstrapped and refuses to run otherwise. Once your platform is live, you switch frompsw wizardtopsw dashboard.
Restoring a project after disaster? PSW has two recovery scenarios depending on what you saved off-box:
- Have your git remote available? Most common case.
git cloneit back, drop your age key in, and the wizard auto-routes you past Start to wherever the project left off — Hardware → Plan → Install → Launch with all your old identities intact. (A one-click wizard UI for this is planned.)- No git, just a panic-backup tarball? Use the “Restore from a previous project” card at the top of the Start screen. Pick the tarball — the form pre-fills with the old domain, Cloudflare token, OPNsense credentials, SMTP relay. Review, hit Submit, continue Hardware → Plan → Install → Launch like a fresh install but with old credentials.
The full umbrella story (three artifacts: git remote + panic backup + per-app bundles, restored separately) lives in disaster-recovery.md . The secrets-side deep dive is in secrets.md § Reusing Secrets Across Project Rebuilds .
After the Wizard#
Once bootstrap succeeds, you stop using psw wizard entirely. Day-to-day you open the deployed dashboard at https://dashboard.<your-domain> — same UI (psw-ui is shared), but served from the core target, protected by Authelia
SSO, and talking to your real infrastructure instead of local files. If you want to run it from your laptop instead (for scripting, or while travelling), psw dashboard --project ~/homelab launches the same code locally against the project on disk. Either way you’re in operations mode
now.
The Five Steps#
The progress bar at the top of every page shows five dots — these are the user-visible steps of the wizard. Several have internal sub-phases that share a dot (for example, USB creation lives under Hardware). You always see where you are: green check for done, pulsing blue for active, grey for ahead.
| Dot | Screen | What it does |
|---|---|---|
| 1. Start | /wizard/start | One-screen setup: domain, SSH key, OPNsense, Cloudflare — plus inline age-key backup |
| 2. Hardware | /wizard/hardware | Build a bootable USB, boot each server from it, import the hardware inventory |
| 3. Plan | /wizard/deploy-plan | Pick apps, define targets, and let AI plan your ZFS storage layout |
| 4. Install | /wizard/install | Generate an unattended Proxmox installer; after install, carve LXC targets |
| 5. Launch | /wizard/bootstrap | Review the plan, run bootstrap, and deploy your user apps |
1. Start#
What you do: Fill out a single form with everything PSW needs to get going:
- Your project domain (e.g.
homelab.example.com) and the ACME/admin email for Let’s Encrypt and service admin accounts. Use a real inbox you check. - The path to your SSH key.
- OPNsense
URL + API key + API secret — you can paste the contents of OPNsense’s
key.txtfile directly and PSW will split out the key/secret for you. A “Test” button verifies the credentials live. - Cloudflare API token.
- SMTP relay (host, port, username, password, from-address, TLS) — used by apps like Authelia (password-reset mail) and Alertmanager (email alerts). Leave blank if you don’t want mail; anything that tries to send will fail softly.
What PSW does on submit: Validates every field, hits the OPNsense API to confirm it’s reachable, then in one transaction:
- Writes
project.ymlwith your domain and canonical admin email,.psw/workstation.ymlwith your local SSH key path, andnetwork.ymlwith gateway/network facts seeded from OPNsense. - Initializes SOPS so every following write is encrypted.
- Encrypts the OPNsense + Cloudflare credentials into
secrets/providers.yml. - Encrypts the SMTP block into
secrets/smtp.yml. - Runs
psw project initin-process, scaffolding the whole user project (services/,roles/,nodes/,secrets/). - Registers the
local-dns+dhcpprovider backends inproject.yml(both reuse the same OPNsense connection). - Sets the
network-verifiedcheckpoint — the network step is done too. - Returns the path of
secrets/.age-keyso the page can flip into backup mode inline.
What it produces: A complete, initialized user project with encrypted provider secrets, ready for hardware discovery.
The inline backup panel: The moment psw project init succeeds, the Start page switches to a backup panel: “Download age-key”, then an acknowledgment checkbox. Losing the age key
means losing every encrypted secret in your project — passwords, tokens, certificates — with no recovery path. PSW refuses to unlock Launch until you’ve clicked “I’ve backed it up”. This is a hard gate, not a suggestion.
Resuming into backup mode: If you bailed after init but before acknowledging, the Start page detects this on reload (via the
age-key-acknowledgedcheckpoint) and lands you directly on the backup panel — it doesn’t re-ask for credentials you’ve already given it.
Why this matters: The old wizard split these into seven separate screens (Welcome, Network, Providers, Initialize, Review, Backup Key, Bootstrap-gate). They all produced artifacts that could be derived or collected in one pass — so Start now does the lot, with a single commit, a single error if anything fails, and a single progress bar.
2. Hardware#
What you do: This dot has two sub-phases served from the same URL (the page flips automatically based on whether any nodes/*/hardware.yml exists yet):
Phase A — Build a USB. Plug a USB drive into your laptop and pick the Proxmox VE ISO you downloaded. The wizard auto-detects the drive the moment you insert it, then installs Ventoy (a bootloader that lets you put multiple ISOs on one USB), copies the Proxmox ISO onto it, creates a persistence partition (a writable space tools can save data to across boots), and adds two small helper programs:
Phase B — Discover hardware. Physically take the USB to your server, boot from it, and follow the on-screen steps: select the Proxmox ISO from Ventoy, choose the persistence partition, wait for the End User License Agreement screen (don’t click anything — press
Ctrl+Alt+F3to get a terminal), check your network (ping— anddhclient -vif DHCP didn’t kick in), mount the USB data partition, then runserver-inspector <name>(where<name>is a short name for this server likesmallorwhite). Shut down and bring the USB back.
What PSW does: When Phase A finishes, you’ve got a single bootable USB that can discover hardware, wipe disks, and install Proxmox. When you re-insert the USB after Phase B, the wizard reads hardware-<name>.yml from the persistence partition and imports it into nodes/<name>/hardware.yml. Multi-node setup? Repeat the physical steps on each machine — every server writes its own file.
What it produces: nodes/*/hardware.yml
— a complete inventory of every piece of your server that PSW needs to know about.
Why it matters: Storage planning, target sizing, and GPU-aware app placement (think Tdarr or Immich using hardware transcoding) all read from this file. See hardware for the full schema and infrastructure for what PSW does with it.
3. Plan#
What you do: Four substeps on one screen:
- Select apps. Core apps are always included (locked on). Check whatever else you want.
- Show prompt. PSW renders a self-contained prompt — your hardware, the storage guide, placement rules, app metadata, JSON schema — and gives you a Copy button.
- Paste response. You paste the prompt into whatever AI you already use (Claude.ai , ChatGPT, Gemini, a local LLM via Ollama , whichever). You paste its JSON reply into PSW.
- Review. Read-only summary of the applied plan. Regenerate to re-ask; Re-paste to try a different reply; Next to move on.
What PSW does:
- Picking apps writes
deployment-plan.ymlwith justuser_apps: [...]. - Show-prompt step renders the planner prompt (~70 KB) and also writes it to
.psw/plan-prompt.mdso you can diff across runs. - Paste-response step validates the AI’s JSON against a strict pydantic schema. On success it atomically writes
deployment-plan.yml(filled with targets + resources + mounts + cluster topology), every node’sstorage.yml(ZFS pools + datasets + NFS), and every node’sproxmox-storage.yml(installer-focused). On failure it surfaces a pointer-and-message error list you can copy back to your AI.
What it produces: deployment-plan.yml, nodes/<name>/storage.yml, and nodes/<name>/proxmox-storage.yml.
Why it matters: The planner decides what you’d otherwise have to solve by hand — bin-packing apps into targets given GPU / NFS / USB / memory constraints, plus designing the ZFS layout. PSW never calls an AI itself; you bring whichever AI you already pay for. See AI Planner for the protocol and deployment plan for the file it produces.
4. Install#
What you do: Another two-phase screen:
Phase A — Proxmox install. All installer fields are pre-filled with smart defaults but editable:
- Network: gateway and DNS server (pre-filled from the OPNsense connection)
- Proxmox settings: admin email (defaults to
admin@yourdomain), root password (leave empty to auto-generate), timezone and keyboard (auto-detected from your workstation) - Per node: static IP (pre-filled from the DHCP pool) and install disk
Plug the USB back into each server and boot from it.
Phase B — Configure infrastructure. Confirm your targets. Names, apps, CPU, memory, and rootdisk size all come from the Plan ’s
resourcesblock — a single source of truth for per-target sizing. Tweak anything you want, then PSW sets up the Proxmox node, applies the ZFS storage layout, and imports the node into the project.
What PSW does:
- Phase A generates an automated Proxmox installer ISO — a custom ISO that runs the Proxmox installer with your exact answers already filled in — and writes it to the USB. Before you boot, PSW registers and verifies a DHCP
reservation for each node’s chosen IP so the router knows about the server before it’s even on. The server boots, installs itself unattended, reboots, and comes online at its static IP. The wizard polls over SSH and marks the step complete only once every node answers
pveversionsuccessfully. If the selected management NIC negotiates below gigabit, PSW shows an amber warning (bad cable, slow switch port, flaky NIC) but doesn’t block you. - Phase B runs
psw target addfor each target, thenpsw node setup-proxmox,psw node apply-storage, andpsw node import.
What it produces: Running Proxmox nodes plus network.yml populated with management.hosts.* and targets.*. The proxmox-installed checkpoint key lands in .psw/checkpoints.yml once the live probe confirms the install really finished.
Why it matters: From here on, the wizard talks to a real server, not just local files.
5. Launch#
What you do: Review the summary at the top of the page (domain, targets, providers, apps), watch psw project check run live against your project (it re-runs every time you land on the page), and when everything is green click Start Bootstrap.
What PSW does: Runs psw deploy bootstrap with your bootstrap target, streaming the full output into a terminal panel right in the page. The phase bar tracks progress through bootstrap’s seven internal steps
. If anything fails, you see it live — no hidden logs to dig up. When bootstrap completes successfully, PSW loops over the user apps from your deployment plan
and adds them one by one via psw app add. They’ll deploy on the next convergence
tick five minutes later. Finally, PSW commits and pushes your project to Forgejo
so convergence has the initial state on record.
What it produces: A fully operational self-hosted solution. The dashboard you were using on your laptop silently transitions to operations mode — the same UI, now talking to the real deployed dashboard at https://dashboard.yourdomain. See bootstrap
for the full list.
Hard gate on the age key.
/wizard/bootstraprefuses to render until theage-key-acknowledgedcheckpoint is set. If you somehow reach it without having acknowledged the backup (e.g. the checkpoint got deleted), you’re sent back to/wizard/start— which detects the “init done, not acknowledged” state and jumps directly to the backup panel.
Phase Detection — The Magic Behind Resuming#
The wizard’s progress is never stored in a “current step” flag. Instead, it’s derived from the files on disk every time you load a page. Each phase is considered “done” when its output artifact exists or when its checkpoint marker is set. This is the chain:
| Artifact / checkpoint | Phase |
|---|---|
project.yml with domain | Start: form submitted |
nodes/*/hardware.yml | Hardware: import done |
network.yml:management.gateway + network-verified checkpoint | Start: network verified (set during Start submission) |
deployment-plan.yml with targets | Plan: apps picked |
nodes/*/storage.yml | Plan: AI layout produced |
network.yml:management.hosts.*.ip + proxmox-installed checkpoint | Install: Phase A done |
network.yml:targets populated | Install: Phase B done |
providers in project.yml + ssh_key_path in .psw/workstation.yml | PSW init done |
age-key-acknowledged checkpoint | Backup done — Launch unlocks |
.psw/converge/state.yml exists | Bootstrap started |
.psw/converge/state.yml with last_result: success | Operational |
Behind the 5 progress-bar dots the wizard tracks a richer set of sub-phases (e.g. USB_CREATED, NETWORK_CONFIGURED, STORAGE_PLANNED). They project onto their parent dot so the bar stays simple, but they still drive which sub-screen you land on when you revisit a step.
Why checkpoints instead of reading secrets? The previous design ran a SOPS decrypt subprocess on every wizard page load to check OPNsense connectivity. That was both slow and silently fragile — a transient sops error would silently drop the user back a step with no message. Checkpoints live in a single plain-text YAML file (
.psw/checkpoints.yml) mapping<name>: <ISO timestamp>— fast to read, easy to inspect, and safe to delete (remove a key to re-unlock its step).
This means:
- Close your laptop and come back tomorrow — the wizard picks up where it was
- Delete an artifact or checkpoint to redo a step — the phase drops back, that step unlocks
- Inspect it at any time with
psw project status— same signal, command-line output
There’s no “state” that can get out of sync with reality. The files are the state.
What the Wizard Uses Under the Hood#
The wizard is a UI on top of the normal PSW granular commands — there’s no secret “wizard-only” code path. The dashboard just orchestrates them in the right order, with the right arguments, and streams their output to your browser:
| Step | Runs |
|---|---|
| Start | psw project init, psw sops setup, psw provider configure local-dns opnsense-unbound, psw provider configure dhcp opnsense-kea (all in-process, no subprocess) |
| Hardware | psw usb install-ventoy, add-iso, create-persistence, add-to-ventoy, link-iso-persistence; then psw node import --from <usb-path> |
| Plan | Writes deployment-plan.yml; then psw node plan (AI) |
| Install | psw node generate-install-iso → auto-installer on USB; then psw target add, psw node setup-proxmox, psw node apply-storage, psw node import |
| Launch | psw deploy bootstrap --target <name>; then psw app add for each user app in deployment-plan.yml; then git commit + git push |
If you ever want to see exactly what ran, scroll the terminal panel — the full command and its output are right there.
How the Wizard Feels Alive#
A normal web form is one-way: you type, click, wait, refresh. The wizard is different — the moment something happens in the real world (you plug a USB in, your server finishes booting, a command prints a line), the page updates on its own. No refresh, no polling by hand.
This is done with Server-Sent Events (a simple web protocol where the server pushes live updates to the browser) fed by three independent background watchers that the dashboard runs alongside its normal web pages:
| Watcher | What it watches | Where you see it |
|---|---|---|
| USB watcher | Plug/unplug events on your workstation | Hardware step — the device list appears and disappears in real time as you insert or remove sticks |
| Command streamer | The live output of whatever psw command is currently running | Every step with a terminal panel (Hardware, Plan, Install, Launch) — you read output line-by-line as it happens, like tailing a log |
| Node prober | Whether each Proxmox node is reachable over SSH yet | Install step — a pulsing yellow dot turns green the moment your server finishes its automated install and comes online |
The watchers are fully independent — the USB detector knows nothing about the terminal, the terminal knows nothing about node probing, and so on. One watcher crashing wouldn’t freeze the others. They just each publish their own stream of events, and the pages that care subscribe to the ones they need.
One command at a time. The command streamer runs
pswas a subprocess, streams its output, and refuses to launch a second command while one is still running (you get a clear “already running” error, not a silent crash). That matters — two concurrent bootstraps writing to the same project would corrupt state.
Multi-node aware. If you’re installing Proxmox on several nodes at once, the prober runs a separate SSH check per IP and stops watching each one the moment it reports a healthy
pveversion. The Install step only unlocks once every node turns green.
Common Questions#
Do I have to use the wizard? No — every step is a granular PSW command . Power users can run the whole sequence from the CLI. The wizard is a convenience layer, not a replacement.
Can I go backwards? Yes. Click any completed dot in the progress bar to revisit it. The page re-renders with your current values pre-filled. Changes are safe — each screen writes its artifact when you move forward.
What if bootstrap fails?
Bootstrap is idempotent. Re-running it resumes from where it stopped — see bootstrap
. The Launch screen lets you retry with one click, or pass --clean if you want a fresh start.
What if I don’t have OPNsense?
Install one first — the wizard’s Start step needs to reach it before anything else happens. The standalone psw opnsense CLI group has commands for provisioning one on Proxmox if you want to do that before starting the wizard.
Can I run the wizard against an existing project? Yes — the wizard uses phase detection , so an existing, partly-configured project just opens at whatever step is next. This is how you resume a half-finished setup.
What happens after the wizard is done?
The dashboard switches to operations mode and starts talking to the deployed dashboard at https://dashboard.yourdomain. From there you add apps
, monitor runs, and edit config — all via GitOps
, no more wizards.
Key Ideas#
- Five screens, end to end — Start, Hardware, Plan, Install, Launch — from bare hardware to live platform
- Phase-driven, not step-driven — progress is detected from real artifacts, never from hidden flags
- Resumable by design — quit at any moment, come back later, continue
- Thin UI over real commands — every screen maps to granular PSW commands you could also run yourself
- Refuses to skip the hard parts — OPNsense credentials, storage planning, and the age-key backup all have to work before bootstrap, not after