From 05c337b581450414244b97ce0b4da8e024d4a501 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sun, 16 Aug 2026 23:01:45 +0200 Subject: [PATCH] [feat](trx-rs): add build/install script and systemd user services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide a one-shot installer and matching systemd *user* units so the server and client can be built, installed system-wide, and run in the background without hand-rolled steps. - script/install.sh: builds all three binaries (trx-server, trx-client, trx-configurator) in release mode and installs them to /usr/local/bin (configurable via --prefix/--bindir/PREFIX, sudo only when the target is not writable). Seeds ~/.config/trx-rs/trx-rs.toml from the example without ever overwriting existing config, then installs the user units with @BINDIR@ substituted to the real path and runs daemon-reload. Flags: --no-sdr, --no-build, --no-systemd, --enable-now. - script/uninstall.sh: stops/disables the units, removes them and the binaries; keeps config unless --purge. - packaging/systemd/{trx-server,trx-client}.service: user units reading the combined config at ~/.config/trx-rs/trx-rs.toml. The client softly depends on the server (Wants/After). KillSignal=SIGINT matches how the binaries shut down cleanly (SIGTERM is not handled). - README: new "Install (optional, Linux + systemd)" section. The web UI assets are embedded in the binary, so an install needs only the binaries plus a config file — no data directory to ship. Signed-off-by: Stan Grams --- README.md | 33 ++++++ packaging/systemd/trx-client.service | 37 +++++++ packaging/systemd/trx-server.service | 34 ++++++ script/install.sh | 156 +++++++++++++++++++++++++++ script/uninstall.sh | 86 +++++++++++++++ 5 files changed, 346 insertions(+) create mode 100644 packaging/systemd/trx-client.service create mode 100644 packaging/systemd/trx-server.service create mode 100755 script/install.sh create mode 100755 script/uninstall.sh diff --git a/README.md b/README.md index 1d65ab18..117aac25 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,39 @@ section and the client reads `[trx-client]`. Open the configured HTTP frontend address in a browser (default `http://localhost:8080`). +### 5. Install (optional, Linux + systemd) + +To build, install the binaries system-wide, and set up systemd **user** services +that run the server and client in the background: + +```bash +script/install.sh +``` + +This builds in release mode, installs `trx-server`, `trx-client`, and +`trx-configurator` to `/usr/local/bin` (using `sudo` only if needed), seeds +`~/.config/trx-rs/trx-rs.toml` from the example (existing config is never +overwritten), and installs `~/.config/systemd/user/{trx-server,trx-client}.service`. + +```bash +script/install.sh --prefix ~/.local # install to ~/.local/bin instead +script/install.sh --no-sdr # build without SoapySDR support +script/install.sh --enable-now # also enable + start the services now +script/install.sh --help # all options +``` + +After editing your config, manage the services with: + +```bash +systemctl --user enable --now trx-server trx-client # start now + on login +journalctl --user -u trx-server -u trx-client -f # follow logs +loginctl enable-linger "$USER" # keep running after logout +``` + +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). + ## How It Works ```mermaid diff --git a/packaging/systemd/trx-client.service b/packaging/systemd/trx-client.service new file mode 100644 index 00000000..1c5e2c02 --- /dev/null +++ b/packaging/systemd/trx-client.service @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# systemd *user* unit for the trx-rs radio client and web frontend. +# +# Install with `script/install.sh` (which substitutes @BINDIR@ and copies this +# into ~/.config/systemd/user/), or by hand: +# sed 's|@BINDIR@|/usr/local/bin|' trx-client.service \ +# > ~/.config/systemd/user/trx-client.service +# systemctl --user daemon-reload +# systemctl --user enable --now trx-client.service +# +# Reads the combined config at ~/.config/trx-rs/trx-rs.toml ([trx-client] +# section) and connects to the server (default 127.0.0.1:4530). The web UI +# defaults to http://127.0.0.1:8080. + +[Unit] +Description=trx-rs radio client and web frontend +Documentation=https://github.com/stanislawgrams/trx-rs +# Prefer the server to be up first; it is a soft dependency so the client still +# starts (and retries) if the server is managed separately. +Wants=trx-server.service +After=trx-server.service + +[Service] +Type=simple +ExecStart=@BINDIR@/trx-client --config %h/.config/trx-rs/trx-rs.toml +Restart=on-failure +RestartSec=2 +# Shuts down cleanly on SIGINT (see trx-server.service for the rationale). +KillSignal=SIGINT +TimeoutStopSec=15 +NoNewPrivileges=true + +[Install] +WantedBy=default.target diff --git a/packaging/systemd/trx-server.service b/packaging/systemd/trx-server.service new file mode 100644 index 00000000..e211ae2c --- /dev/null +++ b/packaging/systemd/trx-server.service @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# systemd *user* unit for the trx-rs radio server. +# +# Install with `script/install.sh` (which substitutes @BINDIR@ and copies this +# into ~/.config/systemd/user/), or by hand: +# sed 's|@BINDIR@|/usr/local/bin|' trx-server.service \ +# > ~/.config/systemd/user/trx-server.service +# systemctl --user daemon-reload +# systemctl --user enable --now trx-server.service +# +# Reads the combined config at ~/.config/trx-rs/trx-rs.toml ([trx-server] +# section). Serial (/dev/ttyUSB*) and audio access require your user to be in +# the `dialout` and `audio` groups — a user unit cannot grant them itself. + +[Unit] +Description=trx-rs radio server (CAT/SDR backend) +Documentation=https://github.com/stanislawgrams/trx-rs + +[Service] +Type=simple +ExecStart=@BINDIR@/trx-server --config %h/.config/trx-rs/trx-rs.toml +Restart=on-failure +RestartSec=2 +# Both binaries shut down cleanly on SIGINT (the server drains audio and exits +# 0); systemd's default SIGTERM is not handled, so signal SIGINT instead. +KillSignal=SIGINT +TimeoutStopSec=15 +NoNewPrivileges=true + +[Install] +WantedBy=default.target diff --git a/script/install.sh b/script/install.sh new file mode 100755 index 00000000..1792716b --- /dev/null +++ b/script/install.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Build trx-rs in release mode and install the binaries system-wide, seed a +# per-user config, and install systemd *user* services to run the server and +# client. Idempotent: existing config is never overwritten. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +BINARIES=(trx-server trx-client trx-configurator) + +# --- Defaults (override via flags or environment) --------------------------- +PREFIX="${PREFIX:-/usr/local}" +BINDIR="${BINDIR:-}" # derived from PREFIX unless set explicitly +NO_SDR=0 +DO_BUILD=1 +DO_SYSTEMD=1 +ENABLE_NOW=0 + +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/trx-rs" +CONFIG_FILE="$CONFIG_DIR/trx-rs.toml" +UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" + +usage() { + cat < ${BINDIR:-$PREFIX/bin} (uses sudo if not writable) + config -> $CONFIG_FILE (from trx-rs.toml.example, only if absent) + services -> $UNIT_DIR/{trx-server,trx-client}.service +EOF +} + +# --- Parse args ------------------------------------------------------------- +while [ $# -gt 0 ]; do + case "$1" in + --prefix) PREFIX="$2"; shift 2 ;; + --bindir) BINDIR="$2"; shift 2 ;; + --no-sdr) NO_SDR=1; shift ;; + --no-build) DO_BUILD=0; shift ;; + --no-systemd) DO_SYSTEMD=0; shift ;; + --enable-now) ENABLE_NOW=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "error: unknown option '$1'" >&2; usage >&2; exit 2 ;; + esac +done + +[ -n "$BINDIR" ] || BINDIR="$PREFIX/bin" + +log() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; } + +# Return 0 (true) if writing into $1 needs elevated privileges. +need_sudo() { + local dir="$1" + while [ ! -e "$dir" ]; do dir="$(dirname "$dir")"; done + [ -w "$dir" ] && return 1 || return 0 +} + +# --- Build ------------------------------------------------------------------ +if [ "$DO_BUILD" -eq 1 ]; then + build_args=(build --release --manifest-path "$PROJECT_ROOT/Cargo.toml") + for b in "${BINARIES[@]}"; do build_args+=(-p "$b"); done + if [ "$NO_SDR" -eq 1 ]; then + # --no-default-features only affects trx-server; harmless elsewhere. + build_args+=(-p trx-server --no-default-features) + log "Building (release, no SDR)…" + else + log "Building (release, with SDR)…" + fi + cargo "${build_args[@]}" +fi + +# --- Install binaries ------------------------------------------------------- +SUDO="" +if need_sudo "$BINDIR"; then + SUDO="sudo" + log "Installing binaries to $BINDIR (using sudo)…" +else + log "Installing binaries to $BINDIR…" +fi + +for b in "${BINARIES[@]}"; do + src="$PROJECT_ROOT/target/release/$b" + if [ ! -x "$src" ]; then + echo "error: $src not found (build failed or --no-build with no prior build?)" >&2 + exit 1 + fi + $SUDO install -Dm755 "$src" "$BINDIR/$b" + log "installed $BINDIR/$b" +done + +# --- Seed per-user config --------------------------------------------------- +if [ -e "$CONFIG_FILE" ]; then + log "Config already present, leaving it untouched: $CONFIG_FILE" +else + mkdir -p "$CONFIG_DIR" + install -m600 "$PROJECT_ROOT/trx-rs.toml.example" "$CONFIG_FILE" + log "Seeded config from example: $CONFIG_FILE" + warn "Edit $CONFIG_FILE for your rig before starting the services." +fi + +# --- systemd user services -------------------------------------------------- +if [ "$DO_SYSTEMD" -eq 1 ]; then + mkdir -p "$UNIT_DIR" + for unit in trx-server trx-client; do + sed "s|@BINDIR@|$BINDIR|g" \ + "$PROJECT_ROOT/packaging/systemd/$unit.service" \ + > "$UNIT_DIR/$unit.service" + log "installed $UNIT_DIR/$unit.service" + done + + if command -v systemctl >/dev/null 2>&1; then + systemctl --user daemon-reload || warn "systemctl --user daemon-reload failed" + if [ "$ENABLE_NOW" -eq 1 ]; then + log "Enabling and starting user services…" + systemctl --user enable --now trx-server.service trx-client.service + warn "Run 'loginctl enable-linger $USER' to keep services running after logout." + else + cat < +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Reverse script/install.sh: stop and remove the systemd user services and the +# installed binaries. User config (~/.config/trx-rs) is left in place unless +# --purge is given. + +set -euo pipefail + +BINARIES=(trx-server trx-client trx-configurator) + +PREFIX="${PREFIX:-/usr/local}" +BINDIR="${BINDIR:-}" +PURGE=0 + +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/trx-rs" +UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" + +usage() { + cat <&2; usage >&2; exit 2 ;; + esac +done + +[ -n "$BINDIR" ] || BINDIR="$PREFIX/bin" + +log() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } + +need_sudo() { + local dir="$1" + while [ ! -e "$dir" ]; do dir="$(dirname "$dir")"; done + [ -w "$dir" ] && return 1 || return 0 +} + +# --- Stop and remove user services ----------------------------------------- +if command -v systemctl >/dev/null 2>&1; then + log "Stopping and disabling user services…" + systemctl --user disable --now trx-client.service trx-server.service 2>/dev/null || true +fi +for unit in trx-client trx-server; do + if [ -e "$UNIT_DIR/$unit.service" ]; then + rm -f "$UNIT_DIR/$unit.service" + log "removed $UNIT_DIR/$unit.service" + fi +done +if command -v systemctl >/dev/null 2>&1; then + systemctl --user daemon-reload || true +fi + +# --- Remove binaries -------------------------------------------------------- +SUDO="" +if need_sudo "$BINDIR"; then SUDO="sudo"; fi +for b in "${BINARIES[@]}"; do + if [ -e "$BINDIR/$b" ]; then + $SUDO rm -f "$BINDIR/$b" + log "removed $BINDIR/$b" + fi +done + +# --- Optionally purge config ------------------------------------------------ +if [ "$PURGE" -eq 1 ]; then + rm -rf "$CONFIG_DIR" + log "purged $CONFIG_DIR" +else + log "Left config in place: $CONFIG_DIR (use --purge to delete)" +fi + +log "Done."