Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Deploying to devices

A bundle goes to a Linux device as a release named by its manifest’s hash. On the device, taktwerk agent verifies and activates releases under an install root and drives their systemd units, or its own built-in supervisor where systemd does not run; the host (taktwerk device, taktwerk deploy, the desktop app) calls it over SSH and copies files by SFTP.

taktwerk device add line-1 --host 192.168.1.20 --user taktwerk --privilege sudo
taktwerk device verify line-1                  # pins the host key, probes the device
taktwerk deploy plant --device line-1          # the plan
taktwerk deploy plant --device line-1 --yes
taktwerk device install-unit line-1 --yes      # once, through sudo
taktwerk device start line-1 plant --yes      # or --at 11:15 --yes: once, later
taktwerk device engine line-1 plant status     # JSON, exit 1 unless it runs (see Operating a running engine)
taktwerk device tunnel line-1 plant            # http://127.0.0.1:<port>/ to the engine

The desktop app and taktwerk serve do the same from the workbench’s Devices page, on the same registry. The bundle file, the manifest, the device layout, the units and the agent’s commands are specified in Devices and bundles; a running engine is operated with the commands of Operating a running engine.

Devices

The host keeps its devices in devices.toml in the user’s config dir (~/.config/taktwerk/ on Linux, %APPDATA%\taktwerk\ on Windows, TAKTWERK_CONFIG_DIR overrides): how to reach each one, its pinned host key and what the last verify found. No passwords.

[[device]]
name = "line-1"
host = "192.168.1.20"
port = 22
user = "taktwerk"          # logs in, owns the install root
privilege = "sudo"         # how unit changes get root: none, sudo or su
root = "/opt/taktwerk"     # the install root (default)
host_key = "SHA256:…"      # pinned by the first verify

[device.verified]          # the last verify: written by verify, read by the app
arch = "aarch64"
glibc = "2.36"
systemd = true
engines = ["0.8.1"]
# also: at, machine, free_bytes, root_exists, root_writable, taktwerk, bundles, problems, notes

Login. An SSH agent’s identities (SSH_AUTH_SOCK; on Windows the OpenSSH agent’s pipe), then key files (-i, else ~/.ssh/id_ed25519, id_ecdsa, id_rsa), then a password asked for on the terminal. Nothing is stored.

Host keys. The first verify shows the device’s host key fingerprint and pins it after a yes on the terminal, or when --accept-host-key <fingerprint> names it. Every later connection refuses any other key. When a device was reinstalled, check its new key and pin it with verify --accept-host-key.

Privilege. Only unit changes need root: start, stop, restart, enable, disable and install-unit with --yes run the agent through the device’s method, and so does creating a missing install root the login user cannot create. sudo runs with -S and asks for the login user’s password only when sudo does; su runs on a PTY and asks for root’s. Everything else runs as the login user, who should own the install root. Reading the journal (logs) needs the login user in the systemd-journal group.

Verify probes over one SSH exec: uname -m, the glibc version against the engines’ floor (2.25), systemd, whether the root exists and is writable, free space under it, installed engines and /etc/os-release; then agent info when an engine is installed. Problems (an unsupported architecture, no or too old glibc, a root the login user cannot use) stop a deploy; notes (no systemd, so the built-in supervisor runs the bundles; a root the deploy will create) do not.

WSL2 distributions

On Windows a WSL2 distribution of the PC is a device without SSH: every command is a wsl.exe -d <distro> -u <user> --exec sh -c …, every file the stdin or stdout of one, and root is wsl -u root, which needs neither a password nor Windows admin rights. There is no host key to pin; verify probes at once. taktwerk device add wsl-ubuntu --wsl Ubuntu (the user defaults to the distribution’s default user) or Add as device on the Devices page writes:

[[device]]
name = "wsl-ubuntu"
transport = "wsl"
distro = "Ubuntu"
user = "fabian"

WSL stops a distribution shortly after its last wsl.exe ends. The workbench keeps each WSL device’s distribution running while it is open; a deployed engine stops with it unless instanceIdleTimeout=-1 is set under [general] in %UserProfile%\.wslconfig. With systemd in the distribution ([boot] systemd=true in /etc/wsl.conf) enabled bundles start when the distribution does; without it the built-in supervisor runs them.

Scan connects to the SSH port of every address of an IPv4 subnet (at most a /16, 64 at a time, 500 ms each) and lists the hosts that answer, with their banner. It never logs in.

Deploying a release

taktwerk deploy <bundle> --device <name> probes the device, checks the bundle for its architecture, builds the manifest for this taktwerk version, reads the active release’s manifest and the unit’s state, and prints the plan: the engine to install, the files to send (those the active release lacks or holds with other content), bytes against free space, and what stands in the way. A running unit is a problem: stop it first. With --yes:

  1. Create the install root if it is missing (through root when the login user cannot).
  2. If bin/taktwerk-<version> is missing: upload the engine under a temporary name, make it executable, run it with --version and refuse it unless it reports this host’s version, then rename it.
  3. Upload the changed files to bundles/<name>/staging/<hash>/ and the manifest’s canonical bytes to .taktwerk/manifest.json there.
  4. agent activate <bundle> <hash> --yes: the device takes the files the upload left out from the active release, verifies everything and switches current.

Then, on the first deploy and when companions change, device install-unit --yes, and device start <bundle> --yes, device enable <bundle> --yes to start at boot.

The engine comes from --engine <file>, else from a directory of per-architecture engines (TAKTWERK_ENGINES, which the desktop app sets to its bundled engines/), laid out as the release builds them: x86_64-linux-gnu/taktwerk, aarch64-linux-gnu/taktwerk, armv7-linux-gnueabihf/taktwerk. A Linux taktwerk deploying to its own architecture can send itself; it must have the http feature for the unit’s --http. The released device engines are built without the host side (feature host: SSH, SFTP, device, deploy), which the agent does not need.

Live view

taktwerk device tunnel <device> <bundle> reads the bundle’s http address from its env file (through agent status), listens on a local port (--local, default any free loopback port) and forwards each connection over SSH to that address on the device; it prints the URL and runs until Ctrl-C. The write token stays on the device unless --token puts it in the URL. The workbench forwards the same way and adds the token itself. On a WSL distribution each connection is a wsl.exe running agent connect <address>, which pipes it to the engine, so neither WSL’s localhost forwarding nor a Windows process on the same port is involved.

Records

The unit records with --record-dir <root>/bundles/<bundle>/records, so every file recorder of the project writes there. taktwerk device records <device> <bundle> copies that directory by SFTP into records/<device>/<bundle>/ of the current directory (--dir for another), where taktwerk serve and its Records screens list the runs:

taktwerk device records line-1 plant          # 12 copied (48211 bytes), 3 unchanged, in ./records/line-1/plant
  • A file is copied when the local one is missing or differs: by size, then modification time, then, for a closed file, the SHA-256 its run’s SHA256SUMS lists. A copy is checked against that sum, written under a hidden .tmp name, given the device’s modification time and renamed into place.
  • Run it again at any time: it copies only what is new or grew, and continues a closed file a cut-off sync left half copied.
  • A run still recording is copied as far as it is written; the readers stop at its last complete line. Its open part is copied again while it grows, and the local .open copy goes once the device has closed the part. Per run the parts come first, CLOSED last.
  • Nothing on the device changes and nothing local is deleted but such an .open copy: parts the device’s retention removed stay.
  • --json prints the report (copied, unchanged, bytes, removed). The device’s engine must have agent records (deploy with this version).