NAME
zeughaus-runner - execute zeughaus graphs and own their terminals
SYNOPSIS
zeughaus-runner [--feed-addr addr:port] [--state-dir path] [--keep-runs n] [join host[:port]/database]
zeughaus-runner [--state-dir path] trigger endpoint node-id [payload]
zeughaus-runner [--state-dir path] hold endpoint on|off
zeughaus-runner shim dir
DESCRIPTION
zeughaus-runner is the only process that executes a zeughaus graph. It joins a session's store as a runtime, binds one weida listener, and announces that listener's pinned endpoint, weida://sha256:fingerprint@host:port/, in its runtime row. Every top-level graph names the runner that executes it by that fingerprint; this process executes exactly the graphs that name it and publishes their values, node errors, frames and edge traffic to every editor that dials it. Several runners may join one session, each executing its own graphs.
The same process is the terminal multiplexer of its editors: every shell tab and every job run is a terminal it owns and serves over /mux, and on unix each terminal lives in a shim process that survives the runner. A job.run node starts its process in such a terminal and keeps a run directory per run. See TERMINALS and JOBS in zeughaus-architecture(7).
The runner refuses to start without a store: when the store cannot be reached it prints [runner] cannot connect to ... (start it with `spacetime start`) and exits 1. At start it prints, on standard error, its terminal locale, its state directory, the session token others join with, its endpoint ([runner] weida endpoint ...) and the fingerprint of every client it trusts.
When the listener cannot start (an identity file that does not parse, a non-loopback bind with no trusted client, a port in use), the runner prints [runner] no weida endpoint: ... and keeps running without an endpoint. Without an endpoint no graph can name it: it executes nothing and serves no terminals.
One runner per state directory: a runner reattaches every shim under <state-dir>/terminals/, so two runners on one directory fight over them. A second runner on one machine needs its own --state-dir.
OPTIONS
Options may stand anywhere on the command line. An argument the runner does not know is ignored.
--feed-addraddr:port- The address the weida listener binds, for every path it serves (frames, events, snapshots, triggers, terminals, runs, hold), not only the frame feed the name recalls. Default
127.0.0.1:0: loopback, with a port the OS chooses, so two runners on one host do not collide. addr is an IP address (IPv6 in brackets,[::1]:7443); a host name is refused. The endpoint announces the bound address; an unspecified bind (0.0.0.0) is announced as127.0.0.1. An editor on another machine therefore needs a routable address here, and a script that keeps an endpoint needs a fixed port: the fingerprint survives a restart, an OS-chosen port does not. On an address other than loopback the listener refuses to serve when no client is trusted. --state-dirpath- The state directory: credentials, workspace, terminals and runs. Default
ZEUGHAUS_STATE_DIR, else$XDG_STATE_HOME/zeughaus, else~/.local/state/zeughaus. Withtriggerandholdit is where the client identity is read from. See zeughaus-files(5). --keep-runsn- How many successful job runs stay on disk. Default
50. When a run starts, successful runs beyond the n newest are deleted; failed runs and runs without an exit record are never deleted. joinhost[:port]/database- Join the session in database database on the store at host. port defaults to
3000; a bare database means the store on127.0.0.1:3000. Withoutjoin, the runner joins127.0.0.1:3000/zeughausand prints this machine's LAN address in its place as the token others join with. Editors take the same token (see zeughaus(1)).
COMMANDS
trigger and hold make the binary a client of a running runner. They dial endpoint over weida with the client identity client.pem from the state directory, the same key an editor on this machine presents, so a script, a cron entry or a hook is the same principal as that editor. No store is involved. The fingerprint in endpoint pins the runner that answers. A missing or unparsable client.pem is an error; the client never creates one. --state-dir is the only option they read, and it may stand anywhere.
triggerendpoint node-id [payload]- Press node node-id (a decimal number) on the runner at endpoint, the way the node's button in an editor does, and wait until the press is delivered. payload is one argument of free text; it reaches the node as its trigger payload, and a job's program sees it as
ZEUGHAUS_PAYLOAD. The press is marked external: the run it starts gets a tab in that runner's lockedTriggeredgroup, where every editor sees it. Printstriggered nodenode-id. Delivered is not fired: a runner ignores a press for a node it does not execute (it logsignoring trigger for ...), and a node may refuse it (a job that is already running, a held runner), which editors show as a node error on that node. holdendpointon|off- Set or clear the runner's hold: while held, it starts no new job run and lets live ones finish. Prints
runner held;nrun(s) liveorrunner released;nrun(s) live. The flag lives in the process: a restarted runner starts released. The palette offers the same as "Runner / Hold" and "Runner / Release". shimdir- Internal; not for use by hand. The runner starts itself this way, once per terminal, with dir =
<state-dir>/terminals/<id>. The shim detaches into a session of its own, starts the programspec.jsonnames in a PTY, appends a job's output to its log, keeps the last 4 MiB of output for the next session, and serves one session at a time onsock. It ends only when a session tells it to close (a closed pane of a shell tab, a terminal closed from the palette) and removes dir then.shimmust be the first argument. See zeughaus-files(5).
ENVIRONMENT
ZEUGHAUS_STATE_DIR- The state directory when
--state-diris not given, if set and not empty. XDG_STATE_HOME,HOME- The state directory otherwise:
$XDG_STATE_HOME/zeughaus, else$HOME/.local/state/zeughaus. The screen-capture portal's restore token always goes to$XDG_STATE_HOME/zeughaus/screencast_token, else$HOME/.local/state/zeughaus/screencast_token, regardless of--state-dirandZEUGHAUS_STATE_DIR. WAYLAND_DISPLAY- When set (Linux), the
capture.screennode captures through xdg-desktop-portal's ScreenCast and Screenshot interfaces; otherwise throughscrap. It is the runner's environment that counts, since only the runner executes the node. SHELL- The program of a shell tab:
$SHELL, else the user's passwd entry, else/bin/sh. Also the shell a failedjob.runwithkeep_on_failureturns into (/bin/shwhen unset). LANG,LANGUAGE,LC_ALL,LC_CTYPE,LC_*- The locale every shell and job inherits. The runner fixes it before any thread exists: its own locale variables when it has any, else the first readable
$XDG_CONFIG_HOME/locale.conf,~/.config/locale.conf,/etc/locale.conf,/etc/default/locale(on macOS theAppleLocaledefault). A valuelocale -adoes not list is dropped, a non-UTF-8LC_ALLbecomesLANG, and a non-UTF-8 character type gets a UTF-8LC_CTYPE. Shims already running keep theirs. HOSTNAME- A fallback for the host name a new identity's certificate carries, after
/proc/sys/kernel/hostnameand/etc/hostname.
Every terminal the runner starts gets TERM=xterm-256color and COLORTERM=truecolor; a job's program also gets ZEUGHAUS_RUN_DIR (its run directory) and, when the press or a string on its run pin carried text, ZEUGHAUS_PAYLOAD.
FILES
All under the state directory; see zeughaus-files(5).
runner.pem,client.pem,clients/*.pem- The runner's identity, the local client identity it creates next to it, and client certificates provisioned by hand. Read at start.
workspace.json- Tabs, groups, splits, id counters and terminals, for the next runner.
terminals/<id>/- One directory per live terminal's shim.
runs/<id>/- One directory per job run:
log,exit,code,artifacts/.
SIGNALS
SIGUSR1- Restart in place (unix). Between two turns of its loop the runner saves
workspace.jsonandexecs the binary at the path it was started from, with the same arguments and PID; a binary rebuilt at that path in the meantime is the one that starts. Nothing drains: shells and runs live in their shims. The new process reattaches every saved terminal, rebuilds tabs, groups and splits around the ones that came back, keeps terminal and tab ids (so an editor keeps its active tab and focus), closes shims nothing refers to, and adopts every run that has no exit record yet: it waits for it and writes its exit record and artifacts, though the node that started it no longer firesokorfailedfor it. The incarnation is new, and with the default--feed-addrso is the port. When theexecfails the runner logsrestart failedand carries on.systemctl --user reload zeughaus-runnersends this signal. SIGINT,SIGTERM- Stop by draining: the runner holds itself, logs
drainingnruns; new jobs refusedonce, and exits 0 when no run is live; with no live runs it exits at once. Adopted runs count as live. Shells stay in their shims for the next runner; under systemd the unit'sKillMode=processkeeps a stop from killing them.
On builds other than unix there are no shims: terminals live in the runner process and end with it.
EXIT STATUS
The serving runner exits 0 after a drained stop, and 1 on a malformed option value, when the store cannot be reached at start, when its async runtime cannot start, or when the store's event channel closes. trigger and hold exit 0 once the press was delivered or the hold answered, and 1 on a usage error or any failure, with the reason on standard error.
EXAMPLES
Serve the default session on this machine's store, then a runner that editors on other machines can reach at a fixed address:
zeughaus-runner
zeughaus-runner --feed-addr 10.0.0.8:7443
A second runner on this machine, with its own state directory, joining the session on another host:
zeughaus-runner --state-dir ~/.local/state/zeughaus-second join 10.0.0.8:3000/zeughaus
Trigger a job from a script. The endpoint is the one the runner printed at start; for the installed unit it is in the journal. The editor shows no node ids; a node's id is its row id in the store's node table (and the id a .zgh file carries):
endpoint=$(journalctl --user -u zeughaus-runner -o cat | sed -n 's/^\[runner\] weida endpoint //p' | tail -n 1)
spacetime sql --server local zeughaus "SELECT id, display_name FROM node WHERE type_id = 'job.run'"
With node set to that id, press it with the current commit as payload; the job's program sees it as ZEUGHAUS_PAYLOAD, and the run's terminal appears in the runner's Triggered group:
zeughaus-runner trigger "$endpoint" "$node" "$(git rev-parse HEAD)"
Let live runs finish before maintenance, then release:
zeughaus-runner hold "$endpoint" on
zeughaus-runner hold "$endpoint" off
FUTURE DIRECTIONS
Not built: reading run files from the editor (the runner serves /runs, nothing calls it); webhooks relayed to /triggers through the weida broker; secrets through weida's wrapped-secret flow; routing between runners over the broker and placing one graph across several runners; queue semantics on edges, and with them more than one run at a time per job node; freezing a job as a hold policy. See zeughaus-architecture(7).
SEE ALSO
zeughaus(1), zeughaus-files(5), zeughaus-architecture(7), zeughaus-nodes(7), zeughaus-keys(7), zeughaus-install(8), zeughaus-glossary(7)
zeughaus-runner/src/main.rs, zeughaus-runner/src/cli.rs, zeughaus-runner/src/jobs.rs, zeughaus-runner/src/transport.rs, zeughaus-runner/src/mux/persist.rs, zeughaus-terminal/src/shim/, zeughaus-link/src/credentials.rs, deploy/zeughaus-runner.service