Skip to content

Publishing Jug programs over SSH

Run utils/jug-publish.sh in the kernel repository to publish built ELF programs through your SSH agent. It generates and commits sums.txt itself. The defaults match configs/jug.cfg:

Setting Default
Remote directory /mnt/cephfs/cdn/content/jug
Public repository https://cdn.vxn.dev/jug
Catalog sums.txt
Batch input iso/bin/*.elf (case-insensitive extension)

The developer machine needs Bash, Python 3.9+, and OpenSSH. The swarm node needs Python 3.9+ and write access to the remote directory as your SSH user. The receiver is sent with each SSH invocation; nothing is installed on the node. The filesystem must support advisory locks, hard links, atomic rename, and fsync. All objects and staging files stay on the same CephFS mount.

First publication and batch updates

Replace user@swarm-node with your SSH destination or an existing SSH config alias. Load your key into the agent and establish the host's trusted SSH host key normally before publishing. The utility uses batch authentication, your SSH config, and your local agent; it does not forward the agent to the node.

export JUG_SSH_HOST=user@swarm-node
ssh-add -l

# Validate the built binaries and show their checksums; no connections or writes.
./utils/jug-publish.sh --all --dry-run

# Publish the whole staged bin directory and check it through the public CDN.
./utils/jug-publish.sh --all

iso/bin becomes /mnt/tar/bin on USB boots and /mnt/iso/bin on CD boots. Rebuild changed applications and refresh the ISO staging tree through the normal make build / make build_iso workflow before publishing. The publisher does not compile applications. To use another built bin directory:

./utils/jug-publish.sh --host cdn-node --all --bin-dir /path/to/bin

Batch publication adds or updates the selected programs and retains other catalog entries. It does not delete programs that disappear from your local bin directory. Before committing, the receiver verifies every retained binary too; a missing or damaged retained program stops the publication.

Update one program

Pass one or more freshly built ELF files. Other programs stay listed:

./utils/jug-publish.sh --host cdn-node ../r2_app/c/tnt/tnt.elf

# A source filename longer than r2's eight-character program name needs an alias.
./utils/jug-publish.sh --host cdn-node --name memento \
  ../r2_app/cpp/memento-hello/memento-hello.elf

Memento can be published and installed like the other programs. On a system booted with the updated graphics kernel and Memento, hosted Jug offers a coordinated relaunch after installation. Confirm with Y after saving work: the desktop closes its windows and hosted children, then the kernel launches the downloaded version without rebooting. SESSION.CFG and the downloaded programs remain on the RAM disk; log in again with the existing credentials. For the first deployment of this support, boot the rebuilt image once. See Jug for compatibility and relaunch behavior.

Each program must be a statically linked r2 ELF64 x86-64 executable, at most 4 MiB, with loadable segments inside r2's process window. Program names are case-insensitive, at most eight characters, using letters, digits, _, or -. --name applies to one file only. Run ./utils/jug-publish.sh --help for flags; --remote-dir and --repo-url override the destination together when hosting elsewhere. Configure Jug's repo to match if you change the public root.

Publication order and recovery

Jug publication snapshots and validates local ELF bytes, verifies SSH staging, locks and merges the catalog, flushes immutable versions, commits sums.txt last, then checks public HTTP downloads.

Publication order. Interruptions before the catalog rename leave the previous catalog live. Public verification runs after commit, so its failure does not undo a completed publication.

The publisher snapshots local bytes before connecting, computes SHA-256, and checks the ELF structure. The receiver stages and independently verifies the upload, then acquires .jug-publish.lock before reading and merging the current catalog. A busy lock reports an error; retry after the other publication ends.

Each version is created without overwriting an existing file at v/<first-40-SHA256-digits>/<program>.elf. The full 64-digit SHA-256 and size are verified even when that path already exists. The shorter directory name keeps paths within Jug's 63-character limit; a prefix collision fails safely.

The receiver flushes binaries and directories, saves the previous catalog in .jug-history/, then atomically renames a flushed temporary catalog to sums.txt last. Its format is compatible with Jug:

# updated YYYY-MM-DD HH:MM:SS UTC
# SHA-256  bytes  path (immutable versions)
<64-digit SHA-256>  <byte count>  v/<40-digit prefix>/tnt.elf

Old versions are retained, so a Jug client using an earlier catalog can still download the bytes matching its checksum. Existing flat catalogs, including plain sha256sum output, are migrated to version paths; the original flat files are also retained. Interruptions before the catalog switch leave the previous catalog live, though an unreferenced version directory can remain. Do not overwrite files inside v/, delete old versions while clients may have cached catalogs, or remove the persistent lock file.

By default the utility downloads the public catalog and every uploaded binary over HTTP(S) and verifies their checksums after the SSH commit. HTTPS uses the developer machine's certificate trust store. --no-verify skips this check when the public endpoint cannot be reached from the developer machine.

Exit 0 means publication and the requested checks succeeded. Exit 1 means local or SSH publication failed; if the SSH connection was lost near the commit, inspect the public catalog before retrying. Exit 3 means the node committed successfully, but public CDN verification failed. Check the edge cache, routing, and mounted directory before retrying. Repeating the same upload reuses the immutable binary objects.

The successful output prints the previous catalog's private backup path. To roll back, hold the same .jug-publish.lock, copy that backup to a temporary file in the repository, set mode 0644, and atomically rename it over sums.txt; then verify the public catalog. The retained versions make the old catalog usable without restoring binary files.

Nginx origin and edge proxy

The Nginx service serving files must mount CephFS content at its configured document root. Publishing onto one swarm node's host filesystem works only when the serving tasks actually see that shared content. The SSH path is a host path; a container's mount path can differ.

Use the server-context snippets in configs/jug-origin.nginx.example on the service reading files and configs/jug-edge.nginx.example on the public proxy. Adjust the origin mount path and upstream name to your deployment, and integrate the locations with your existing configuration. Test with nginx -t before reloading each service.

sums.txt must be fetched fresh: turn off proxy caching for its exact URL and send Cache-Control: no-store. Nginx's proxy_cache off also disables an inherited cache. On the file-serving origin, disable open_file_cache for the catalog so a cached descriptor cannot serve the previous inode after a rename. Version URLs can use long-lived caching because their contents never change. See the official proxy cache documentation, response headers documentation, and open file cache documentation.

Block /jug/.jug* on both layers: it contains staging data, the lock, and private history. The examples do so independently of other dotfile rules. Do not apply long-lived caching to errors or inherit negative caching for new version URLs.

On r2, fetch the new catalog with Refresh in the Jug window or fg jug update, then Download / fg jug install tnt (or fg jug upgrade). A running program changes only after it exits and relaunches or you use Jug's Restart action / fg jug restart tnt.

Development checks

The tests run the complete SSH receiver through a local SSH substitute, including all current iso/bin binaries. They cover old version retention, legacy catalogs, corrupted and interrupted transfers, locks, commit failures, quoting, and public CDN verification. No real node is contacted.

bash -n utils/jug-publish.sh
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests/jug_publish -v

Related documentation: SDK Overview, Jug Program Manager, Build and Run, Memento.