← The specification

Roadmap

Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page

Status: Accepted

The version table below is generated from the version manifests by scripts/gen_roadmap_table.py — edit a manifest, not the table. just generated rewrites it and refuses a hand edit. Everything else on this page is written by hand.

Sequenced to keep a working, demonstrable artifact at every milestone. The ordering principle: build the thing being wrapped before the wrapper. lemonfiber should target a stack that is already known-good, so that debugging is never “is it lemonfiber or is it the stack?”


The version train

Releases ship as a serial train of semver versions, each scoped by a manifest in 70-operations/versions/ — the single source of truth for a version’s goals and status; the table below renders that truth. Minors are themed feature slices; patches (x.y.Z) are hotfixes; a major carries the capability that justifies the number. No version ships a stub — a release refuses while a requirement it locks is not yet built (OPS-R54).

Version Milestone Delivers Status
0.1.0 M2 Core: manifest, compose driver, CLI Released
0.2.0 M3 Setup wizard + doctor Released
0.3.0 M4 Backup & restore Released
0.4.0 M4 Auto-wiring, seed, quality, trace and the first-content walk Released
0.5.0 M5 How the product speaks — errors, notifications, health, dashboard data Released
0.6.0 M5 Trust checks: VPN, storage, queue health Released
0.7.0 M5 Trust checks: providers, support bundle, auto-remediation Released
0.8.0 M6 Live TUI: forms, lifecycle, logs and diagnostics as surfaces Released
0.9.0 M7 Speaking plainly — interface tiers and the plain-language layer Released
0.10.0 M7 The front door, its security, and the privacy stance Released
0.11.0 M8 The household asks — requests, identity, client apps Released
0.12.0 M8 Living within limits — disk, retention, bandwidth Released
0.13.0 M9 Changing your mind — reconfigure, migrate, uninstall, credentials Released
0.14.0 M9 Keeping it running — updates, backup, rollback, the journal Released
0.15.0 M9 Away from the keyboard — remote control, autostart, customisation Released
0.16.0 M14 Plugins — what one is, installing one, and what that leaves readable Released
0.17.0 M14 Plugins — what the bundled stack can do, and where a plugin comes from Released
0.18.0 M14 Plugins — the calls that configure what one installed, and what they may carry where Staged
0.19.0 M16 The household’s library: find, ask for and play, under the member’s own limits Planned
0.20.0 M11 Ecosystem glue: cross-seed, autobrr, quality-sync, subtitles Planned
0.21.0 M11 Ecosystem glue: self-healing, cleanup, transcoding, statistics Planned
0.22.0 M12 Safely reachable — remote access and one account Planned
0.23.0 M13 See everything — metrics, dashboards, uptime Planned
0.24.0 M15 Runs without Docker — engine abstraction, Podman, native Planned
1.0.0 M6 The dashboard — a bare lemonfiber opens it. Everything specced is built, and the interfaces stop moving. Planned

Patch releases (hotfixes)

A patch (x.y.Z) fixes an already-released version from its tag, bypassing the goal gate — a cited fix plus maintainer authorisation (OPS-R37). Patches appear in the changelog but never enter the goal-locked train.


M0 — Specification ✅

This repo. Decisions recorded, contracts defined, standards set.

Exit criteria: all sections Accepted; stack.toml schema defined precisely enough to implement against.


M0.5 — Governance in force

Before any implementation repo exists, the rules that bind it must. Standing this up after code has already landed means retrofitting citations onto history, which nobody does.

Deliverable Notes
50-governance accepted Done — the rules themselves
spec-check workflow Citation extraction, merge-base resolution, ordering check
Repo templates Issue templates, PR template with the Spec: trailer, SECURITY.md
OVERRIDES.md Append-only override record, initially empty
Spec-side CI Duplicate-ID, withdrawn-ID reuse, link resolution, affected-repo declaration
Branch protection Checks required on every repo

Exit criteria: a PR citing a non-existent identifier is closed with guidance; a PR citing a valid one passes; an override is recorded and opens a tracking issue.

Why before M1: the very first commit to lemonfiber-media-stack should cite a requirement. If governance arrives later, the initial history is exempt by accident, and “we’ll backfill” never happens.


M1 — lemonfiber-media-stack works standalone (with brand alongside)

No Rust involved. The stack must be usable with bare docker compose. The brand repo — tokens, marks, and its contrast CI — can be finished in parallel here, since the web UI that consumes it doesn’t arrive until M7 and nothing gates on it earlier.

Deliverable Notes
compose.yml + compose/ All 19 services, one fragment per profile, one atomic profile each, pinned image tags
stack.toml Manifest: services, profiles, forms, ports, health endpoints
.env.example Every variable documented inline
Storage overlay stacks/compose.storage.nas.yml
Service configs recyclarr/, homepage/, caddy/
CI Per-form docker compose config; manifest ↔ compose parity; mount, binding, killswitch and tag lints, each with a negative test
README.md Standalone usage without lemonfiber

Exit criteria: every form starts cleanly via raw docker compose --profile …; hardlink import verified end-to-end; VPN killswitch verified by hand.

Why first: proves the profile model empirically. If partial stacks don’t actually boot, that invalidates ADR-0002 and everything downstream.


M2 — lemonfiber core: manifest, compose driver, CLI

Headless. No TUI yet — every capability reachable as a plain subcommand, which keeps it scriptable and testable.

Deliverable Notes
Workspace + cargo-dist scaffold Three-platform CI matrix from day one
stack.toml parser + validation Compile-time schema_version check in build.rs
Embedded assets Submodule + include_dir! + --stack-dir override
Platform detection macOS / Linux-native / Linux-Desktop / Windows-WSL2
Compose command builder Pure function, golden-file tested
Form closure + composition Union of closures, intersected with configured protocols (B1-R4, B1-R5)
lemonfiber up/down/restart/ps/logs/pull Non-interactive
.env read/write Comment- and order-preserving

Exit criteria: lemonfiber up tv matches hand-written docker compose exactly; golden tests cover every form on every platform.

Form composition is in 1.0, not deferred. lemonfiber up full proxy is a set union over profiles — trivial to implement — and it is what makes proxy viable as a form rather than a special-cased flag.


M3 — Setup wizard + doctor

The milestone that delivers the actual product thesis.

Deliverable Notes
Wizard state machine Explicit steps, back-navigation, resumable
Preflight checks Docker present, Compose ≥ minimum, daemon reachable
Empirical hardlink test Create, stat, compare inode/link count
Storage-mode detection Filesystem type, network mount, exFAT, WSL2 boundary
ProtonVPN guidance Explicitly covers the NAT-PMP-at-key-generation gotcha
Jellyfin mode selection Platform-aware; only offers native where it helps
lemonfiber doctor Check trait; every failure carries a remedy
VPN leak test exec into gluetun + qbittorrent, compare public IPs

Exit criteria: a fresh machine reaches a running tv form in under 15 minutes with no service web UI opened. Leak test provably catches a misconfigured VPN.


M4 — Seed & backup

Turns config from precious into reproducible (P6). Spans two versions: 0.3.0 (backup & restore — a configured stack captured to an archive) and 0.4.0 (auto-wiring & seed — the graph rebuilt from nothing).

Deliverable Notes
ServarrClient Shared API client across Sonarr/Radarr/Lidarr/Prowlarr
API key extraction Parse each app’s config.xml
Download client registration SABnzbd + qBittorrent into every *arr and Bindery
Root folder registration Per media type
Prowlarr app sync Push indexers to each *arr
Bindery indexer wiring Torznab endpoints — app sync does not cover it (D1-R15); deferred, its API cannot be pinned without a live instance to verify against
Jellyfin → Seerr identity One household account, not two (D1-R7)
Homepage key injection Widgets work on first boot
Drift-aware writes Never revert an operator’s manual change (C9-R3)
lemonfiber backup / restore Quiesced, not a live SQLite copy (E3-R1)

Exit criteria: rm -rf config && lemonfiber up tv && lemonfiber seed restores a working stack in under 2 minutes. Seed is idempotent — running twice changes nothing.


M5 — Trust checks

The P3 trust pillar made real (0.5.0–0.7.0): the foundations everything is expressed in terms of, then the diagnostics that prove the stack is safe to run, beyond the setup-time checks M3 established.

Deliverable Notes
VPN egress proof Continuous confirmation traffic leaves through the tunnel, not just at setup
Storage & hardlink verification The data root still links; degraded capability alerted
Queue & provider health Stuck queues and rotted credentials surfaced as findings
Auto-remediation Where a fix is safe and unambiguous, offer to apply it

Exit criteria: each trust check catches its failure on a deliberately broken stack and states a remedy.


M6 — Live TUI

The second surface (0.8.0, closing at 1.0.0), ratatui over the same core the CLI drives — the read-only dashboard and the interactive surfaces over it. It is what 1.0.0 delivers, and the reason that release is a major rather than another minor.

Deliverable Notes
Ratatui event loop Async, non-blocking; Docker I/O never stalls a frame
Dashboard Health, VPN IP + forwarded port, transfers, disk, queue depth — the read-only screen
Form switcher Interactive picker with closure preview
Log viewer Streamed, filterable, scrollback
Doctor view Interactive re-run, remedies inline
Wizard in TUI Same state machine, richer presentation

Exit criteria: dashboard sustains 1 Hz refresh at <2% CPU idle; input stays responsive while pulling images.


M7 — Web surface & UX

A third surface (0.9.0–0.10.0): a web view over the same core, plus the cross-cutting UX that spans every surface. The view is served by lemonfiber as a JSON API and drawn by a static app kept in its own repo (ADR-0011).

Exit criteria: the web surface renders the same live state the TUI shows and can complete setup, over the core with no surface-specific logic.

Setup is completable from the web surface (G1-R14), so it writes. The C6 goals guarding a writable loopback API are locked by 0.9.0.


M8 — Household & content

Content and household features (0.11.0–0.12.0): managing what the stack holds and who in the household reaches it.

Exit criteria: a household member’s request flows end to end, and content management acts without reverting an operator’s manual choices.


M9 — Lifecycle & maintenance

Living with a running stack (0.13.0–0.15.0): reconfiguration, migration, uninstall, credentials, remote control, autostart & boot persistence, hosting lemonfiber’s own long-running commands, stack and self updates, backup, rollback, the journal, customisation and the service catalogue.

Exit criteria: every lifecycle operation is reversible or explicitly confirmed, and an unattended stack recovers across a reboot.


M10 — Release engineering

Ships with 1.0.0: the install paths a non-contributor follows, and the gate that ships no stubs (OPS-R54).

Deliverable Notes
cargo-dist release workflow mac (arm64/x86_64), Linux (gnu/musl), Windows
homebrew-tap Auto-published by CI
Shell + PowerShell installers curl | sh, irm | iex
Real Windows + Linux testing Not just “it compiles”
Docs site Generated from this spec

Exit criteria: a non-contributor installs and runs on all three platforms following only the README.


The rest of the way to 1.0.0 (M11–M16)

These were once sequenced behind a second epoch opening at 2.0.0. There is no second epoch: 1.0.0 means everything specced is built and the interfaces stop moving, so everything below is on the way to it rather than after it. They ship as minors on the one train, authored to the same bar as everything before them.

The plugin system comes first of them, because it changes the stack manifest and introduces the capability vocabulary — every feature after it is then built on the shape that ships rather than retrofitted onto it, and the schema has several minors of use behind it before 1.0.0 freezes it.


M14 — The platform

0.16.0–0.18.0. Other people’s stacks, and the surface that makes them possible. F3–F8 and F10 are Accepted; recipes (F8) are locked by 0.18.0.

Deliverable Notes Version
Plugin manifests F3 — declarative data describing a service, whose container lemonfiber writes rather than accepts (ADR-0021) 0.16.0
The capability vocabulary F4 — what a capability is, what its probes must show, and what happens when two things claim one 0.16.0
Plugin lifecycle F6 — rehearse, install, prove, verify the stack around it, and reverse through the journal on either failure 0.16.0
Plugin provenance F7 — four redundant answers to why did my stack do that 0.16.0
Writing a plugin F10 — the generated schema, the two vocabularies an author may name from, and a template that validates and proves unmodified 0.16.0
The converted wiring F9-R4 — the stack asks for capabilities rather than naming services, and a wiring that is genuinely about one service is shown as by-name 0.16.0
Capabilities of the bundled services F9 — they declare what they can do and pass the probes 0.17.0
The plugin catalogue F5 — reviewed, signed, and an operator’s own source on the same technical terms with unreviewed said plainly 0.17.0
Mobile client handoff G9 — a household member’s phone reaches the library in one step 0.17.0
Plugins and wiring over the API F6-R14 to F6-R16 and F4-R26 to F4-R29 — what is installed, installing, updating and removing, the wiring, and the operator’s choice of what fills a capability with their reason for it, served where the companion reaches them (ADR-0031) 0.18.0
Recipes and named adapters F8 — the ordered calls that turn a first-run flow into data, and what they may carry where (ADR-0022) 0.18.0
The household’s web surface G10 — a member signs in through the same form and is shown what the core says they may do, with approval and allowance stated before they ask and refusals read as refusals; playback and the door a member faces wait on the core 0.18.0
Integration keys C10 — a named key with one scope, read, act or member:<account>, minted at the command line or over the web API with the password given again, shown once and revoked by name (ADR-0037) 0.18.0
Home Assistant F12 — the stack’s health, alerts, a short list of controls and a member’s own library in Home Assistant, through sdk-python and an integration key 0.18.0
The image for a NAS L3 — an image of lemonfiber built by the release, with templates for Unraid, TrueNAS SCALE, Synology and Compose, holding the host’s Docker as a native install does (ADR-0038) 0.18.0
An assistant’s way in F13 — an MCP server for operators and members over integration keys, its tools generated from the contract and its writes rehearsed, with members let in only once the operator allows it 0.18.0

Recipes come last of the three, deliberately. A manifest describes a container whose reach lemonfiber fixes, so the worst one can do is fail to parse. A recipe runs with lemonfiber’s own authority on behalf of a manifest a stranger wrote, and it is the riskiest mechanism in this design — so it arrives in 0.18.0, after two versions of operating the simplest plugin there is.

The capability work is split across 0.16.0 and 0.17.0, and the difference is visible to an operator rather than internal. 0.16.0 ships the vocabulary and the converted wiring: a plugin can claim a capability, be refused for claiming one it cannot demonstrate, and take over one another plugin was filling. 0.17.0 ships the bundled declarations, which is what makes a plugin able to stand in for a bundled service. The model has to exist before a service can be described in it, and describing them all is the larger half of the work.

The wiring conversion sits with the vocabulary rather than with the declarations because five of F4’s requirements are what it makes buildable — reporting an ask nothing fills, journalling a substitution, refusing one that would leave a capability unfilled — and none of them has a subject while every consumer still names a service.

Exit criteria for 0.16.0: a plugin authored outside this project installs, proves itself, and adds a service to a running stack with no change to lemonfiber — and removing it puts the stack back.

Exit criteria for 0.17.0: a plugin replaces a bundled service, and nothing that consumed that service’s capability had to be changed for it to work. The first-run flows that some substitutions need wait for F8, which 0.18.0 locks.


M16 — The household’s library

0.19.0. What a member can find, ask for and play, on their own account and under their own limits (ADR-0027).

Deliverable Notes
Playing on the member’s own account D11 — a grant on the member’s media-server account, and a guard in front of every byte the media server serves
What a title is, and where it is served D11 — artwork, details, how far through a member got, and where each title streams, all read as the member
Searching and asking D4-R15, D4-R16 — a member’s own search and ask, answered by the core under their limits
The member’s name, and a default member D6-R19, D6-R20 — a greeting, and the member’s side the operator previews without reading anybody’s
The household’s web surface, playing G10-R4, G10-R7, G10-R8 — asking and playing in the browser, on the same reads

Exit criteria: a member finds a title they may watch, sees its artwork and what it is, and plays it at home on their own account; a title outside their limits is never offered, located or streamed.


M11 — Ecosystem glue

0.20.0–0.21.0. The integrations a mature stack grows into, each one verified rather than merely wired.

Deliverable Notes
Cross-seeding H1 — open-source and self-hostable, no proprietary matcher
Announce-driven grabbing H2 — autobrr, for what the scheduled search misses
Quality-profile sync H3 — one preset, applied everywhere it means something
Subtitles H4 — fetched, matched, and checked for the right film
Queue self-healing H5 — the stalls C7 categorises, fixed where fixing is safe
Library cleanup H6 — what to remove, proposed and confirmed
Transcoding H7 — the shape of the library that plays everywhere
Playback statistics H8 — what the household actually watched

Exit criteria: each integration proves its effect on a live stack, not its presence in a configuration file.


M12 — Safely reachable

0.22.0. Reaching the stack from outside the house without opening it to the world.

Deliverable Notes
Remote access I1 — a self-hosted overlay, not a port forward
One account I2 — single sign-on across the services the household touches

Exit criteria: a household member reaches the library from outside with no port exposed to the internet, signing in once.


M13 — See everything

0.23.0. The stack’s own telemetry, for the operator who wants graphs rather than a dashboard.

Deliverable Notes
Notification back-ends B9 — self-hostable delivery channels, and a message confirmed delivered rather than fired into the dark
Metrics & dashboards K1 — exported, not screen-scraped
Uptime monitoring K2 — the stack notices its own absence

Exit criteria: a deliberate outage appears in the metrics and raises the notification the trust checks already know how to send.


M15 — Runs anywhere

0.24.0. The container engine becomes one implementation behind an abstraction rather than an assumption, which lifts the single-engine non-goal (ADR-0010).

Deliverable Notes
Container-engine abstraction One port, Docker behind it; nothing above it names an engine
Podman A first-class alternative, not a compatibility shim
Native, without containers Services run as processes; the same manifest describes both
Upgrade in place An existing install moves to 0.24.0 keeping its configuration and data

Exit criteria: the same stack starts, passes doctor and serves media under Docker, under Podman, and with no container runtime present.

Beyond 1.0.0

Not scheduled. Recorded so they’re not rediscovered as novel.

Idea Note
Keyring-backed secrets OS keychain instead of plaintext .env
GUI Tauri; only if terminal-first proves to be the barrier

Several former post-1.0 candidates are scheduled features on the way to 1.0.0: autobrr/cross-seed (H1, H2), Janitorr/Maintainerr (H6), transcoding (H7, open-source Unmanic), and the plugin system (F3), and remote access for the household (I1).

Explicitly rejected

See vision § non-goals.