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>
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.1for same-host connections; - use a LAN address or
0.0.0.0only for deliberately remote listeners; - enable authentication before exposing server control or the web UI;
- replace every example password and token;
- keep credential files mode
0600and owned bytrx-rs; - set
cookie_secure = truewhen 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.