curl | sh actually work
The page an administrator uses to bring a node online: name it, get a one-line command and a six-digit code, hand both to whoever is at the machine. That is the last piece between "someone runs a command" and self-service onboarding. POST /api/compute/pairing-script now returns JSON - script, code, expiry and the command - rather than the bare script. There are two callers with two needs: a person at a terminal wants a script to pipe to sh, and the console wants all three values so it can show them once and then forget them. Returning text would have forced the console to parse a shell script to recover a six-digit number, which works until someone reformats the script. The code is shown once and there is no button to fetch it again, because the controller keeps only an Argon2id hash. That is the server's limit, not a UI choice, and offering a "show code" button would be a lie about what it can do. ## Three defects the template flow exposed Running `curl ... | sh` on node 238 - the flow the console hands out - found that the template could never pair, for three separate reasons: - It had no prompt and no code. `000000` is the placeholder that means "nobody filled this in", so the script now asks. A rendered script from the console has the code substituted and skips the prompt, so nobody is asked for a number they do not need. - It had no node name, and the name is what the code is bound to. It now asks too. The first version failed with "check the code has not expired" while the code was fine and the *name* was the placeholder - an error pointing at the wrong thing. - It hardcoded `controller.invalid` as its controller address, so every deployment but one got a script that could not resolve its own controller. The address now comes from the Host header the template was fetched with, and `UMBRA_CONTROLLER` overrides it. The error message itself was part of the problem: a DNS failure reported "check the code has not expired", which is a worse fault than no message because it sends an operator to check the one thing that was working. ## Verified `curl -fsSL http://192.168.123.14:8442/api/node/bootstrap | sh` on 238, code and name typed at the prompts: paired, agent installed, service registered, node `online`. `/api/node/bootstrap` is unauthenticated and returns only the inert template - a script that can check a machine and then stop. It is listed by name in the test that pins unauthenticated node routes, alongside /pair, with the reason each is safe. ## Also found, not fixed `VIEWS` in web/assets/admin.js is `['dashboard','accounts','organizations','audit']` but `view-projects` exists in the markup, so the projects view can never be activated. It predates this work and I have left it alone rather than change navigation behaviour as a side effect of adding a page. 525 passed, 27 skipped.
UmbraTrans local bootstrap workspace
This workspace contains the unprivileged agent-comm server requested by the Oldbox handoff. It does not contain the UmbraTrans application source: no application bundle was present locally. A staged archive, if present, is left untouched.
Layout
agent_comm/— Python 3.11+ standard-library, stdio-only MCP server.agent_comm/tests/— dependency-free unit tests.umbratrans_web/— phase-1 account/user-management backend slice (FastAPI, Argon2id, sessions, admin routes, in-memory and local JSON development repositories). It does not connect to PostgreSQL; seeumbratrans_web/README.md.umbratrans_web/migrations/— proposed, unapplied phase-1 identity SQL and its rollback.opencode.json— non-secret OpenCode registration for the confirmed NAS2 mailbox.agent-comm.local.example.json— non-secret local fallback registration..env.example— non-secret environment template.DATA-SEPARATION.md— development/production boundary.
Confirmed NAS2 mailbox
The oldbox desktop account mounted NAS2 netbackup through its user GVfs session and confirmed the UmbraTrans directory readable, searchable, and writable. The server requires the explicit NAS deployment mode, the enclosing GVfs mount, and an affirmative mount confirmation:
/run/user/1002/gvfs/smb-share:server=nas2.local,share=netbackup/UmbraTrans/agent-mailbox
Run read-only health checks:
cd /home/umbra/src/UmbraTrans
AGENT_COMM_MODE=nas \
AGENT_COMM_NAS_MOUNT=/run/user/1002/gvfs \
AGENT_COMM_NAS_CONFIRMED=1 \
AGENT_COMM_ROOT='/run/user/1002/gvfs/smb-share:server=nas2.local,share=netbackup/UmbraTrans/agent-mailbox' \
/home/umbra/.local/bin/uv run python -m agent_comm --check
If the user GVfs session is absent, do not guess another path or broaden share permissions. Use the private local fallback documented in agent-comm.local.example.json, or have a privileged operator restore the confirmed user mount.
The server speaks newline-delimited MCP JSON-RPC over stdin/stdout. It opens no network socket and never executes queued instructions.
OpenCode registration
opencode.json contains the exact NAS configuration and paths only. It must never contain passwords, tokens, private keys, or other secrets. A Mac-side reader still needs separate configuration and verification before an outbox message can be considered received.
Development workflow
The project uses uv and a workspace-local virtual environment:
cd /home/umbra/src/UmbraTrans
/home/umbra/.local/bin/uv sync --python /usr/bin/python3
/home/umbra/.local/bin/uv run python -m unittest discover -s agent_comm/tests -v
/home/umbra/.local/bin/uv run ruff check .
/home/umbra/.local/bin/uv run ruff format --check .
/home/umbra/.local/bin/uv run python -m compileall -q agent_comm
make check
The tests cover schema and size validation, atomic no-overwrite writes, restrictive modes, path traversal, symlink refusal, secret-pattern redaction, inbox processing, and read-only initialization.
The account-management slice has its own targets:
cd /home/umbra/src/UmbraTrans
/home/umbra/.local/bin/uv run pytest umbratrans_web/tests
make web-check
Its tests use the in-memory repository and generated per-run passwords; they never read a production configuration, never connect to a database, and contain no credential material.
Backups
scripts/umbratrans_backup.sh is installed root-owned as /usr/local/sbin/umbratrans-backup and scheduled by the enabled umbratrans-backup.timer. It writes only to the confirmed NAS2 netbackup/UmbraTrans/backups tree:
- PostgreSQL:
pg_dumpcustom-format logical dump, never a live data-directory copy. - Configuration: PostgreSQL configuration files only.
- Workspace: source/configuration archive excluding
.venv, Git internals, runtime data, local mailboxes, and secret-bearing files.
Each file is staged outside the share, published with an atomic rename as umbra, checksummed, and verified after publication. Retention is 30 days for PostgreSQL/configuration/manifests and 14 days for workspace archives; stale temporary files older than one hour are removed. The timer fails closed if the user GVfs mount is absent and does not mount or broaden the NAS share.
The current first-run backup set is present under:
/run/user/1002/gvfs/smb-share:server=nas2.local,share=netbackup/UmbraTrans/backups
Check timer status with systemctl status umbratrans-backup.timer and the last run with journalctl -u umbratrans-backup.service.