From fa05a5f8238e9e1417235711656e5062ccc843a7 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Fri, 7 Aug 2026 18:23:28 +0000 Subject: Initial push --- scripts/max25-lite-setup.sh | 517 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100755 scripts/max25-lite-setup.sh (limited to 'scripts/max25-lite-setup.sh') diff --git a/scripts/max25-lite-setup.sh b/scripts/max25-lite-setup.sh new file mode 100755 index 0000000..412d83e --- /dev/null +++ b/scripts/max25-lite-setup.sh @@ -0,0 +1,517 @@ +#!/usr/bin/env bash +# MAX25-Stack-Lite — semi-automatic setup wizard (dialog / whiptail) +# Detects serial + audio devices, writes max25d.ini (+ crdop.ini for CRDOP path). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +UI_TITLE="MAX25-Stack-Lite setup" +UI_BACKTITLE="MAX25-Stack-Lite | local packet-radio setup" +UI_WIDTH=72 +UI_HEIGHT=18 +TMP_FILES=() + +UI="" +if command -v whiptail >/dev/null 2>&1; then + UI=whiptail +elif command -v dialog >/dev/null 2>&1; then + UI=dialog +else + echo "max25-lite-setup: install whiptail or dialog, then run this command again." >&2 + exit 1 +fi + +cleanup() { + local file + for file in "${TMP_FILES[@]}"; do + rm -f -- "$file" + done +} +trap cleanup EXIT HUP INT TERM + +require_terminal_size() { + local columns lines + [[ -t 1 && -t 2 ]] || return 0 + columns="$(tput cols 2>/dev/null || true)" + lines="$(tput lines 2>/dev/null || true)" + if [[ "$columns" =~ ^[0-9]+$ && "$lines" =~ ^[0-9]+$ ]] \ + && ((columns < UI_WIDTH || lines < UI_HEIGHT)); then + printf '%s\n' \ + "max25-lite-setup: terminal is ${columns}x${lines}; need at least ${UI_WIDTH}x${UI_HEIGHT}." \ + "Resize the terminal and run max25-lite-setup again." >&2 + exit 1 + fi +} + +show_help() { + "$UI" --backtitle "$UI_BACKTITLE" --title "Setup help" --ok-label "Back" \ + --msgbox "Use Up/Down to move, Space to mark a radio-list choice, and Enter to continue. + +Cancel or Esc stops the wizard without writing configuration files. + +The wizard writes local-only configuration. It does not start the modem, key a radio, or transmit. + +For serial and BayCom/based paths, select the device connected to this computer. For CRDOP, select the local capture/playback device." \ + "$UI_HEIGHT" "$UI_WIDTH" +} + +msgbox() { + "$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" --ok-label "Continue" \ + --msgbox "$1" 14 "$UI_WIDTH" +} + +yesno() { + local prompt="$1" rc + while :; do + if "$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --yes-label "Yes" --no-label "No" --help-button --help-label "Help" \ + --yesno "$prompt" 10 "$UI_WIDTH"; then + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +inputbox() { + local prompt="$1" default="${2:-}" value rc + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --inputbox "$prompt" 10 "$UI_WIDTH" "$default" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +menu() { + local prompt="$1" value rc + shift + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --menu "$prompt" "$UI_HEIGHT" "$UI_WIDTH" 10 "$@" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +radiolist() { + local prompt="$1" value rc + shift + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --radiolist "$prompt" "$UI_HEIGHT" "$UI_WIDTH" 10 "$@" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +require_terminal_size + +list_serial_ports() { + local -a ports=() + shopt -s nullglob + for pat in /dev/ttyUSB* /dev/ttyACM* /dev/ttyS*; do + [[ -e "$pat" ]] || continue + ports+=("$pat") + done + shopt -u nullglob + if ((${#ports[@]} == 0)); then + ports=("/dev/ttyUSB0") + fi + printf '%s\n' "${ports[@]}" +} + +list_alsa_cards() { + if command -v arecord >/dev/null 2>&1; then + arecord -l 2>/dev/null | awk '/card [0-9]+:/ {c=$2; gsub(":", "", c); desc=$0; gsub(/^.*card [0-9]+: /, "", desc); gsub(/,.*/, "", desc); printf "hw:%s,0 %s\n", c, desc}' || true + fi + if command -v aplay >/dev/null 2>&1; then + aplay -l 2>/dev/null | awk '/card [0-9]+:/ {c=$2; gsub(":", "", c); desc=$0; gsub(/^.*card [0-9]+: /, "", desc); gsub(/,.*/, "", desc); printf "hw:%s,0 %s (playback)\n", c, desc}' || true + fi +} + +msgbox "Welcome to MAX25-Stack-Lite setup. + +This wizard detects serial and audio devices on this computer, writes max25d.ini, and shows how to start max25d and max25-terminal locally. + +It changes files only after you choose a save location. Use Help for key hints, or Cancel to leave without saving." + +MODEM_KIND="$(menu "Packet radio modem path:" \ + serial "Serial TNC / USB KISS (T-Modem, TNC2)" \ + baycom "BayCom/based PC-COM (SER12 on COM/ttyS)" \ + crdop "CRDOP sound-card soft modem (MAX25-SoftModem)" \ + )" || exit 0 + +DEVICE_ID="tnc2c" +HARDWARE="tncs" +SERIAL_PATH="" +CRDOP_CAPTURE="" +CRDOP_PLAYBACK="" +WRITE_CRDOP_INI=0 +WRITE_BCPR_INI=0 +BCPR_INI_DEST="" + +if [[ "$MODEM_KIND" == "baycom" ]]; then + DEVICE_ID="max25e0" + HARDWARE="tncs" + WRITE_BCPR_INI=1 + mapfile -t PORTS < <(list_serial_ports) + menu_args=() + idx=0 + for p in "${PORTS[@]}"; do + menu_args+=("$p" "serial port" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + SERIAL_PATH="$(radiolist "Select PC-COM serial port (often /dev/ttyS0):" "${menu_args[@]}")" || exit 0 +elif [[ "$MODEM_KIND" == "serial" ]]; then + mapfile -t PORTS < <(list_serial_ports) + menu_args=() + idx=0 + for p in "${PORTS[@]}"; do + menu_args+=("$p" "serial port" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + SERIAL_PATH="$(radiolist "Select serial device:" "${menu_args[@]}")" || exit 0 + if [[ "$SERIAL_PATH" == /dev/ttyACM* ]]; then + if yesno "Device looks like USB KISS (T-Modem). Use device id tmodem instead of tnc2c?"; then + DEVICE_ID="tmodem" + fi + fi +else + HARDWARE="soft-modems" + DEVICE_ID="soft-crdop" + WRITE_CRDOP_INI=1 + mapfile -t ALSA_LINES < <(list_alsa_cards) + if ((${#ALSA_LINES[@]} == 0)); then + msgbox "No ALSA cards listed (arecord/aplay). You can edit ~/.config/crdop/crdop.ini later." + CRDOP_CAPTURE="hw:0,0" + CRDOP_PLAYBACK="hw:0,0" + else + menu_args=() + idx=0 + for line in "${ALSA_LINES[@]}"; do + dev="${line%% *}" + label="${line#* }" + menu_args+=("$dev" "$label" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + sel="$(radiolist "Select capture/playback ALSA device (hw:N,0):" "${menu_args[@]}")" || exit 0 + CRDOP_CAPTURE="$sel" + CRDOP_PLAYBACK="$sel" + fi +fi + +CALLERID="$(inputbox "Your AX.25 caller ID (e.g. CB-0):" "CB-0")" || exit 0 +CALLID="$(inputbox "Default destination callsign for CONNECT tests:" "QST")" || exit 0 + +if yesno "Write system config /etc/max25/max25d.ini? (No = ./local/max25d.ini in repo)"; then + INI_DEST="/etc/max25/max25d.ini" + CRDOP_INI_DEST="${HOME}/.config/crdop/crdop.ini" + BCPR_INI_DEST="/etc/max25/max25-bcpr.ini" + NEED_SUDO=1 +else + INI_DEST="${ROOT}/local/max25d.ini" + CRDOP_INI_DEST="${ROOT}/local/crdop.ini" + BCPR_INI_DEST="${ROOT}/local/max25-bcpr.ini" + NEED_SUDO=0 +fi + +if [[ "$NEED_SUDO" -eq 1 ]]; then + if ! command -v sudo >/dev/null 2>&1; then + msgbox "System configuration needs sudo, but sudo is not available. + +Choose No at the save-location screen to write local configuration in this repository." + exit 1 + fi + sudo mkdir -p "$(dirname "$INI_DEST")" + mkdir -p "$(dirname "$CRDOP_INI_DEST")" +else + mkdir -p "$(dirname "$INI_DEST")" +fi + +write_max25d_ini() { + local dest="$1" + if [[ "$MODEM_KIND" == "baycom" ]]; then + cat >"$dest" <"$dest" <"$dest" <"$dest" <"$dest" <