Skip to content

starting and stopping

Thomas Mangin edited this page Jul 26, 2026 · 3 revisions

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

Running Ze is the same as running any other Unix daemon with one small twist: SSH credentials must exist before the daemon can expose its CLI. The first time you install Ze, you run ze init once to create the credentials, and after that you treat the process like any other long-running service.

The first run

ze init

ze init is interactive. It prompts for a username, a password, an SSH host, an SSH port, and a name (instance name). Defaults are 127.0.0.1:2222. The database (database.zefs) is created with bcrypt-hashed credentials; the ED25519 host key is auto-generated on first daemon start. You only run it once per install. Running it a second time fails with error: database already exists, which is deliberate.

If you genuinely need to re-initialise (credentials lost, database corrupted, fresh start on the same host), stop the daemon first and then use --force.

ze signal stop
ze init --force

--force moves the old database to database.zefs.replaced-<date> as a backup before creating a new one. The data can come from piped stdin; only the confirmation "Type 'yes' to proceed" requires a terminal (either stdin if it is a terminal, or /dev/tty if stdin is a pipe).

Starting the daemon

ze start                          # Start from the database
ze start <config-file>            # Start with an explicit config file
ze -d start <config-file>         # Start with debug logging
ze -                              # Read the config from stdin

A bare ze <config-file> used to work and no longer does. Position 1 of ze accepted a command name or a free-form config path, so a config file named like a command (bgp, signal) was dispatched as that command and never loaded. The path now follows the start keyword. The one free-form value left at position 1 is -, which cannot collide with a command name and reads the config from stdin.

The useful flags are all listed in the command reference. The short version:

Flag Purpose
-d, --debug Shorthand for ze.log=debug and ze.log.relay=debug.
-f <file> Use filesystem storage instead of the blob database.
--plugin <name> Load an extra plugin at start. Repeatable.
--pprof <addr:port> Start the pprof HTTP profiler.
--chaos-seed <N> Deterministic chaos testing. 0 disables with zero overhead.
--web <port>, --mcp <port> Start the web UI and the MCP server on specific ports.

Stopping

ze signal stop                    # Graceful stop, no GR marker
ze signal restart                 # Graceful stop WITH GR marker, for restart

ze signal stop tears down every session with NOTIFICATION Cease (code 6, subcode 2, Administrative Shutdown) and shuts the daemon down cleanly. Peers drop the routes. Use it when you want to stop, not when you want to replace the binary.

ze signal restart is the one you want for binary upgrades. It does the same shutdown but writes the GR marker file on the way out, so when you start the daemon again, peers that negotiated Graceful Restart hold their routes during the handover. The full story is in Upgrade and restart.

Both commands exit non-zero on failure. 1 means the daemon is not running. 2 means no SSH credentials (you forgot ze init). 4 means delivery failed for another reason.

Reloading

ze signal reload                  # Re-parse the on-disk config and apply

This is the hot-reload path. It re-parses the config, hands the new tree to the config provider, and every subsystem reconfigures in place. Peers get added, removed, or updated without restarting the daemon. If a change forces a session reset (you changed a capability the peer cannot renegotiate, you moved the local IP, you changed authentication), Ze tells you which sessions will bounce before it bounces them.

The same thing works through Unix signals: SIGHUP reloads, SIGTERM and SIGINT shut down gracefully, SIGUSR1 triggers a status callback logged via the structured logger at Warn level. Use the SSH path from scripts so you get a non-zero exit code on failure. Use the signals when you are already at a shell.

Every participant is applied

A reload can be carried out two ways: as a set of decomposed operations (add this peer, remove that one), or as a plain apply of each participant's config diff. The operation path is now taken only when it covers every participant that would otherwise be applied, using the same predicate that selects participants for verification, so a participant cannot be verified by one path and skipped by the other.

Before that, one participant yielding operations put the whole transaction on the operation path, and a participant with real diffs but no decomposer was verified and then never applied. Its change was dropped while the reload reported success. A reload that both added a BGP peer and changed a WireGuard interface did exactly this: the interface participant logged nothing at all between received SIGHUP and sighup reload complete.

The trade-off is deliberate. Falling back all-or-nothing costs that transaction's cross-participant operation ordering, which is what reloads had before the operation path existed. Losing ordering is recoverable; losing the change is not.

A plugin whose config root is a nested path (ddos/local, anomaly/detect, traffic/usage) is now driven by reload too. Changed keys were previously grouped under their top-level root only, so a lookup for the plugin's declared nested root found nothing and the plugin was skipped for both verify and apply while the daemon reported the reload complete. Startup was unaffected, because it reads the declared roots directly, which is why only reload was dead. Eight plugins were affected.

Under systemd

[Unit]
Description=Ze BGP Daemon
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/ze start /etc/ze/config.conf
ExecReload=/usr/local/bin/ze signal reload
ExecStop=/usr/local/bin/ze signal stop
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
StandardError=journal
SyslogIdentifier=ze

[Install]
WantedBy=multi-user.target

Initialise credentials before you enable the unit.

sudo ze init
sudo systemctl enable --now ze

LimitNOFILE=65535 matters: Ze can hold a lot of file descriptors under load. Letting the systemd default cap you at 1024 is a common reason a large deployment drops sessions.

Health checks

ze status                         # Liveness, 0 = running, 1 = not

ze status dials the SSH port without completing the handshake. It is cheap enough to run from a systemd watchdog or a load-balancer TCP probe. For a deeper check, go through the CLI.

ze cli -c "peer list"             # Brief peer list
ze cli -c "bgp summary"           # Per-peer state and uptime

If you want a dedicated service healthcheck that drives route announcement and withdrawal from a shell command, that is a different feature. See healthcheck.

See also

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

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally