# 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 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.