#!/bin/bash # # Network watchdog for the CUPS print server. # Default behavior: when connectivity checks fail repeatedly, switch the # configured interface back to DHCP so the box can regain network access. # # Edit /etc/cups-watchdog/network-watchdog.conf to set STATIC_IP, # STATIC_PREFIX, STATIC_GATEWAY and STATIC_DNS. You can also run: # /opt/cups-watchdog/network-watchdog.sh static # /opt/cups-watchdog/network-watchdog.sh dhcp # set -u CONFIG_FILE="${CUPS_NETWORK_WATCHDOG_CONFIG:-/etc/cups-watchdog/network-watchdog.conf}" STATE_DIR="/run/cups-watchdog" INTERFACE="" STATIC_IP="" STATIC_PREFIX="24" STATIC_GATEWAY="" STATIC_DNS="114.114.114.114 223.5.5.5" PING_TARGETS="223.5.5.5 114.114.114.114" FAIL_THRESHOLD=3 DHCP_AFTER_FAILURE=1 LOG_FILE="/var/log/cups-watchdog/network.log" MAX_LOG_SIZE_KB=256 if [ -f "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 . "$CONFIG_FILE" fi mkdir -p "$STATE_DIR" mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true acquire_lock() { local lock_dir="$STATE_DIR/network.lock" if ! mkdir "$lock_dir" 2>/dev/null; then echo "$(date '+%Y-%m-%d %H:%M:%S') [network-watchdog] another run is active" exit 0 fi trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT } rotate_log() { local max_bytes=$((MAX_LOG_SIZE_KB * 1024)) if [ -f "$LOG_FILE" ] && [ "$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)" -gt "$max_bytes" ]; then mv "$LOG_FILE" "$LOG_FILE.1" 2>/dev/null || true : > "$LOG_FILE" fi } log_msg() { local msg="$1" local line rotate_log line="$(date '+%Y-%m-%d %H:%M:%S') [network-watchdog] $msg" echo "$line" echo "$line" >> "$LOG_FILE" 2>/dev/null || true } detect_interface() { if [ -n "${INTERFACE:-}" ] && ip link show "$INTERFACE" >/dev/null 2>&1; then echo "$INTERFACE" return 0 fi ip route show default 2>/dev/null | awk '{print $5; exit}' } nm_connection_for_interface() { local iface="$1" nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v iface="$iface" '$2 == iface {print $1; exit}' } configure_dhcp_nmcli() { local iface="$1" local conn conn="$(nm_connection_for_interface "$iface")" [ -n "$conn" ] || conn="$iface" nmcli con mod "$conn" ipv4.method auto ipv4.addresses "" ipv4.gateway "" ipv4.dns "" >/dev/null nmcli con down "$conn" >/dev/null 2>&1 || true nmcli con up "$conn" >/dev/null } configure_dhcp_netplan() { local iface="$1" local target_file="/etc/netplan/99-cups-watchdog-dhcp.yaml" cat > "$target_file" << EOF # DHCP recovery config generated by cups network watchdog. network: version: 2 renderer: networkd ethernets: $iface: dhcp4: yes EOF chmod 600 "$target_file" rm -f /etc/netplan/01-static-ip.yaml /etc/netplan/99-cups-static-ip.yaml /etc/netplan/99-cups-watchdog-static.yaml 2>/dev/null || true netplan apply } configure_dhcp_interfaces() { local iface="$1" if [ -f /etc/network/interfaces ]; then cp /etc/network/interfaces /etc/network/interfaces.cups-watchdog.bak fi cat > /etc/network/interfaces << EOF # DHCP recovery config generated by cups network watchdog. auto lo iface lo inet loopback auto $iface iface $iface inet dhcp EOF systemctl restart networking 2>/dev/null || /etc/init.d/networking restart 2>/dev/null || true } configure_dhcp() { local iface iface="$(detect_interface)" if [ -z "$iface" ]; then log_msg "cannot switch to DHCP: network interface not found" return 1 fi log_msg "switching $iface to DHCP" if command -v nmcli >/dev/null 2>&1 && systemctl is-active --quiet NetworkManager 2>/dev/null; then configure_dhcp_nmcli "$iface" elif [ -d /etc/netplan ] && command -v netplan >/dev/null 2>&1; then configure_dhcp_netplan "$iface" else configure_dhcp_interfaces "$iface" fi } configure_static_nmcli() { local iface="$1" local conn conn="$(nm_connection_for_interface "$iface")" [ -n "$conn" ] || conn="$iface" nmcli con mod "$conn" ipv4.method manual ipv4.addresses "$STATIC_IP/$STATIC_PREFIX" ipv4.gateway "$STATIC_GATEWAY" ipv4.dns "$STATIC_DNS" >/dev/null nmcli con down "$conn" >/dev/null 2>&1 || true nmcli con up "$conn" >/dev/null } configure_static_netplan() { local iface="$1" local target_file="/etc/netplan/99-cups-watchdog-static.yaml" local dns_list="" local d for d in $STATIC_DNS; do dns_list="$dns_list, $d" done dns_list="${dns_list#, }" cat > "$target_file" << EOF # Static network config generated by cups network watchdog. network: version: 2 renderer: networkd ethernets: $iface: dhcp4: no addresses: - $STATIC_IP/$STATIC_PREFIX routes: - to: default via: $STATIC_GATEWAY nameservers: addresses: [$dns_list] EOF chmod 600 "$target_file" rm -f /etc/netplan/01-dhcp.yaml /etc/netplan/99-cups-dhcp.yaml /etc/netplan/99-cups-watchdog-dhcp.yaml 2>/dev/null || true netplan apply } configure_static_interfaces() { local iface="$1" local netmask="255.255.255.0" case "$STATIC_PREFIX" in 8) netmask="255.0.0.0" ;; 16) netmask="255.255.0.0" ;; 24) netmask="255.255.255.0" ;; 25) netmask="255.255.255.128" ;; 26) netmask="255.255.255.192" ;; 27) netmask="255.255.255.224" ;; 28) netmask="255.255.255.240" ;; 29) netmask="255.255.255.248" ;; 30) netmask="255.255.255.252" ;; esac if [ -f /etc/network/interfaces ]; then cp /etc/network/interfaces /etc/network/interfaces.cups-watchdog.bak fi cat > /etc/network/interfaces << EOF # Static network config generated by cups network watchdog. auto lo iface lo inet loopback auto $iface iface $iface inet static address $STATIC_IP netmask $netmask gateway $STATIC_GATEWAY dns-nameservers $STATIC_DNS EOF systemctl restart networking 2>/dev/null || /etc/init.d/networking restart 2>/dev/null || true } configure_static() { local iface iface="$(detect_interface)" if [ -z "$iface" ]; then log_msg "cannot switch to static: network interface not found" return 1 fi if [ -z "${STATIC_IP:-}" ] || [ -z "${STATIC_GATEWAY:-}" ]; then log_msg "cannot switch to static: STATIC_IP or STATIC_GATEWAY is empty in $CONFIG_FILE" return 1 fi log_msg "switching $iface to static $STATIC_IP/$STATIC_PREFIX" if command -v nmcli >/dev/null 2>&1 && systemctl is-active --quiet NetworkManager 2>/dev/null; then configure_static_nmcli "$iface" elif [ -d /etc/netplan ] && command -v netplan >/dev/null 2>&1; then configure_static_netplan "$iface" else configure_static_interfaces "$iface" fi } connectivity_ok() { local iface local gateway local target iface="$(detect_interface)" if [ -n "$iface" ] && [ -f "/sys/class/net/$iface/carrier" ]; then if [ "$(cat "/sys/class/net/$iface/carrier" 2>/dev/null || echo 0)" != "1" ]; then return 1 fi fi gateway="$(ip route show default 2>/dev/null | awk '{print $3; exit}')" if [ -n "$gateway" ] && ping -c 1 -W 2 "$gateway" >/dev/null 2>&1; then return 0 fi for target in $PING_TARGETS; do [ -n "$target" ] || continue if ping -c 1 -W 2 "$target" >/dev/null 2>&1; then return 0 fi done return 1 } check_network() { local count_file="$STATE_DIR/network.fail_count" local fail_count=0 if connectivity_ok; then echo 0 > "$count_file" log_msg "connectivity ok" return 0 fi if [ -f "$count_file" ]; then fail_count="$(cat "$count_file" 2>/dev/null || echo 0)" fi fail_count=$((fail_count + 1)) echo "$fail_count" > "$count_file" log_msg "connectivity failed ($fail_count/$FAIL_THRESHOLD)" if [ "$fail_count" -ge "$FAIL_THRESHOLD" ] && [ "$DHCP_AFTER_FAILURE" = "1" ]; then configure_dhcp echo 0 > "$count_file" fi } case "${1:-check}" in check) acquire_lock check_network ;; dhcp) acquire_lock configure_dhcp ;; static) acquire_lock configure_static ;; *) echo "Usage: $0 [check|dhcp|static]" exit 2 ;; esac