Skip to content

Install an Edge Node

An edge node is the box that actually moves your media — receives a stream on one protocol and re-emits it on another. This page covers everything from download to a running, registered node.

There are two install paths:

  • Production install (recommended) — one command (install-edge.sh) that sets up the binary, systemd services, PTP support, and registers with the manager. No browser needed. Jump to step 1.
  • Dev / testing — run the binary directly and register via a browser-based setup wizard. Jump to dev / testing install.
  • A Linux host (Ubuntu 24.04, Debian 12+, RHEL 9+, or any systemd-based distro). x86_64 and aarch64 builds are published.
  • The manager already running and reachable on wss://. If you haven’t done that yet, see Install the manager.
  • About 5 minutes.

Terminal window
curl -fsSL -O "https://github.com/Bilbycast/bilbycast-edge/releases/latest/download/bilbycast-edge-$(uname -m)-linux-full.tar.gz"
curl -fsSL -O "https://github.com/Bilbycast/bilbycast-edge/releases/latest/download/bilbycast-edge-$(uname -m)-linux-full.tar.gz.sha256"
sha256sum -c "bilbycast-edge-$(uname -m)-linux-full.tar.gz.sha256"
tar xzf "bilbycast-edge-$(uname -m)-linux-full.tar.gz"
cd bilbycast-edge-*
  1. Sign in to the manager UI.
  2. Admin → Nodes, click + Add Node.
  3. Pick device type Edge, give it a name, click Save.
  4. Copy the one-shot registration token the modal shows.

From inside the extracted tarball directory:

Terminal window
sudo bash packaging/install-edge.sh \
--manager wss://YOUR_MANAGER:8443/ws/node \
--registration-token <token>

This single command does everything:

  • Creates the bilbycast service user.
  • Installs the binary under /opt/bilbycast/edge/ with a current symlink (the layout the manager’s remote-upgrade feature expects).
  • Writes config.json and secrets.json with the manager URL and registration token.
  • Installs linuxptp (ptp4l, phc2sys, pmc) for PTP / ST 2110 support — on Debian/Ubuntu only, see below.
  • Installs and enables bilbycast-ptp.service (the PTP helper daemon, defaults to mode=off).
  • Installs and enables bilbycast-edge.service (auto-starts on boot, auto-restarts on crash).
  • Registers the node with the manager automatically — no setup wizard or browser needed.

Works on x86_64 and aarch64 Linux. Prerequisites are installed with apt, so only Debian/Ubuntu hosts get them automatically — the script has no dnf / yum path. On RHEL/Fedora it prints a warning and carries on, leaving linuxptp and (full variant) the VAAPI/QSV runtime libraries uninstalled, so install them yourself:

Terminal window
sudo dnf install -y linuxptp alsa-lib libva libdrm mesa-va-drivers
sudo dnf install -y libvpl # x86_64 only — Intel QSV

curl, jq and sha256sum must already be present on any distro — the script exits rather than installing them.

Optional flags:

Terminal window
sudo bash packaging/install-edge.sh \
--manager wss://YOUR_MANAGER:8443/ws/node \
--registration-token <token> \
--output-nics enp1s0 # enable SO_TXTIME wire pacing on this NIC
Flag Purpose
--output-nics <nic1,nic2> Enable kernel-paced wire emission (SO_TXTIME) on the listed NICs. Installs a boot-persistent ETF qdisc on each NIC and sets BILBYCAST_ENABLE_TXTIME=1 in the environment file. Each NIC must have >= 3 hardware tx queues (validated). Omit to stay on the default clock_nanosleep tier — fine for most deployments. See ETF qdisc setup for when you need this.
--channel <name> Release channel (stable / nightly / beta). Default stable.
--variant <name> Binary variant. Default full, and the only one the script can install — it accepts default or full, but no default artefact is published, so --variant default aborts with No artefact for arch=… variant=default in manifest. (rockchip is published, but it is not in the flag’s allowlist — see the Rockchip note above.) Build a default-features binary from source with a bare cargo build --release if you need one.
--accept-self-signed Allow connecting to a manager with a self-signed certificate.
--upgrade-installer Refresh the service unit and install script without touching config or versions.

If your manager uses a self-signed certificate:

Terminal window
sudo bash packaging/install-edge.sh \
--manager wss://YOUR_MANAGER:8443/ws/node \
--registration-token <token> \
--accept-self-signed
Terminal window
sudo systemctl status bilbycast-edge
sudo systemctl status bilbycast-ptp

Both should be active (running). Then check the manager:

  • The node appears at Admin → Nodes with status online.
  • The node detail page shows the capabilities the edge advertised (replay, display, st2110-30, …) and a Resources card with the hardware probe results.

If the node doesn’t show up, check the manager log for an auth_failed event under category connection, or tail the edge journal:

Terminal window
sudo journalctl -u bilbycast-edge -f

PTP (for ST 2110 / MXL): The installer already set up the PTP service — it’s running but idle (mode=off). To enable it, open the manager UI’s per-node Time (PTP) page, pick a mode (Auto / Grandmaster / Slave only), and click Apply. The change takes effect within ~1 second. No SSH needed. See Time (PTP) for the full reference.

Hardware video encoders (NVENC / QSV): If you plan to use hardware video transcoding, install the vendor runtime libraries — see Hardware encoder runtime below.

Wire pacing runs automatically on every UDP output. The default tier handles compressed TS through 2 Gbps with sub-3 ms PCR accuracy — no extra setup needed. For sub-us pacing (ST 2110-21 narrow profile, T-STD-strict contribution decoders), pass --output-nics <nic> during install or follow the manual steps at ETF qdisc setup. Full reference: Wire-Time Precision.

Subsequent upgrades can be driven from the manager UI (no SSH): Admin → Nodes → Upgrade. See Remote Upgrade. If remote upgrade isn’t available, see Manual upgrade below.


Use this when the manager’s remote upgrade isn’t an option — the node is too old to have the upgrade module, the manager is unreachable, or something else is blocking the remote path.

The install script needs jq and curl:

Terminal window
# Debian / Ubuntu
sudo apt install -y jq curl
# RHEL / Fedora
sudo dnf install -y jq curl
Terminal window
curl -fsSL https://github.com/Bilbycast/bilbycast-edge/releases/latest/download/install-edge.sh \
| sudo bash -s -- --upgrade-installer

This downloads the latest release, verifies the Sigstore signature, swaps the current symlink to the new version, and refreshes the systemd unit. Your existing config.json and secrets.json are not touched.

Then restart the service:

Terminal window
sudo systemctl daemon-reload
sudo systemctl restart bilbycast-edge

Verify:

Terminal window
/opt/bilbycast/edge/current/bilbycast-edge --version
sudo systemctl status bilbycast-edge

The script has no version pin. It always fetches the manifest from releases/latest/download — --channel only cross-checks the channel recorded inside that manifest and aborts on a mismatch — and --target-version is not one of its arguments: passing it aborts with Unknown argument: --target-version. To land a specific version by hand, fetch that release’s tarball, unpack it into versions/<version>/ and swap the current symlink atomically: see Upgrade manually (fallback).

Symptom Cause Fix
jq is required but not installed Missing prerequisite sudo apt install -y jq
Exit code 226/NAMESPACE after upgrade The systemd unit has sandbox directives (ProtectSystem, ProtectHome, PrivateTmp, LockPersonality, RestrictNamespaces) that some kernels don’t support (Raspberry Pi, minimal ARM boards, older kernels) Strip the sandbox block: sudo sed -i '/^ProtectSystem=/d; /^ReadWritePaths=/d; /^ProtectHome=/d; /^PrivateTmp=/d; /^LockPersonality=/d; /^RestrictNamespaces=/d' /etc/systemd/system/bilbycast-edge.service && sudo systemctl daemon-reload && sudo systemctl reset-failed bilbycast-edge && sudo systemctl start bilbycast-edge. This is safe — the edge runs as an unprivileged user, which is the real security boundary. Newer versions of the install script ship a unit without these directives.
Service running but manager UI still shows old version Service wasn’t restarted after the symlink swap sudo systemctl restart bilbycast-edge
Upgrade button missing in manager UI Node predates the remote upgrade module (v0.43.0 and earlier — v0.58.0 is the first tagged release that carries it) Run the manual upgrade above. Once on a current version, the button appears and all future upgrades work from the UI.

After a successful manual upgrade, all future upgrades can be done from the manager UI — see Remote Upgrade.


Use this path when you want to test locally, don’t need systemd, or want to use the browser-based setup wizard. This does not install PTP or systemd services — for production, use the production install above.

Same as the production download step above.

libx264 / libx265 (and libnuma) are statically linked into the full binary, so no codec runtime package is needed — and the binary is not tied to a distro’s ABI-versioned libx264.so/libx265.so SONAME (which is why earlier builds failed to start on newer Ubuntu releases). Install only the stable-SONAME system libraries:

Terminal window
# Debian / Ubuntu (full variant)
sudo apt update
sudo apt install libdrm2 libasound2t64 libva2 libva-drm2 va-driver-all
sudo apt install libvpl2 # x86_64 only — Intel QSV
# RHEL / Fedora
sudo dnf install libdrm alsa-lib libva mesa-va-drivers

On Ubuntu 22.04 / Debian 12 the ALSA package is libasound2 (not libasound2t64).

libva2 is only the VAAPI loader. Without a backend driver behind it, av_hwdevice_ctx_create(VAAPI) fails, the startup probe advertises no VAAPI capability, and both display output and transcode decode fall back to CPU. The only sign is a single hw encoder family vaapi unavailable (…) warning in the node’s startup log — nothing on the flow itself reports it — which is why install-edge.sh pulls the drivers too. va-driver-all / mesa-va-drivers covers AMD (Mesa radeonsi); modern Intel additionally needs the iHD driver — intel-media-va-driver, or the -non-free build listed under Hardware encoder runtime below.

Same as the production step above — create the node in the manager UI and copy the registration token.

4. Run the edge and complete the setup wizard

Section titled “4. Run the edge and complete the setup wizard”
Terminal window
./bilbycast-edge --config config.json --bind-addrs '0.0.0.0,[::]'

The config file doesn’t have to exist yet — the edge creates it. Two things happen on first boot:

  • The edge prints a setup token to stdout (needed only when reaching the wizard from a different machine — loopback callers bypass it).
  • The REST API and setup wizard come up on port 8080.

A freshly created config.json binds loopback only (127.0.0.1 + [::1]), so --bind-addrs is what makes the wizard reachable from another machine — without it (or a server.listen_addrs entry in config.json) http://EDGE-IP:8080/setup is simply refused. Working on the node itself? Drop the flag and browse to http://localhost:8080/setup. Putting the API on the LAN also means turning on server.auth.

Open the wizard in a browser:

http://EDGE-IP:8080/setup

The wizard guides you through:

  • Device name — appears in the manager UI.
  • Manager URL — the wss:// endpoint of your manager (e.g. wss://manager.example.com:8443/ws/node).
  • Registration token — paste the value you copied from the manager.
  • Accept self-signed certificate — tick this only if your manager uses a self-signed cert.

Click Save. The wizard validates the settings, writes config.json and secrets.json, and tells you to restart. Registration with the manager happens on the next start, not on submit — the manager client is spawned once at boot from the config read at process start — and the wizard disables itself only after that registration succeeds. So restart the edge now — the wizard’s own Listen Addresses field defaults to 0.0.0.0,[::], so keep the flag unless you narrowed it there:

Terminal window
./bilbycast-edge --config config.json --bind-addrs '0.0.0.0,[::]'

If you ticked the self-signed-cert option, BILBYCAST_ALLOW_INSECURE=1 has to be set as well — the edge honours accept_self_signed_cert only when both halves are present:

Terminal window
BILBYCAST_ALLOW_INSECURE=1 ./bilbycast-edge --config config.json --bind-addrs '0.0.0.0,[::]'
  • The edge log shows manager: connected.
  • The node appears online in the manager at Admin → Nodes.
  • curl http://localhost:8080/health returns {"status":"ok", ...} — the field is literally ok, so a monitoring probe must not match on healthy. Full body: API reference → GET /health.

When you’re ready to move this node to production, run install-edge.sh from the production install — it will pick up your existing config.json and secrets.json and install the systemd services around them.


The *-full binary compiles in NVENC and QSV bridges, but the actual GPU encoder runs in vendor-shipped runtime libraries. If you only use software encoders (x264 / x265), skip this section.

x86_64 only — Intel QuickSync (QSV):

Terminal window
sudo apt update
sudo apt install libvpl2 libmfx-gen1.2 intel-media-va-driver-non-free
sudo usermod -aG render bilbycast # service user needs /dev/dri/renderD* access
Package Role
libvpl2 oneVPL dispatcher (libvpl.so.2).
libmfx-gen1.2 Intel VPL GPU runtime — the actual encoder. Most-commonly-missed package.
intel-media-va-driver-non-free VAAPI driver (iHD_drv_video.so).

QSV needs Broadwell (5th gen) for H.264; HEVC needs Kaby Lake (7th gen) or newer.

NVIDIA NVENC:

Terminal window
# Ubuntu 22.04 / 24.04:
sudo ubuntu-drivers autoinstall
sudo reboot
# Debian 12+:
sudo apt install nvidia-driver
sudo reboot

Verify:

Terminal window
sudo -u bilbycast nvidia-smi # NVENC: must list the GPU
ls -l /dev/dri/ # QSV: renderD128 must be readable by bilbycast

Every release ships a Sigstore-signed manifest.json. The sha256sum -c step catches corruption; verifying the signature proves the manifest was published by the Bilbycast release workflow.

Install cosign:

Terminal window
COSIGN_VERSION=v2.4.1
case "$(uname -m)" in
x86_64) COSIGN_ARCH=amd64 ;;
aarch64) COSIGN_ARCH=arm64 ;;
*) echo "Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
COSIGN_ASSET="cosign-linux-${COSIGN_ARCH}"
curl -fsSL -o /tmp/cosign \
"https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/${COSIGN_ASSET}"
expected="$(curl -fsSL "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign_checksums.txt" | awk -v a="${COSIGN_ASSET}" '$2 == a {print $1}')"
got="$(sha256sum /tmp/cosign | awk '{print $1}')"
[[ -n "${expected}" && "${got}" == "${expected}" ]] || { echo "cosign checksum mismatch"; exit 1; }
sudo install -m 0755 /tmp/cosign /usr/local/bin/cosign && rm /tmp/cosign

Then verify:

Terminal window
curl -fsSL -O "https://github.com/Bilbycast/bilbycast-edge/releases/latest/download/manifest.json"
curl -fsSL -O "https://github.com/Bilbycast/bilbycast-edge/releases/latest/download/manifest.sig.bundle"
cosign verify-blob \
--bundle manifest.sig.bundle \
--certificate-identity-regexp 'https://github.com/Bilbycast/bilbycast-edge/.github/workflows/nightly-release.yml@refs/tags/v.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
manifest.json

Advanced — manual config without the wizard

If you’d rather skip both install-edge.sh and the wizard and write the config files by hand:

config.json:

{
"version": 2,
"server": { "listen_addr": "0.0.0.0", "listen_port": 8080 },
"manager": {
"enabled": true,
"urls": ["wss://manager.example.com:8443/ws/node"]
},
"inputs": [],
"outputs": [],
"flows": []
}

secrets.json (must be chmod 600):

{
"version": 2,
"manager_registration_token": "<token-from-manager>"
}
Terminal window
chmod 600 secrets.json
./bilbycast-edge --config config.json

For a self-signed manager cert, add "accept_self_signed_cert": true inside the manager block and export BILBYCAST_ALLOW_INSECURE=1.

CLI flags:

Flag Purpose
-c, --config <PATH> Path to config (default ./config.json)
-p, --port <PORT> Override REST API listen port
-b, --bind <ADDR> Override REST API listen address (legacy single-address form — ignored whenever server.listen_addrs is set, which it is in a freshly created config)
--bind-addrs <ADDRS> Comma-separated dual-stack listeners, e.g. 0.0.0.0,[::]. Takes precedence over --bind and over server.listen_addr / server.listen_addrs
--monitor-port <PORT> Override embedded dashboard port (default 9090)
-l, --log-level <LEVEL> trace / debug / info / warn / error
--print-setup-token Print the first-boot setup token without launching
--print-capabilities Print the compiled-in feature <name> and advertised capability <name> lines and exit — loads no config, opens no socket

Environment variables:

Variable Purpose
BILBYCAST_ALLOW_INSECURE=1 Required to honour accept_self_signed_cert: true
BILBYCAST_REPLAY_DIR Storage root for replay recordings
BILBYCAST_MEDIA_DIR Media-player library directory (4 GiB per file, 16 GiB total)
RUST_LOG=info Log level (also configurable via --log-level)