diff --git a/ansible/roles/scanner_airspy_aprs/tasks/main.yml b/ansible/roles/argus_airspy_aprs/tasks/main.yml similarity index 100% rename from ansible/roles/scanner_airspy_aprs/tasks/main.yml rename to ansible/roles/argus_airspy_aprs/tasks/main.yml diff --git a/ansible/roles/scanner_airspy_aprs/templates/airspy-aprs.service.j2 b/ansible/roles/argus_airspy_aprs/templates/airspy-aprs.service.j2 similarity index 100% rename from ansible/roles/scanner_airspy_aprs/templates/airspy-aprs.service.j2 rename to ansible/roles/argus_airspy_aprs/templates/airspy-aprs.service.j2 diff --git a/ansible/roles/scanner_airspy_aprs/templates/airspy_aprs.sh b/ansible/roles/argus_airspy_aprs/templates/airspy_aprs.sh similarity index 100% rename from ansible/roles/scanner_airspy_aprs/templates/airspy_aprs.sh rename to ansible/roles/argus_airspy_aprs/templates/airspy_aprs.sh diff --git a/ansible/roles/scanner_airspy_aprs/templates/direwolf_airspy_aprs.conf b/ansible/roles/argus_airspy_aprs/templates/direwolf_airspy_aprs.conf similarity index 100% rename from ansible/roles/scanner_airspy_aprs/templates/direwolf_airspy_aprs.conf rename to ansible/roles/argus_airspy_aprs/templates/direwolf_airspy_aprs.conf diff --git a/ansible/roles/scanner_digirig/tasks/main.yml b/ansible/roles/argus_ax25/tasks/main.yml similarity index 58% rename from ansible/roles/scanner_digirig/tasks/main.yml rename to ansible/roles/argus_ax25/tasks/main.yml index aee84cd..75a1343 100644 --- a/ansible/roles/scanner_digirig/tasks/main.yml +++ b/ansible/roles/argus_ax25/tasks/main.yml @@ -1,12 +1,13 @@ -# Role: scanner_digirig -# Configure direwolf with the Digirig device +# CONFIGURE ARGUS AX.25 VHF STACK! +# This uses a dedicated 2 meter radio with a dedicated antenna, a Digirig and software like +# Direwolf, Socat and the Linux kernel AX.25 implementation. # # - Set up udev rules to make special device drivers for the Digirig in /dev/radio so we don't # have to relay on /dev/ttyUSB0, 1 or whatever number the OS happens to assign. This gives the -# device names consistency. +# device names consistency. ALso name the ALSA sound device "digirig". # -# - Set up systemd service to start Direwolf when the device is connected via USB, and to stop it -# when the radio is disconnected. +# - Set up systemd service to start the AX.25 stack when the device is connected via USB, and to +# stop it when the radio is disconnected. # # - Copy configuration files # @@ -15,17 +16,18 @@ file: path: "{{item}}" state: directory - owner: busnet - group: busnet + owner: argus + group: argus mode: u=rwx,g=rwx,o=rx with_items: - - /opt/busnet/direwolf - - /opt/busnet/direwolf/config - - /opt/busnet/direwolf/logs + - /opt/argus/scripts + - /opt/argus/direwolf + - /opt/argus/direwolf/config + - /opt/argus/direwolf/logs -- name: Make sure busnet user is in groups for audio and USB access +- name: Make sure argus user is in groups for audio and USB access ansible.builtin.user: - name: busnet + name: argus groups: "{{item}}" append: yes with_items: @@ -34,10 +36,10 @@ - audio - plugdev -- name: Grant full passwordless sudo access to busnet user +- name: Grant full passwordless sudo access to argus user ansible.builtin.copy: - content: "busnet ALL=(ALL) NOPASSWD:ALL" - dest: /etc/sudoers.d/busnet + content: "argus ALL=(ALL) NOPASSWD:ALL" + dest: /etc/sudoers.d/argus mode: '0440' owner: root group: root @@ -50,108 +52,45 @@ - ax25-apps - libax25 state: present - -- name: Ensure AX.25 and NET/ROM kernel modules are loaded - community.general.modprobe: - name: "{{ item }}" - state: present - with_items: - - ax25 - - netrom ################################################################################################### -# Direwolf bash script and config +# AX25 VHF bash script ################################################################################################### -- name: Copy digirig-direwolf.sh AX.25 stack script +- name: Copy ax25-vhf.sh script copy: - src: "templates/digirig-direwolf.sh" - dest: "/opt/busnet/direwolf/digirig-direwolf.sh" - owner: busnet - group: busnet + src: "templates/ax25-vhf.sh" + dest: "/opt/argus/scripts/ax25-vhf.sh" + owner: argus + group: argus mode: "0755" - name: Copy Direwolf config for Digirig template: src: "templates/digirig-direwolf.conf.j2" - dest: "/opt/busnet/direwolf/config/digirig.conf" - owner: busnet - group: busnet + dest: "/opt/argus/direwolf/config/digirig.conf" + owner: argus + group: argus ################################################################################################### -# SYSTEMD: Direwolf +# SYSTEMD: Install argus-ax25-vhf.service ################################################################################################### -- name: Copy AX.25 bash script to start direwolf and utils - template: - src: "templates/digirig-direwolf.conf.j2" - dest: "/opt/busnet/direwolf/config/digirig.conf" - owner: busnet - group: busnet - # udev rules start this automatically when the radio USB is connected, and stop when disconnected. -- name: Install systemd unit file for Direwolf/AX.25 stack +- name: Install systemd unit file for AX.25 VHF stack template: - src: "templates/digirig-direwolf.service.j2" - dest: "/etc/systemd/system/digirig-direwolf.service" + src: "templates/argus-ax25-vhf.service.j2" + dest: "/etc/systemd/system/argus-ax25-vhf.service" owner: root group: root mode: "0644" -- name: Enable digirig-direwolf service +- name: Enable argus-ax25-vhf.service systemd: - name: "digirig-direwolf" + name: "argus-ax25-vhf" daemon_reload: true enabled: true -################################################################################################### -# SYSTEMD: Net/Rom -################################################################################################### - -- name: Copy mheardd and netromd systemd service files - ansible.builtin.copy: - src: "templates/{{ item }}" - dest: "/etc/systemd/system/{{ item }}" - owner: root - group: root - mode: '0644' - with_items: - - mheardd.service - - netromd.service - register: services_copied - -- name: Enable and start AX.25/NET/ROM daemons - ansible.builtin.systemd: - name: "{{ item }}" - enabled: true - state: started - daemon_reload: "{{ services_copied.changed }}" - with_items: - - mheardd - - netromd - -################################################################################################### -# SYSTEMD: NET/ROM network interface init -# These get created when the service starts and they just stay running until you shut down the -# service or reboot. -################################################################################################### - -- name: Install systemd unit file for NET/ROM network interfaces - ansible.builtin.copy: - src: "templates/netrom-init.service.j2" - dest: "/etc/systemd/system/netrom-init.service" - owner: root - group: root - mode: '0644' - register: netrom_init_service - -- name: Enable and start netrom-init service - ansible.builtin.systemd: - name: netrom-init - enabled: true - state: started - daemon_reload: "{{ netrom_init_service.changed }}" - ################################################################################################### # AX35 and NET/ROM Config ################################################################################################### @@ -168,6 +107,32 @@ - nrports - nrbroadcast +################################################################################################### +# SYSTEMD: netromd and mheardd services +################################################################################################### + +- name: Copy mheardd and netromd systemd service files + ansible.builtin.copy: + src: "templates/{{ item }}" + dest: "/etc/systemd/system/{{ item }}" + owner: root + group: root + mode: '0644' + with_items: + - mheardd.service + - netromd.service + register: services_copied + +- name: Enable and start mheardd and netromd services + ansible.builtin.systemd: + name: "{{ item }}" + enabled: true + state: started + daemon_reload: "{{ services_copied.changed }}" + with_items: + - mheardd + - netromd + ################################################################################################### # UDEV RULES (keep these last) ################################################################################################### diff --git a/ansible/roles/scanner_digirig/templates/98-digirig-udev.rules.j2 b/ansible/roles/argus_ax25/templates/98-digirig-udev.rules.j2 similarity index 66% rename from ansible/roles/scanner_digirig/templates/98-digirig-udev.rules.j2 rename to ansible/roles/argus_ax25/templates/98-digirig-udev.rules.j2 index 6d28e6f..b0dd37a 100644 --- a/ansible/roles/scanner_digirig/templates/98-digirig-udev.rules.j2 +++ b/ansible/roles/argus_ax25/templates/98-digirig-udev.rules.j2 @@ -1,19 +1,25 @@ # udev rule to provide fixed device name for my Digirig and start/stop services automatically! - -# This information was retrieved by the following. Assuming the radio USB sound card is hooked -# up to /dev/ttyUSB0 # -# craig@scanner:~$ udevadm info /dev/ttyUSB0 | grep ID_SERIAL +# This is how I found the serials, vendor and product id values: +# +# Plug in the digirig and not anything else so you know it's card card #1, then get the vendor and +# product id values like this: +# +# $ udevadm info /dev/ttyUSB0 | grep ID_SERIAL # E: ID_SERIAL=Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_809986b5195bed119acbed165820eef3 # E: ID_SERIAL_SHORT=809986b5195bed119acbed165820eef3 # - +# $ sudo udevadm info -a /sys/class/sound/card1 | grep -E 'idVendor|idProduct' +# ATTRS{idProduct}=="0012" +# ATTRS{idVendor}=="0d8c" # This creates /dev/radio/digirig-sound which is a USB sound card device # It does not matter what USB port it's plugged into, or how many other USB devices are also plugged in. # --------------------------------------------------------------------------------------------------- SUBSYSTEM=="tty", ENV{ID_SERIAL_SHORT}=="809986b5195bed119acbed165820eef3", SYMLINK+="radio/digirig-sound", MODE="660", GROUP="plugdev" +# This sets a custom sound card name of "digirig" in the Linux ALSA system. +SUBSYSTEM=="sound", ATTRS{idVendor}=="0d8c", ATTRS{idProduct}=="0012", ATTR{id}="digirig" # Automatically start direwolf bash script when the radio is connected. Identify the radio by the # unique setial number. diff --git a/ansible/roles/scanner_digirig/templates/digirig-direwolf.service.j2 b/ansible/roles/argus_ax25/templates/argus-ax25-vhf.service.j2 similarity index 67% rename from ansible/roles/scanner_digirig/templates/digirig-direwolf.service.j2 rename to ansible/roles/argus_ax25/templates/argus-ax25-vhf.service.j2 index 50e98de..bb50425 100644 --- a/ansible/roles/scanner_digirig/templates/digirig-direwolf.service.j2 +++ b/ansible/roles/argus_ax25/templates/argus-ax25-vhf.service.j2 @@ -1,14 +1,14 @@ -# This file is managed by BusNet Ansible. +# This file is managed by Ansible. # -# This service starts the direwolf/AX.25 stack bash script when the Digirig device is connected to -# this computer. The bash script then initializes the Linux AX.25 stack. +# This service starts the dedicated 2m AX.25 stack bash script when the Digirig device is connected +# to this computer. # # There is a corresponding udev rule that triggers this service automatically when it detects that # the radio's USB devices are connected. There is also a udev rule that shuts it down when the USB # is disconnected. [Unit] -Description=Direwolf TNC and AX.25 Stack +Description=Argus AX.25 VHF Stack #After=network.target # Link the service life cycle to the hardware device /dev/radio/digirig-sound. I don't know why, but @@ -19,11 +19,11 @@ After=dev-radio-digirig\x2dsound.device [Service] Type=simple -ExecStart=/opt/busnet/direwolf/digirig-direwolf.sh +ExecStart=/opt/argus/scripts/ax25-vhf.sh Restart=on-failure RestartSec=5 -User=busnet -Group=busnet +User=argus +Group=argus # This tells systemd to kill all child processe started by the bash script. KillMode=control-group diff --git a/ansible/roles/argus_ax25/templates/ax25-vhf.sh b/ansible/roles/argus_ax25/templates/ax25-vhf.sh new file mode 100644 index 0000000..4b276e4 --- /dev/null +++ b/ansible/roles/argus_ax25/templates/ax25-vhf.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# Start the AX.25 VHF Stack +# @author Craig McDaniel +# +# This script runs Direwolf, socat and kissatach. It starts them in that order and makes sure they +# all stay running and work together. If one crashes, this script stops everything. This effectively +# sets up a AX.25 interface on a Linux computer using the Linux AX.25 stack. +# +# It's designed to be uses with systemd for things like auto start, auto re-start. +# +# +# Here's a diagram of the stack this creates: +# ------------------------------------------------------------------------------------------------- +# +# RF signals! _ RF signals! +# \ _/ +# \ / +# +-------+ +# | Radio | ----- RF signals! +# +-------+ +# | +# | Analog cable +# | +# +---------+ +# | Digirig | +# +---------+ +# | +# | USB sound card +# | +# +----|----------------------------------------------------------------------------+ +# | | This is your computer. The things in here are virtual components. | +# | | | +# | +-----------+ +-------+ +------------+ Linux kernel | +# | | Direwolf | <---> | Socat | <----> | Kissattach | <------> ax0 interface | +# | +-----------+ +-------+ +------------+ / | | +# | | /--------+ | | +# | AGWPE port / | | +# | available for +---------------+ +------------------+ | +# | use | mheard daemon | | netromd: NET/ROM | | +# | +---------------+ | Daemoon. | | +# | +------------------+ | +# +---------------------------------------------------------------------------------+ + +# Command to execute direwolf including any options and location of config file. +DIREWOLF_CMD="/usr/local/bin/direwolf -t 0 -X 1 -c /opt/argus/direwolf/config/digirig.conf" + +# The AX.25 port for the radio defined in /etc/ax25/axports. Note that this is not the same thing as +# the kernel AX.25 interfaces, which are named things like ax0, ax1, etc... +AX25_PORT_NAME="digirig" + +# The NET/ROM port for the radio defined in /etc/ax25/nrports. Note that this also is not the same +# thing as the kernel NET/ROM interface like nr0, nr1, etc... +NETROM_PORT_NAME="digirig-nr" + +# The PTY/TTY (whatever it's called) that socat will create for kissattach +TNC_DEV="/dev/radio/digirig-tnc" + +# This is the socat command to that connects one end of a virtual pipe to Direwolf's KISS TCP port, +# and the other end to the PTY/TTY. The IP and port specified here must match the Direwolf config +# file. +SOCAT_CMD="/usr/bin/socat PTY,raw,echo=0,link=${TNC_DEV} TCP4:127.0.0.1:8001" + +# These are the commands to create the kernel AX.25 interface using the kissattach utility. +KISSATTACH_CMD="/usr/sbin/kissattach ${TNC_DEV} ${AX25_PORT_NAME}" +KISSPARAMS_CMD="kissparms -c 1 -p ${AX25_PORT_NAME}" + +# This is for creating the NET/ROM kernel interface. +NRATTACH_CMD="nrattach ${NETROM_PORT_NAME}" + +# Cleanup the AX.25 stack +cleanup_stack() +{ + # Kill kissattach explicitly (it forks, so we can't track its PID) + sudo pkill -f "kissattach ${TNC_DEV} ${AX25_PORT_NAME}" + sleep 1 + + # Kill socat by PID + if [ -n "$SOCAT_PID" ]; then + sudo kill $SOCAT_PID 2>/dev/null + sleep 1 + fi + + # Kill direwolf by PID and wait for it to finish + if [ -n "$DIREWOLF_PID" ] && kill -0 $DIREWOLF_PID 2>/dev/null; then + kill $DIREWOLF_PID 2>/dev/null + wait $DIREWOLF_PID 2>/dev/null + fi + + # The file /etc/ax25/nrports mapps the $NETROM_PORT_NAME to a unique callsign. We need to + # figure out what the associated callsign for the netrom port is. + # Example: digirig-nr K0BIT-8 8BITS 235 NET/ROM Port + local NETROM_CALLSIGN=$(grep "^${NETROM_PORT_NAME}" /etc/ax25/nrports | awk '{print $2}') + + # make sure we actually have a corresponding NET/ROM device and callsign in nrports. If not, + # then we can just be done right now. + if [ -z "$NETROM_CALLSIGN" ]; then + return + fi + + # Find all NET/ROM interfaces (like nr0, nr1, nr2, etc...) and check their callsigns. + # Shut down any NET/ROM interface with the $NETROM_CALLSIGN attached to it. + for IFACE in $(ip link show | grep -oE 'nr[0-9]+'); do + # Get the callsign for this interface + local IFACE_CALL=$(ip addr show $IFACE | grep 'netrom' | awk '{print $2}') + + if [ "$IFACE_CALL" = "$NETROM_CALLSIGN" ]; then + echo "Bringing down NET/ROM interface ${IFACE} ..." + sudo ip link set $IFACE down + return + fi + done +} + + +# Start the AX.25 stack +start_stack() +{ + # Start Direwolf in background + $DIREWOLF_CMD & + DIREWOLF_PID=$! + # NOTE: If Direwolf encounters an error, it will just WAIT something like 15 seconds before + # exiting. It does not fail fast. That means that the following check will pass, but then + # we go to move on to create socat which will fail. I don't have a good solution for this + # case just yet, but I'm recording this note here. + if ! kill -0 $DIREWOLF_PID; then + echo "Direwolf failed to start." + cleanup_stack + exit 1 + fi + echo "Direwolf started (PID: $DIREWOLF_PID)" + + # Start socat PTY bridge. + # socat creates a full duplex/bidirectional pipe between a serial port ${TNC_DEV} and Direwolf's + # KISS TCP interface, which is running on localhost:8001. The kissattach program needs a serial + # port to work, and this is how we accomplish connecting kissattach to direwolf. + sleep 3 + sudo $SOCAT_CMD & + SOCAT_PID=$! + if ! kill -0 $SOCAT_PID; then + echo "Socat failed to start." + cleanup_stack + exit 1 + fi + echo "socat started (PID: $SOCAT_PID)" + + # Run kissatach to create an AX.25 network interface on the system. + # Kissattach annoyingly forks itself in the background, so we can't directly monitor the process. + # Kissattach cannot use KISS over TCP directly to Direwolf. It expects a serial terminal to exist, + # which is why we use socat to create the pipe between Direwolf's KISS TCP port and this serial + # device. + sleep 1 + sudo $KISSATTACH_CMD + if [ $? -ne 0 ]; then + echo "kissattach failed to start." + cleanup_stack + fi + + # We have to set something on the AX.25 interface that Direwolf requires via kissparams. + sudo $KISSPARAMS_CMD + if [ $? -ne 0 ]; then + echo "kissparams failure detected." + cleanup_stack + fi + + # We must run the nrattach command to create a kernel NET/ROM interface. + sudo $NRATTACH_CMD + if [ $? -ne 0 ]; then + echo "Command ${NRATTACH_CMD} failed." + cleanup_stack + exit 1 + fi + + # Restart mheardd daemon. If mheardd was started before ax1, ax2 or other AX.25 kernel interface + # was created, and now we receive AX.25 packets on this new interface we just created, mheardd + # just throws errors about a new, unknown interface. Ugh. + if sudo systemctl is-enabled --quiet mheardd.service; then + echo "Restarting mheardd..." + sudo systemctl restart mheardd.service + fi + + # If netromd is not running, restart it. Otherwise, leave it alone. + if sudo systemctl is-enabled --quiet netromd.service; then + if ! sudo systemctl is-active --quiet netromd.service; then + echo "Re-starting netromd..." + sudo systemctl start netromd.service + fi + fi +} + + +# This is our POSIX signal handler so we cleanly shut down before exiting. +handle_signal() +{ + echo "We received a SIGINT or SIGTERM signal. Shutting down VHF AX.25 stack." + cleanup_stack + exit 0 +} + + +################################################################################################### +# +# MAIN ENTRY POINT +# +################################################################################################### + +echo "" +echo "======================================================================" +echo "VHF AX.25 Stack Started" +echo "======================================================================" +echo "" + +# This is the bash way of intercepting these signals. We execute the cleanup function when we receive +# either one. +trap handle_signal SIGINT SIGTERM + +# Load required kernel modules +echo "Loading kernel modules..." +sudo modprobe ax25 +if [ $? -ne 0 ]; then + echo "Failed to load ax25 kernel module" + exit 1 +fi +sudo modprobe netrom +if [ $? -ne 0 ]; then + echo "Failed to load netrom kernel module" + exit 1 +fi + +# Clean up any orphaned kissattach processes from previous runs. We must do this because kissattach +# forks itself in the background, and we therefore cannot keep track of its PID. We use pkill to +# make sure no previous kissattach is running. +sudo pkill -f "kissattach ${TNC_DEV} ${AX25_PORT_NAME}" +sleep 1 + +# Note that we don't have to do the same for nrattach. nrattach runs and then exits. + +# Start the AX.25 stack +start_stack +echo "All processes started. Monitoring via wait..." + +# 'wait -n' waits for ANY background process to exit. +# It returns as soon as Direwolf or Socat dies. Kissattach forks itself so we can't wait on it. +wait -n + +# If we get here, one of the processes died. Cleanup the stack and exit. +cleanup_stack diff --git a/ansible/roles/scanner_digirig/templates/axports b/ansible/roles/argus_ax25/templates/axports similarity index 100% rename from ansible/roles/scanner_digirig/templates/axports rename to ansible/roles/argus_ax25/templates/axports diff --git a/ansible/roles/scanner_digirig/templates/digirig-direwolf.conf.j2 b/ansible/roles/argus_ax25/templates/digirig-direwolf.conf.j2 similarity index 54% rename from ansible/roles/scanner_digirig/templates/digirig-direwolf.conf.j2 rename to ansible/roles/argus_ax25/templates/digirig-direwolf.conf.j2 index aa08aaf..f27b94e 100644 --- a/ansible/roles/scanner_digirig/templates/digirig-direwolf.conf.j2 +++ b/ansible/roles/argus_ax25/templates/digirig-direwolf.conf.j2 @@ -1,4 +1,4 @@ -# This file is managed by BusNet Ansible +# This file is managed by Ansible # # This is the Direwolf configuration for connecting Direwolf to the Digirig USB sound card. This assumes # that we will operate using 1200 baud mode in 2 meters VHF with a dedicated radio for this purpose. @@ -6,7 +6,9 @@ # There us a udev rule which creates "/dev/radio/digirig-sound" so that we do not have to worry about # what USB port or tty number is assigned when this device is connected to the computer. -ADEVICE plughw:Device,0 +# We create a custom ALSA sound card name of "digirig" in our udev rules. Direwolf uses ALSA directly, +# so this tells Direwolf to use the "digirig" sound card, subdevice #0 (there is only 1 subdevice). +ADEVICE plughw:CARD=digirig,DEV=0 # The custom udev rule makes this device available by name. PTT /dev/radio/digirig-sound RTS @@ -19,4 +21,7 @@ MODEM 1200 FX25TX 1 AGWPORT 8000 -KISSPORT 8001 \ No newline at end of file +KISSPORT 8001 + +# In the future, I might set up a RV BBS! I would beacon it here. +#PBEACON every=1 overlay=S symbol="bus" lat=29.958260914551104 long=-90.05442788530239 comment="I'm like testing APRS, man" via=WIDE1-1,WIDE2-1 diff --git a/ansible/roles/scanner_digirig/templates/mheardd.service b/ansible/roles/argus_ax25/templates/mheardd.service similarity index 100% rename from ansible/roles/scanner_digirig/templates/mheardd.service rename to ansible/roles/argus_ax25/templates/mheardd.service diff --git a/ansible/roles/scanner_digirig/templates/netromd.service b/ansible/roles/argus_ax25/templates/netromd.service similarity index 82% rename from ansible/roles/scanner_digirig/templates/netromd.service rename to ansible/roles/argus_ax25/templates/netromd.service index 637905f..c2921fb 100644 --- a/ansible/roles/scanner_digirig/templates/netromd.service +++ b/ansible/roles/argus_ax25/templates/netromd.service @@ -1,7 +1,7 @@ [Unit] Description=NET/ROM Routing Daemon -After=digirig-direwolf.service -BindTo=digirig-direwolf.service +After=ax25-vhf.service +BindTo=ax25-vhf.service # systemd has trouble with this daemon. Presumably it's old code and systemd cannot determine if # the daemon starts. These settings seem to work. diff --git a/ansible/roles/scanner_digirig/templates/nrbroadcast b/ansible/roles/argus_ax25/templates/nrbroadcast similarity index 100% rename from ansible/roles/scanner_digirig/templates/nrbroadcast rename to ansible/roles/argus_ax25/templates/nrbroadcast diff --git a/ansible/roles/scanner_digirig/templates/nrports b/ansible/roles/argus_ax25/templates/nrports similarity index 100% rename from ansible/roles/scanner_digirig/templates/nrports rename to ansible/roles/argus_ax25/templates/nrports diff --git a/ansible/roles/scanner_ft991a/tasks/main.yml b/ansible/roles/argus_ft991a/tasks/main.yml similarity index 100% rename from ansible/roles/scanner_ft991a/tasks/main.yml rename to ansible/roles/argus_ft991a/tasks/main.yml diff --git a/ansible/roles/scanner_ft991a/tasks/ubuntu_noble.yml b/ansible/roles/argus_ft991a/tasks/ubuntu_noble.yml similarity index 100% rename from ansible/roles/scanner_ft991a/tasks/ubuntu_noble.yml rename to ansible/roles/argus_ft991a/tasks/ubuntu_noble.yml diff --git a/ansible/roles/scanner_ft991a/templates/99-ft991a-udev.rules.j2 b/ansible/roles/argus_ft991a/templates/99-ft991a-udev.rules.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/99-ft991a-udev.rules.j2 rename to ansible/roles/argus_ft991a/templates/99-ft991a-udev.rules.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-asound.conf.j2 b/ansible/roles/argus_ft991a/templates/ft991a-asound.conf.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-asound.conf.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-asound.conf.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-direwolf-hf.conf.j2 b/ansible/roles/argus_ft991a/templates/ft991a-direwolf-hf.conf.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-direwolf-hf.conf.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-direwolf-hf.conf.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-direwolf-uhf.conf.j2 b/ansible/roles/argus_ft991a/templates/ft991a-direwolf-uhf.conf.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-direwolf-uhf.conf.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-direwolf-uhf.conf.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-direwolf-vhf.conf.j2 b/ansible/roles/argus_ft991a/templates/ft991a-direwolf-vhf.conf.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-direwolf-vhf.conf.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-direwolf-vhf.conf.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-direwolf.service.j2 b/ansible/roles/argus_ft991a/templates/ft991a-direwolf.service.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-direwolf.service.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-direwolf.service.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-monitor.sh b/ansible/roles/argus_ft991a/templates/ft991a-monitor.sh old mode 100644 new mode 100755 similarity index 61% rename from ansible/roles/scanner_ft991a/templates/ft991a-monitor.sh rename to ansible/roles/argus_ft991a/templates/ft991a-monitor.sh index 0b015bf..739d3a5 --- a/ansible/roles/scanner_ft991a/templates/ft991a-monitor.sh +++ b/ansible/roles/argus_ft991a/templates/ft991a-monitor.sh @@ -16,19 +16,57 @@ # - Run kissattach to the socat PTY to create a kernel AX.25 interface. # - Make sure mheardd daemon is restarted so it can see new AX.25 interface. # +# Here's a diagram of the stack this creates: +# ------------------------------------------------------------------------------------------------- +# +# RF signals! _ RF signals! +# \ _/ +# \ / +# +-------+ FT991a has built in CAT and sound card accessible +# | Radio | ----- via USB socket on rear of radio. +# +-------+ +# | +# | USB +# | +# +----|----------------------------------------------------------------------------+ +# | | This is your computer. The things in here are virtual components. | +# | | | +# | +-----------+ +-------+ +------------+ Linux kernel | +# | | Direwolf | <---> | Socat | <----> | Kissattach | <------> ax2 interface | +# | +-----------+ +-------+ +------------+ / | | +# | | /--------+ | | +# | AGWPE port / | | +# | available for +---------------+ +------------------+ | +# | use | mheard daemon | | netromd: NET/ROM | | +# | +---------------+ | Daemoon. | | +# | +------------------+ | +# +---------------------------------------------------------------------------------+ -# === Configuration === +# Direwolf executable DIREWOLF_PATH="/usr/local/bin/direwolf" -PORT_NAME="ft991a" + +# The AX.25 port for the radio defined in /etc/ax25/axports. Note that this is not the same thing as +# the kernel AX.25 interfaces, which are named things like ax0, ax1, etc... +AX25_PORT_NAME="ft991a" + +# The NET/ROM port for the radio defined in /etc/ax25/nrports. Note that this also is not the same +# thing as the kernel NET/ROM interface like nr0, nr1, etc... +NETROM_PORT_NAME="ft991a-nr" + +# The PTY/TTY (whatever it's called) that socat will create for kissattach TNC_DEV="/dev/radio/ft991a-tnc" + +# The direwolf KISS TCP port that socat will connect to KISS_TCP_PORT=8006 -NR_PORT="ft991a-nr" -POLL_INTERVAL=5 + +# Rigctl stuff RIGCTLD_HOST="localhost" RIGCTLD_PORT=4000 POLL_RETRIES=1 +POLL_INTERVAL=5 -# Frequency ranges (Hz) +# Frequency ranges in Hz for the bands tha radio supports. This is how we know what direwolf config +# to use. HF_MIN=1800000 HF_MAX=30000000 VHF_MIN=144390000 @@ -37,9 +75,9 @@ UHF_MIN=432000000 UHF_MAX=433000000 # Config files per band -CONFIG_HF="/opt/busnet/direwolf/config/ft991a-hf.conf" -CONFIG_VHF="/opt/busnet/direwolf/config/ft991a-vhf.conf" -CONFIG_UHF="/opt/busnet/direwolf/config/ft991a-uhf.conf" +CONFIG_HF="/opt/argus/direwolf/config/ft991a-hf.conf" +CONFIG_VHF="/opt/argus/direwolf/config/ft991a-vhf.conf" +CONFIG_UHF="/opt/argus/direwolf/config/ft991a-uhf.conf" # State tracking CUR_BAND="NONE" @@ -101,6 +139,8 @@ cleanup_stack() sudo pkill -f "kissattach ${TNC_DEV}" sleep 1 + # Note that we don't have to do the same for nrattach. nrattach runs and then exits. + if [ -n "$SOCAT_PID" ]; then sudo kill $SOCAT_PID 2>/dev/null sleep 1 @@ -111,6 +151,26 @@ cleanup_stack() wait $DIREWOLF_PID 2>/dev/null fi + # The file /etc/ax25/nrports mapps the $NETROM_PORT_NAME to a unique callsign. We need to + # figure out what the associated callsign for the netrom port is. + # Example: digirig-nr K0BIT-8 8BITS 235 NET/ROM Port + local NETROM_CALLSIGN=$(grep "^${NETROM_PORT_NAME}" /etc/ax25/nrports | awk '{print $2}') + + # Find all NET/ROM interfaces (like nr0, nr1, nr2, etc...) and check their callsigns. + # Shut down any NET/ROM interface with the $NETROM_CALLSIGN attached to it. + if [ -n "$NETROM_CALLSIGN" ]; then + for IFACE in $(ip link show | grep -oE 'nr[0-9]+'); do + # Get the callsign for this interface + local IFACE_CALL=$(ip addr show $IFACE | grep 'netrom' | awk '{print $2}') + + if [ "$IFACE_CALL" = "$NETROM_CALLSIGN" ]; then + echo "Bringing down NET/ROM interface ${IFACE} ..." + sudo ip link set $IFACE down + return + fi + done + fi + echo "FT991a AX.25 ${CUR_BAND} stack is now shut down." CUR_BAND="NONE" DIREWOLF_PID="" @@ -136,30 +196,30 @@ start_stack() echo "Starting $BAND stack..." - # Start Direwolf - echo "Starting Direwolf..." + # Start Direwolf in background $DIREWOLF_PATH -t 0 -X 1 -c "$CONFIG" & DIREWOLF_PID=$! - sleep 2 - + # NOTE: If Direwolf encounters an error, it will just WAIT something like 15 seconds before + # exiting. It does not fail fast. That means that the following check will pass, but then + # we go to move on to create socat which will fail. I don't have a good solution for this + # case just yet, but I'm recording this note here. if ! kill -0 $DIREWOLF_PID 2>/dev/null; then - echo "FATAL: Direwolf failed to start" >&2 + echo "Direwolf failed to start" >&2 DIREWOLF_PID="" return 1 fi echo "Direwolf started (PID: $DIREWOLF_PID)" # Start socat PTY bridge - echo "Starting socat PTY bridge..." + sleep 3 sudo socat PTY,raw,echo=0,link=$TNC_DEV TCP4:127.0.0.1:$KISS_TCP_PORT & SOCAT_PID=$! - sleep 1 - if ! kill -0 $SOCAT_PID 2>/dev/null; then - echo "FATAL: socat failed to start" >&2 + echo "socat failed to start" >&2 cleanup_stack return 1 fi + echo "socat started (PID: $SOCAT_PID)" # Wait for PTY device to exist local WAIT_COUNT=0 @@ -169,7 +229,7 @@ start_stack() done if [ ! -L $TNC_DEV ]; then - echo "FATAL: PTY device $TNC_DEV not created" >&2 + echo "PTY device $TNC_DEV not created" >&2 cleanup_stack return 1 fi @@ -177,7 +237,7 @@ start_stack() # Kissattach connects the PTY device created by socat to the kernel. echo "Running kissattach..." - sudo kissattach $TNC_DEV $PORT_NAME + sudo kissattach $TNC_DEV $AX25_PORT_NAME if [ $? -ne 0 ]; then echo "FATAL: kissattach failed" >&2 cleanup_stack @@ -189,27 +249,43 @@ start_stack() echo "Setting kissparms..." case $BAND in HF) - sudo kissparms -p $PORT_NAME -c 1 -t 350 + sudo kissparms -p $AX25_PORT_NAME -c 1 -t 350 echo "Set kissparams for HF: window=1, txdelay=350ms" ;; VHF) - sudo kissparms -p $PORT_NAME -c 3 -t 200 + sudo kissparms -p $AX25_PORT_NAME -c 3 -t 200 echo "Set kissparams for VHF: window=3, txdelay=200ms" ;; UHF) - sudo kissparms -p $PORT_NAME -c 3 -t 150 + sudo kissparms -p $AX25_PORT_NAME -c 3 -t 150 echo "Set kissparams for UHF: window=3, txdelay=150ms" ;; esac + # We must run the nrattach command to create a kernel NET/ROM interface. + sudo nrattach $NETROM_PORT_NAME + if [ $? -ne 0 ]; then + echo "nrattach failed." + cleanup_stack + return 1 + fi + # Restart mheardd daemon. If mheardd was started before ax1, ax2 or other AX.25 kernel interface # was created, and now we receive AX.25 packets on this new interface we just created, mheardd # just throws errors about a new, unknown interface. Ugh. - if systemctl is-active --quiet mheardd.service; then + if sudo systemctl is-enabled --quiet mheardd.service; then echo "Restarting mheardd..." sudo systemctl restart mheardd.service fi + # If netromd is not running, restart it. Otherwise, leave it alone. + if sudo ystemctl is-enabled --quiet netromd.service; then + if ! sudo systemctl is-active --quiet netromd.service; then + echo "Re-starting netromd..." + sudo systemctl start netromd.service + fi + fi + CUR_BAND="$BAND" echo "FT991a AX.25 ${CUR_BAND} stack is online." echo "" @@ -241,9 +317,16 @@ echo "" # either one. trap handle_signal SIGINT SIGTERM -# Check if Direwolf exists -if [ ! -f $DIREWOLF_PATH ]; then - echo "FATAL: Direwolf executable not found at $DIREWOLF_PATH" >&2 +# Load required kernel modules +echo "Loading kernel modules..." +sudo modprobe ax25 +if [ $? -ne 0 ]; then + echo "Failed to load ax25 kernel module" + exit 1 +fi +sudo modprobe netrom +if [ $? -ne 0 ]; then + echo "Failed to load netrom kernel module" exit 1 fi diff --git a/ansible/roles/scanner_ft991a/templates/ft991a-rigctld.service.j2 b/ansible/roles/argus_ft991a/templates/ft991a-rigctld.service.j2 similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a-rigctld.service.j2 rename to ansible/roles/argus_ft991a/templates/ft991a-rigctld.service.j2 diff --git a/ansible/roles/scanner_ft991a/templates/ft991a_monitor.py b/ansible/roles/argus_ft991a/templates/ft991a_monitor.py similarity index 100% rename from ansible/roles/scanner_ft991a/templates/ft991a_monitor.py rename to ansible/roles/argus_ft991a/templates/ft991a_monitor.py diff --git a/ansible/roles/scanner_pat/tasks/config.json.j2 b/ansible/roles/argus_pat/tasks/config.json.j2 similarity index 100% rename from ansible/roles/scanner_pat/tasks/config.json.j2 rename to ansible/roles/argus_pat/tasks/config.json.j2 diff --git a/ansible/roles/scanner_pat/tasks/main.yml b/ansible/roles/argus_pat/tasks/main.yml similarity index 100% rename from ansible/roles/scanner_pat/tasks/main.yml rename to ansible/roles/argus_pat/tasks/main.yml diff --git a/ansible/roles/scanner_pat/templates/config.json.j2 b/ansible/roles/argus_pat/templates/config.json.j2 similarity index 100% rename from ansible/roles/scanner_pat/templates/config.json.j2 rename to ansible/roles/argus_pat/templates/config.json.j2 diff --git a/ansible/roles/scanner_pat/templates/pat.service.j2 b/ansible/roles/argus_pat/templates/pat.service.j2 similarity index 100% rename from ansible/roles/scanner_pat/templates/pat.service.j2 rename to ansible/roles/argus_pat/templates/pat.service.j2 diff --git a/ansible/roles/scanner_pulseaudio/README.md b/ansible/roles/argus_pulseaudio/README.md similarity index 100% rename from ansible/roles/scanner_pulseaudio/README.md rename to ansible/roles/argus_pulseaudio/README.md diff --git a/ansible/roles/scanner_pulseaudio/handlers/main.yml b/ansible/roles/argus_pulseaudio/handlers/main.yml similarity index 100% rename from ansible/roles/scanner_pulseaudio/handlers/main.yml rename to ansible/roles/argus_pulseaudio/handlers/main.yml diff --git a/ansible/roles/scanner_pulseaudio/tasks/main.yml b/ansible/roles/argus_pulseaudio/tasks/main.yml similarity index 100% rename from ansible/roles/scanner_pulseaudio/tasks/main.yml rename to ansible/roles/argus_pulseaudio/tasks/main.yml diff --git a/ansible/roles/scanner_pulseaudio/templates/network-audio.pa.j2 b/ansible/roles/argus_pulseaudio/templates/network-audio.pa.j2 similarity index 100% rename from ansible/roles/scanner_pulseaudio/templates/network-audio.pa.j2 rename to ansible/roles/argus_pulseaudio/templates/network-audio.pa.j2 diff --git a/ansible/roles/scanner_pulseaudio/templates/pulseaudio.service.j2 b/ansible/roles/argus_pulseaudio/templates/pulseaudio.service.j2 similarity index 100% rename from ansible/roles/scanner_pulseaudio/templates/pulseaudio.service.j2 rename to ansible/roles/argus_pulseaudio/templates/pulseaudio.service.j2 diff --git a/ansible/roles/scanner_digirig/templates/digirig-direwolf.sh b/ansible/roles/scanner_digirig/templates/digirig-direwolf.sh deleted file mode 100644 index 552f96a..0000000 --- a/ansible/roles/scanner_digirig/templates/digirig-direwolf.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -# @author Craig McDaniel -# -# This script runs Direwolf, socat and kissatach. It starts them in that order and makes sure they -# all stay running and work together. If one crashes, this script stops everything. This effectively -# sets up a AX.25 interface on a Linux computer using the Linux AX.25 stack. -# -# It's designed to be uses with systemd for things like auto start, auto re-start. -# -# -# RF signals! _ RF signals! -# \ _/ -# \ / -# +-------+ -# | Radio | ----- RF signals! -# +-------+ -# | -# | Analog cable -# | -# +---------+ -# | Digirig | -# +---------+ -# | -# | USB sound card -# | -# +----|----------------------------------------------------------------------------+ -# | | This is your computer. The things in here are virtual components. | -# | | | -# | +-----------+ +-------+ +------------+ Linux kernel | -# | | Direwolf | <---> | Socat | <----> | Kissattach | <------> ax0 interface | -# | +-----------+ +-------+ +------------+ / | | -# | | /--------+ | | -# | AGWPE port / | | -# | available for +---------------+ +------------------+ | -# | use | mheard daemon | | netromd: NET/ROM | | -# | +---------------+ | Daemoon. | | -# | +------------------+ | -# +---------------------------------------------------------------------------------+ - -# Configuration -DIREWOLF_CMD="/usr/local/bin/direwolf -t 0 -X 1 -c /opt/busnet/direwolf/config/digirig.conf" -SOCAT_CMD="/usr/bin/socat PTY,raw,echo=0,link=/dev/radio/digirig-tnc TCP4:127.0.0.1:8001" -KISSATTACH_CMD="/usr/sbin/kissattach /dev/radio/digirig-tnc digirig" -KISSPARAMS_CMD="kissparms -c 1 -p digirig" - -cleanup() { - echo - echo "Exit signal detected. Cleaning up..." - trap - SIGTERM # prevent recursion - kill 0 - exit -} - -# Trap exit signals -trap cleanup SIGINT SIGTERM - -echo -echo "======================================================================" -echo "Starting Direwolf..." -echo "======================================================================" -echo -$DIREWOLF_CMD & -DIREWOLF_PID=$! -sleep 1 -if ! kill -0 $DIREWOLF_PID; then - echo "Direwolf failed to start." - exit 1 -fi - -# Wait 3 seconds for Direwolf to initialize, then start socat. -# socat creates a full duplex/bidirectional pipe between a serial port /dev/radio/digirig-tnc and -# Direwolf's KISS TCP interface, which is running on localhost:8001. The kissattach program needs -# a serial port to work, and this is how we accomplish connecting kissattach to direwolf. -sleep 3 - -echo -echo "======================================================================" -echo "Starting socat..." -echo "======================================================================" -echo -sudo $SOCAT_CMD & -SOCAT_PID=$! -if ! kill -0 $SOCAT_PID; then - echo "Socat failed to start." - kill $DIREWOLF_PID - exit 1 -fi - - -# Run kissatach to create an AX.25 network interface on the system. -# -# Kissattach annoyingly forks itself in the background, so we can't directly monitor the process. -# We rely on the control-group feature of systemd to kill all child processes for us, even if -# they fork! -# -# Kissattach cannot use KISS over TCP directly to Direwolf. It expects a serial terminal to exist, -# which is why we use socat to create the pipe between Direwolf's KISS TCP port and this serial -# device. -sleep 1 - -echo -echo "======================================================================" -echo "Starting kissattach and setting kissparams..." -echo "======================================================================" -echo -sudo $KISSATTACH_CMD -if [ $? -ne 0 ]; then - echo "kissattach failed to start." - cleanup -fi - -# We have to set something on the AX.25 interface that Direwolf requires. -sudo $KISSPARAMS_CMD - -echo -echo "All processes started. Monitoring via wait..." - -# 'wait -n' waits for ANY background process to exit. -# It returns as soon as Direwolf or Socat dies. -wait -n - -# If we get here, one of the processes died. -cleanup diff --git a/ansible/roles/scanner_digirig/templates/netrom-init.service.j2 b/ansible/roles/scanner_digirig/templates/netrom-init.service.j2 deleted file mode 100644 index dfd16ea..0000000 --- a/ansible/roles/scanner_digirig/templates/netrom-init.service.j2 +++ /dev/null @@ -1,22 +0,0 @@ -[Unit] -Description=Initialize NET/ROM Interfaces at Boot -Documentation=man:nrattach(8) -After=network.target -Before=digirig-direwolf.service ft991a-direwolf.service mheardd.service netromd.service -ConditionPathExists=/etc/ax25/nrports - -[Service] -Type=oneshot -RemainAfterExit=yes - -# Create NET/ROM interfaces for each port in nrports -# nrattach finds first free device: digirig-nr gets nr0, ft991a-nr gets nr1 -ExecStart=/usr/sbin/nrattach digirig-nr -ExecStart=/usr/sbin/nrattach ft991a-nr - -# On stop/shutdown, bring down the interfaces cleanly -ExecStop=/usr/sbin/ifconfig nr0 down -ExecStop=/usr/sbin/ifconfig nr1 down - -[Install] -WantedBy=multi-user.target