Skip to content

Repo: lemonfiber

Status: Accepted

The lemonfiber binary — CLI, TUI and web UI over one core. Rust, Hippocratic 3.0.

Implements: the operator-facing half of 10-functional, under the structure fixed in 20-architecture/component-model.


One binary, three surfaces, one core. It sets the stack up, runs it, and proves it’s working. It is not a general Docker manager (vision non-goals) — its value is that it knows about this stack.

lemonfiber/
├── Cargo.toml workspace
├── crates/
│ ├── lemonfiber/ bin — chooses a surface and runs it
│ │ ├── cli.rs clap: subcommands, flags, exit codes
│ │ ├── dashboard.rs ratatui: the dashboard and the log viewer
│ │ ├── ui.rs binds the socket the web surface is served on
│ │ └── main.rs surface selection
│ ├── lemonfiber-api/ lib — axum: the JSON endpoints, and the frontend served
│ ├── lemonfiber-core/ lib — all logic, no UI
│ ├── lemonfiber-ports/ lib — the boundary and its vocabulary
│ ├── lemonfiber-fixtures/ lib — the fakes for those traits
│ └── lemonfiber-manifest/ lib — stack.toml
├── assets/media-stack/ git submodule, embedded at build
├── build.rs validates submodule schema_version
├── .docs/ repo-local technical docs
└── tests/ integration + golden files

Crate responsibilities are fixed in component-model; this doc covers what’s specific to building the repo.

Per the three-layer model, lemonfiber carries its own .docs/ tree for Rust-specific technical detail that is not a product decision (those live in this spec) and not a code comment.

lemonfiber/.docs/
├── 00-index.md
├── architecture/ how subsystems are built — render loop, docker split,
│ the vpn-port-forwarding push mechanism, …
├── adr/ repo-local decisions (crate choices, not product ones)
├── conventions/ code-comments.md (copied from spec), naming, error style
├── cicd/ pipeline detail
├── runbooks/ release cutting, submodule bumping
└── features/ subsystem notes

Code links here (Q-R5); these pages cite spec requirement IDs. This is the middle layer that keeps requirement IDs out of code comments (GOV-R6) while still connecting code to the spec.

flowchart TD
start[lemonfiber invoked] --> args{subcommand?}
args -->|"up/down/doctor/…"| cli[CLI path]
args -->|"ui"| web[Start web server]
args -->|none| tty{stdout a TTY?}
tty -->|yes| conf{configured?}
tty -->|no| help[Print help, exit]
conf -->|yes| dash[TUI dashboard]
conf -->|no| wiz[Offer setup]

This realises G1-R3 and G1-R4: bare invocation opens the TUI at a terminal and prints help when piped, never blocking on stdin.

Crate Role Why
clap CLI parsing Derive; generates help and completions
ratatui + crossterm TUI ADR-0003; crossterm for real Windows support
axum Web server Minimal, tokio-native; only in the web module
bollard Docker API Reads only; only in lemonfiber-core::docker
tokio Async Shallow — component-model
reqwest Service HTTP Seed clients
serde + toml Manifest, config
etcetera Platform paths XDG / AppData / ~/Library; permissive dependencies throughout
include_dir Embed stack + web assets ADR-0005
thiserror Library errors lemonfiber-core
color-eyre Binary errors The lemonfiber crate only

unsafe is denied crate-wide (see code-standards).

assets/media-stack is a git submodule pinned to a lemonfiber-media-stack tag. build.rs:

  1. Fails clearly if the submodule is empty (git submodule update --init).
  2. Parses stack.toml and fails the build if schema_version is unsupported (ARCH-R6).

So an incompatible stack/binary pairing cannot compile, let alone ship.

Step Produces
build.rs Schema validation; embedded stack
cargo build The binary

The app is built in lemonfiber-web’s own CI and tagged there; this repo carries the result as a pinned submodule and embeds it with include_dir! (ADR-0012). No Node toolchain enters the Rust build or the operator’s machine (ARCH-R19).

Path (Linux shown) Holds
~/.config/lemonfiber/ .env, expected-state baseline, journal
~/.local/share/lemonfiber/stack/ Materialised stack files
~/.local/share/lemonfiber/config/ Per-service config (the /config mounts)
~/.local/share/lemonfiber/backups/ Backup archives

Resolved via etcetera, so macOS and Windows get their conventional locations. --stack-dir overrides the stack path (F1-R3).

Most of lemonfiber-core is testable without Docker or a terminal, by design (ARCH-R11). Detail in testing-strategy; the load-bearing point:

  • Command construction is pure → golden-file tested across every form × every platform, no daemon.
  • Docker access is behind a trait → mockable.
  • Platform is one component → faked, so all four environments run from one machine (ARCH-R35).
Here (lemonfiber) Spec
How the render loop is built That it must not block (B3-R4)
Which crate parses TOML The manifest contract
The exact port-push shell mechanism That the port must be re-pushed (C2-R5)
Rust module structure That core cannot render (ARCH-R11)

Rule of thumb: what and why are the spec’s; how, in Rust is this repo’s .docs/.

This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page