#!/usr/bin/env bash # Terminal Colors C_GREEN='\033[92m' C_YELLOW='\033[93m' C_BLUE='\033[94m' C_CYAN='\033[96m' C_RED='\033[91m' C_BOLD='\033[1m' C_RESET='\033[0m' clear echo -e "${C_CYAN}${C_BOLD}" echo " _ ___ ___ ___ ___ " echo " /_\ | _ \| _ \/ __| | _ ) ___ __ _ __ ___ _ _ " echo " / _ \| _/| /\__ \ | _ \/ -_) _\` / _/ _ \ ' \ " echo "/_/ \_\_| |_|_\|___/ |___/\___\__,_\__\___/_||_| " echo -e " Linux/Android APRS Multi-Profile Beacon Setup Wizard${C_RESET}\n" echo "----------------------------------------------------------------" echo "This wizard will help you configure your APRS beacon as a" echo "background service and manage it via the control panel." echo "----------------------------------------------------------------\n" # Check and install Python3 if ! command -v python3 &> /dev/null; then echo -e "${C_YELLOW}[!] Python3 not found on the system. Installing...${C_RESET}" if command -v apt-get &> /dev/null; then sudo apt-get update && sudo apt-get install -y python3 else echo -e "${C_RED}[!] Error: Python3 could not be installed automatically. Please install it manually.${C_RESET}" exit 1 fi fi INSTALL_DIR="$HOME/.aprs-beacon" PROFILES_DIR="$INSTALL_DIR/profiles" LOGS_DIR="$INSTALL_DIR/logs" mkdir -p "$PROFILES_DIR" "$LOGS_DIR" echo "$(pwd)" > "$INSTALL_DIR/.repo_path" # GUI Dependencies Installation (for Linux) HAS_GUI=true # Graphic environment check if [ -z "$DISPLAY" ]; then HAS_GUI=false echo -e "${C_YELLOW}[!] Display environment (DISPLAY) not detected. Performing headless install.${C_RESET}" fi if [ "$HAS_GUI" = true ]; then echo -e "${C_BLUE}[i] Checking GUI, System Tray, and Image libraries (pystray, tkinter, pillow)...${C_RESET}" if ! python3 -c "import tkinter; import pystray; import PIL" &>/dev/null; then echo -e "${C_YELLOW}[!] Required GUI packages are missing. Installing...${C_RESET}" if command -v apt-get &> /dev/null; then echo -e "${C_BLUE}[i] Administrative privileges (sudo) may be requested:${C_RESET}" sudo apt-get update && sudo apt-get install -y python3-tk python3-pystray python3-pil else echo -e "${C_RED}[!] Error: Package manager (apt-get) not found. Please install python3-tk, python3-pystray, and python3-pil manually.${C_RESET}" fi else echo -e "${C_GREEN}[+] Required GUI packages are ready.${C_RESET}" fi fi # 0. Profile Name echo -e "\n=== 0. Profile Configuration ===" while true; do read -p "Profile Name to Install (e.g. mobile, qth, default) [Default: default]: " PROFILE_NAME PROFILE_NAME=$(echo "$PROFILE_NAME" | tr 'A-Z' 'a-z' | xargs) if [ -z "$PROFILE_NAME" ]; then PROFILE_NAME="default" fi if [[ "$PROFILE_NAME" =~ ^[a-z0-9_-]+$ ]]; then break fi echo -e "${C_RED}Error: Profile name can only contain lowercase letters, numbers, hyphens, or underscores!${C_RESET}" done # 1. Callsign while true; do read -p "1. Callsign (e.g. N0CALL-9): " CALLSIGN CALLSIGN=$(echo "$CALLSIGN" | tr 'a-z' 'A-Z' | xargs) if [ -n "$CALLSIGN" ]; then break fi echo -e "${C_RED}Error: Callsign cannot be empty!${C_RESET}" done # 2. APRS-IS Passcode read -p "2. Password (APRS-IS Passcode) [Press Enter to calculate automatically]: " PASSCODE PASSCODE=$(echo "$PASSCODE" | xargs) if [ -z "$PASSCODE" ]; then echo -e "${C_BLUE}[i] Calculating passcode automatically from callsign...${C_RESET}" PASSCODE=$(python3 -c " callsign = '${CALLSIGN}'.split('-')[0] hash_val = 0x73e2 for i in range(0, len(callsign), 2): char1 = ord(callsign[i]) << 8 char2 = ord(callsign[i+1]) if (i + 1 < len(callsign)) else 0 hash_val ^= (char1 + char2) print(hash_val & 0x7fff) ") echo -e "${C_GREEN}[+] Calculated Passcode: $PASSCODE${C_RESET}" fi # 3. Status Comment echo -e "\n3. Status Comment Setting:" echo -e " ${C_YELLOW}Tip: Add frequency and tone info at start of message (e.g. 145.550MHz T088)${C_RESET}" echo -e " ${C_YELLOW}Tip: Add 'https://' to show a clickable link on the map (e.g. https://example.com)${C_RESET}" read -p "Status Comment [Default: APRS Background Beacon]: " COMMENT COMMENT=$(echo "$COMMENT" | xargs) if [ -z "$COMMENT" ]; then COMMENT="APRS Background Beacon" fi # 4. Symbol echo -e "\n4. Select Symbol to Display on Map:" echo " 1) Helicopter (X) [Default]" echo " 2) Car / Vehicle (>)" echo " 3) Pedestrian / Hiker ([)" echo " 4) House / QTH Station (-)" echo " 5) Fixed Radio / Station (#)" echo " 6) Other (Enter your own character)" read -p "Choice [1-6]: " SYM_CHOICE SYM_CHOICE=$(echo "$SYM_CHOICE" | xargs) SYMBOL_TABLE="/" SYMBOL_CODE="X" case "$SYM_CHOICE" in 2) SYMBOL_CODE=">" ;; 3) SYMBOL_CODE="[" ;; 4) SYMBOL_CODE="-" ;; 5) SYMBOL_CODE="#" ;; 6) read -p "Enter Symbol Character (e.g. > or [ or X): " CUSTOM_SYM SYMBOL_CODE=$(echo "$CUSTOM_SYM" | xargs) if [ -z "$SYMBOL_CODE" ]; then SYMBOL_CODE="X" fi ;; *) SYMBOL_CODE="X" ;; esac LATITUDE="" LONGITUDE="" # 5. Coordinates read -p "5. Do you want to automatically detect your coordinates via IP? [Y/n]: " AUTO_LOC AUTO_LOC=$(echo "$AUTO_LOC" | tr 'A-Z' 'a-z' | xargs) if [ "$AUTO_LOC" != "n" ]; then echo -e "${C_BLUE}[i] Detecting coordinates via IP...${C_RESET}" IP_LOC=$(python3 -c " import urllib.request, json urls = ['http://ip-api.com/json', 'https://ipapi.co/json/'] for url in urls: try: req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) with urllib.request.urlopen(req, timeout=4) as r: d = json.loads(r.read().decode('utf-8')) if d.get('status') == 'success' or 'latitude' in d: lat = d.get('lat') or d.get('latitude') lon = d.get('lon') or d.get('longitude') city = d.get('city') or 'Unknown' country = d.get('country_name') or d.get('country') or 'Unknown' print(f'{lat},{lon},{city},{country}') break except: continue ") if [ -n "$IP_LOC" ]; then IFS=',' read -r LAT LON CITY COUNTRY <<< "$IP_LOC" echo -e "${C_GREEN}[+] Automatically Detected Location: $CITY, $COUNTRY ($LAT, $LON)${C_RESET}" LATITUDE="$LAT" LONGITUDE="$LON" else echo -e "${C_RED}[!] IP location detection failed.${C_RESET}" fi fi if [ -z "$LATITUDE" ] || [ -z "$LONGITUDE" ]; then echo -e "${C_BLUE}[i] Opening OpenStreetMap in browser to help find coordinates...${C_RESET}" python3 -m webbrowser "https://www.openstreetmap.org" &>/dev/null & echo -e "${C_YELLOW}[!] Please enter coordinates manually (copy latitude/longitude from map):${C_RESET}" while true; do read -p " Latitude (e.g. 41.037002): " LATITUDE LATITUDE=$(echo "$LATITUDE" | xargs) if python3 -c "float('$LATITUDE')" &>/dev/null; then break fi echo -e "${C_RED}Error: Invalid latitude value!${C_RESET}" done while true; do read -p " Longitude (e.g. 28.985012): " LONGITUDE LONGITUDE=$(echo "$LONGITUDE" | xargs) if python3 -c "float('$LONGITUDE')" &>/dev/null; then break fi echo -e "${C_RED}Error: Invalid longitude value!${C_RESET}" done fi # 6. Interval while true; do read -p "6. How often should beacons be sent (minutes)? [Default: 5]: " INTERVAL_MINUTES INTERVAL_MINUTES=$(echo "$INTERVAL_MINUTES" | xargs) if [ -z "$INTERVAL_MINUTES" ]; then INTERVAL_MINUTES=5 break fi if python3 -c "int('$INTERVAL_MINUTES') >= 1" &>/dev/null; then break fi echo -e "${C_RED}Error: Interval must be at least 1 minute!${C_RESET}" done # 6.5 APRS Thursday read -p "6.5 Join APRS Thursday event? (ANSRVR) [y/N]: " OPT_THURSDAY OPT_THURSDAY=$(echo "$OPT_THURSDAY" | tr 'A-Z' 'a-z' | xargs) APRS_THURSDAY="false" if [ "$OPT_THURSDAY" = "y" ]; then APRS_THURSDAY="true" fi # Save Configuration cat < "$PROFILES_DIR/$PROFILE_NAME.json" { "callsign": "$CALLSIGN", "passcode": $PASSCODE, "latitude": $LATITUDE, "longitude": $LONGITUDE, "use_termux_gps": false, "symbol_table": "$SYMBOL_TABLE", "symbol_code": "$SYMBOL_CODE", "comment": "$COMMENT", "interval_minutes": $INTERVAL_MINUTES, "aprs_thursday": $APRS_THURSDAY, "server": "rotate.aprs2.net", "port": 14580 } EOF echo -e "\n${C_GREEN}[+] Configuration file saved: $PROFILES_DIR/$PROFILE_NAME.json${C_RESET}" # Copy / Download files echo -e "${C_BLUE}[i] Installing application scripts...${C_RESET}" if [ -f "./aprs_beacon.py" ]; then cp "./aprs_beacon.py" "$INSTALL_DIR/aprs_beacon.py" else curl -sL -o "$INSTALL_DIR/aprs_beacon.py" "https://raw.githubusercontent.com/mcturan/aprs/main/aprs_beacon.py" fi chmod +x "$INSTALL_DIR/aprs_beacon.py" if [ -f "./aprs_manager.py" ]; then cp "./aprs_manager.py" "$INSTALL_DIR/aprs_manager.py" else curl -sL -o "$INSTALL_DIR/aprs_manager.py" "https://raw.githubusercontent.com/mcturan/aprs/main/aprs_manager.py" fi chmod +x "$INSTALL_DIR/aprs_manager.py" # Backward compatibility (migration of old single config) if [ -f "$INSTALL_DIR/config.json" ]; then mv "$INSTALL_DIR/config.json" "$PROFILES_DIR/default.json" 2>/dev/null mv "$INSTALL_DIR/aprs_beacon.log" "$LOGS_DIR/default.log" 2>/dev/null fi # Send Test Beacon echo -e "\n${C_BLUE}[i] Sending test beacon to verify settings...${C_RESET}" python3 "$INSTALL_DIR/aprs_beacon.py" --profile "$PROFILE_NAME" --once if [ $? -eq 0 ]; then echo -e "${C_GREEN}[+] Test Successful! Position packet transmitted to APRS-IS.${C_RESET}" else echo -e "${C_RED}[!] Test Failed! Could not transmit packet to server.${C_RESET}" echo -e "${C_YELLOW}[i] For details: cat $LOGS_DIR/$PROFILE_NAME.log${C_RESET}" read -p "Do you want to proceed anyway? [y/N]: " PROCEED PROCEED=$(echo "$PROCEED" | tr 'A-Z' 'a-z' | xargs) if [ "$PROCEED" != "y" ]; then echo "Setup cancelled." exit 1 fi fi # Linux systemd startup echo -e "\n7. Startup Settings (Linux):" read -p "Start this profile automatically on system boot? [Y/n]: " AUTO_START AUTO_START=$(echo "$AUTO_START" | tr 'A-Z' 'a-z' | xargs) # Generate systemd user service template SYSTEMD_USER_DIR="$HOME/.config/systemd/user" mkdir -p "$SYSTEMD_USER_DIR" cat < "$SYSTEMD_USER_DIR/aprs-beacon@.service" [Unit] Description=APRS Background Beacon Service (%i) After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=$(which python3) $INSTALL_DIR/aprs_beacon.py --profile %i Restart=always RestartSec=30 WorkingDirectory=$INSTALL_DIR [Install] WantedBy=default.target EOF systemctl --user daemon-reload if [ "$AUTO_START" != "n" ]; then echo -e "${C_BLUE}[i] Enabling and starting daemon service...${C_RESET}" systemctl --user enable aprs-beacon@$PROFILE_NAME.service systemctl --user restart aprs-beacon@$PROFILE_NAME.service loginctl enable-linger "$USER" 2>/dev/null fi # Create desktop shortcut launcher (if GUI available) if [ "$HAS_GUI" = true ]; then DESKTOP_DIR="$HOME/.local/share/applications" mkdir -p "$DESKTOP_DIR" cat < "$DESKTOP_DIR/aprs-manager.desktop" [Desktop Entry] Type=Application Name=APRS Multi-Beacon Manager Comment=APRS Multi-Profile Background Beacon Manager Exec=$INSTALL_DIR/aprs_manager.py gui Icon=radio Terminal=false Categories=Utility;Network; EOF chmod +x "$DESKTOP_DIR/aprs-manager.desktop" # Add manager to session autostart AUTOSTART_DIR="$HOME/.config/autostart" mkdir -p "$AUTOSTART_DIR" cp "$DESKTOP_DIR/aprs-manager.desktop" "$AUTOSTART_DIR/" fi # Post-install info summary echo -e "\n${C_GREEN}${C_BOLD}================================================================${C_RESET}" echo -e "${C_GREEN}${C_BOLD} APRS PROFILE ($PROFILE_NAME) SUCCESSFULLY ACTIVATED!${C_RESET}" echo -e "${C_GREEN}${C_BOLD}================================================================${C_RESET}" echo -e "${C_BOLD}Profile Name :${C_RESET} $PROFILE_NAME" echo -e "${C_BOLD}Callsign :${C_RESET} $CALLSIGN" echo -e "${C_BOLD}Interval :${C_RESET} Every $INTERVAL_MINUTES minute(s)" echo -e "${C_BOLD}APRS Thursday :${C_RESET} $([ "$APRS_THURSDAY" = "true" ] && echo "Enabled" || echo "Disabled")" echo -e "${C_BOLD}Autostart on Boot:${C_RESET} Yes" echo -e "----------------------------------------------------------------" if [ "$HAS_GUI" = true ]; then echo -e "${C_CYAN}Desktop Interface :${C_RESET} Launch 'APRS Multi-Beacon Manager' from application menu." echo -e "${C_CYAN}System Tray Icon :${C_RESET} System tray icon will appear upon launching." else echo -e "${C_YELLOW}Note: No graphic display detected. Launch control panel from terminal.${C_RESET}" fi echo -e "${C_CYAN}CLI / Terminal Management:${C_RESET}" echo -e " - Open Interactive Menu: python3 $INSTALL_DIR/aprs_manager.py" echo -e " - List Beacon Profiles: python3 $INSTALL_DIR/aprs_manager.py list" echo -e " - Start Profile Daemon: python3 $INSTALL_DIR/aprs_manager.py start $PROFILE_NAME" echo -e " - Stop Profile Daemon: python3 $INSTALL_DIR/aprs_manager.py stop $PROFILE_NAME" echo -e "${C_GREEN}${C_BOLD}================================================================${C_RESET}\n"