diff --git a/README.md b/README.md index 117aac25..e361cbae 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,11 @@ Serial (`/dev/ttyUSB*`) and audio access require your user to be in the `dialout` and `audio` groups. Remove everything with `script/uninstall.sh` (add `--purge` to also delete the config). +For an unattended or remotely accessible installation, follow the +[safe deployment guide](docs/Deployment.md). It covers a dedicated service +account, device permissions, authentication, firewall and reverse-proxy +boundaries, verification, upgrades, and rollback. + ## How It Works ```mermaid diff --git a/docs/Deployment.md b/docs/Deployment.md new file mode 100644 index 00000000..f1285025 --- /dev/null +++ b/docs/Deployment.md @@ -0,0 +1,287 @@ +# 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.