Setup wizard
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted · Audience: Operator · Area: A — Getting started
Purpose
Turn “clone a compose file and hope” into a guided conversation that ends in a working stack. This is the feature the product is judged on: if setup fails or confuses, nothing else in the catalogue matters.
The wizard’s job is not merely to collect values. It is to make decisions the operator cannot reasonably make themselves, by detecting the environment, testing assumptions, and only asking questions whose answers it genuinely cannot determine.
Behaviour
It runs when there’s nothing to run
Invoking lemonfiber with no configuration present offers setup. It is never a hidden command the operator has to discover.
Every question earns its place
A question is asked only if all three hold:
- lemonfiber cannot determine the answer by detection.
- The answer changes behaviour.
- The operator can plausibly answer it.
This is why timezone is detected and confirmed rather than asked, why PUID/PGID is asked only on native Linux (elsewhere it’s meaningless), and why native-mode Jellyfin is offered only on platforms where it buys something. A question the operator can’t answer is not thoroughness; it’s abdication.
Steps
| # | Step | Asks or detects |
|---|---|---|
| 1 | Welcome | Nothing. States what’s about to happen and roughly how long. |
| 2 | Environment preflight | Detects OS, Docker presence, Compose version, daemon reachability |
| 3 | Protocols | Usenet, torrents, or neither |
| 4 | Prerequisites | A1 — the account checklist, derived from the protocol choice |
| 5 | Data location | Proposes a default; empirically tests hardlinks (C5) |
| 6 | Credentials | A3 — validated as entered |
| 7 | Quality preference | D2 — plain language, not custom formats |
| 8 | Library serving | Whether to run Jellyfin; if so, mode |
| 9 | Household | Whether others will use it; drives D6 |
| 10 | Autostart | Whether to start on boot (B8) |
| 11 | Review | Complete summary. Nothing has touched disk yet. |
| 12 | Apply | Writes config, creates directories, materialises stack files |
| 13 | Start | Pulls images with progress, starts the chosen form, waits for health |
| 14 | Wire | D1 — connects everything via API |
| 15 | Finish | Prints URLs; offers the first-content walkthrough |
Nothing is written until Review is confirmed
Steps 1–11 are read-only. The operator can abandon at any point and leave no trace beyond a resumable progress file. This matters because a half-applied setup is worse than none — it produces a state nobody designed.
Time is set honestly
Image pulls are multiple gigabytes. Step 1 states the expected duration, and step 13 shows real per-image progress. Silent multi-minute waits read as a hang and prompt operators to kill the process mid-write.
It is resumable, not restartable
Quitting mid-wizard preserves answers. Returning resumes at the same step. The operator is never made to re-answer eleven questions because step 12 failed.
A started stack is wired before setup finishes
Containers that run and know nothing of each other are not a working stack: the
download clients are not in the automation apps, the indexers are not in the
search, and the first request goes nowhere. Step 14 is what makes them one stack.
It wires the services exactly as seed does, and it says
what it did with each connection, made, skipped or failed, before Finish prints
anything (A2-R17). A stack that did not start is not wired, because there is
nothing yet to answer the calls.
On the companion
The companion does not offer this, and the reason is not squeamishness: a phone
cannot perform the act that makes a phone able to perform acts
(N1-R4). But not offered and not there
are different things, and N15-R2 requires an operator who reaches first-run
setup from the app to be declined with that reason rather than to meet an absence
(N15). An absence reads as a defect, and the
operator’s next move is to go looking for the version that has it.
What this wizard settled stays readable. N15-R1 requires those answers shown as
facts about the stack — this is the time zone, this is where the library lives —
and forbids treating the act of showing them as offering setup. Reading what was
decided is not deciding it again. A rule against offering first-run setup that
also hid its outcome would leave an operator unable to find out what their own
stack had been configured to do, which is not what N1-R4 declines.
The web API serves none of those facts in a form a surface can read as facts.
GET /api/setup answers with the wizard’s standing: whether setup is on offer, and
a plan of what applying would write rather than what the stack holds. The setup
document that states the data root, the protocols and the service user is written
only by the command line, and the configuration listing says a value is the
operator’s without saying whether setup wrote it.
A2-R16 is the read that answers both questions N15 asks: what setup settled,
and whether this stack is set up at all.
States
| State | Meaning |
|---|---|
absent |
No configuration; wizard offered |
in-progress |
Partially answered, nothing applied. Resumable. |
reviewing |
All answers collected, awaiting confirmation |
applying |
Writing config and starting services. The only non-atomic phase. |
applied |
Configuration on disk and valid |
failed-apply |
Apply was interrupted. Must be recoverable, not wedged. |
failed-apply is the dangerous state and gets explicit handling: on next run
lemonfiber detects the partial state, reports exactly what was written, and offers to
resume, roll back, or start over.
Edge cases
| Situation | Behaviour |
|---|---|
| Docker not installed | Platform-specific install instructions and exit. Never a stack trace. |
| Docker installed but daemon stopped | Distinguish from “not installed” — the remedies differ entirely (start Docker Desktop vs. install it). |
| Compose version too old | Name the found version, the required version, and the upgrade path. |
| Chosen data location can’t hardlink | Explain the consequence in concrete terms (slower imports, double disk, broken seeding), then offer: choose elsewhere, or continue in copy mode. Never silently degrade. |
| Data location already contains media | Detect it, don’t overwrite. Offer to adopt it as the existing library — this is the migration path. |
| Port already bound | Name the conflicting process where the OS permits, and offer a remap. |
Interrupted during applying |
On next run, detect and offer resume / roll back / start over. |
| Image pull fails partway | Retry the failed image only. Don’t restart the whole pull. |
| A service fails its health check | Report which, show its logs inline, and offer to continue without it where the form permits. |
| Operator declines both protocols | Valid — a library-only configuration. Skip all download-related steps entirely. |
| Run non-interactively (piped/CI) | Fail with a clear message naming what would have been asked, and point at the flag-driven equivalent. Never hang waiting on stdin. |
| Configuration already exists | Don’t offer setup. Point at A4 reconfiguration instead. |
Acceptance criteria
| ID | Requirement |
|---|---|
| A2-R1 | Invoking lemonfiber with no configuration MUST offer setup without requiring a subcommand. |
| A2-R2 | Steps preceding Review MUST NOT write to disk, except a resumable progress file. |
| A2-R3 | Review MUST show every value that will be written before anything is applied. |
| A2-R4 | Progress MUST survive quitting, and the wizard MUST resume at the step reached. |
| A2-R5 | lemonfiber MUST NOT ask any question whose answer it can reliably detect. |
| A2-R6 | PUID/PGID MUST be requested only where file ownership is user-visible (native Linux Docker). |
| A2-R7 | Native Jellyfin mode MUST be offered only on platforms where it enables hardware transcoding. |
| A2-R8 | Hardlink capability MUST be tested empirically, never inferred from filesystem name. |
| A2-R9 | “Docker absent” and “Docker present but unreachable” MUST produce distinct messages and remedies. |
| A2-R10 | An interrupted apply MUST be detected on next run and MUST offer resume, roll back, or start over. |
| A2-R11 | Image pull progress MUST be shown per image, with an expected-duration statement beforehand. |
| A2-R12 | Declining both protocols MUST be a supported path that skips all download configuration. |
| A2-R13 | In a non-interactive context, the wizard MUST fail with a message naming the required flags, and MUST NOT block on stdin. |
| A2-R14 | When configuration already exists, lemonfiber MUST direct the operator to reconfiguration rather than re-running setup. |
| A2-R15 | Total elapsed time for an operator with prerequisites in hand SHOULD be under 15 minutes (NFR). |
| A2-R16 | The web API MUST serve what setup settled — the data root, the protocols and the service user — read from the configuration as it stands, together with whether this stack is set up, on a read that offers no setup and changes nothing (N15-R1, N15-R2). |
| A2-R17 | After the start step, setup MUST wire the stack as seed does (D1), and report each connection made, skipped or failed before it finishes; a stack that did not start is not wired. |
Related
- A1 Prerequisites · A3 Credential validation
- A4 Reconfiguration — changing answers later
- G1 Interface tiers — the wizard renders in TUI and web
- G4 Error model — how every failure above is presented
- J1 First run