-
Notifications
You must be signed in to change notification settings - Fork 2
appliance
Pre-Alpha. This page describes behavior that may change.
Ze ships a bootable 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. The default target is x86_64; set GOKRAZY_ARCH=arm64 for a native Apple Silicon QEMU image.
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.
| Component | Purpose |
|---|---|
| Linux kernel | Boot and hardware drivers. |
| Gokrazy init | Process supervisor, entropy seeding, watchdog heartbeat. |
| Ze | BGP daemon with DHCP client, NTP, and all internal plugins compiled in. |
| ze-serial-shell | Authenticated emergency shell on the serial console (login required). |
Ze owns network configuration (DHCP) and time (NTP), so the gokrazy default DHCP and NTP packages are excluded from the image. interface { dhcp-auto true } discovers all ethernet interfaces and runs DHCP on each; environment { ntp { enabled true } } syncs the clock.
The root filesystem is a read-only SquashFS. Persistent data lives on a separate ext4 partition mounted at /perm.
The bundled gokrazy init is kept current. The pinned release pulls in golang.org/x/net 0.56.0 or newer, which clears CVE-2026-25680.
Install once on the build machine. macOS:
brew install e2fsprogs # ext4 filesystem tools
brew install qemu # VM runtime (testing only)For ISO creation, also install grub-mkstandalone (or grub2-mkstandalone) and xorriso; ze appliance iso checks for them before staging.
The gokrazy build tool (gok) is vendored in the repo at vendor/github.com/gokrazy/ and built automatically by Make. A vendored gokrazy updater handles OTA pushes. No separate install is needed.
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-depsAfter this, subsequent builds work offline.
The first build creates SSH credentials and a TLS certificate for the web UI:
make ze-gokrazy USER=admin PASS=secretSubsequent rebuilds reuse the existing database (same credentials, same TLS cert):
make ze-gokrazyTo use a database from a running instance or from another machine:
make ze-gokrazy ZEFS=/path/to/database.zefsThe first build does these things:
- Builds
bin/zefor the host. - Runs
ze initwith the supplied credentials and generates a self-signed TLS certificate. - Cross-compiles Ze for the target arch (
linux/amd64by default,linux/arm64withGOKRAZY_ARCH=arm64orimage.archinappliance.json). - Builds a 2 GB disk image.
- Formats the persistent
/permpartition. - Injects
database.zefs(credentials plus TLS cert) into/perm/ze/. - Bakes a build manifest (appliance, timestamp, arch, image name) into
/perm/ze/build.jsonsoze versionon the installed box can report which build it runs.
After injecting the database, the builder verifies the write rather than trusting the tool's exit code. It reads the /perm image back and confirms the source database bytes are present (catching silent debugfs failures, which can exit 0 on internal errors), runs e2fsck -fn for ext4 structural integrity, and on a Linux root also loop-mounts the partition. The build fails loudly on the first error.
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.
make ze-gokrazy-runThis 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.
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=4mOr import into Proxmox:
qm importdisk <vmid> tmp/gokrazy/ze.img <storage>The machine boots to a serial console (ttyS0, 115200 baud; the gokrazy config sets SerialConsole: ttyS0,115200 and loglevel=8). ze-serial-shell requires authentication with the local admin credentials before granting shell access. If the credentials database is missing or unreadable, access is granted without authentication for emergency recovery. When admin-enabled: false is set in the appliance config, the serial console denies the built-in admin (fail-closed) and prints "local admin login disabled".
On first boot the device auto-inits: if it has a database.zefs but no committed config, Ze enters bootstrap mode (DHCP client on every ethernet interface, SSH enabled) so the box is reachable before any operator config exists. See Zero-Touch Provisioning for the bootstrap details. Once configured, Ze starts automatically, gets a DHCP address, and listens for BGP connections per the effective 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.
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.
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. |
Gokrazy supports atomic A/B partition updates over the network. Push the most recent image to the running instance:
bin/ze-setup appliance push <name>This 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, so you cannot brick the box with a bad update.
For full image rebuilds (when you also want to update the kernel or partition layout), run ze appliance build <name> again and re-flash.
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.
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.
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.
Ze reaches out to external services from background workers, off the critical boot and commit path. An unreachable NTP time source, RADIUS change-of-authorization server, or managed fleet hub no longer stalls startup or a config commit/apply. The daemon comes up (or the commit completes) and the worker keeps retrying in the background.
A physical interface named in the config but absent from the machine is warned and skipped rather than aborting the whole apply. One image can boot on hardware where a configured NIC is missing or a cable is unplugged, without failing every other interface in the config. A genuine error on a present interface still aborts and rolls back.
gokrazy/
.gitignore # excludes *.img
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
cmd/ze-serial-shell/ # serial console login gate (replaces serial-busybox)
cmd/ze-gok/ # gok wrapper (built by make bin/gok)
The gok build tool source is vendored in the main vendor/github.com/gokrazy/ directory. 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 build commands (ze appliance) and PXE provisioning (ze install remote) live in the build-host ze-setup binary, which keeps build-host tooling out of the on-device ze binary. Build it from the repo root:
make ze-setup # produces bin/ze-setupThe ze appliance command (in the ze-setup binary) provides a structured workflow for managing appliance fleets from a bastion host. Each appliance has its own directory under ~/.config/ze/appliances/<name>/ with a JSON config, encrypted secrets, and a TLS certificate (override the location with --dir or ZE_APPLIANCE_DIR).
| Command | Purpose |
|---|---|
bin/ze-setup appliance init <name> |
Interactive wizard: set target IP, SSH user, passphrase. Creates the encrypted appliance directory. |
bin/ze-setup appliance build <name> |
Build a full disk image (assemble ZeFS + gok + ext4 inject + checksum + manifest). --all builds every appliance. |
bin/ze-setup appliance kernel <name> |
Download or build the installer kernel (reads arch/profile from config). |
bin/ze-setup appliance initrd |
Download or build the installer initrd. |
bin/ze-setup appliance iso <name> |
Build a bootable installer ISO from an existing image. |
bin/ze-setup appliance run <name> |
Boot the image in QEMU for local testing. |
bin/ze-setup appliance push <name> |
OTA update to the running appliance (A/B partition swap). |
bin/ze-setup appliance config-push <name> |
Push config to a running device. The device auto-reverts after 30s if BGP does not stabilize. |
bin/ze-setup appliance config-push --all --parallel 4 |
Push config to all addressed appliances in parallel. |
bin/ze-setup appliance list |
List all configured appliances. |
bin/ze-setup appliance show <name> |
Show config summary, cert expiry, managed status. |
bin/ze-setup appliance export <name> |
Export encrypted archive for disaster recovery (--all for everything). |
bin/ze-setup appliance import <archive> |
Restore appliance from encrypted archive. |
Appliance secrets are encrypted at rest using Argon2id + XChaCha20-Poly1305 when a passphrase is set. For fleet operations, ze appliance unlock starts a passphrase agent (like ssh-agent) holding the derived key in memory.
When config is pushed to a device (config-push), the device:
- Validates the new config against YANG schemas.
- Applies the config atomically.
- Waits 30 seconds for BGP sessions to stabilize.
- If BGP does not stabilize, automatically reverts to the previous config.
This prevents a bad config push from leaving a device unreachable.
When the appliance runs the VPP data plane, reserve hugepages at boot so VPP starts on a performant memory layout. Add image.hugepages to the appliance config and ze appliance build bakes the hugepages kernel arguments into the boot command line (the checked-in gokrazy config is left untouched):
"image": {
"arch": "amd64",
"size-bytes": 2147483648,
"memory": "8gb",
"hugepages": { "size": "1gb", "page-size": "2mb" }
}hugepages.size is the total reservation and page-size is 2mb or 1gb. Declare image.memory so the build rejects a reservation over half of target RAM and ze appliance run sizes QEMU's memory to match. 1 GiB pages need CPU pdpe1gb support and CONFIG_HUGETLBFS in the kernel profile. ze doctor surfaces both, and warns when the reservation is missing, insufficient, or clamped.
ze appliance iso builds a bootable UEFI installer ISO around an existing appliance image. The image is gzip-compressed inside the ISO (a 2 GiB image with ~100 MiB of content compresses to roughly 100 MiB), and a minimal initrd decompresses and writes it to the target disk on boot. The architecture comes from the appliance config (image.arch); amd64 produces BOOTX64.EFI, arm64 produces BOOTAA64.EFI.
bin/ze-setup appliance build prod
bin/ze-setup appliance kernel --profile hardware prod # installer kernel (reads arch from config)
bin/ze-setup appliance initrd # installer initrd
bin/ze-setup appliance iso prodUse ze appliance iso --check to verify prerequisites (kernel, initrd, grub, xorriso) before building. The Makefile shortcut make ze-iso CONFIG=prod.json SSH_PASSWORD='...' runs the whole pipeline (init, kernel, initrd, image, ISO).
The ISO verifies the embedded image checksum before writing and matches the booted media by a builder-generated ze.media-id token. It does not regenerate credentials or fetch a separate ZeFS database; the installed disk receives the selected image bytes, including the /perm/ze/database.zefs and /perm/ze/build.json that build already injected. After installation, the system powers off so removable media can be removed before the next boot.
With more than one fixed disk, pass an explicit whole-disk target at ISO creation time:
bin/ze-setup appliance iso --target /dev/vda prodThe ISO can be written with dd, Etcher, or Rufus in DD mode. Ventoy works when the installer kernel includes loop and FAT/exFAT support (the hardware profile has this; the qemu profile does not).
The installer is a single statically-linked Go binary that runs as PID 1 in the initrd. There is no busybox and no shell: the old busybox shell initrd is gone. The same binary drives both install paths, downloading the disk image over HTTP for a PXE install or mounting the appliance ISO read-only for USB/optical media, then writing the selected image to a safe target disk.
It is built to survive an awkward first boot on unknown hardware:
| Feature | Behavior |
|---|---|
| Boot-NIC pinning | The kernel command line can pin the installer to the NIC it booted from (by MAC), so it does not race DHCP across every interface. |
| DHCP recovery | If the pinned NIC has no carrier or DHCP fails, the installer retries rather than aborting. |
| MAC-pinned recovery | A retry reuses the same boot-NIC identity, so it stays on the interface that has connectivity. |
| Gated rescue shell | On repeated failure the installer offers a recovery console (retry network and install, show network state, reboot, power off) gated behind the local admin password. With no admin credential set, it reboots after a short delay instead of exposing an open shell. |
After an HTTP install the installer injects the credentials database and reboots. After an ISO install it powers off so the media can be removed before the next boot.
The installer kernel (ze appliance kernel, for PXE and ISO installs) supports named profiles that select the driver set. This is separate from the on-device runtime kernel below.
| Profile | Drivers | Use case |
|---|---|---|
qemu (default) |
virtio NIC + block | QEMU tests, fast build. |
hardware |
virtio + EFI + framebuffer + Intel/Realtek/Broadcom/Mellanox NICs + AHCI + NVMe | Bare-metal PXE/ISO install. |
hardware-kms |
hardware plus Intel iGPU KMS |
Boxes with integrated graphics. |
Set image.kernel-profile in appliance.json so ze appliance kernel <name> picks it up, or pass --profile. The build script verifies every required option resolved to =y and fails loudly otherwise. The build backend defaults to Docker when available and falls back to the shared QEMU backend; force one with --builder docker or --builder qemu.
Installer artifacts land under a single build/ tree: the installer kernel at build/kernel/Image, the initrd at build/initrd/initrd.img.gz, and (for PXE) the assembled TFTP root under build/pxe/. Resolved kernel and initrd artifacts are also cached under ~/.cache/ze/ and reused on later runs.
Kernel rebuilds are incremental. The QEMU kernel builder keeps a compiler cache (ccache) across runs, so a second kernel build after a small config change is much faster than the first.
The on-device runtime kernel is built separately from the installer kernel. Ze's L2TP LNS and PPPoE paths need PPP/L2TP support compiled into the runtime kernel, which the pinned upstream gokrazy kernel is not assumed to provide. The repo-local runtime kernel config keeps these built in (=y): CONFIG_PPP, CONFIG_PPPOL2TP, CONFIG_PPPOE, CONFIG_L2TP, and CONFIG_L2TP_V3 (the former CONFIG_L2TP_NETLINK was removed in kernel 7.0, where netlink is always built with L2TP). Tunnel and WireGuard support comes from CONFIG_NET_UDP_TUNNEL, CONFIG_WIREGUARD, and CONFIG_TUN. The image also sets a serial console (SerialConsole: ttyS0,115200) and loglevel=8 for full boot diagnostics on a headless box.
Build the runtime kernel before building an appliance that terminates L2TP subscribers:
make ze-kernel # default: docker, amd64
make ze-kernel KERNEL_BUILDER=qemu # force the shared QEMU backend
make ze-kernel KERNEL_ARCH=arm64 # runtime arm64 kernel
make ze-gokrazy USER=admin PASS=secretmake ze-kernel emits vmlinuz, lib/modules/, and DTBs and overlays them into the module-cache copy used by make ze-gokrazy. The first overlay backs up the pinned cache; make ze-kernel-clean restores it. The pinned appliance kernel is version 7.1.1.
The ze.wait installer kernel parameter sets the maximum number of server probe attempts before giving up (default 30, 0 to skip the probe). It is useful for slow NIC carrier detection or debugging PXE boot sequences. See Zero-Touch Provisioning for the full installer kernel command line.
- Install for non-appliance builds (binary, container, from source).
- Zero-Touch Provisioning for PXE-based bare-metal provisioning.
- 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.
Unreviewed draft. This wiki was authored in bulk and has not been reviewed. File corrections on the issue tracker.
- Overview
- YANG Model
- Editor Workflow
- Archive and Rollback
- System
- Interfaces
- VRRP
- BFD
- FIB
- OSPF
- IS-IS
- MPLS / LDP / RSVP-TE
- RSVP-TE
- SRv6
- Static Routes
- Policy Routing
- Firewall
- Traffic Control
- Class of Service
- L2TP/PPP
- PPPoE
- VPP Data Plane
- RPKI
- IPsec VPN
- TACACS+ AAA
- RADIUS AAA
- AS112 DNS
- Authorization
- Fleet
- BGP
- Starting and Stopping
- Show Commands
- Monitoring
- Flow Export
- DDoS Mitigation
- Anomaly Detection
- Health Checks
- Audit Trail
- Production Diagnostics
- Logging
- Operational Reports
- Healthcheck
- Self-Update
- Zero-Touch Provisioning
- MRT Analysis
- Upgrade and Restart
- Storage
- Policy
- Core
- Resilience
- Validation
- Capabilities
- Address Families
- Protocol
- Subsystems
- Infrastructure
- Route Server at an IXP
- Transit Edge with RPKI
- Public Looking Glass
- ExaBGP Migration Walkthrough
- FlowSpec Injection
- Chaos-Tested Peering
- AS Path Topology