| pick your distro, get ZFS on root
kldload — your platform, your way, free
Source

Appliance Recipe: IRLP Ham Radio Node

A kldload appliance for ham radio operators — IRLP/EchoLink repeater linking over WireGuard, digital modes (FT8, JS8Call, APRS), SDR integration, and audio logging on ZFS. Voice goes in one radio, comes out another, encrypted over the internet. Digital mode contacts get logged, decoded, and archived on ZFS datasets with automatic snapshots.

The problem: Ham radio repeaters have limited range. Linking them over the internet traditionally requires exposed public ports, unencrypted VoIP, and complex NAT configuration. Digital mode software runs on Windows with no backup strategy.

The solution: Two kldload nodes connected via WireGuard. Voice goes in one radio, comes out the other — encrypted, NAT-friendly, single UDP port. Digital modes run headless with ZFS-backed logging.

Ham radio and kldload are a natural fit. The ham community has been building Linux-based repeater controllers, packet nodes, and digital mode stations since the 1990s. kldload adds what was always missing: ZFS for reliable storage (audio logs, QSO databases, APRS position data), WireGuard for encrypted linking (replaces the unencrypted VoIP that IRLP and EchoLink use natively), and the offline install capability (critical for emergency/EMCOMM deployments where internet might be intermittent). The CM108 PTT circuit below is the same design used by almost every homebrew digital mode interface — it's proven, cheap, and works with every radio from a Baofeng to a Kenwood. For SDR-based reception (APRS iGate, wideband monitoring, weather satellites), see the Signal Observatory recipe.

Architecture

Site A (radio + antenna)              Site B (radio + antenna)
┌──────────────────────┐              ┌──────────────────────┐
│  FM Transceiver      │              │  FM Transceiver      │
│       │              │              │       │              │
│  ┌────▼────┐         │              │  ┌────▼────┐         │
│  │USB Audio│ CM108   │              │  │USB Audio│ CM108   │
│  │  Card   │ + PTT   │              │  │  Card   │ + PTT   │
│  └────┬────┘         │              │  └────┬────┘         │
│       │              │              │       │              │
│  ┌────▼────────────┐ │              │  ┌────▼────────────┐ │
│  │  kldload      │ │              │  │  kldload      │ │
│  │  SvxLink        │ │              │  │  SvxLink        │ │
│  │  wg0: 10.99.0.1 ├─┼── UDP ──────┼──┤  wg0: 10.99.0.2│ │
│  └─────────────────┘ │   51820      │  └─────────────────┘ │
└──────────────────────┘              └──────────────────────┘

Hardware

Component Details Cost
Mini PC Any x86_64 (Raspberry Pi works too with ARM build) $100-200
USB sound card CM108/CM119 chipset (has GPIO for PTT) $5-15
FM transceiver Handheld or mobile radio (e.g., Baofeng UV-5R) $25-50
Audio cables 3.5mm to radio mic/speaker jacks $10
USB stick kldload installer $5
Total ~$150-280

CM108 PTT wiring

The CM108 USB audio chipset has a GPIO3 pin (pin 13) that can be used for Push-To-Talk control without a separate interface board:

CM108 GPIO3 (pin 13) → 2N2222 transistor base (via 4.7K resistor)
Transistor collector → Radio PTT line
Transistor emitter → Ground

Hamlib supports this natively as PTT type CM108.


Step 1: Install kldload

cat > /tmp/answers.env << 'EOF'
KLDLOAD_DISTRO=debian
KLDLOAD_DISK=/dev/sda
KLDLOAD_HOSTNAME=irlp-node-a
KLDLOAD_USERNAME=admin
KLDLOAD_PASSWORD=changeme
KLDLOAD_PROFILE=server
KLDLOAD_NET_METHOD=dhcp
EOF
kldload-install-target --config /tmp/answers.env

apt install -y svxlink-server svxlink-calibration-tools
apt install -y alsa-utils sox hamlib-utils

Step 3: Configure USB audio

# Find the USB sound card
arecord -l
# card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio]

# Test recording (speak into the radio, key up)
arecord -D plughw:1,0 -f S16_LE -r 8000 -c 1 -d 5 /tmp/test.wav
aplay /tmp/test.wav

# Set levels
alsamixer -c 1
# Set capture to ~80%, playback to ~70%

# Save ALSA state
alsactl store 1

Step 4: Set up CM108 PTT

# udev rule for CM108 GPIO access
cat > /etc/udev/rules.d/50-cm108-ptt.rules << 'EOF'
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0666"
EOF
udevadm control --reload-rules
udevadm trigger

# Test PTT with hamlib
rigctl -m 1 -p /dev/hidraw0 -P CM108 T 1   # key up
rigctl -m 1 -p /dev/hidraw0 -P CM108 T 0   # key down

# /etc/svxlink/svxlink.conf

[GLOBAL]
LOGICS=SimplexLogic
CFG_DIR=/etc/svxlink/svxlink.d
TIMESTAMP_FORMAT="%c"
CARD_SAMPLE_RATE=48000

[SimplexLogic]
TYPE=Simplex
RX=Rx1
TX=Tx1
MODULES=ModuleEchoLink
CALLSIGN=YOURCALL-L
SHORT_IDENT_INTERVAL=10
LONG_IDENT_INTERVAL=60
ROGER_SOUND=0

[Rx1]
TYPE=Local
AUDIO_DEV=alsa:plughw:1
AUDIO_CHANNEL=0
SQL_DET=VOX
VOX_FILTER_DEPTH=20
VOX_THRESH=500
DEEMPHASIS=0
PEAK_METER=1
DTMF_DEC_TYPE=INTERNAL

[Tx1]
TYPE=Local
AUDIO_DEV=alsa:plughw:1
AUDIO_CHANNEL=0
PTT_TYPE=Hidraw
HID_DEVICE=/dev/hidraw0
HID_PTT_PIN=GPIO3
PREEMPHASIS=0
DTMF_TONE_LENGTH=100
DTMF_TONE_SPACING=50

# /etc/svxlink/svxlink.d/ModuleEchoLink.conf

[ModuleEchoLink]
NAME=EchoLink
ID=2
TIMEOUT=60
SERVERS=servers.echolink.org
CALLSIGN=YOURCALL-L
PASSWORD=your-echolink-password
SYSOPNAME=Your Name
LOCATION=[Svx] Your City, ST
MAX_QSOS=4
MAX_CONNECTIONS=4
LINK_IDLE_TIMEOUT=300

Step 7: Set up WireGuard between nodes

Node A:

umask 077
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key

cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <node-a-private-key>

[Peer]
PublicKey = <node-b-public-key>
Endpoint = node-b.example.com:51820
AllowedIPs = 10.99.0.2/32
PersistentKeepalive = 25
EOF

systemctl enable --now wg-quick@wg0

Node B: Same config, swap IPs and keys.


Instead of EchoLink (which requires public servers), use SvxLink’s remotetrx to link two nodes directly over the WireGuard tunnel.

Node A — runs svxlink (the repeater controller):

# In svxlink.conf, change Rx1 to use a network receiver
[Rx1]
TYPE=Net
HOST=10.99.0.2
TCP_PORT=5210
CODEC=S16

Node B — runs remotetrx (remote transceiver):

# /etc/svxlink/remotetrx.conf

[GLOBAL]
LOGICS=Trx1
CFG_DIR=/etc/svxlink/svxlink.d
TIMESTAMP_FORMAT="%c"

[Trx1]
TYPE=Net
RX=Rx1
TX=Tx1
LISTEN_PORT=5210
AUTH_KEY=shared-secret-key

[Rx1]
TYPE=Local
AUDIO_DEV=alsa:plughw:1
AUDIO_CHANNEL=0
SQL_DET=VOX
VOX_FILTER_DEPTH=20
VOX_THRESH=500

[Tx1]
TYPE=Local
AUDIO_DEV=alsa:plughw:1
AUDIO_CHANNEL=0
PTT_TYPE=Hidraw
HID_DEVICE=/dev/hidraw0
HID_PTT_PIN=GPIO3
# Node B
systemctl enable --now remotetrx

# Node A
systemctl enable --now svxlink

Now voice on Node A’s radio comes out Node B’s radio and vice versa — over an encrypted WireGuard tunnel. No public ports exposed except UDP 51820.


Step 9: Firewall

cat > /etc/nftables.d/irlp.nft << 'EOF'
table inet filter {
  chain input {
    # SSH
    tcp dport 22 accept

    # WireGuard
    udp dport 51820 accept

    # SvxLink remotetrx (only on WireGuard interface)
    iifname "wg0" tcp dport 5210 accept

    # EchoLink (if used — only needed on one node)
    udp dport { 5198, 5199 } accept
    tcp dport 5200 accept
  }
}
EOF
systemctl reload nftables

Step 10: ZFS snapshots for audio logs

# Dataset for recorded audio
zfs create -o mountpoint=/srv/radio-logs -o compression=zstd rpool/srv/radio-logs

# SvxLink can log audio — configure in svxlink.conf:
# REC_DIR=/srv/radio-logs

# Daily snapshots
echo '0 0 * * * root zfs snapshot rpool/srv/radio-logs@daily-$(date +\%Y\%m\%d)' >> /etc/crontab

# Replicate logs to backup node
echo '0 2 * * * root syncoid rpool/srv/radio-logs 10.99.0.2:rpool/srv/radio-logs-backup' >> /etc/crontab

Test

# Check audio
arecord -D plughw:1,0 -f S16_LE -r 8000 -c 1 -d 3 /tmp/test.wav && aplay /tmp/test.wav

# Check PTT
rigctl -m 1 -p /dev/hidraw0 -P CM108 T 1 && sleep 2 && rigctl -m 1 -p /dev/hidraw0 -P CM108 T 0

# Check WireGuard
wg show wg0

# Check SvxLink
systemctl status svxlink
journalctl -u svxlink -f

# Key up on the local radio, you should hear it on the remote radio


Digital Modes — FT8, JS8Call, APRS

Digital modes are where ham radio meets data infrastructure. FT8 lets you make contacts worldwide with 5 watts and a wire antenna — the software decodes signals buried in the noise floor. JS8Call adds keyboard-to-keyboard messaging over HF. APRS provides position tracking and messaging on VHF. All of them generate data that belongs on ZFS: QSO logs, decoded messages, position databases, audio recordings. The same kldload box that runs SvxLink for voice linking can run WSJT-X for FT8 and Direwolf for APRS simultaneously — different USB sound cards, different radio ports, same ZFS storage.

FT8 / WSJT-X (headless)

# Install WSJT-X
apt install -y wsjtx

# ZFS dataset for FT8 logs and audio
zfs create -o mountpoint=/srv/ft8 -o compression=zstd rpool/srv/ft8

# For headless operation, use jtdx or wsjtx with a virtual display
apt install -y xvfb
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99

# Or use the command-line decoder directly
jt9 -8 -d 3 -f 1500 /srv/ft8/audio/capture.wav

JS8Call (keyboard messaging over HF)

# Install JS8Call
apt install -y js8call

# JS8Call can relay messages between stations — a mesh network over HF
# Combined with WireGuard, you can bridge JS8Call stations over the internet
# for a hybrid RF/internet messaging network

APRS with Direwolf (packet radio + position tracking)

# Install Direwolf — software TNC (modem) for APRS
apt install -y direwolf

# ZFS dataset for APRS logs
zfs create -o mountpoint=/srv/aprs -o compression=zstd-9 rpool/srv/aprs

cat > /etc/direwolf.conf << 'DW'
ADEVICE plughw:1,0
CHANNEL 0
MYCALL YOURCALL-10
MODEM 1200

# iGate — relay APRS packets to the internet
IGSERVER noam.aprs2.net
IGLOGIN YOURCALL-10 12345

# Log all received packets
LOGDIR /srv/aprs/logs

# Beacon your own position every 30 minutes
PBEACON delay=1 every=30 via=WIDE1-1,WIDE2-1 \
  lat=YOUR_LAT long=YOUR_LON symbol="/-" \
  comment="kldload APRS iGate"
DW

# systemd service
cat > /etc/systemd/system/direwolf.service << 'EOF'
[Unit]
Description=Direwolf APRS TNC
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/direwolf -c /etc/direwolf.conf -t 0
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now direwolf

Fldigi (multi-mode digital)

# Fldigi supports PSK31, RTTY, Olivia, MFSK, and dozens more
apt install -y fldigi

# For headless contest logging, use fllog + flrig for rig control
apt install -y fllog flrig

Winlink (email over radio)

# Pat — open-source Winlink client for Linux
# Sends and receives email over HF/VHF radio — critical for EMCOMM
go install github.com/la5nta/pat@latest

# Configure Pat with your callsign and radio
pat configure

# Connect to a Winlink RMS gateway
pat connect telnet    # internet (for testing)
pat connect ardop     # HF radio (ARDOP modem)

Emergency Communications (EMCOMM)

kldload's offline install capability makes it ideal for emergency communications. The ISO includes all packages — no internet required. Boot from USB, install to a laptop or mini PC, and you have a complete radio station: voice linking (SvxLink), digital modes (WSJT-X, Direwolf), email (Pat/Winlink), position tracking (APRS), and audio logging (ZFS). When the internet is down and cell towers are overloaded, ham radio is the communications backbone. A kldload EMCOMM kit is: a laptop, a USB sound card, a handheld radio, and a USB stick with the ISO. Total weight: 3 pounds. Setup time: 10 minutes.
# EMCOMM kit checklist:
# - kldload USB stick (pre-built ISO with all packages)
# - Laptop or mini PC with USB ports
# - CM108 USB sound card + audio cables
# - Handheld radio (Baofeng UV-5R or similar)
# - Antenna (roll-up J-pole for VHF, wire dipole for HF)
# - 12V battery + inverter (or USB power bank for the laptop)

# Boot the USB. Install in 5 minutes. You have:
# - SvxLink for voice repeater linking
# - Direwolf for APRS packet radio
# - Pat for Winlink email
# - WSJT-X for FT8 long-distance contacts
# - ZFS-backed logging for everything
# - WireGuard if any internet is available (satellite, cellular)

FCC Note

FCC Part 97 prohibits obscuring the meaning of amateur radio transmissions over the air. The WireGuard encryption applies to the internet backhaul, not the RF transmission. The RF side remains unencrypted as required. This is the same approach used by IRLP, EchoLink, AllStarLink, and 44Net Connect — all of which encrypt internet traffic while keeping RF transmissions in the clear.