Files
LEDMatrix/scripts/install/install_wifi_monitor.sh
T
ChuckandClaude Opus 5 12f3790994 fix(install): render the systemd units from their templates, not from heredocs (#547)
* fix(install): render the systemd units from their templates, not from heredocs

The installers carried their own inline copies of units that also exist as
templates under systemd/, and the copies drifted.

install_service.sh renders ledmatrix.service from the template correctly, then
wrote ledmatrix-web.service from a heredoc that predated it -- missing
Wants=network-online.target, RestartSec=10, SyslogIdentifier, CacheDirectory,
CacheDirectoryMode and Environment=USE_THREADING=1. install_web_service.sh had
a third copy, and install_wifi_monitor.sh a fourth, that one already differing
from its template (syslog where the template says journal).

startup_validator.py compares the installed unit against the template, so a
rig installed this way warned on every boot -- and the remedy the warning
names, "re-run scripts/install/install_service.sh", reinstalled the same stale
copy. The warning could never clear. Reproduced on a live rig running exactly
that unit.

All three installers now render systemd/*.service through the same placeholder
substitution. The template gains a __USER__ placeholder rather than hardcoding
User=root, because the web interface runs as whoever installed it.

That last point was a second, independent cause of a permanent warning: the
validator substituted a fixed "root", so any non-root install reported drift
forever. It now reads User= from the installed unit -- an install-time
decision, not something the template dictates -- and compares everything else
strictly. first_time_install.sh already reads the installed User= the same way.

Tests cover a non-root web unit not warning, a genuinely changed directive in
that unit still warning, the User= fallback, and a grep-based guard that no
installer under scripts/install/ contains an inline unit body. That guard is
what found the install_wifi_monitor.sh copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9

* fix(install): escape sed replacements, use mktemp, and make render failures fatal

Address CodeRabbit findings on install_service.sh, install_web_service.sh and
install_wifi_monitor.sh:

- Values interpolated into each script's sed expression (project root path,
  username) were not escaped, so a value containing &, \ or the | delimiter
  would corrupt the rendered systemd unit. Add a shared
  sed_escape_replacement() helper in the new scripts/install/lib_systemd_render.sh
  (sourced by all three scripts) and apply it to every sed replacement.
- install_service.sh rendered the main and web units to the predictable path
  /tmp/ledmatrix.service.tmp before installing them -- a symlink/TOCTOU race
  (CWE-377). Use mktemp for both, with a trap to clean up on exit.
- install_service.sh treated a missing template as a mere warning and then
  checked only whether a unit already existed at the destination before
  enabling/starting it, so a render failure could silently fall back to
  enabling a stale, previously-installed unit. Both unit blocks now exit
  non-zero on a missing template or a failed render.

Also rename the ambiguous loop variable `l` to `line` in
test/test_systemd_unit_drift.py (Ruff E741); ruff isn't wired into any CI
workflow in this repo today, so this isn't currently CI-blocking, but the
rename is trivial and correct regardless.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3bPMESe2TfrGvbs1ef9c5

* test(install): cover sed_escape_replacement against sed-special characters

CodeRabbit asked for regression coverage using a project path containing an
ampersand; the earlier commits on this branch already fixed the escaping,
mktemp usage, and enable/start-on-fatal-render-failure findings, and the
l->line rename was already applied -- this closes the one remaining gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 08:41:19 -04:00

206 lines
7.1 KiB
Bash
Executable File

#!/bin/bash
# WiFi Monitor Service Installation Script
# Installs the WiFi monitor daemon service for LED Matrix
set -e
# Get the actual user who invoked sudo
if [ -n "$SUDO_USER" ]; then
ACTUAL_USER="$SUDO_USER"
else
ACTUAL_USER=$(whoami)
fi
# Get the home directory of the actual user
USER_HOME=$(eval echo ~$ACTUAL_USER)
# Determine the Project Root Directory (parent of scripts/install/)
PROJECT_ROOT_DIR=$(cd "$(dirname "$0")/../.." && pwd)
# shellcheck source=scripts/install/lib_systemd_render.sh
source "$PROJECT_ROOT_DIR/scripts/install/lib_systemd_render.sh"
echo "Installing LED Matrix WiFi Monitor Service for user: $ACTUAL_USER"
echo "Using home directory: $USER_HOME"
echo "Project root directory: $PROJECT_ROOT_DIR"
# Check if required packages are installed
echo ""
echo "Checking for required packages..."
MISSING_PACKAGES=()
if ! command -v hostapd >/dev/null 2>&1; then
MISSING_PACKAGES+=("hostapd")
fi
if ! command -v dnsmasq >/dev/null 2>&1; then
MISSING_PACKAGES+=("dnsmasq")
fi
if ! command -v nmcli >/dev/null 2>&1 && ! command -v iwlist >/dev/null 2>&1; then
MISSING_PACKAGES+=("network-manager")
fi
if [ ${#MISSING_PACKAGES[@]} -gt 0 ]; then
echo "Installing required packages for WiFi setup:"
for pkg in "${MISSING_PACKAGES[@]}"; do
echo " - $pkg"
done
echo ""
# Install packages automatically (no prompt)
# Use apt directly if running as root, otherwise use sudo
if [ "$EUID" -eq 0 ]; then
apt update || echo "⚠ apt update failed, continuing anyway..."
apt install -y "${MISSING_PACKAGES[@]}" || {
echo "⚠ Package installation failed, but continuing with WiFi monitor setup"
echo " You may need to install packages manually: apt install -y ${MISSING_PACKAGES[*]}"
}
else
sudo apt update || echo "⚠ apt update failed, continuing anyway..."
sudo apt install -y "${MISSING_PACKAGES[@]}" || {
echo "⚠ Package installation failed, but continuing with WiFi monitor setup"
echo " You may need to install packages manually: sudo apt install -y ${MISSING_PACKAGES[*]}"
}
fi
echo "✓ Package installation completed"
fi
# Render the unit from systemd/ledmatrix-wifi-monitor.service rather than
# inlining a second copy here. The copy this replaced had already drifted --
# it wrote StandardOutput/StandardError=syslog where the template says journal.
echo ""
echo "Creating systemd service file..."
TEMPLATE="$PROJECT_ROOT_DIR/systemd/ledmatrix-wifi-monitor.service"
if [ ! -f "$TEMPLATE" ]; then
echo "ERROR: unit template not found at $TEMPLATE"
exit 1
fi
ESCAPED_PROJECT_ROOT_DIR=$(sed_escape_replacement "$PROJECT_ROOT_DIR")
SERVICE_FILE_CONTENT=$(sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|root|g" "$TEMPLATE")
if [ "$EUID" -eq 0 ]; then
echo "$SERVICE_FILE_CONTENT" | tee /etc/systemd/system/ledmatrix-wifi-monitor.service > /dev/null
else
echo "$SERVICE_FILE_CONTENT" | sudo tee /etc/systemd/system/ledmatrix-wifi-monitor.service > /dev/null
fi
# Check WiFi connection status before enabling service
echo ""
echo "Checking WiFi connection status..."
WIFI_CONNECTED=false
ETHERNET_CONNECTED=false
# Check WiFi status
if command -v nmcli >/dev/null 2>&1; then
# Check if WiFi is connected
WIFI_STATUS=$(nmcli -t -f DEVICE,TYPE,STATE device status 2>/dev/null | grep -i wifi || echo "")
if echo "$WIFI_STATUS" | grep -q "connected"; then
WIFI_CONNECTED=true
SSID=$(nmcli -t -f active,ssid device wifi 2>/dev/null | grep "^yes:" | cut -d: -f2 | head -1)
if [ -n "$SSID" ]; then
echo "✓ WiFi is connected to: $SSID"
else
echo "✓ WiFi is connected"
fi
else
echo "⚠ WiFi is not connected"
fi
# Check Ethernet status
ETH_STATUS=$(nmcli -t -f DEVICE,TYPE,STATE device status 2>/dev/null | grep -E "ethernet|eth" || echo "")
if echo "$ETH_STATUS" | grep -q "connected"; then
ETHERNET_CONNECTED=true
echo "✓ Ethernet is connected"
fi
elif command -v ip >/dev/null 2>&1; then
# Fallback: check using ip command
if ip addr show wlan0 2>/dev/null | grep -q "inet " && ! ip addr show wlan0 2>/dev/null | grep -q "192.168.4.1"; then
WIFI_CONNECTED=true
echo "✓ WiFi appears to be connected (has IP address)"
else
echo "⚠ WiFi does not appear to be connected"
fi
# Check Ethernet
if ip addr show eth0 2>/dev/null | grep -q "inet " || ip addr show 2>/dev/null | grep -E "eth|enp" | grep -q "inet "; then
ETHERNET_CONNECTED=true
echo "✓ Ethernet appears to be connected (has IP address)"
fi
else
echo "⚠ Cannot check network status (nmcli and ip commands not available)"
fi
# Warn if neither WiFi nor Ethernet is connected
if [ "$WIFI_CONNECTED" = false ] && [ "$ETHERNET_CONNECTED" = false ]; then
echo ""
echo "⚠ WARNING: Neither WiFi nor Ethernet is connected!"
echo " The WiFi monitor service will automatically enable AP mode when no network"
echo " connection is detected. This will create a WiFi network named 'LEDMatrix-Setup'"
echo " that you can connect to for initial configuration."
echo ""
echo " If you want to connect to WiFi first, you can:"
echo " 1. Connect to WiFi using: sudo nmcli device wifi connect <SSID> password <password>"
echo " 2. Or connect via Ethernet cable"
echo " 3. Or proceed with installation - you can connect to LEDMatrix-Setup AP after reboot"
echo ""
echo "Proceeding with WiFi monitor installation..."
echo " (WiFi monitor will enable AP mode if no network connection is detected)"
fi
# Reload systemd
echo ""
echo "Reloading systemd..."
if [ "$EUID" -eq 0 ]; then
systemctl daemon-reload
else
sudo systemctl daemon-reload
fi
# Enable and start the service
echo "Enabling WiFi monitor service to start on boot..."
if [ "$EUID" -eq 0 ]; then
systemctl enable ledmatrix-wifi-monitor.service
else
sudo systemctl enable ledmatrix-wifi-monitor.service
fi
echo "Starting WiFi monitor service..."
if [ "$EUID" -eq 0 ]; then
systemctl start ledmatrix-wifi-monitor.service || echo "⚠ Failed to start service (may start on reboot)"
else
sudo systemctl start ledmatrix-wifi-monitor.service || echo "⚠ Failed to start service (may start on reboot)"
fi
# Check service status
echo ""
echo "Checking service status..."
if [ "$EUID" -eq 0 ]; then
SYSTEMCTL_CMD="systemctl"
else
SYSTEMCTL_CMD="sudo systemctl"
fi
if $SYSTEMCTL_CMD is-active --quiet ledmatrix-wifi-monitor.service 2>/dev/null; then
echo "✓ WiFi monitor service is running"
else
echo "⚠ WiFi monitor service failed to start. Check logs with:"
if [ "$EUID" -eq 0 ]; then
echo " journalctl -u ledmatrix-wifi-monitor -n 50"
else
echo " sudo journalctl -u ledmatrix-wifi-monitor -n 50"
fi
fi
echo ""
echo "WiFi Monitor Service installation complete!"
echo ""
echo "Useful commands:"
echo " sudo systemctl status ledmatrix-wifi-monitor # Check status"
echo " sudo systemctl restart ledmatrix-wifi-monitor # Restart service"
echo " sudo journalctl -u ledmatrix-wifi-monitor -f # View logs"
echo ""