# RadioRations post-install notes

## First boot checklist

1. Confirm devices:

   ```bash
   ls -l /dev/aioc-aprs /dev/aioc-winlink /dev/gps 2>/dev/null
   # Single-radio installs may still use /dev/aioc-serial
   ls -l /dev/aioc-serial /dev/gps 2>/dev/null
   cat /proc/asound/cards
   cgps -s
   ```
2. `rr status` — services and ports
3. Start preferred APRS modem: `rr graywolf` or `rr direwolf`
4. **Graywolf web UI** (port 8080) — AGW and KISS are **off by default**; enable them for LinBPQ / clients:
   - **Audio / channel**
     - Input/output = `aioc-aprs` (ALSA name from udev)
     - Channel 0 for mono AIOC (see channel left/right notes if levels look wrong)
   - **PTT**
     - CM108 HID → prefer `/dev/aioc-aprs-ptt` (not raw `hidrawN`; see Graywolf PTT section below)
   - **AGWPE** (not enabled until you turn it on)
     - Enable AGW server
     - Listen / bind: `127.0.0.1:8000` (or `0.0.0.0:8000` only if you intentionally expose it)
     - Callsign: your APRS callsign-SSID (e.g. `KM7HBN-12`)
     - Used by clients that speak AGW (some tools; Pat on dual-radio usually uses **8002** on `direwolf-winlink` instead)
   - **KISS TCP** (required for LinBPQ on the APRS radio)
     - Add a KISS interface if none exists
     - Type: **TCP (server)**
     - Mode: **Modem** (Graywolf does RF; external TNC not required)
     - Port: **8001**
     - Enable **local only / localhost** so it is not open on the LAN or Wi‑Fi
     - LinBPQ `bpq32.cfg` port 1 should use `TCPPORT=8001` / `127.0.0.1`
   - **Verify ports**
     ```bash
     sudo ss -tlnp | egrep '8000|8001|8080'
     ```
     Expect Graywolf on **8080**; after enabling, **8001** (KISS) and optionally **8000** (AGW).
   - **iGate** (optional): server filter e.g. `r/lat/lon/dist_km` (~200 mi ≈ 322 km)
5. **Pat** (Winlink client) — finish interactive setup (installer does not assume your callsign beyond config.env templates):
   ```bash
   pat configure
   # or: pat init   # first-time guided setup if config is empty
   ```
   Typical values for this stack:
   - **mycall** / locator — your station callsign and Maidenhead grid
   - **http_addr** — `0.0.0.0:8081` (or leave default and use the service unit)
   - **ax25 engine** — `agwpe`
   - **agwpe.addr** — `localhost:8002` if using dual-radio `direwolf-winlink`;  
     `localhost:8000` if Pat shares the APRS modem’s AGW port instead
   - Secure login / Winlink password — set when you have a Winlink account
   Then: `sudo systemctl restart pat` and open the web UI (port 8081).
6. **LinBPQ** (port 8090): change telnet password from `changeme`
7. Dual radio: LinBPQ port 1 → KISS 8001 (APRS), port 2 → KISS 8003 (Winlink)
8. **After first reboot** (udev + groups):
   ```bash
   ls -l /dev/aioc* /dev/gps
   cat /proc/asound/cards
   groups   # should include dialout,audio,plugdev
   ```
   If AIOC symlinks are missing, identify serials and edit `/etc/udev/rules.d/99-ham-radio.rules` (see below).

## Launcher cheatsheet

```bash
rr status
rr graywolf          # APRS via Graywolf + start Winlink TNC
rr direwolf          # APRS via Direwolf + start Winlink TNC
rr winlink           # Winlink TNC + Pat only
rr stop / stop-winlink
rr restart gpsd
rr restart graywolf linbpq
rr logs
```

Aliases for `restart`: `gps`→gpsd, `wl`→direwolf-winlink, `bpq`→linbpq, `aprs`→graywolf.

---

## Identifying AIOC adapters (serial numbers)

All genuine AIOC units share the same USB IDs (`1209:7388`). **Friendly names must use the unique serial number** so names stay stable when ports renumber after reboot or cable swaps.

### 1. List USB and current names

```bash
lsusb
ls -l /dev/aioc* /dev/gps /dev/ttyACM* 2>/dev/null
cat /proc/asound/cards
```

### 2. Dump serial for every ACM device

```bash
for d in /dev/ttyACM*; do
  echo "======== $d ========"
  udevadm info -a -n "$d" | egrep 'KERNEL==|idVendor|idProduct|serial|product|manufacturer' | head -20
  echo
done
```

AIOCs show `idVendor==1209`, `idProduct==7388`, and a hex `serial` (e.g. `63d7d065`).\
GPS (u-blox) shows `1546:01a7` and should **not** be used as an AIOC serial.

### 3. Physical “which cable is which”

1. Note both serials from the loop above.
2. Unplug **only** the APRS AIOC.
3. Run `ls -l /dev/aioc*` again — the symlink that **disappeared** is the one you unplugged.
4. Plug it back in; map that serial → role (`aioc-aprs` vs `aioc-winlink`).

### 4. HID (PTT) devices

```bash
for h in /dev/hidraw*; do
  echo "======== $h ========"
  udevadm info -a -n "$h" | egrep 'KERNEL==|idVendor|idProduct|serial|product' | head -15
  echo
done
```

Match serial to the same AIOC, or create stable symlinks (recommended):

```bash
# Example lines in 99-ham-radio.rules
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="YOUR_APRS_SERIAL", SYMLINK+="aioc-aprs-ptt", MODE="0660", GROUP="audio"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="YOUR_WINLINK_SERIAL", SYMLINK+="aioc-winlink-ptt", MODE="0660", GROUP="audio"
```

### 5. Graywolf PTT — pin to the stable symlink

Graywolf’s web UI only lists raw `/dev/hidrawN` devices. Those numbers **swap** when USB order changes, which can key the wrong radio on a dual-AIOC station.

Config is stored in SQLite (not a text file):

```text
/var/lib/graywolf/graywolf.db
```

Requires `sqlite3` (installed by the RadioRations package step).

**See current PTT settings:**

```bash
sudo sqlite3 -header -column /var/lib/graywolf/graywolf.db \
  "SELECT id, channel_id, method, device, gpio_pin, invert FROM ptt_configs;"
```

**Point CM108 PTT at the stable APRS symlink** (stop Graywolf first so it does not overwrite the DB on shutdown):

```bash
sudo systemctl stop graywolf

sudo sqlite3 /var/lib/graywolf/graywolf.db \
  "UPDATE ptt_configs SET device='/dev/aioc-aprs-ptt' WHERE method='cm108' OR method LIKE '%cm108%';"

sudo systemctl start graywolf
# or: rr restart graywolf
```

Confirm the web UI now shows `/dev/aioc-aprs-ptt`, then test TX — only the APRS radio should key.

**Caution:** Using **Detect Devices** in the UI and saving a `hidrawN` again can overwrite the symlink path. Prefer the symlink, or re-run the `UPDATE` if it drifts.

---

## Updating udev after replacing an AIOC

1. Identify the **new** serial (steps above).

2. Edit rules:

   ```bash
   sudo nano /etc/udev/rules.d/99-ham-radio.rules
   ```

3. Replace only the serial string for that role. Example dual-radio layout:

   ```bash
   # APRS — serial CHANGEME_APRS
   SUBSYSTEM=="sound", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_APRS", ATTR{id}="aioc-aprs"
   SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_APRS", MODE="0660", GROUP="audio", SYMLINK+="aioc-aprs-ptt"
   KERNEL=="ttyACM*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_APRS", SYMLINK+="aioc-aprs", MODE="0660", GROUP="dialout"
   
   # Winlink — serial CHANGEME_WINLINK
   SUBSYSTEM=="sound", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_WINLINK", ATTR{id}="aioc-winlink"
   SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_WINLINK", MODE="0660", GROUP="audio", SYMLINK+="aioc-winlink-ptt"
   KERNEL=="ttyACM*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", ATTRS{serial}=="CHANGEME_WINLINK", SYMLINK+="aioc-winlink", MODE="0660", GROUP="dialout"
   
   # GPS
   KERNEL=="ttyACM*", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps", MODE="0660", GROUP="dialout"
   KERNEL=="ttyUSB*", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps", MODE="0660", GROUP="dialout"
   ```

4. Reload and verify:

   ```bash
   sudo udevadm control --reload-rules
   sudo udevadm trigger
   ls -l /dev/aioc-aprs /dev/aioc-winlink /dev/gps
   cat /proc/asound/cards
   ```

5. Restart services that hold the old device open:

   ```bash
   rr restart graywolf        # or direwolf
   rr restart winlink         # if Winlink AIOC changed
   rr restart gpsd            # if GPS path churned
   ```

**Do not** match AIOCs by `ttyACM` number alone — those change. Always use `ATTRS{serial}`.

---

## Firewall (UFW)

Starlink/CGNAT + a home gateway often already hide the Pi from the public internet. UFW still limits **who on LAN / Tailscale** can open admin TCP ports.

**RF users are unaffected** — packet/APRS/BBS over the air never goes through these rules.

### Policy (shareable defaults)

| Source | Ports | Purpose |
|--------|-------|---------|
| RFC1918 `10/8`, `172.16/12`, `192.168/16` | 22, 8080, 8081, 8010, 8090 | SSH + Graywolf / Pat / LinBPQ on LAN |
| Tailscale CGNAT `100.64.0.0/10` | same | Remote admin over tailnet |
| Everywhere else | deny (default) | No WAN exposure |

Modem ports (8000–8003) and gpsd (2947) stay localhost between apps — no UFW allow rules.

### Apply / re-apply

Installer runs `scripts/09-ufw.sh`. Manually:

```bash
sudo bash scripts/09-ufw.sh
sudo ufw status numbered
sudo ufw status verbose
```

### Optional tightening

If you only use one LAN (e.g. `192.168.1.0/24`), delete broader RFC1918 rules and allow only that subnet. The installer keeps full RFC1918 so the same project works on any home or field router.

## Exclusive sound cards

- **Same AIOC:** Direwolf and Graywolf cannot run together (launcher stops one before starting the other).
- **Dual AIOC:** APRS modem on `aioc-aprs` and `direwolf-winlink` on `aioc-winlink` **can** run at the same time.

## Chrony GPS offset

```bash
chronyc sources -v
chronyc sourcestats
```

Tune `offset` on `refclock SHM 0` in `/etc/chrony/chrony.conf`, then `rr restart chrony` (or `sudo systemctl restart chrony`). USB GPS without PPS is typically tens of milliseconds, not microseconds.

## Re-running the installer

```bash
cp config.env.example config.env   # only if missing
./install.sh
```

Optional skips: `SKIP_DIREWOLF_BUILD=yes`, `SKIP_LINBPQ_DOWNLOAD=yes`.

## Not installed by this script

- **Graywolf** — installed from GitHub releases (arm64 .deb) unless SKIP_GRAYWOLF_INSTALL=yes