Architecture¶
hox is a small Python package (src/hox_cli/) with three dependencies: cyclopts for the command line, httpx2 for HTTP with the client certificate, and packaging for requirements and wheel tags. It doesn't depend on slapos.core: everything it reads from the master is available as JSON from the API.
Boundaries¶
Each remote boundary has a module and an exception type. cli.py turns those exceptions into a one-line message and exit code 1.
| Module | Talks to | Exception |
|---|---|---|
config.py |
hox.toml, parsed once into frozen dataclasses |
ConfigError |
master.py |
the master: Slap (slap protocol) and Api (hateoas reads, form submits) |
MasterError |
node.py |
the node over SSH, and local git checkouts | NodeError |
dev.py |
git and GitHub: pushed? served? Also the checks | DevError |
lock.py |
a uv.lock |
LockError |
port.py |
a Hop3 recipe, PyPI, uv | PortError |
The slap protocol needs only four kinds of marshalled value, so master.marshal replaces slapos.core's xml-marshaller in a few lines, tested against the output of slapos.core 1.26.1.
Commands to the node are fixed strings built from configuration values with shlex.quote, appended to [node] ssh.
Commands¶
cli.py has one function per command, and the output formatting. The work is in modules built on the boundaries:
status.py: what the master, the node and GitHub say, forstatusandinstances;panel.py: the panel's forms, forsetup,release,node-certificateandre6st-token. Every step looks first and acts only when what it needs is missing;deploy.py: the waits ofdeploy, aftersetupandrequest;report.py:build-report, from the node's logs;try_start.py:try-start. This module isn't imported for its functions: its source is sent over SSH and run by the node's Python, so it uses only the standard library.
port.py maps a recipe to a Release one hop3.toml section at a time (a mapper per section), then renders it into software.cfg and versions.cfg.