Skip to content

appliance

Thomas Mangin edited this page May 14, 2026 · 9 revisions

Pre-Alpha. This page describes behavior that may change.

Ze ships a bootable x86_64 VM appliance image: a minimal Linux system built with gokrazy that runs Ze as its only application. No package manager, no shell (except an emergency serial console), atomic A/B updates, automatic process supervision. The result is a 2 GB disk image suitable for N100-class mini PCs, Proxmox VMs, or QEMU testing.

If you just want to run Ze on an existing Linux host, use the binary or container builds from the install page instead. The appliance is for when you want a locked-down, single-purpose networking box.

What's in the image

Component Purpose
Linux kernel (x86_64) Boot and hardware drivers.
Gokrazy init Process supervisor, DHCP client, NTP, web status UI.
Ze BGP daemon with all internal plugins compiled in.
serial-busybox Emergency shell on the serial console (not started by default).

The root filesystem is a read-only SquashFS. Persistent data lives on a separate ext4 partition mounted at /perm.

Prerequisites

Install once on the build machine. macOS:

brew install e2fsprogs    # ext4 filesystem tools
brew install qemu         # VM runtime (testing only)

The gok build tool is vendored in the repo at gokrazy/tools/vendor/ and built automatically by Make. No separate install is needed.

First-time setup

After cloning the repo, download gokrazy system packages (Linux kernel, init, serial console) into the Go module cache. This is a one-time ~42 MB download. The exact versions are pinned in gokrazy/ze/builddir/*/go.mod (tracked in git, verified by go.sum):

make ze-gokrazy-deps

After this, subsequent builds work offline.

Build an image

The first build creates SSH credentials and a TLS certificate for the web UI:

make ze-gokrazy USER=admin PASS=secret

Subsequent rebuilds reuse the existing database (same credentials, same TLS cert):

make ze-gokrazy

To use a database from a running instance or from another machine:

make ze-gokrazy ZEFS=/path/to/database.zefs

The first build does six things:

  1. Builds bin/ze for the host.
  2. Runs ze init with the supplied credentials and generates a self-signed TLS certificate (the --web-cert flag wires this up).
  3. Cross-compiles Ze for linux/amd64.
  4. Builds a 2 GB disk image.
  5. Formats the persistent /perm partition.
  6. Injects database.zefs (credentials plus TLS cert) into /perm/ze/.

The database is kept at tmp/gokrazy/init/database.zefs between builds, so browsers that trust the certificate on first use will not prompt again after image rebuilds. The final image lands at tmp/gokrazy/ze.img.

Test in QEMU

make ze-gokrazy-run

This boots the image with the ports you care about forwarded to localhost:

Host port Guest service How to use it
18080 Gokrazy web UI (port 80) http://localhost:18080/
28080 Ze web UI (port 8080) http://localhost:28080/
2222 Ze SSH CLI (port 22) ssh -p 2222 admin@localhost

Quit QEMU with Ctrl-A X.

The gokrazy web UI (:18080) shows process status, stdout/stderr ring buffers, and resource usage. Default credentials for it live in gokrazy/ze/config.json under Update.HTTPPassword.

Deploy to hardware

Write the image to a USB drive or internal disk on your target machine:

# Linux
sudo dd if=tmp/gokrazy/ze.img of=/dev/sdX bs=4M status=progress

# macOS
sudo dd if=tmp/gokrazy/ze.img of=/dev/rdiskN bs=4m

Or import into Proxmox:

qm importdisk <vmid> tmp/gokrazy/ze.img <storage>

The machine boots to a serial console (115200 baud). Ze starts automatically, gets a DHCP address, and begins listening for BGP connections according to the baked-in /etc/ze/ze.conf.

Configuration

Seed config

The initial Ze config is embedded in the read-only root filesystem at /etc/ze/ze.conf. It is baked in at build time from the ExtraFileContents field of gokrazy/ze/config.json. The shipped seed enables the web UI on :8080 and SSH on :22, with logging at info level:

environment {
    log {
        level info
    }

    web {
        enabled true
        server default {
            ip 0.0.0.0
            port 8080
        }
    }

    ssh {
        enabled true
        server default {
            ip 0.0.0.0
            port 22
        }
    }
}

To change the seed config, edit ExtraFileContents in gokrazy/ze/config.json and rebuild.

Runtime config

Once booted, use ze config edit over SSH to modify the running configuration. Changes are stored in /perm/ze/database.zefs and persist across reboots and image updates.

Environment variables

Ze's environment is set in gokrazy/ze/config.json under PackageConfig:

Variable Value Purpose
ze.config.dir /perm/ze Persistent storage for database.zefs.
ze.bgp.api.socketpath /tmp/ze.socket Unix socket for the local API.
ze.bgp.daemon.drop false No privilege dropping (no zeuser on gokrazy).
ze.log info Log level.
ze.log.backend stderr Logs land in the gokrazy ring buffer.

Updating

Gokrazy supports atomic A/B partition updates over the network. Build a new image and push it to the running instance:

GOARCH=amd64 bin/gok --parent_dir gokrazy -i ze update

This rebuilds and pushes the new root filesystem without touching /perm. The machine reboots into the new version. If the update fails mid-flight, the previous root partition is still intact — you cannot brick the box with a bad update.

For full image rebuilds (when you also want to update the kernel or partition layout), use make ze-gokrazy USER=admin PASS=secret again and re-flash.

Architecture notes

Internal plugins only

Gokrazy has no shell and no PATH. Ze's external plugin mechanism uses /bin/sh -c to fork processes, so it does not work here. Instead, every Ze plugin (bgp-rib, bgp-gr, bgp-adj-rib-in, …) is compiled into the ze binary as an internal plugin and runs as a goroutine. This is the default build and covers all standard BGP functionality. If you need an external plugin, the gokrazy appliance is the wrong target.

Process supervision

Gokrazy's init restarts Ze if it exits with a non-zero status, except exit code 125 which means "don't restart". Ze handles SIGTERM for graceful shutdown. Logs (stdout/stderr) are captured in ring buffers visible through the gokrazy web UI.

Persistent storage

The /perm partition (ext4) survives image updates. Ze stores its database (database.zefs), TLS certificates, and config state there. The pointer is ze.config.dir=/perm/ze.

Repo layout

gokrazy/
  .gitignore              # excludes *.img
  tools/
    tools.go              # blank import pinning the gok version
    go.mod, go.sum        # gok dependency pins
    vendor/               # vendored gok source (~16 MB, committed)
  ze/
    config.json           # gokrazy instance config (what to build, how to start)
    builddir/
      codeberg.org/thomas-mangin/ze/
        go.mod            # ze dependency pins + relative replace directive
        go.sum
      github.com/rtr7/kernel/
        go.mod, go.sum    # Linux kernel version pin
      github.com/gokrazy/gokrazy/
        go.mod, go.sum    # gokrazy init version pin
        cmd/dhcp/         # DHCP client
        cmd/ntp/          # NTP client
        cmd/heartbeat/    # watchdog heartbeat
        cmd/randomd/      # entropy seeder
      github.com/gokrazy/serial-busybox/
        go.mod, go.sum    # emergency serial shell

The tools/vendor/ directory holds the gok tool source, committed to git. The builddir/ files are small text (go.mod + go.sum, ~27 KB). System packages (kernel, init) live in the Go module cache after make ze-gokrazy-deps.

Appliance management CLI

The ze appliance command provides a structured workflow for managing appliance fleets from a bastion host:

Command Purpose
ze appliance init <name> Interactive wizard: set target IP, SSH user, passphrase. Creates encrypted appliance directory at ~/.config/ze/appliances/<name>/.
ze appliance build <name> Build a full disk image for the named appliance.
ze appliance run <name> Boot the image in QEMU for local testing.
ze appliance push <name> OTA update to the running appliance (A/B partition swap).
ze appliance config-push <name> Push config to a running device. The device auto-reverts after 30s if BGP does not stabilize.
ze appliance config-push --all --parallel 4 Push config to all appliances in parallel.
ze appliance list List all configured appliances.
ze appliance show <name> Show appliance details.
ze appliance export <name> Export encrypted archive for disaster recovery.
ze appliance import <archive> Restore appliance from encrypted archive.

Appliance secrets are encrypted at rest using Argon2id + XChaCha20-Poly1305 when a passphrase is set.

Device-side config behavior

When config is pushed to a device (config-push), the device:

  1. Validates the new config against YANG schemas.
  2. Applies the config atomically.
  3. Waits 30 seconds for BGP sessions to stabilize.
  4. If BGP does not stabilize, automatically reverts to the previous config.

This prevents a bad config push from leaving a device unreachable.

See also

  • Install for non-appliance builds (binary, container, from source).
  • Building for host builds and toolchain setup.
  • CLI for editing the running config over SSH.
  • Web UI for the browser interface reachable on port 8080 of the appliance.

Adapted from main/docs/guide/appliance.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally