Skip to content

Run a validator

A Zentalk validator runs the relay binary, routing encrypted messages through the network. Install it with Homebrew or the installer script, then manage it as a system service.

Homebrew install
brew tap ZentaChain/tap
brew install zentalk-validator

This installs the relay binary (currently v1.2.1) as zentalk-validator — the binary only; key, configuration, and service setup are separate steps (see below). Upgrade with brew upgrade zentalk-validator; remove with brew uninstall zentalk-validator && brew untap ZentaChain/tap. The tap also provides a zentalk-api formula (currently v1.0.0) for the API server binary.

Install with the installer script (Linux, systemd)

Section titled “Install with the installer script (Linux, systemd)”

The install.sh script in the zentalk-validator repository performs a full installation as root:

Scripted install
sudo OPERATOR_ADDRESS=0xYourEthereumAddress bash install.sh

What it does:

  1. Creates a dedicated system user zentalk (no home directory, no login shell).
  2. Creates /opt/zentalk-validator/{keys,data,logs} with keys/ at mode 700.
  3. Downloads the release binary for your architecture (amd64, arm64, armv7) to /usr/local/bin/relay.
  4. Verifies the SHA256 checksum against the release checksums.txt and aborts on mismatch.
  5. Verifies the GPG signature on checksums.txt when a .asc signature is published (see below).
  6. Prompts for (or reads) your Ethereum operator address and writes /opt/zentalk-validator/config.env.
  7. Generates an RSA key at /opt/zentalk-validator/keys/relay.pem (mode 600) if none exists — existing keys are preserved.
  8. Installs and starts the zentalk-validator systemd service (hardened: NoNewPrivileges, PrivateTmp, ProtectSystem=strict, ProtectHome), plus a detect-only update-check timer.

Installer environment variables:

Variable Default Description
OPERATOR_ADDRESS prompted Your Ethereum wallet address (0x + 40 hex chars)
ZENTALK_VERSION latest Release to install (semver, e.g. v1.2.1)
RELAY_PORT 8080 Relay port written to config.env
HEALTH_PORT 8170 Health/monitoring port written to config.env
NETWORK mainnet mainnet or testnet
UPDATE_CHECK_INTERVAL 6h Update-check timer interval

Checksum files can be GPG-signed (checksums.txt.asc). The installer and updater auto-detect the signature and verify it when gpg is installed:

Variable Default Behavior
ZENTALK_GPG_SIG_FINGERPRINT unpinned Expected signing-key fingerprint; verification fails on any other key
ZENTALK_GPG_SIG_FAIL_ON_UNPINNED true Abort when a signature exists but no fingerprint is pinned (set false to fall back to SHA256-only — not recommended)
ZENTALK_GPG_SIG_REQUIRED false Hard-fail unless the checksums file was GPG-verified

See the release-key details in the zentalk-validator repository (docs/RELEASE_SIGNING.md).

install-macos.sh installs to ~/zentalk-validator/ (binary, keys/private.pem, config.env, logs/) and registers a LaunchAgent labeled io.zentalk.validator at ~/Library/LaunchAgents/io.zentalk.validator.plist. Manage it with launchctl load/unload on that plist; logs go to ~/zentalk-validator/logs/validator.log. Checksum and GPG verification match the Linux installer.

The validator relay binary accepts:

Flag Required Default Description
-operator Yes Ethereum wallet address (0x...)
-network No mainnet mainnet or testnet
-port No 9001 Relay port (message routing)
-health-port No relay port + 90 (9091) Health check and monitoring port
-key No Path to the RSA key file (PEM)
-genkey No false Generate a new RSA key pair
-version No Print version and exit

These are the binary’s built-in defaults; install.sh installations instead run with ports 8080/8170, set by the RELAY_PORT and HEALTH_PORT values the installer writes to config.env (below).

Manual start
relay \
-operator 0xYourEthereumAddress \
-network mainnet \
-port 8080 \
-health-port 8170 \
-key /opt/zentalk-validator/keys/relay.pem

Configuration lives in /opt/zentalk-validator/config.env (Linux) and is loaded by the systemd unit:

Variable Default Description
OPERATOR_ADDRESS Operator wallet address (required)
RELAY_PORT 8080 Relay port
HEALTH_PORT 8170 Health port — bound on all interfaces; close it yourself
DHT_PORT 8180 libp2p port. Derived as RELAY_PORT + 100; recorded, not configurable
NETWORK mainnet Network selection
ZENTALK_LOG_LEVEL info debug, info, warn, error
ZENTALK_LOG_FORMAT text text or json

Edit the file and sudo systemctl restart zentalk-validator to apply.

Port Direction Purpose
8080 Inbound Relay server (message routing)
8180 Inbound libp2p — peering depends on this port
8170 Must not be reachable Health and metrics — nothing closes it for you

Three points, each of which has been got wrong in production:

8180 is not optional and not configurable. The relay opens its libp2p host on RELAY_PORT + 100 and reads no flag or variable for it. Set -port 9000 and libp2p moves to 9100.

A validator with only 8080 open looks healthy. It starts, serves /health, reports active, and still dials out — so it appears in the DHT. It just cannot be dialled back, and a network whose nodes can only connect outbound forms no connections at all. Five validators were once deployed exactly that way: all green, none peered.

8170 must not be reachable — and it is not closed for you. It serves the operator’s ETH address, the peer list, message counts, the update window and an unauthenticated Prometheus registry. The relay binary binds it on all interfaces (pkg/network/health.go calls ListenAndServe on ":8170"). The 127.0.0.1:8170 you may have read elsewhere is the port mapping in docker-compose.yml, so only the Docker path has it: after install.sh (systemd) or install-macos.sh (LaunchAgent) the endpoint answers on every address the host has, until your firewall says otherwise. Reach it from the host or through an SSH tunnel.

Firewall (UFW)
sudo ufw allow 8080/tcp
sudo ufw allow 8180/tcp
sudo ufw deny 8170/tcp # not merely "not allowed" — actively closed

The installer does not run these for you: it prints the three port numbers, including the derived one for your RELAY_PORT, and leaves the policy to you — it cannot see whether yours lives in a cloud security group, a host firewall or a NAT device.

Task Command
Status sudo systemctl status zentalk-validator
Follow logs sudo journalctl -u zentalk-validator -f
Restart sudo systemctl restart zentalk-validator
Stop sudo systemctl stop zentalk-validator
Health check curl http://localhost:8170/health
Runtime stats curl -H "Authorization: Bearer $ZENTALK_HEALTH_TOKEN" http://localhost:8170/stats

/stats, /metrics, /topology, /updates and /version require Authorization: Bearer $ZENTALK_HEALTH_TOKEN; only /health is open. With no token set they answer 401 to everyone, including on localhost — the gate is fail-closed. /stats returns local operational metrics — messages_relayed, uptime, route-cache hit ratio — not on-chain claims. The status.sh script in the validator repository renders a status dashboard from the same endpoints and the local config, key, and log paths on Linux and macOS.

Update detection is detect-only: a systemd timer polls GitHub every 6 hours and writes the flag file /opt/zentalk-validator/data/update-available when a newer release exists. Nothing is auto-applied. To update:

Apply an update
sudo bash update.sh # latest release
sudo ZENTALK_VERSION=v1.2.1 bash update.sh # specific version

update.sh stops the service, backs up the current binary, downloads and verifies the new one (SHA256 + optional GPG, same rules as install), restarts the service, and waits up to 30 seconds for http://localhost:8170/health to pass. If the health check fails, it automatically rolls back to the previous binary. Keys, configuration, and data are always preserved.

Uninstall
sudo bash uninstall.sh

The script prompts for confirmation, offers to back up your keys and config.env to ~/zentalk-validator-backup/ first, then removes the service, update-check timer, binaries, data directory, and the zentalk system user.