NAME
zeughaus-install - build, install and run zeughaus from source
SYNOPSIS
spacetime start
spacetime publish --server local zeughaus --module-path zeughaus-module
cargo run -p zeughaus-runner
cargo run -p zeughaus
deploy/install.sh
DESCRIPTION
A zeughaus session is three processes: the store (SpacetimeDB with the zeughaus-module published into it), which holds the graph; the runner (zeughaus-runner), which executes the graph and owns the terminals; and any number of editors (zeughaus), which edit and display but never execute. They start in that order. See zeughaus-architecture(7) for how they talk.
This page goes from an empty machine to a running session, first by hand from the checkout, then installed as systemd user services. It covers Linux, where zeughaus is developed (Wayland, systemd). Every command runs from the root of the zeughaus checkout unless a section says otherwise.
CHECKOUT LAYOUT
The workspace depends on the QUIC transport weida by path (weida = { path = "../weida/crates/weida" } in the workspace Cargo.toml), so both repositories are checked out next to each other. This page uses ~/src:
~/src/
├── zeughaus/ this workspace; every command below runs here
└── weida/ the QUIC transport, ../weida as seen from zeughaus/
mkdir -p ~/src
cd ~/src
git clone https://github.com/tuco86/zeughaus
git clone https://github.com/tuco86/weida
cd zeughaus
REQUIREMENTS
- Rust
- A stable toolchain through
rustup. Norust-toolchainfile pins one. The workspace uses edition 2024 and declares norust-version;weidadeclaresrust-version = "1.88", the highest floor either repository states. The store module and the browser build compile to WebAssembly:rustup target add wasm32-unknown-unknown. - SpacetimeDB CLI 2.10
- The
spacetimebinary runs the store and publishes the module. Its version must share major and minor with the client SDK, which the workspace pins asspacetimedb-sdk = "2.10"(locked at 2.10.0); the module is built againstspacetimedb = "2.10". The installed unit runs it from~/.local/bin/spacetime, so install it there. With the CLI present,spacetime version install 2.10.0 --useselects the matching version andspacetime --versionshows it. - weida
- The sibling checkout
../weidafrom CHECKOUT LAYOUT. - PipeWire headers and libclang
zeughaus-capturelinks the systemlibpipewire-0.3andlibspa-0.2through bindgen on Linux, so the build host needs their development headers and libclang.- xdg-desktop-portal
- Screen capture (
capture.screen) goes through the portal's ScreenCast and Screenshot interfaces whenWAYLAND_DISPLAYis set. Needed on the machine that runs the runner, and only for that node. - LM Studio (optional)
- The
llm.chatnode talks to LM Studio's OpenAI-compatible endpoint, by defaulthttp://localhost:1234/v1(itsbase_urlsetting); an emptymodelsetting takes the first loaded model. Without LM Studio the other nodes work andllm.chatreports that it cannot reach it. - ffmpeg (optional)
- Only
zeughaus ctl <socket> recorduses it, in the headless editor built with theremotefeature. Nothing in a normal session calls it. - trunk and a WebGPU browser (optional)
- Only for the browser build:
cargo install trunk wasm-bindgen-cli, and a browser with WebGPU. See THE BROWSER BUILD.
BUILD
cargo build -p zeughaus-runner -p zeughaus
zeughaus-module is excluded from the native workspace; spacetime publish builds it for its WebAssembly target in the next step. The dev profile optimizes dependencies (opt-level = 3) and keeps the workspace crates at opt-level = 1, so a debug build draws large graphs without stutter.
PUBLISH THE MODULE
The module is published into a running store. Start the store in a terminal of its own and leave it running:
spacetime start
It listens on 0.0.0.0:3000, its default --listen-addr. In a second terminal, publish the module as the database zeughaus, the session every process joins when it is given no join argument:
spacetime publish --server local zeughaus --module-path zeughaus-module
Publish again after every change to the schema in zeughaus-module/src. The Rust bindings both binaries use are generated from the module and checked in; after a schema change, regenerate them before building:
spacetime generate --lang rust --out-dir zeughaus-sync/src/module_bindings --module-path zeughaus-module
A fresh checkout needs no generate: the checked-in bindings match the checked-in module.
RUN BY HAND
Three processes, in this order, each in its own terminal. Do not run them next to the installed services (see TROUBLESHOOTING).
The store, already running from the previous section:
spacetime startThe runner. It executes the graph and owns the terminals:
cargo run -p zeughaus-runnerAt start it prints, on standard error:
[runner] terminal locale (...): LANG=... [runner] state dir /home/you/.local/state/zeughaus [runner] session 10.0.0.8:3000/zeughaus -> http://127.0.0.1:3000 / zeughaus [runner] weida endpoint weida://sha256:<fingerprint>@127.0.0.1:<port>/ [runner] trusted client sha256:<fingerprint>The
sessionline carries the token another machine joins with (this machine's LAN address, port 3000, databasezeughaus). Theweida endpointline is the URL the runner announces to editors through the store, and the onezeughaus-runner triggerandzeughaus-runner holdtake (see zeughaus-runner(1)). On first start the runner createsrunner.pemandclient.pemin its state directory (--state-dir, elseZEUGHAUS_STATE_DIR, else$XDG_STATE_HOME/zeughaus, else~/.local/state/zeughaus).The editor, as many as you like:
cargo run -p zeughausIt prints
[stdb] session token: <token>when it reached the store. The token is also in the command palette (Ctrl+Space) as a copy command. It finds the runner through the store and dials its endpoint with theclient.pemthe runner left in the shared state directory. The tab bar shows one section per connected runner. See zeughaus(1).
INSTALL AS USER SERVICES
deploy/install.sh
Installs this checkout as the user's running zeughaus (Linux, systemd user manager). cargo and spacetime must be on PATH. In order, it:
- runs
cargo install --lockedforzeughausandzeughaus-runnerinto~/.cargo/bin($CARGO_INSTALL_ROOT/binor$CARGO_HOME/binwhen set). This happens first, so an already running stack keeps serving while they compile. - copies
zeughaus-store.serviceandzeughaus-runner.serviceto~/.config/systemd/user/, writes the desktop entry~/.local/share/applications/net.doodleshnookie.Zeughaus.desktopwith the binary directory filled in, and installs the icon as~/.local/share/icons/hicolor/scalable/apps/net.doodleshnookie.Zeughaus.svg. It refreshes an existingicon-theme.cachein that hicolor directory (never creates one) and runskbuildsycoca6when present, thensystemctl --user daemon-reloadand enables both units. - starts
zeughaus-storeand waits up to ten seconds forspacetime server ping localto answer; if it does not, it exits and points at the store's journal. - publishes the module:
spacetime publish --server local zeughaus --module-path zeughaus-module, without-y. - reloads
zeughaus-runnerif it is active, else starts it. - sends
SIGUSR1to every runningzeughauswhose/proc/<pid>/exeis the installed binary and which handles that signal; each one reopens as it was. An editor built before the handler existed is left alone with the messageeditor <pid> cannot restart itself; close and reopen it.
The units hard-code their binaries: zeughaus-store runs ~/.local/bin/spacetime and zeughaus-runner runs ~/.cargo/bin/zeughaus-runner. With a different install root, the desktop entry follows it but the units do not.
The units
zeughaus-store.servicespacetime start --non-interactive: listens on0.0.0.0:3000like a store started by hand, so other machines can join, and fails instead of asking when the port is taken.Restart=on-failureafter 2 s. Wanted bydefault.target.zeughaus-runner.service~/.cargo/bin/zeughaus-runner, withRequires=andAfter=on the store. A runner that cannot connect exits, andRestart=on-failurestarts it again after 3 s, which covers a store still starting after login.KillMode=process: every terminal lives in a shim process the runner forked, and these share the unit's cgroup; with this setting a stop or restart ends only the runner, and the next one reattaches the shims, so shells and job runs survive.ExecReloadsendsSIGUSR1: the runner saves<state-dir>/workspace.jsonand executes its binary again at the same path under the same PID, so a freshcargo installis picked up without touching a terminal.PATHis set to~/.local/bin,~/.cargo/bin,/usr/local/bin,/usr/binand/bin; shells started in terminals are login shells and build their own. Wanted bydefault.target.
Desktop entry
The editor is started from the application menu as Zeughaus. The entry's file name and its StartupWMClass are the window's Wayland app id, net.doodleshnookie.Zeughaus; that is how the compositor finds the entry and its icon, because the window itself cannot send one on Wayland. The entry runs the editor without join, so it joins the local session.
Logs
journalctl --user -u zeughaus-runner
journalctl --user -u zeughaus-store
Add -f to follow. Editors started from the menu are not units.
JOINING FROM ANOTHER MACHINE
Machine A runs the store and a runner; machine B runs an editor (or another runner). Below, 10.0.0.8 stands for A's LAN address.
The store on A already listens on all interfaces (0.0.0.0:3000, by hand and as a unit). The runner does not: its weida listener binds loopback with an OS-chosen port by default, and it announces the address it bound, so an editor on B could reach the store but not the runner (no values, frames or terminals). Start A's runner on a routable address with a fixed port:
cargo run -p zeughaus-runner -- --feed-addr 10.0.0.8:7443
0.0.0.0 does not help: an unspecified bind is announced as 127.0.0.1. For the installed runner, override ExecStart in a drop-in opened with systemctl --user edit zeughaus-runner:
[Service]
ExecStart=
ExecStart=%h/.cargo/bin/zeughaus-runner --feed-addr 10.0.0.8:7443
The runner only accepts clients whose certificates it trusts: its own client.pem plus every clients/*.pem in its state directory, read at start. The editor never creates a key. Copy A's client.pem into B's state directory:
mkdir -p ~/.local/state/zeughaus
scp 10.0.0.8:.local/state/zeughaus/client.pem ~/.local/state/zeughaus/client.pem
chmod 600 ~/.local/state/zeughaus/client.pem
Then join with the token A printed ([runner] session ... or [stdb] session token: ...), of the form host[:port]/database; the port defaults to 3000, and a bare database means the store on 127.0.0.1:3000:
zeughaus join 10.0.0.8:3000/zeughaus
zeughaus-runner join 10.0.0.8:3000/zeughaus adds a runner on B to the same session; it keeps its own state directory and executes only the graphs that name it.
B's editor needs A's port 3000 (TCP) and the runner's --feed-addr port (QUIC, which is UDP).
THE BROWSER BUILD
cd zeughaus
trunk serve
Run from the zeughaus/ crate directory, not the workspace root. It serves on http://127.0.0.1:8080 (zeughaus/Trunk.toml). The browser needs WebGPU: iced_nodegraph has no WebGL fallback. The browser editor edits locally: it does not connect to a store, so it neither sees nor changes a session, and no runner computes what it shows. The native-only plugins (capture, db, record, llm) are not in it, and it draws no terminals.
UPDATING
git pull
cd ../weida && git pull && cd ../zeughaus
deploy/install.sh
install.sh is also the update: it installs over the running binaries, then reloads the runner (same PID, terminals stay) and restarts every editor of this install in place. To confirm the runner picked up the new binary:
readlink /proc/$(systemctl --user show -p MainPID --value zeughaus-runner)/exe
The path no longer ends in (deleted) once it has.
The publish step never passes -y. When a schema change needs the data cleared, the publish refuses and the script stops there, before the runner and the editors are touched. Clearing the database deletes every graph in the session; that decision is the user's. Once it is settled, for example with spacetime publish --server local zeughaus --module-path zeughaus-module --delete-data, run install.sh again.
TROUBLESHOOTING
- A second store fails on the port
- Port 3000 is held by another
spacetime start: one started by hand next tozeughaus-store, or the other way round. The unit uses--non-interactiveand fails at once;install.shthen stops at "store". Stop the one started by hand. - A second runner on the same state directory fights over the shims
- Terminals live in shims under
<state-dir>/terminals/, and a runner reattaches every shim it finds there. Never run a runner by hand next to the installed one. A second runner on one machine needs its own--state-dir. - Runner or editor cannot talk to the store on connect
- The
spacetimehost andspacetimedb-sdkdiffer in major or minor version, and the wire format does not match.spacetime --versionmust report 2.10;spacetime version install 2.10.0 --usefixes it. - The runner exits at once
[runner] cannot connect to http://127.0.0.1:3000 / zeughaus: ... (start it with `spacetime start`). The runner refuses to start without a store. Start the store, and publish the module if the database does not exist yet. As a unit it retries every 3 s on its own.- The editor edits a graph nobody runs
[stdb] cannot reach ... -- editing locally. Without a store the editor edits a local scratch graph under the tab bar sectionLocal: nothing computes it, the status bar says so, and it is gone on close unless saved as a.zghfile withCtrl+S. Start the store and restart the editor.- The editor shows no values from a runner
[editor] no client identity in ...: no runner ever ran on this machine and noclient.pemwas copied here. See JOINING FROM ANOTHER MACHINE.- The Wayland window has no icon
- winit cannot hand a window icon to a Wayland compositor; the compositor finds it through the desktop entry named after the app id
net.doodleshnookie.Zeughaus. Install the entry and the icon withdeploy/install.sh. On KDE, a stale menu cache updates afterkbuildsycoca6or the next login.
FUTURE DIRECTIONS
Not built: a browser editor that syncs with the store. The wasm build edits locally only.
SEE ALSO
zeughaus(1), zeughaus-runner(1), zeughaus-architecture(7), zeughaus-files(5), deploy/install.sh, deploy/zeughaus-store.service, deploy/zeughaus-runner.service, deploy/net.doodleshnookie.Zeughaus.desktop, zeughaus/Trunk.toml, CLAUDE.md