install.sh)| Path | What | Owner |
|---|---|---|
/opt/radio-remote/releases/<timestamp>/ |
program releases (read-only to the service) | root |
/opt/radio-remote/current |
symlink to the active release | root |
/opt/radio-remote/venv/ |
Python environment | root |
/etc/radio-remote/config.toml |
settings | radio-remote, mode 0600 |
/var/lib/radio-remote/radio-remote.db |
users, sessions, audit log | radio-remote |
/var/backups/radio-remote/ |
backups (hold password hashes: mode 0600) | root |
/etc/systemd/system/radio-remote*.service/.timer |
units | root |
/etc/udev/rules.d/99-radio-remote.rules |
ModemManager exclusion for the radio's USB bridge | root |
Edit in the web UI (Admin > Config) or in the file. The file is the only place for the settings the UI refuses to
change on purpose: server.*, storage.*. The transmit permission (safety.allow_ptt) has its own switch in the same page
(Transmitting (PTT)): enabling asks for the administrator's password, applies at once, is written to the file and is audited.
Other changes need sudo systemctl restart radio-remote (the log level and audio gains apply live). The UI's
"Restart service now" button does the same.
System card (Admin > Config). Two buttons that are always there: Restart the service… (confirmation, then the page reloads by itself when the service is back) and
Reboot the system… (needs your password again; refused while the radio transmits; it only appears when the root helper radio-remote-power.path is installed, which install.sh and
update.sh do). Like Update now, the page itself cannot reboot anything: it only drops /var/lib/radio-remote/power-request, and a root helper (scripts/power_from_request.sh,
started by systemd) reboots the machine after about 3 seconds, and does nothing else. Both actions are written to the audit log.
| Key | Meaning | Default |
|---|---|---|
server.host / port |
where the app listens. Keep 127.0.0.1; Caddy/Tailscale face the network |
127.0.0.1 / 8080 |
server.allowed_origins |
extra browser origins allowed for WebSockets (needed only if a proxy rewrites Host) |
[] |
radio.model |
mock or ft991a |
mock |
radio.serial_port / baud |
CAT port (auto probes) and speed |
auto / 38400 |
radio.hamlib_model |
reserved (Hamlib backend is not implemented) | 0 |
safety.allow_ptt |
must be true before a real radio can be keyed. Set it with the Transmitting (PTT) switch in Admin > Config (password required) or in the file |
false |
safety.tx_timeout_s |
server-enforced maximum continuous transmission | 120 |
safety.tx_warn |
one steady beep (about 1.5 s) on the transmitting device shortly before the time limit; the timer under PTT also turns red | true |
safety.tx_warn_s |
how many seconds before the limit the beep sounds (1-60) | 10 |
safety.ptt_heartbeat_timeout_s |
un-key if the PTT holder is silent this long | 1.0 |
safety.control_request_timeout_s |
an unanswered request for control passes after this long (trusted users skip the wait) | 10 |
audio.enabled / backend |
remote audio on/off; auto = alsa for a real radio, test tone for the mock |
true / auto |
audio.input_device / output_device |
ALSA devices (plughw:CARD=...,DEV=0) |
auto-detect |
audio.rx_gain_db / tx_gain_db |
-30..30 dB; a limiter always follows TX gain | 0 |
audio.opus_bitrate / max_peers |
codec rate; simultaneous listeners | 32000 / 3 |
ui.tuning_steps_hz |
step choices in the UI | 10..10000 |
ui.meter_alc_full / meter_comp_full |
raw ALC / COMP value at which the radio's own meter is full (provisional calibration) | 160 / 255 |
ui.swr_warn |
SWR warning: while transmitting at or above this ratio the SWR bar turns red and a message appears; 0 = off, otherwise 1.2..10 |
3.0 |
ui.swr_raw_at_3 |
only for radios that report a raw SWR value (FT-991A): the raw reading (10..255) at which the radio's own meter shows 3:1; the ratio is estimated on a straight line from 1:1 = 0. Provisional: the CAT manual gives no calibration | 100 |
logging.level |
DEBUG/INFO/WARNING/ERROR | INFO |
updates.check |
once a day ask GitHub whether a newer release exists and tell the administrators (nothing is installed automatically); also a switch in Admin > Config > Updates | true |
rigctl.enabled / port / allow / set |
the logbook link (logbook): off by default; port 4532; allow = private or a list of addresses/networks; set = false makes it read-only. Also a card in Admin > Config; applies at once |
false / 4532 / private / true |
updates.repo |
the GitHub repository to ask (owner/name); file only |
dev-jmatias/YaesuFT991A-Remote |
storage.data_dir |
database location | /var/lib/radio-remote |
sudo systemctl status radio-remote # is it running?
sudo systemctl restart radio-remote # restart (un-keys the transmitter first)
sudo systemctl stop radio-remote # stop
sudo systemctl disable --now radio-remote # stop and do not start at boot
journalctl -u radio-remote -f # follow the log
journalctl -u radio-remote --since "1 hour ago" --no-pager
systemctl list-timers radio-remote-backup.timer
sudo systemctl status caddy # the HTTPS front end
The service restarts itself after a crash (Restart=always) and is restarted by systemd if it hangs (watchdog, 20 s).
A switched-off or unplugged radio does not restart the service: it reconnects by itself. Stopping or restarting the
service releases PTT before the process exits.
In the UI, Admin > Diagnostics shows version, CPU/RAM/temperature, radio and CAT counters, audio state, clients, the
control holder and the recent log. sudo scripts/doctor.sh prints similar information plus USB/serial/audio devices.
Update notice. Once a day the server asks GitHub whether a newer release exists (one anonymous, read-only request to
api.github.com; switch it off in Admin > Config > Updates or with [updates] check = false). When there is one,
administrators see a bar at the top of the page ("Radio Remote v1.2.0 is available") with a link to the release notes. Nothing is
downloaded or installed by the notice itself.
Update from the page. In Admin > Config > Updates, when a newer version is known, press Update now…, type your password and confirm. The page only
drops a request file; a small root helper that systemd starts for it (radio-remote-update.path / .service, installed by install.sh and update.sh) runs
self_update.sh (download, SHA-256 check, backup, install, health check, automatic rollback). The service restarts, so Radio Remote is unavailable for about a
minute (a transmission in progress is stopped); the Updates card shows the log and the page reloads by itself when it is done. The result is also in
/var/lib/radio-remote/update.log, and the request and the outcome are in the audit log. A Pi that was installed before the helper existed gets it with its next
update from the command line.
Update from the Pi (needs internet on the Pi):
sudo /opt/radio-remote/current/scripts/self_update.sh --check # is there a newer release? (no root needed)
sudo /opt/radio-remote/current/scripts/self_update.sh # download, verify the checksum, install
It downloads the release's update package (radio-remote-vX.Y.Z.tar.gz) and its SHA256SUMS, refuses the package if the checksum
differs or it holds anything unexpected, then runs the package's own update.sh. --force reinstalls the newest release even if it is
not newer.
Without internet on the Pi, or from the source code:
# on a computer with internet: download radio-remote-vX.Y.Z.tar.gz from the Releases page, copy it to the Pi, then
tar xzf radio-remote-vX.Y.Z.tar.gz && cd radio-remote
sudo ./update.sh --dry-run # optional preview
sudo ./update.sh
(From a git checkout: git pull, then sudo ./update.sh; build the manual first with python scripts/build_docs.py if you want it on the Pi.)
update.sh: backs up config and users, installs the new version beside the old one (including Python packages,
before anything is switched), switches, restarts, and checks health. If the new version is not healthy it switches back
to the previous release automatically and keeps the failed one for inspection. The three newest releases are kept.
Config and the user database are never modified by an update.
Manual rollback: sudo ln -sfn /opt/radio-remote/releases/<older> /opt/radio-remote/current && sudo systemctl restart radio-remote.
(If the newer release changed Python dependencies, the older code normally still runs; if not, re-run
/opt/radio-remote/venv/bin/pip install -r /opt/radio-remote/releases/<older>/requirements.txt.)
A timer runs a backup daily (14 kept). Manual:
sudo scripts/backup.sh # -> /var/backups/radio-remote/radio-remote-<time>.tar.gz
sudo scripts/restore.sh /var/backups/radio-remote/radio-remote-20261001-030000.tar.gz
What is backed up: /etc/radio-remote/config.toml and /var/lib/radio-remote/radio-remote.db (a consistent
snapshot even while running). That is everything worth keeping; the program itself is reproducible from git.
Restore stops the service, keeps the replaced files as *.before-restore, restores, restarts and checks health.
Copy backups off the Pi occasionally: they are on the same SD card.
Settings only, from the web page. Admin > Config > Settings backup has Download settings (a small .toml file with the
radio, audio, meter and limit settings: no accounts, no passwords) and Restore from file…. A restore takes only the settings the web page
may change; the listener address, the storage folder and the transmit permission (server.*, storage.*, safety.allow_ptt) are never taken
from a file, so an old backup or a file from another Pi cannot lock you out or switch transmitting on. The replaced config is kept as
config.toml.bak, the change is audited (config_backup, config_restored) and a restart applies it. Use the daily archive above (or
restore.sh) when you also need the user accounts.
On the Pi: sudo -u radio-remote /opt/radio-remote/venv/bin/python -m radio_remote.cli --config /etc/radio-remote/config.toml reset-password <name>
(with PYTHONPATH=/opt/radio-remote/current/backend). It prompts for the new password and signs that user out everywhere.
The manual is installed with the program and served at https://<pi>/docs/ (the Help button in the app). It is built from docs/*.md
by python scripts/build_docs.py --pdf (HTML + a PDF) and travels inside every release as docs-html/.