End-to-end spec for the Furtka first-boot installer. Extends Robert's wireframes in [installer-wireframes.md](./installer-wireframes.md) with the post-install pattern proven by YunoHost (see [competitors.md](./competitors.md#yunohost)). Concrete tech picks are locked in at the bottom so Robert has unambiguous targets for the next coding session.
**Status:** Draft spec 2026-04-13. Not implemented yet. Open questions for Robert at the end.
---
## Goals
1.**Your dad can install this.** Boot USB, answer plain-language questions, get a working server. No terminal required on the happy path.
2.**HTTPS from boot #1.** No plaintext admin UI at any stage — unlike Umbrel ([#546](https://github.com/getumbrel/umbrel/issues/546)) and CasaOS. Even the pre-install wizard uses HTTPS via a local CA.
3.**No post-install CLI required.** By screen S8 (confirm), the user has a reachable domain, valid TLS, admin credentials, and the base OS installing. Everything else is reachable from the web UI.
---
## Entry point
1. User flashes ISO to USB (Etcher / `dd` / Rufus).
2. Boots target machine from USB into a **headless live environment**.
3. Live image auto-starts:
-`webinstaller` Flask app on port 443 with TLS
- mDNS advertising `proksi.local`
- Local CA cert generated on first boot, served at `http://proksi.local/ca.crt` (plaintext port 80 serves **only** the CA cert download and a redirect to HTTPS — nothing else)
4. User opens `https://proksi.local` on any device on the same LAN.
5.**First screen before S1:** browser warning page with a one-click "Download Furtka CA" button and OS-specific install instructions (macOS Keychain, Windows cert store, iOS profile, Android). After install, user clicks "Continue" and proceeds to S1 with no further warnings.
> **Why not self-signed (YunoHost model)?** Users learn to click through warnings, which kills trust. A single one-time CA install gives clean green padlocks for every Furtka service after.
Each screen is one Flask route under `/install/<step>`. The Flask `settings` dict in `webinstaller/app.py` accumulates answers and is flushed to `user_configuration.json` + `credentials.json` at S8.
### S1 — Welcome / Account
From Robert's wireframe Screen 1, trimmed.
| Field | Type | Notes |
|-------|------|-------|
| Hostname | text | Default `furtka`. Becomes `hostname` in archinstall config. |
| Admin username | text | Becomes first entry in `users[].username`. |
| Password | password | |
| Password (confirm) | password | Must match. |
| Language | select | Sets `archinstall-language` + `locale_config.locale`. |
**Dropped from wireframe:** "Backend on/off" and "Backend address" — meaning is unclear, see open questions.
### S2 — Drive Selection
From Robert's wireframe Screen 2.
- Lists drives via `webinstaller/drives.py::list_scored_devices()` (already implemented — do not duplicate).
- Each row shows: device path, size, score, SMART status.
- Radio selection for boot drive, with highest-score drive pre-selected.
- **"Auto setup" button** — accepts the recommendation, jumps past S3.
- Remaining drives carried into S3.
| Field | Type | Maps to |
|-------|------|---------|
| Boot drive | radio | `disk_config.device` |
### S3 — Per-Device Purpose
From Robert's wireframe Screen 3. One screen per non-boot drive.
| Purpose | Action at install time |
|---------|------------------------|
| Mass storage (default for large HDDs) | Added to a shared data pool mounted at `/mnt/data`. |
| **Free `*.furtka.cloud` subdomain** | User picks `myname.furtka.cloud`. DNS is ours, wildcard cert via DNS-01 issued automatically. Managed gateway path. | The "your dad" default. |
| **Bring your own domain** | User enters e.g. `example.com`. Wizard shows two NS records (`ns1.furtka.cloud`, `ns2.furtka.cloud`) to paste at their registrar. We handle the rest. | Users who already have a domain. |
| **Skip — LAN only** | No public domain. Apps accessible as `<app>.proksi.local` via mDNS, with certs from the local CA. | Paranoid users / offline networks. |
Screen validates propagation in the background (NS query every 5s, green check when `nslookup` resolves to our NS). User can click "Next" past yellow, but not past red.
| BYO domain | Waits for NS propagation (can take minutes–hours). Page auto-refreshes. Shows "⏳ Waiting for `example.com` to point to us…" → green check. |
| LAN only | Local CA cert used for `*.proksi.local`. One line: "✅ Local TLS ready." |
User can't advance past S6 until cert is ready (or they go back to S5 and pick a different option).
### S7 — Diagnostic (NEW)
YunoHost-style health check before confirming. Runs in parallel, shows results as they land.
| Check | Green | Yellow | Red |
|-------|-------|--------|-----|
| DNS resolves target domain | `nslookup` succeeds | Succeeds but propagation incomplete | Fails |
| Ports 80 + 443 reachable from WAN | Inbound probe succeeds | CGNAT detected (fallback to Tailscale/Cloudflare tunnel suggested) | Blocked |
| Boot drive mounted | — | — | Fails |
| Docker available | — | — | Missing |
| SMART healthy (all drives) | PASSED | No SMART data | FAILED |
**Rules:** user can continue past yellow with a "I understand" checkbox. Red blocks Next and offers a fix-it shortcut back to the relevant screen.
### S8 — Overview + Confirm
Shows the full `user_configuration.json` and `credentials.json` side by side (credentials masked, with a "show" toggle). User clicks **Install** → wizard POSTs to `/install/run`, which:
| Reverse proxy | **Caddy** | Automatic Let's Encrypt built-in. Caddyfile is the simplest config of any reverse proxy — matches the "simple" ethos. Auto-reloads on config change. | Traefik (label-based config is elegant for Docker but overkill and Kubernetes-flavored), nginx (battle-tested but manual SSL = every competitor's failure mode). |
| Identity provider | **Authentik** | Bundled SSO from day one — every app template wires to it at install (YunoHost's best move). Active development, clean admin UI, OIDC + SAML + LDAP. | Authelia (lighter but worse UI and no built-in user management — needs external LDAP), external-only (loses the YunoHost wedge of SSO-by-default). |
| Managed gateway DNS | **NS delegation** to `ns1.furtka.cloud` / `ns2.furtka.cloud` | User delegates once at registrar; we handle wildcard cert via Let's Encrypt DNS-01, subdomain creation, propagation. The single biggest UX cliff every competitor dies on. | CNAME-per-subdomain (clunky, users see our hostnames in records), manual A records (the exact pain point we're solving). |
| Local HTTPS | **Local CA** generated at first boot | Single cert install in browser → green padlock for every service, no warnings ever. | Self-signed (YunoHost's model — users learn to click through warnings), public CA for `.proksi.local` (impossible — `.local` is reserved for mDNS), Tailscale-style ACME (good but adds a vendor dependency). |
| Base OS | **Arch** (confirmed) | Rolling releases, Robert's existing Proxmox work. No user-visible difference. | Debian fallback remains documented in README. |
| Container runtime | **Docker + Compose** | Confirmed in README. Authentik and Caddy ship as Compose stacks. | Podman (cleaner rootless story, but Compose ecosystem is smaller and Authentik ships Docker-first). |
---
## What this spec does NOT cover (deferred)
- App store UI and install flow (separate spec after bootable ISO lands).
- Backup target scheduling (S3 stub points to this).
1.**"Backend on/off" and "Backend address" fields** in your Screen 1 wireframe — what do these configure? Dropped from S1 above until this is clear. If it's "connect to Furtka cloud for managed gateway," that's already covered by the S5 "free subdomain" path.
2.**Local CA vs Tailscale-style ACME** — I locked in local CA because it's self-contained and works offline. Tailscale's approach (public cert for `*.ts.net`) is smoother but requires Tailscale. Your call.
3.**Wizard UI framework** — currently Jinja templates + plain HTML (see `webinstaller/templates/`). If you want HTMX or Alpine for the async parts (S6 cert wait, S7 diagnostics), decide now so the templates don't need rework later.
4.**Language list** — which languages ship in v1? Defaulting to English + German + Polish would cover us; anything else up to you.
5.**Auto-setup branch from S2** — should it skip S3 entirely (purpose auto-assigned by drive type) or still show S3 with pre-filled defaults for confirmation?