Install Edge as a Linux Service
This page is the manual step-by-step alternative to the one-command install-edge.sh path on the Install an Edge Node page. Use it when you want full control over each piece, or when the script doesn’t fit your distro.
The walkthrough below produces the same layout install-edge.sh creates:
Both should be active (running). The node should appear online in the manager within seconds.
The script is idempotent — re-running it on an existing install updates the binary and units in place without losing config or data.
Manual step-by-step install
Section titled “Manual step-by-step install”If you prefer to control each step (or are on a non-standard distro where the script doesn’t fit), the walkthrough below is the explicit equivalent of what install-edge.sh does.
1. Create the service user
Section titled “1. Create the service user”# `|| true` — the bilbycast user may already exist from a manager or# relay install on this box; useradd would otherwise exit non-zero.sudo useradd -r -s /sbin/nologin -d /var/lib/bilbycast bilbycast || true-r makes a system user (no login, no home created). /sbin/nologin blocks interactive login. The home stub at /var/lib/bilbycast is just a sentinel — we’ll create the real data dirs below.
2. Lay out the directories
Section titled “2. Lay out the directories”The install root holds the binary tree (versions/<v>/ + current symlink, the same shape the upgrade module manipulates). The data root holds anything the edge writes at runtime (replay segments, media library, instance state). The bilbycast user owns both so it can write through.
# Pick the version you extracted in step 1 — listed at the top of the tarball# path or in the binary itself (`./bilbycast-edge --version`).VERSION=0.58.0
sudo mkdir -p /opt/bilbycast/edge/versions/${VERSION}sudo mkdir -p /var/lib/bilbycast/edge/replaysudo mkdir -p /var/lib/bilbycast/edge/media
sudo chown -R bilbycast:bilbycast /opt/bilbycast /var/lib/bilbycastsudo chmod 0750 /opt/bilbycast/edge /var/lib/bilbycast/edgesudo chmod 0750 /var/lib/bilbycast/edge/replay /var/lib/bilbycast/edge/mediaWhy bilbycast owns the install root: the upgrade module (bilbycast-edge/src/upgrade/) creates versions/<new>/, atomically swaps the current symlink, and prunes old versions — all running as the service user. Root-owned would block the swap silently.
Recordings (replay) and the media-player library can grow large — give them their own filesystem if you can.
3. Install the tarball into versions/<v>/
Section titled “3. Install the tarball into versions/<v>/”From inside the extracted tarball directory, copy the whole tree (binary + licence files + packaging/ scripts) into the matching versions/ subdirectory and atomically point current at it:
# `./* `expands to everything in the extracted tarballsudo cp -r ./* /opt/bilbycast/edge/versions/${VERSION}/sudo chown -R bilbycast:bilbycast /opt/bilbycast/edge/versions/${VERSION}sudo chmod 0755 /opt/bilbycast/edge/versions/${VERSION}/bilbycast-edge
# Atomic symlink swap so `current` always points at a real version dir.sudo -u bilbycast ln -sfn versions/${VERSION} /opt/bilbycast/edge/current.tmpsudo -u bilbycast mv -Tf /opt/bilbycast/edge/current.tmp /opt/bilbycast/edge/currentVerify the symlink resolves:
ls -la /opt/bilbycast/edge/current/bilbycast-edge# → /opt/bilbycast/edge/current -> versions/0.58.0# → versions/0.58.0/bilbycast-edge (executable)The packaging/ scripts referenced later (provision-edge-node.sh, setup-etf-qdisc.sh) land under /opt/bilbycast/edge/current/packaging/ automatically.
4. Install the config
Section titled “4. Install the config”The setup wizard (or your manual config in step 5 of Install an edge node) wrote config.json and secrets.json next to the binary. Move them into the install root so they sit alongside the current symlink — both the running edge and any future-installed version reference the same files:
sudo install -m 0640 -o bilbycast -g bilbycast \ config.json /opt/bilbycast/edge/config.jsonsudo install -m 0600 -o bilbycast -g bilbycast \ secrets.json /opt/bilbycast/edge/secrets.jsonconfig.json is bilbycast-owned (not root) because the edge writes back to it when the manager pushes UpdateConfig / Create-/Update-/Delete-Input/Output/Flow/Tunnel commands (config/persistence.rs::save_config_split). Root-owned would block the persistence silently — every UI-driven config change would disappear on the next restart. The edge auto-pairs secrets.json from the same directory as the config, so no path reference is required inside config.json.
5. Drop the systemd unit
Section titled “5. Drop the systemd unit”Write /etc/systemd/system/bilbycast-edge.service:
[Unit]Description=bilbycast-edge media transport gatewayAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=bilbycastGroup=bilbycastWorkingDirectory=/var/lib/bilbycast/edge
# ExecStart resolves through the `current` symlink, so a successful# manager-driven upgrade lands automatically when the old binary exits.ExecStart=/opt/bilbycast/edge/current/bilbycast-edge --config /opt/bilbycast/edge/config.jsonRestart=on-failureRestartSec=2s
# File-descriptor headroom for SRT / RIST / RTP listenersLimitNOFILE=65536
# Required by engine::wire_emit, which spawns one SCHED_FIFO std::thread# per UDP-socket-owning output (UDP, RTP, ST 2110-*, 302M) on the# default clock_nanosleep tier. The kernel allows unprivileged# SCHED_FIFO whenever LimitRTPRIO is non-zero and RestrictRealtime is# off, so no capability grant is required for the scheduling-class# change itself.RestrictRealtime=falseLimitRTPRIO=99
# Permits BILBYCAST_MLOCKALL=1 in /etc/bilbycast/edge.env — see below.LimitMEMLOCK=infinity
# CAP_NET_ADMIN — required by mainline kernel ≥ 6.x (and every recent# Ubuntu / RHEL backport) for setsockopt(SO_TXTIME) with any non-# CLOCK_MONOTONIC clockid. Wire pacing uses CLOCK_TAI, the only# clockid the etf qdisc on Intel ice / igc / igb and Mellanox mlx5# accepts. Required when BILBYCAST_ENABLE_TXTIME=1 opts in to the# SO_TXTIME release tier (tiers 1–2). Without this cap the probe# returns EPERM, wire-emit silently falls back to the clock_nanosleep# tier (tier 4 — the default), and on any host whose etf qdisc has# `skip_sock_check off` (the kernel default) those fallback packets# are then also dropped at the qdisc. The cap is NOT required for# the default tier-4 path and does NOT let the edge install qdiscs —# qdisc install stays operator-side via `setup-etf-qdisc.sh`.CapabilityBoundingSet=CAP_NET_ADMINAmbientCapabilities=CAP_NET_ADMIN
# Logging + storage roots. The defaults below put the edge on the# clock_nanosleep tier with no qdisc / no PTP required — the right# choice for every deployment that doesn't need sub-µs PCR_AC. To opt# in to the SO_TXTIME release tier, see "Optional: enable SO_TXTIME"# below (this requires the ETF qdisc + PTP).Environment=RUST_LOG=infoEnvironment=BILBYCAST_REPLAY_DIR=/var/lib/bilbycast/edge/replayEnvironment=BILBYCAST_MEDIA_DIR=/var/lib/bilbycast/edge/media
# Uncomment if your manager uses a self-signed certificate:# Environment=BILBYCAST_ALLOW_INSECURE=1# Uncomment after installing the ETF qdisc + PTP to enable SO_TXTIME:# Environment=BILBYCAST_ENABLE_TXTIME=1
# Hardening — sensible defaults that don't break anything the edge does.# ReadWritePaths covers both the install root (for upgrades + config# persistence) and the data root (replay / media / instance state).NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/opt/bilbycast/edge /var/lib/bilbycast/edge
[Install]WantedBy=multi-user.targetThe hardening block (NoNewPrivileges, ProtectSystem=strict, etc.) is optional but recommended. ReadWritePaths allows writes to the install root (so the upgrade module can manipulate versions/ and the edge can persist config.json) and the data root (so replay / media / instance state can land).
Wire pacing runs automatically on every UDP-socket-owning output (UDP, RTP including FEC and 2022-7 dual-leg, 302M, ST 2110-20/-23/-30/-31/-40). SRT, RIST, RTMP, HLS, CMAF, and WebRTC are paced internally by their own protocol layers and need no extra setup.
The default release tier is clock_nanosleep on a SCHED_FIFO thread
(tier 4 in the table below) — no qdisc, no PTP, no HW-PTP NIC, no env
var required. That path comfortably handles compressed TS through
2 Gbps with sub-3 ms PCR_AC max on commodity Linux. The kernel-paced
SO_TXTIME upgrade (tiers 1–2) is opt-in via BILBYCAST_ENABLE_TXTIME=1
and only worth enabling when the per-flow rate or receiver strictness
genuinely demands sub-µs precision — see Wire-Time Precision.
The edge picks the highest pacing tier the host can deliver at output
startup and logs the choice (wire-emit '<id>': starting (anchor=…, tier=…)).
The active tier is also surfaced in OutputStats.wire_pacing_tier for
the manager UI.
| Tier | Mechanism | Inter-packet jitter envelope | Requires |
|---|---|---|---|
| 1 | SO_TXTIME + ETF qdisc with offload on a PTP-disciplined NIC | Sub-µs | ETF qdisc + HW-PTP NIC + ptp4l + phc2sys + CAP_NET_ADMIN + BILBYCAST_ENABLE_TXTIME=1 |
| 2 | SO_TXTIME + software ETF qdisc | ~1–10 µs | ETF qdisc + CAP_NET_ADMIN + BILBYCAST_ENABLE_TXTIME=1 |
| 4 ⭐ | clock_nanosleep on SCHED_FIFO | ~50–500 µs typical, ms-tail under load | systemd unit’s LimitRTPRIO=99 (already set above). Default — no setup required. |
| 5 | clock_nanosleep on SCHED_OTHER | ~1–5 ms | None — non-Linux, or Linux without RT grant |
For tier-1 broadcast PCR_AC compliance (T-STD ≤ 500 ns), install the
ETF qdisc on the egress NIC, install the boot-time bilbycast-etf-qdisc@.service
to persist it, set up PTP, and opt in via BILBYCAST_ENABLE_TXTIME=1.
The edge does not install the qdisc itself — tc qdisc requires
CAP_NET_ADMIN, deliberately operator-side. See ETF qdisc setup
below — the same chain gives every output (TS as well as ST 2110) tier-1
PCR_AC. The default clock_nanosleep tier is the right choice unless
you have a measured reason to upgrade; enabling SO_TXTIME without the
full prerequisite stack produces silent degradation worse than the
default.
5b. Hardware-encoder runtime (only if you’ll use NVENC or QSV)
Section titled “5b. Hardware-encoder runtime (only if you’ll use NVENC or QSV)”The *-full binary compiles in the FFmpeg → NVENC and FFmpeg → QSV bridges, but the actual GPU encoder implementation lives in vendor-shipped runtime libraries that are not bundled with the bilbycast tarball. If your flows only use software encoders (x264 / x265) you can skip this section entirely.
Intel QuickSync (QSV) — x86_64 only:
sudo apt updatesudo apt install libvpl2 libmfx-gen1.2 intel-media-va-driver-non-freesudo usermod -aG render bilbycast # service user needs /dev/dri/renderD* access| Package | Role |
|---|---|
libvpl2 | oneVPL dispatcher (libvpl.so.2). bilbycast links to this. |
libmfx-gen1.2 | Intel VPL GPU runtime (libmfx-gen.so.1.2) — the actual hardware encoder. The package most installs miss. Without it, MFXLoad returns MFX_ERR_NOT_FOUND and h264_qsv / hevc_qsv fail to open. |
intel-media-va-driver-non-free | VAAPI driver (iHD_drv_video.so). Required for some pixel-format and zero-copy paths inside libmfx-gen. The intel-media-va-driver upstream package works too. |
QSV needs Broadwell (5th gen) or newer for H.264; HEVC needs Kaby Lake (7th gen) or newer.
NVIDIA NVENC:
# Ubuntu 22.04 / 24.04 — auto-pick recommended branch:sudo ubuntu-drivers autoinstall# Or pin a specific branch (e.g. 580 LTS):sudo apt install nvidia-driver-580 # workstationsudo apt install nvidia-driver-580-server # headless serverssudo reboot
# Debian 12+ — non-free repo must be enabled:sudo apt install nvidia-driversudo rebootThe proprietary NVIDIA driver bundles libnvidia-encode.so.1 and libcuda.so.1, both of which bilbycast dlopens at flow start. The Nouveau open-source driver does not expose NVENC.
After install, verify the GPU is visible to the service user:
sudo -u bilbycast nvidia-smi # NVENC: must list the GPUls -l /dev/dri/ # QSV: bilbycast must be in render group; renderD128 should be readableIf you skip this step but still configure a video_encode block with h264_qsv / h264_nvenc, the encoder will fail to open at flow start and the edge will surface a Critical event under category video_encode. Software encoders (x264, x265) keep working regardless because they’re statically linked into the *-full binary.
5c. Install PTP support (required for ST 2110 / MXL)
Section titled “5c. Install PTP support (required for ST 2110 / MXL)”PTP is managed by a small companion daemon (bilbycast-ptp-helper) that watches a config file and starts/stops ptp4l + phc2sys when the operator changes the PTP mode from the manager UI. Skip this step only if you will never use ST 2110 or MXL flows on this node.
Install linuxptp:
# Debian / Ubuntusudo apt update && sudo apt install -y linuxptp
# RHEL / Fedorasudo dnf install -y linuxptpInstall the PTP script and config template:
sudo install -d -m 0755 /opt/bilbycast/binsudo install -m 0755 \ /opt/bilbycast/edge/current/packaging/bilbycast-ptp-gm.sh \ /opt/bilbycast/bin/bilbycast-ptp-gm.shsudo install -m 0644 \ /opt/bilbycast/edge/current/packaging/bilbycast-ptp-gm.conf \ /opt/bilbycast/bin/bilbycast-ptp-gm.confSeed the default PTP config (mode = off — operator enables via the manager UI):
sudo install -d -o bilbycast -g bilbycast -m 0755 /var/lib/bilbycastcat <<'EOF' | sudo tee /var/lib/bilbycast/ptp.conf > /dev/nullmode = offiface =domain = 127priority1 =scan_timeout = 5EOFsudo chown bilbycast:bilbycast /var/lib/bilbycast/ptp.confCreate the runtime directories:
sudo install -d -o bilbycast -g bilbycast -m 0755 /var/run/bilbycast-ptpsudo install -d -o bilbycast -g bilbycast -m 0755 /var/log/bilbycast-ptpsudo install -d -m 0755 /etc/linuxptpInstall the systemd unit:
sudo install -m 0644 \ /opt/bilbycast/edge/current/packaging/bilbycast-ptp.service \ /etc/systemd/system/bilbycast-ptp.serviceOn Ubuntu with AppArmor, ptp4l may be confined. Install the shipped local override so it can read the staged config:
if [ -d /etc/apparmor.d/local ] && [ -f /opt/bilbycast/edge/current/packaging/apparmor-local-ptp4l ]; then sudo install -m 0644 \ /opt/bilbycast/edge/current/packaging/apparmor-local-ptp4l \ /etc/apparmor.d/local/usr.sbin.ptp4l sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.ptp4l 2>/dev/null || truefi6. Enable and start
Section titled “6. Enable and start”sudo systemctl daemon-reloadsudo systemctl enable --now bilbycast-edgesudo systemctl enable --now bilbycast-ptpWatch the first boot:
sudo systemctl status bilbycast-edgesudo systemctl status bilbycast-ptpsudo journalctl -u bilbycast-edge -fYou should see manager: connected within a few seconds, and the node should flip to online in the manager UI. The PTP helper will be running but idle (mode = off) until you pick a PTP mode from the manager’s Time page.
7. Day-2 operations
Section titled “7. Day-2 operations”Restart after a config change:
sudo systemctl restart bilbycast-edgeTail recent logs:
sudo journalctl -u bilbycast-edge -fBump log level temporarily (no restart of any in-flight flows — the env var only takes effect on a fresh start):
sudo systemctl edit bilbycast-edge# Add under [Service]:# Environment=RUST_LOG=debugsudo systemctl restart bilbycast-edgeUpgrade to a new release (recommended — from the manager UI):
Once the edge has registered with the manager and shows up in /admin/nodes, every subsequent upgrade can be driven from the browser:
- Go to Managed Nodes, click Upgrade… on the row.
- Pick a
(version, channel)and click Stage upgrade. - The edge fetches the Sigstore-signed manifest, verifies it against its compiled-in allowlist, downloads the tarball, atomically swaps a
currentsymlink, and respawns under systemd. A boot watchdog automatically rolls back if the new binary fails to come up healthy.
See Remote Upgrade for the full operator runbook (per-node, group bulk rollout, automatic rollback, troubleshooting).
Upgrade manually (fallback):
If you can’t reach the manager UI, or you’re upgrading the very first edge before the manager-driven path is wired up, you can still upgrade by hand. Drop the new binary next to the old one under versions/, then atomically swap the current symlink:
NEW_VERSION=0.59.0 # the version you just downloaded
# Re-run step 1 of "Install an edge node" to fetch + extract the new tarball.
sudo systemctl stop bilbycast-edge
sudo mkdir -p /opt/bilbycast/edge/versions/${NEW_VERSION}sudo install -m 0755 -o bilbycast -g bilbycast \ bilbycast-edge /opt/bilbycast/edge/versions/${NEW_VERSION}/bilbycast-edge
# Atomic symlink swap. Old version stays in versions/ for instant rollback.sudo ln -sfn versions/${NEW_VERSION} /opt/bilbycast/edge/current.tmpsudo mv -Tf /opt/bilbycast/edge/current.tmp /opt/bilbycast/edge/current
sudo systemctl start bilbycast-edge
# Rollback (if the new version misbehaves): point current at the old version dir.# sudo ln -sfn versions/0.58.0 /opt/bilbycast/edge/current.tmp# sudo mv -Tf /opt/bilbycast/edge/current.tmp /opt/bilbycast/edge/current# sudo systemctl restart bilbycast-edgeThe config + secrets at /opt/bilbycast/edge/{config.json,secrets.json} carry across upgrades untouched.
Common failures
Section titled “Common failures”| Symptom | Cause | Fix |
|---|---|---|
Failed to start bilbycast-edge with Permission denied on /var/lib/bilbycast/edge or /opt/bilbycast/edge | The bilbycast user can’t write to a path. | sudo chown -R bilbycast:bilbycast /var/lib/bilbycast /opt/bilbycast |
bind: address already in use on port 8080 | Something else is already on :8080. | Free the port, or override with --port 8090 in the unit’s ExecStart. |
auth_failed events in the manager log, edge keeps re-trying | Registration token already used or expired, or the node was deleted from the manager. | Re-register: in the manager UI generate a fresh token, paste it into /opt/bilbycast/edge/secrets.json, restart the service. |
Edge connects but accept_self_signed_cert is silently ignored | The BILBYCAST_ALLOW_INSECURE=1 safety guard isn’t set. | Uncomment the matching line in the unit, then daemon-reload && restart. |
AppArmor blocks /dev/dri or ALSA on a display-output flow | Distribution AppArmor profile is too strict. | Add /dev/dri/** rw, and /dev/snd/** rw, to the profile, or run with hardening relaxed (ProtectSystem=full). |
What about the manager?
Section titled “What about the manager?”The manager install guide’s Production — systemd block walks through the same pattern for the manager (service user, /opt/bilbycast-manager/, /etc/bilbycast-manager/manager.env, hardened unit with ProtectSystem=strict). There’s no separate “manager Ubuntu service” guide because the install guide already covers it inline.
ETF qdisc setup (opt-in) for tier-1 PCR accuracy and ST 2110-21 narrow profile
Section titled “ETF qdisc setup (opt-in) for tier-1 PCR accuracy and ST 2110-21 narrow profile”You only need this section if you have a concrete reason to leave the
default clock_nanosleep tier. Three cases earn the upgrade:
- ST 2110-20 / -23 narrow profile (Imagine, Lawo, EVS Xeebra, Grass Valley LDX, Bridge Tech VB330 in test mode) — at 1080p50 (~ 250 k pps) and 4K60 (~ 1 M pps), per-packet timing must be within microseconds of the frame raster. Userspace pacing can’t hit that budget.
- T-STD-compliant PCR_AC (≤ 500 ns) on compressed TS over UDP / RTP —
required by contribution-grade decoders with
PCR_ACalarms enabled (Appear X10, Cobalt 9202, Cisco D9824). - Sustained CPU contention pushing tier-4 p99 above ~30 ms (many transcoded outputs on a tight box) — kernel ETF moves pacing off the SCHED_FIFO thread so CPU contention no longer perturbs it. Tier 2 (software ETF, no HW-PTP NIC) is enough here.
If none of those apply, skip this section. The default tier-4 path handles compressed TS through 2 Gbps on a standard NIC with sub-3 ms PCR_AC max — fine for VLC, ffplay, OBS, web players, cloud receivers, and most professional decoders in standard tolerance mode.
One-shot: provision-edge-node.sh
Section titled “One-shot: provision-edge-node.sh”The shipped wrapper does all four steps below in one idempotent,
reboot-persistent run — installs linuxptp, writes systemd units for
ptp4l@${MEDIA_IFACE}.service + phc2sys@${MEDIA_IFACE}.service, lays
down the ETF qdisc via the bilbycast-etf-qdisc@${MEDIA_IFACE}.service
boot unit, and (optionally) static ARP for known peers:
sudo MEDIA_IFACE=enp1s0 \ bash /opt/bilbycast/edge/current/packaging/provision-edge-node.shOptional flags:
PTP_ONLY=1— install onlyptp4l+phc2sys, no ETF, no ARP. Safe to run on a NIC that also carries SSH / management.PEERS="10.0.0.5=00:0e:c6:4a:53:06 10.0.0.10=00:11:22:33:44:55"— pin known peers to static ARP entries, eliminating ARP refresh stalls on low-latency unicast.
The script is idempotent — re-running it updates the systemd units in place. Everything it writes is a systemd unit with enable --now, so the config survives reboots without further action. If the NIC name changes (for instance, because of a kernel/driver upgrade renaming eno4 → enp1s0), re-run with the new MEDIA_IFACE.
After the wrapper finishes, you still need to opt the edge in by uncommenting BILBYCAST_ENABLE_TXTIME=1 in /etc/bilbycast/edge.env and restarting the service — see step 4 below.
If you’d rather lay each piece down by hand, the manual four-step walkthrough below is the equivalent.
Step 1: install the ETF qdisc on the egress NIC
Section titled “Step 1: install the ETF qdisc on the egress NIC”sudo bash /opt/bilbycast/edge/current/packaging/setup-etf-qdisc.sh enp1s0Replace enp1s0 with your actual broadcast egress NIC. The script installs mqprio at root (3 traffic classes, 3 hardware tx queues) and etf clockid CLOCK_TAI delta 200000 offload skip_sock_check on on the prioritized class. offload enables hardware tx pacing on supported NICs (Mellanox CX-6 / CX-7, Intel E810, Intel i210); silently degrades to software ETF on unsupported NICs (still 1–10 µs jitter). skip_sock_check on is non-negotiable — without it ARP, DHCP, ssh, and every default UDP socket on the host get dropped at the qdisc.
Verify:
tc -s qdisc show dev enp1s0should list mqprio at root and etf on the prioritized class — not the default pfifo_fast.
Step 2: install the boot-time systemd unit so the qdisc survives reboots
Section titled “Step 2: install the boot-time systemd unit so the qdisc survives reboots”The one-shot tc call from step 1 doesn’t survive a reboot. Install the templated bilbycast-etf-qdisc@.service unit that ships with the edge:
sudo install -m 0644 \ /opt/bilbycast/edge/current/packaging/bilbycast-etf-qdisc@.service \ /etc/systemd/system/sudo systemctl daemon-reloadsudo systemctl enable --now bilbycast-etf-qdisc@enp1s0The unit ordering is After=network-online.target sys-subsystem-net-devices-<iface>.device + Before=bilbycast-edge.service, so the qdisc is in place by the time the edge starts. Verify:
systemctl status bilbycast-etf-qdisc@enp1s0tc -s qdisc show dev enp1s0Removal: sudo systemctl disable --now bilbycast-etf-qdisc@enp1s0. (Optional manual teardown: sudo tc qdisc del dev enp1s0 root.)
Step 3: confirm PTP discipline on the system clock (tier 1 only)
Section titled “Step 3: confirm PTP discipline on the system clock (tier 1 only)”Tier 1 needs ptp4l + phc2sys running against a PTP grandmaster. Tier 2 (software ETF, no HW-PTP NIC, no PTP) works without this step but caps out around 1–10 µs jitter.
SO_TXTIME schedules transmission against CLOCK_TAI. Without ptp4l + phc2sys running, the kernel’s TAI clock is just wall time + leap-second offset — sender and receiver drift relative to each other and the receiver’s VRX bound fails.
Don’t start ptp4l/phc2sys by hand. Open the manager UI’s per-node Time (PTP) page and pick Slave only (or Auto if you don’t know yet). The bilbycast-ptp-helper daemon shipped with install-edge.sh will start the right ptp4l@<iface> + phc2sys services for you within ~1 s and re-apply on every config change — no sudo from the operator at runtime. Full operator runbook + role decision tree: Time (PTP).
Verify both daemons are alive afterwards:
systemctl status ptp4l@<iface>.service phc2sys@<iface>.serviceBoth should be active (running). The edge keeps emitting valid bytes without PTP, just not narrow-profile-aligned.
Step 4: opt the edge in to SO_TXTIME and restart
Section titled “Step 4: opt the edge in to SO_TXTIME and restart”The edge defaults to the clock_nanosleep release tier regardless of whether ETF is installed. To use the SO_TXTIME tier, set BILBYCAST_ENABLE_TXTIME=1 in the env file:
sudo tee -a /etc/bilbycast/edge.env > /dev/null <<'EOF'BILBYCAST_ENABLE_TXTIME=1EOFsudo systemctl restart bilbycast-edge(Use whichever env-file mechanism your unit uses — EnvironmentFile=/etc/bilbycast/edge.env if you adopted install-edge.sh, otherwise inline Environment=BILBYCAST_ENABLE_TXTIME=1 in the unit’s [Service] block.)
Step 5: confirm the active tier on the edge
Section titled “Step 5: confirm the active tier on the edge”sudo journalctl -u bilbycast-edge --since "5 minutes ago" | grep wire-emitYou should see a line like wire-emit '<output-id>': starting (anchor=Pcr, tier=so_txtime) for every output that owns a UDP socket. With ETF qdisc + PTP from steps 1–3 and the env var from step 4, the host is running tier 1 or 2 (sub-µs to ~10 µs jitter). If the line shows tier=clock_nanosleep despite the env var, the SO_TXTIME setsockopt failed — typically because CAP_NET_ADMIN isn’t granted on the unit, see step 5 of the unit block above.
You can also check via the manager UI’s per-output card or directly:
curl -k https://<edge>:8080/api/v1/stats | jq '.data.flows[].outputs[] | {id: .output_id, tier: .wire_pacing_tier, late: .wire_pacing_late}'wire_pacing_late should stay at 0 — non-zero means the kernel rejected datagrams as “target tx time in the past”, typically because of a transient host-clock or scheduling stall.
What if any step is skipped?
Section titled “What if any step is skipped?”| Skipped | Behaviour |
|---|---|
Step 4 (BILBYCAST_ENABLE_TXTIME=1 not set) | Edge runs at tier 4 — the default. Compressed TS through 2 Gbps with sub-3 ms PCR_AC max. ST 2110-21 narrow profile fails the receiver-side VRX bound. |
| Step 1 + 2 (no ETF qdisc) but env var set | The SO_TXTIME setsockopt succeeds but the kernel’s default qdisc ignores SCM_TXTIME — silent degradation, worse than the default. Always install the qdisc before setting the env var. |
| Step 3 (no PTP) but ETF installed | Tier 2 jitter (~ 1–10 µs) is achievable, but the pacer’s TAI anchor isn’t GM-aligned — multi-edge 2022-7 across hosts and ST 2110-21 narrow profile both fail. |
| Step 1–4 all skipped | Edge runs at tier 4 (clock_nanosleep on SCHED_FIFO) — the default. Fine for VLC / ffplay / OBS / cloud receivers / standard professional gear up through 2 Gbps. |
For the architecture, full failure-mode matrix, and per-NIC notes, see Wire-Time Precision and ST 2110.
Where to read next
Section titled “Where to read next”- Configuration reference — every input, output, and flow field.
- ST 2110 — uncompressed video / audio / ANC essence flows + narrow-profile pacing.
- Display output — drive an HDMI / DisplayPort connector for confidence-monitor playout.
- Replay — continuous flow recording and clip playback.
- Edge events and alarms — what shows up in
journalctland the manager events feed.