Documentation Index
Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt
Use this file to discover all available pages before exploring further.
Features
-
lager box config— declarative per-box provisioning. A new top-level command tree that replaces ad-hoc SSH-and-edit workflows with a single JSON manifest at/etc/lager/box_config.jsonper Lager Box. The file declares mounts, named Docker volumes, container environment variables, host apt packages, kernel sysctl settings, in-container pip packages, cargo crates, and npm packages — andlager box config applyreconciles the box to match. Re-applying the same config is a no-op via SHA-256 comparison against the last applied snapshot, so it’s safe to wire into CI. The full operator surface:init,show,validate,diff,apply(with--dry-runand--yes),audit,status,edit(round-trips through$EDITOR/nano/viwith shim-side validation on save),copy --from --to,import FILE,export FILE, andrepair. Multi-box fanout via--box A,B,Conshowandapplyfor fleet operations. Every section has CRUD verbs:mount add/remove/list,pip add/remove/list,apt add/remove/list,cargo add/remove/list,npm add/remove/list,sysctl set/unset/list,env set/unset/list,volume add/remove/list. -
npm support inside the container. A new
npm_packagesfirst-class field onbox_config.jsonlets you declare Node.js global packages alongside the existing pip and cargo lists. Scoped packages (@types/node) and versioned packages (lodash@4.17.21) are both supported. The container Dockerfile now shipsnodejs npmand setsNPM_CONFIG_PREFIX=/home/www-data/.npm-global(pre-created and chowned to thewww-dataruntime user) sonpm install -gworks without root. -
Rust toolchain baked into the container image. rustup is now installed into
/opt/rust(owned bywww-data) withRUSTUP_HOME,CARGO_HOME, andPATHset in the Dockerfile, socargo installruns cleanly from the post-bounce loop. No more manual rust installation per Lager Box.cargo_packagesentries accept bothnameandname@version. -
Audit log of every config mutation. Every
add/set/remove/unset/applyoperation is recorded to/etc/lager/box_config.audit.log(JSONL, append-only) with an ISO-8601 timestamp.lager box config auditreads it back. Filters compose:--tail 20,--since 1h,--verb apt-add,--json. Useful for “what changed today” or “every apt operation ever.” -
Automatic rollback on failed bounces. When
lager box config apply’s container restart fails (for example, because docker rejected a malformed mount), the previously applied snapshot is restored to/etc/lager/box_config.jsonvia SSHsudo cpand a re-bounce brings the box back up on the prior good config. Sysctl values are reverse-diffed to their previous state in the same pass. The restore goes through direct SSH file ops rather than the in-container shim because the container is necessarily dead by the time the rollback fires.lager box config repair --box Xexposes the same recovery as a standalone command for situations that automatic rollback can’t reach — for example, when an operator hand-edits the JSON to invalid syntax outside the CLI. -
Sudoers auto-bootstrap.
lager install(on new boxes) andlager update(on existing boxes) now install/etc/sudoers.d/lager-box-configwith the narrow NOPASSWD grantslager box config applyneeds:apt-getwithSETENV:forDEBIAN_FRONTEND, path-scopedtee/rm/sysctl --systemfor the sysctl conf,mkdir/chownfor mount auto-prep, and a path-scopedcpfor the rollback snapshot restore. A marker file at/etc/lager/.boxcfg-sudoers-v2letslager updateskip re-bootstrapping once the current rule shape is in place. Operators never type a sudoers snippet by hand.
Bug Fixes
-
lager updatecontainer startup timeout raised from 5 to 10 minutes. First-time docker builds with cargo and npm layers were timing out on slower Lager Boxes._bounce_container’s SSH ceiling was also bumped from 300s to 900s for the same reason — covers cargo crate compilation + pip and npm install loops with headroom. -
SSH user resolution. The new shared SSH runner used by
lager box configwas callingget_box_user(box_ip)even though that helper keys by box name, so every Lager Box with a stored custom SSH user silently fell back tolagerdata. The runner now reverse-resolves the name viaget_box_name_by_ipbefore the lookup, and uses~/.ssh/lager_boxvia-ito match the rest of the CLI’s SSH conventions. -
DEBIAN_FRONTEND=noninteractiveactually propagates on apt installs. Default Ubuntu sudoers’env_resetstripsDEBIAN_FRONTENDset as asudo VAR=value cmdargument unlessSETENV:is granted. Packages with debconf prompts (iptables-persistentand similar) were hanging on a prompt that never showed. The new sudoers rule grantsSETENV:only on/usr/bin/apt-getso the env var propagates. -
cargofound inside the container during apply.start_box.sh’s cargo install loop usedbash -lc(login shell), which re-sourced/etc/profileand resetPATH— wiping the Dockerfile’sENV PATH=/opt/rust/cargo/bin:.... Switched tobash -c(non-login) so the dockerENVis honored. Same fix applied to the npm install loop. -
Real exit codes captured from pip/cargo/npm install loops. The previous
if ! cmd; then _rc=$?pattern instart_box.shcaptured$?after bash’s!inversion — so_rcwas always0even on real failures, and error messages reported(rc=0)for non-zero exits. Refactored toif cmd; then : else _rc=$?so error codes propagate accurately. -
Env values with whitespace,
$, backticks, or single quotes survive the bounce. The docker-args renderer used to emit--env 'KEY=hello world'to stdout, whichstart_box.shinterpolated unquoted intodocker run— bash variable expansion does not re-parse quotes, so values got word-split and the literal quote characters leaked through. The renderer now writes a bash-sourceable file declaringBOX_CONFIG_MOUNTS,BOX_CONFIG_ENV, andBOX_CONFIG_HOST_PATHSarrays viashlex.quote;start_box.shsources that file and uses"${BOX_CONFIG_MOUNTS[@]}"so each element preserves its content verbatim. -
lager box config editno longer rejects valid saves with non-zero editor exit. Some vim plugins return1from:wqeven when the save succeeded. The command now compares tempfile contents before and after the editor exits — content changed AND non-zero rc means “user saved, proceed”; content unchanged AND non-zero rc means “abort.” Bonus:nanois preferred overvias the fallback when$EDITORis unset.
Improvements
-
lager box config showreads as a tree. Bold uppercaseHOST/CONTAINERgroup headers with horizontal-rule underlines, bold section labels indented two spaces, and├── /└──branches under each section. Mount paths align around->; env/sysctl keys align around=; empty sections render as(none)leaves so operators discover what’s configurable. The header carries a color-coded[Up To Date]/[Unapplied Changes!]marker driven by ahashvsapplied-hashcomparison. -
applyshows the pending diff inline before confirming. When--yesis not passed, the confirm prompt is preceded by a per-field diff of what’s about to change — closes the most common pre-apply workflow (“run diff first, then apply”) into a single command. -
Tightened sudoers rule.
tee,rm, andsysctl --systemin the recommended sudoers grant are now path-locked to the exact files and flagsapplyinvokes, so a compromisedlagerdataaccount cannot escalate to root via those binaries.apt-getandmkdir/chownstay unscoped because the package list and host paths are user-defined. -
flock against the in-container shim. Two concurrent
lager box config Xinvocations against the same Lager Box used to do read-modify-write onbox_config.jsonand silently drop one mutation. The shim nowflocks/etc/lager/box_config.lockaround the whole dispatch. -
Post-apply consistency check. After the bounce + API-ready probe but before recording the new applied-hash, the apply path re-runs
validate+showagainst the box. If either drifts from what was bounced (the JSON was hand-edited mid-apply, say),applied-hashis left untouched and the operator is told to re-run apply.
Installation
To install this version:lager update --box <name> on each existing Lager Box to deploy the matching box-side code and pick up the sudoers rule.

