High-Level Overview
All three entry points reach the box through the same Tailscale tunnel. They end at the same drivers. Inside the box they take different routes. A command that drives a net through the box API posts directly to port 9000.lager supply is
one. lager python instead uploads a script to the execution service on port 5000. That
service runs the script as a subprocess. The subprocess gets full access to the lager.*
hardware libraries.
A CI runner is a developer machine that is ephemeral. It uses the same path as the command it
runs.
Terminology
Lager Box Internals
lager (started with --restart always) runs every service.
The services are peer processes, not a pipeline. A start script launches each one and
restarts it if it dies.
Only one of them calls another. The box API on port 9000 reaches the hardware service on port
8080 over HTTP. The debug and MCP services are independent. A script running under the execution
service drives its drivers itself.
Port Summary
The Exposed column describes a box that publishes its ports, which is the default. A box
started with
start_box.sh --no-publish (or LAGER_NO_PUBLISH=1) publishes none of them.
Every service still listens inside the container. The lagernet Docker network still reaches
it, and a reverse proxy owns the host ports. No Yes row answers at <box-ip>:<port>.Port 22 is the exception. SSH is the host’s own daemon rather than a published container port,
so --no-publish does not affect it.Why there are two HTTP ports
A box answers on:9000 and on :5000, and the split is historical rather than
functional.
:9000 is the box API and the primary one. Net metadata, instrument discovery,
box locking, file download and version reporting all go there.
:5000 is the older script-upload path. lager python still uses it to send a
script to the box and to stop a running one. Nothing new is added to it.
Some state answers on both. Lock state is one: the box exposes it on each
server, and the CLI reads it from :9000.
Open both to your VPN. A box that publishes only :9000 answers lager nets
and lager hello but fails lager python.
Optional Control Plane Integration
A Lager Box publishes SSH keys from a key directory,/etc/lager/authorized_keys.d/. An external control plane can therefore provision access with no human typing SSH commands. Put a <name>.pub file there, and the key reaches the box account’s ~/.ssh/authorized_keys in about five seconds. The box bind-mounts /etc/lager into the runtime container, so a control plane can write that file from inside the container. That is how it bootstraps before it has any SSH access to the box.
start_box.sh owns only the region of authorized_keys between its # BEGIN LAGER MANAGED KEYS and # END LAGER MANAGED KEYS markers. It rebuilds that region from the key directory on every pass. Two consequences follow:
-
Deleting a
.pubrevokes the key. Nothing else does; editingauthorized_keysby hand inside the marked region is undone on the next pass. -
Keys installed by other means stay untouched.
ssh-copy-idand cloud-init append outside the marked region, andstart_box.shpreserves those lines verbatim. Any other system that manages this file must claim its own distinct marker pair. Two managers that share one pair each rebuild the other’s region on every pass. -
Preserved is not the same as durable.
start_box.shpreserves a loose line against its own rebuild. It cannot preserve that line against someone else’s. A second key manager rebuildsauthorized_keysfrom its own source. It keeps only its own marked region, so it drops every loose line.start_box.shthen re-creates its region from the key directory alone. A key that never reached that directory does not come back. For this reasonlager ssh-setup,lager update, andlager installdo both. They append the public key, and they write it into the key directory aslager-box-<user>-<host>.pub. Any tool that installs a key it expects to survive must do the same.
Net Abstraction
A Net is the central abstraction that decouples CLI commands from physical hardware details. The record backingpsu1 looks like this:
psu1 keeps
working.
Supported Net Types
Execution Flows
CLI Command Execution
Step-by-step data path forlager supply psu1 voltage 3.3 --yes:
The hardware service owns and caches the driver for each physical device. It serializes access
under a per-device lock. Concurrent requests to the box API cannot interleave I/O on the same
instrument.
Custom Script Execution (lager python)
The lager python command uploads a user-written Python script to the execution service on
port 5000. This is a different path from the box API commands above. The service runs the script
as its own subprocess. The script constructs and drives its drivers in-process. It does not call
the hardware service.
lager.* hardware libraries. The box streams its output back in real time.

