Files
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

288 lines
9.7 KiB
Markdown

# 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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```toml
[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:
```toml
[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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```nginx
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:
```bash
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
```bash
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:
```bash
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.