Files
trx-rs/docs/Deployment.md
T
sjg be9d5c301b
CI / lint (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / reuse (push) Canceled after 0s
[docs](trx-rs): add safe deployment guide
Document deployment with a dedicated service account, restricted device access, authenticated network listeners, systemd user services, reverse proxying, verification, upgrades, and rollback.

Assisted-By: OpenAI Codex (GPT-5)
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-08-18 22:53:00 +02:00

9.7 KiB

Safe deployment

This guide deploys trx-rs on Linux as a dedicated, unprivileged user with systemd user services. It keeps radio-device access, configuration, and runtime data separate from an administrator's account.

The examples use trx-rs as the account name and /opt/trx-rs/bin for root-owned executables. Adapt group names and firewall commands to your Linux distribution.

1. Decide what must be reachable

Only expose listeners that another machine actually needs:

Listener Typical port Recommended exposure
Server control TCP 4530 Loopback or trusted radio LAN only
Server audio TCP 4531 and per-rig ports Trusted radio LAN only
Client web UI TCP 8080 or a chosen port Loopback behind HTTPS proxy
Client rigctl Per-rig TCP ports Loopback or trusted LAN only
Client JSON Configured TCP port Loopback or trusted LAN only

127.0.0.1 accepts connections only from the same host. Use a specific LAN address when possible, or 0.0.0.0 when the listener must accept connections on every IPv4 interface. Binding a socket does not configure the host firewall.

Do not expose unauthenticated control, audio, rigctl, or JSON listeners to the public Internet. Prefer a VPN for links between radio sites. Put the web UI behind an HTTPS reverse proxy when it is remotely accessible.

2. Create the service account

Create a non-login service account with a home directory:

sudo useradd --create-home --shell /usr/sbin/nologin trx-rs
sudo chmod 0750 /home/trx-rs

Add only the hardware groups required on this host. Common group names are dialout for serial devices and audio for sound devices:

sudo usermod -aG dialout,audio trx-rs

SDR USB access is distribution- and device-specific. Install the vendor's udev rules or add a narrowly scoped rule for the device's USB vendor/product IDs. Avoid making all USB devices world-writable. After reconnecting the device, verify access as the service account:

sudo -u trx-rs test -r /dev/ttyUSB0
sudo -u trx-rs test -w /dev/ttyUSB0
sudo -u trx-rs SoapySDRUtil --find

Run only the checks relevant to the configured hardware. Group membership and udev-rule changes normally require reconnecting the device or restarting the service.

3. Build and install immutable binaries

Build from a reviewed revision as a normal development user, not as root:

git clone https://github.com/stanislawgrams/trx-rs.git
cd trx-rs
git switch --detach <reviewed-tag-or-commit>
cargo build --release -p trx-server -p trx-client -p trx-configurator

Install root-owned binaries into a directory the service user cannot modify:

sudo install -d -o root -g root -m 0755 /opt/trx-rs/bin
sudo install -o root -g root -m 0755 \
  target/release/trx-server \
  target/release/trx-client \
  target/release/trx-configurator \
  /opt/trx-rs/bin/

If SDR support is not needed, build trx-server with --no-default-features. Keep the source revision and Rust toolchain used for the build in deployment records.

4. Install and validate configuration

Create private configuration and state directories, then seed the example:

sudo install -d -o trx-rs -g trx-rs -m 0700 \
  /home/trx-rs/.config/trx-rs \
  /home/trx-rs/.config/systemd/user
sudo install -o trx-rs -g trx-rs -m 0600 \
  trx-rs.toml.example /home/trx-rs/.config/trx-rs/trx-rs.toml
sudoedit /home/trx-rs/.config/trx-rs/trx-rs.toml

At minimum:

  • remove unused example rigs and remotes;
  • select the correct serial, TCP, or SDR device;
  • give every enabled rig a unique ID and audio port;
  • use 127.0.0.1 for same-host connections;
  • use a LAN address or 0.0.0.0 only for deliberately remote listeners;
  • enable authentication before exposing server control or the web UI;
  • replace every example password and token;
  • keep credential files mode 0600 and owned by trx-rs;
  • set cookie_secure = true when the web UI is served through HTTPS.

For a remote server, control and each per-rig audio listener need an explicit non-loopback address:

[trx-server.listen]
enabled = true
listen = "0.0.0.0"
port = 4530

[trx-server.listen.auth]
tokens_file = "/home/trx-rs/.config/trx-rs/server-tokens"

[[trx-server.rigs]]
id = "station-hf"

[trx-server.rigs.audio]
enabled = true
listen = "0.0.0.0"
port = 4531

When using several [[trx-server.rigs]] entries, configure audio under each [trx-server.rigs.audio] section. Do not rely on the legacy flat [trx-server.audio] section.

For a web UI behind a reverse proxy, keep the backend on loopback and choose an unused port. The proxy upstream must use the same address and port:

[trx-client.frontends.http]
enabled = true
listen = "127.0.0.1"
port = 7345

[trx-client.frontends.http.auth]
enabled = true
users_file = "/home/trx-rs/.config/trx-rs/http-users.json"
cookie_secure = true

Validate the configuration before starting either daemon:

sudo -u trx-rs /opt/trx-rs/bin/trx-server \
  --check-config --config /home/trx-rs/.config/trx-rs/trx-rs.toml
sudo -u trx-rs /opt/trx-rs/bin/trx-client \
  --check-config --config /home/trx-rs/.config/trx-rs/trx-rs.toml

Treat unknown-key and deprecated-section warnings as deployment errors. They often mean a setting is not applied where expected.

5. Install the systemd user services

Render the packaged units with the immutable binary directory:

sed 's|@BINDIR@|/opt/trx-rs/bin|g' packaging/systemd/trx-server.service \
  | sudo tee /home/trx-rs/.config/systemd/user/trx-server.service >/dev/null
sed 's|@BINDIR@|/opt/trx-rs/bin|g' packaging/systemd/trx-client.service \
  | sudo tee /home/trx-rs/.config/systemd/user/trx-client.service >/dev/null
sudo chown trx-rs:trx-rs \
  /home/trx-rs/.config/systemd/user/trx-server.service \
  /home/trx-rs/.config/systemd/user/trx-client.service
sudo chmod 0644 \
  /home/trx-rs/.config/systemd/user/trx-server.service \
  /home/trx-rs/.config/systemd/user/trx-client.service

Enable lingering so the user manager runs without an interactive login, then start it:

sudo loginctl enable-linger trx-rs
trx_uid=$(id -u trx-rs)
sudo systemctl start "user@${trx_uid}.service"

Manage the user services through that user's runtime directory:

trx_uid=$(id -u trx-rs)
sudo -u trx-rs XDG_RUNTIME_DIR="/run/user/${trx_uid}" \
  systemctl --user daemon-reload
sudo -u trx-rs XDG_RUNTIME_DIR="/run/user/${trx_uid}" \
  systemctl --user enable --now trx-server.service trx-client.service

The client unit has ordering, but not activation, on trx-server.service. If both are enabled locally, the client starts after the server process is started. If the local server is disabled because all remotes are external, the client does not start it. The client retries remote connections; systemd cannot order startup against a service on another host.

6. Firewall and reverse proxy

Allow only required ports and sources. For example, permit server control and audio only from the trusted radio subnet, not from every interface. Exact commands differ between nftables, firewalld, and ufw.

For the web UI, terminate TLS in a maintained reverse proxy and send traffic to the loopback backend. Preserve WebSocket upgrade headers if the proxy requires them. Example nginx location:

location / {
    proxy_pass http://127.0.0.1:7345;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Test the backend directly before debugging a 502 Bad Gateway response:

curl --fail --show-error http://127.0.0.1:7345/

A refused connection means nothing is listening at the proxy's configured address and port. Check for port conflicts with ss -ltnp and confirm the application log reports the same bind address as the proxy upstream.

7. Verify the deployment

trx_uid=$(id -u trx-rs)
sudo -u trx-rs XDG_RUNTIME_DIR="/run/user/${trx_uid}" \
  systemctl --user status trx-server.service trx-client.service
sudo journalctl _UID="${trx_uid}" \
  -u trx-server.service -u trx-client.service --since today
sudo ss -ltnp

Verify all of the following:

  • both required services remain active without a restart loop;
  • radio hardware opens successfully;
  • logs show the intended control, audio, and frontend bind addresses;
  • the client connects to every configured control and audio endpoint;
  • only intended interfaces expose listeners;
  • authentication works and anonymous access is rejected where configured;
  • the HTTPS proxy returns the UI and supports live updates;
  • the service account cannot write /opt/trx-rs/bin.

Do not rely only on systemctl reporting active: individual frontend tasks can fail after the main client process starts. Always inspect startup logs and probe each required endpoint.

8. Upgrade and roll back

Build and validate a new revision before replacing the installed executables. Back up configuration and state first. Stop the services, install all binaries from the same build, and restart:

trx_uid=$(id -u trx-rs)
sudo -u trx-rs XDG_RUNTIME_DIR="/run/user/${trx_uid}" \
  systemctl --user stop trx-client.service trx-server.service
sudo install -o root -g root -m 0755 \
  target/release/trx-server \
  target/release/trx-client \
  target/release/trx-configurator \
  /opt/trx-rs/bin/
sudo -u trx-rs XDG_RUNTIME_DIR="/run/user/${trx_uid}" \
  systemctl --user start trx-server.service trx-client.service

Keep the previous binaries or package revision available for rollback. If the new version fails, stop both services, restore the complete previous binary set, restore configuration only if its format changed, and start the services again.