Skip to content

Install & getting up and running

There are two things you might install:

  1. The server — the Rafe web app (this is what you browse to).
  2. Optional SDR add-ons — SatDump (weather/Inmarsat decode) and AIS-catcher (ships), plus remote collectors on Raspberry Pis.

The RS-BA1 server is a separate, self-contained package with its own instructions.


1. Server (Linux Mint / Ubuntu)

A single script provisions everything from a fresh Linux Mint 21/22 or Ubuntu 22.04/24.04 machine: system packages, the Python venv, every decoder built from source, the headless-fldigi audio engine, whisper.cpp, Pat/ARDOP and the systemd units.

git clone <this-repo> icom-remote && cd icom-remote
sudo bash deploy/install-mint.sh

It is idempotent (safe to re-run) and overridable:

sudo RUN_USER=patrick APP_DIR=/opt/icom-remote bash deploy/install-mint.sh

To also stand up the Raspberry Pi collector netboot server (phase 10 — a safe proxyDHCP + TFTP + NFS server for diskless SDR nodes), add WITH_PXE=1 (and it enables web management by default; set ENABLE_WEB_MANAGE=0 to keep it CLI-only):

sudo WITH_PXE=1 bash deploy/install-mint.sh

Then build the Pi roots and register nodes — see docs/pxe-collectors.md.

What it installs (phases 1–9):

  • apt: build tools, PulseAudio/ALSA, multimon-ng, WSJT-X, fldigi+Xvfb, RTL-SDR + SoapySDR (RTL/Lime modules), pat, and the digital-voice build deps.
  • venv: aiohttp numpy scipy pillow aprslib sgp4 soundfile pyserial pyais and SSTV from source.
  • source builds into /opt: ft8_lib (FT8/FT4), ardopcf (Winlink), codec2 (FreeDV rx+tx), m17-cxx-demod (M17), mbelib+dsd-fme (P25), whisper.cpp + the base.en model.
  • services: icom-remote, fldigi-headless, whisper-server (and Pat).

Build gotchas and their fixes are recorded in deploy/BUILD-NOTES.md.

Fresh Mint tip: the desktop's Update Manager holds the apt/dpkg lock on first boot — let it finish before (or during; the script waits) the install. On a headless server also disable sleep so it can't suspend: sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

First run

# 1. point it at your radio + set a secret
sudoedit /opt/icom-remote/icom-remote.env      # ICOM_HOST, TX_ENABLED, SECRET_KEY…

# 2. create a login (give the web login its OWN password — TX radiates!)
cd /opt/icom-remote && USERS_FILE=/opt/icom-remote/users.json \
    .venv/bin/python -m app.users add <name>

# 3. start it
sudo systemctl start fldigi-headless whisper-server icom-remote

The app listens on 127.0.0.1:8000. Front it with a reverse proxy for TLS.

Reverse proxy (nginx + self-signed TLS)

To reach it over HTTPS on the LAN (e.g. https://<host>/):

sudo apt install -y nginx
sudo openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
    -keyout /etc/ssl/icom-remote/key.pem -out /etc/ssl/icom-remote/cert.pem \
    -subj "/CN=<host-ip>" -addext "subjectAltName=IP:<host-ip>"

Configure an nginx vhost that redirects 80→443 and proxies / to 127.0.0.1:8000 with WebSocket upgrade headers (Upgrade/Connection from a map $http_upgrade), proxy_read_timeout 3600s. The app works behind a path prefix too (e.g. mounted at /icom in a proxy chain).

icom-remote.env

Key Meaning
ICOM_HOST the radio (or an rsba1server) IP
ICOM_USER / ICOM_PASS RS-BA1 network login
TX_ENABLED 1 to allow transmit at all
SECRET_KEY session signing key (openssl rand -hex 32)
BIND_HOST / BIND_PORT default 127.0.0.1:8000
USERS_FILE path to users.json

The env only seeds radios.json on first run; after that the in-app radio switcher (⚙) is the source of truth.


2. SDR decode add-ons (on the box with the SDR)

These are only needed for the SDR features (weather sats, Inmarsat, AIS) and only on the host that has the SDR (e.g. a LimeSDR Mini). They are third-party binaries — install deliberately.

SatDump (weather sats + Inmarsat)

# Ubuntu 24.04 example
curl -sL -o /tmp/satdump.deb \
  https://github.com/SatDump/SatDump/releases/download/1.2.2/satdump_1.2.2_ubuntu_24.04_amd64.deb
sudo apt install -y /tmp/satdump.deb

Then the DECODE button on Meteor-M/NOAA/Inmarsat birds in the SATS view drives SatDump against the SDR (native limesdr source; RTL-SDR/Airspy also supported). Set SATDUMP_SOURCE in the environment to use a different source.

AIS-catcher (ships)

sudo apt install -y libsoapysdr-dev librtlsdr-dev cmake build-essential
git clone --depth 1 https://github.com/jvde-github/AIS-catcher /opt/AIS-catcher
cmake -S /opt/AIS-catcher -B /opt/AIS-catcher/build -DCMAKE_BUILD_TYPE=Release
make -C /opt/AIS-catcher/build -j"$(nproc)"

Then AIS in the toolbox → LIME AIS feeds ships from the local SDR.

The local SDR can only do one of these at a time (AIS or a satellite decode) — they stop each other. Remote collectors are independent.


3. Remote collectors (Raspberry Pi + cheap RTL-SDR)

To extend coverage, put cheap dongles on Pis around the site and feed the central server. On each Pi: install the RS-BA1 package + AIS-catcher, then:

python3 -m rsba1server --collect ais \
    --feed <server-ip>:10110 --serial <dongle-serial> --sdr-gain 40

Run it under systemd so it survives reboots. See the RS-BA1 server guide for the full picture, including serving several SDRs as switchable radios.

Instead of hand-imaging an SD card per Pi, boot them diskless from the server: one shared root, zero-touch provisioning, central management. The server runs a safe proxyDHCP (it never assigns IPs — the router keeps doing DHCP) plus TFTP/NFS; each Pi netboots and runs its assigned collector (Inmarsat, AIS, or an rtl_tcp relay feeding the native DATV/TETRA decoders). Online nodes show up in the app's COLLECTOR NODES panel.

sudo bash deploy/pxe/install-pxe-server.sh
sudo COLLECTOR_EXTRAS=satdump,aiscatcher bash deploy/pxe/build-rpi-root.sh
sudo bash deploy/pxe/register-node.sh <pi-serial> inmarsat freq=1541450000

Full guide (Pi EEPROM boot-order setup, roles, safety, troubleshooting): docs/pxe-collectors.md.


Updating

Redeploy the tree to /opt/icom-remote (rsync, excluding .venv, icom-remote.env, users.json, radios.json, freqmem.json, recordings/) and sudo systemctl restart icom-remote. Static assets are cache-busted with a ?v=N query, so bump it (or hard-refresh) after a UI change.