diff --git a/README.md b/README.md index f02d61f..cbe7c31 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - 多种打印机驱动可选 - 驱动管理器(默认仅允许内网访问) - 网络和打印服务守护(断网自动切 DHCP,CUPS 卡死自动恢复) +- 静态 IP 支持网关变更自愈:先切 DHCP,再按原静态 IP 尾号迁移到新网段 - 支持一键卸载 ## 快速安装 @@ -151,6 +152,9 @@ lpinfo -v | grep usb ### 手机发送打印任务很慢 脚本已配置 `HostNameLookups Off` 解决此问题。如果仍然慢,检查网络连接。 +### 更换路由器或网关后找不到打印服务器 +网络守护会在连续检测失败后先切换到 DHCP 恢复连接。如果发现 DHCP 获取到的新网关和原静态网关不同,会保留原静态 IP 的最后一段,例如 `192.168.11.219` 的 `219`,自动改成新网段下的 `x.x.x.219`。 + ### 界面显示英文 确保安装时下载了中文模板、中文首页和前端汉化脚本。重新运行安装脚本可以修复。 diff --git a/setup_cups.sh b/setup_cups.sh index 5ce5c5c..160e78c 100755 --- a/setup_cups.sh +++ b/setup_cups.sh @@ -1196,6 +1196,7 @@ install_watchdogs() { local current_interface=$(get_interface) local current_netmask=$(get_netmask) local current_dns=$(get_dns) + local current_host_octet="${current_ip##*.}" current_dns="${current_dns:-114.114.114.114 223.5.5.5}" local ping_targets="223.5.5.5 114.114.114.114" @@ -1213,9 +1214,12 @@ STATIC_IP="$current_ip" STATIC_PREFIX="$current_netmask" STATIC_GATEWAY="$current_gateway" STATIC_DNS="$current_dns" +STATIC_HOST_OCTET="$current_host_octet" PING_TARGETS="$ping_targets" FAIL_THRESHOLD=3 DHCP_AFTER_FAILURE=1 +REBASE_STATIC_ON_GATEWAY_CHANGE=1 +REBASE_WAIT_SECONDS=20 LOG_FILE="/var/log/cups-watchdog/network.log" MAX_LOG_SIZE_KB=256 EOF diff --git a/watchdog/network-watchdog.sh b/watchdog/network-watchdog.sh index a485192..420f308 100644 --- a/watchdog/network-watchdog.sh +++ b/watchdog/network-watchdog.sh @@ -3,6 +3,8 @@ # 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. +# If DHCP reveals a different gateway, the script can rebuild the static IP +# with the new network prefix while keeping the old host octet. # # Edit /etc/cups-watchdog/network-watchdog.conf to set STATIC_IP, # STATIC_PREFIX, STATIC_GATEWAY and STATIC_DNS. You can also run: @@ -20,9 +22,12 @@ STATIC_IP="" STATIC_PREFIX="24" STATIC_GATEWAY="" STATIC_DNS="114.114.114.114 223.5.5.5" +STATIC_HOST_OCTET="" PING_TARGETS="223.5.5.5 114.114.114.114" FAIL_THRESHOLD=3 DHCP_AFTER_FAILURE=1 +REBASE_STATIC_ON_GATEWAY_CHANGE=1 +REBASE_WAIT_SECONDS=20 LOG_FILE="/var/log/cups-watchdog/network.log" MAX_LOG_SIZE_KB=256 @@ -71,6 +76,56 @@ detect_interface() { ip route show default 2>/dev/null | awk '{print $5; exit}' } +current_ipv4_for_interface() { + local iface="$1" + + ip -4 -o addr show dev "$iface" scope global 2>/dev/null | awk '{split($4, a, "/"); print a[1]; exit}' +} + +current_prefix_for_interface() { + local iface="$1" + + ip -4 -o addr show dev "$iface" scope global 2>/dev/null | awk '{split($4, a, "/"); print a[2]; exit}' +} + +current_gateway_for_interface() { + local iface="$1" + local gateway + + gateway="$(ip route show default dev "$iface" 2>/dev/null | awk '{print $3; exit}')" + if [ -z "$gateway" ]; then + gateway="$(ip route show default 2>/dev/null | awk '{print $3; exit}')" + fi + echo "$gateway" +} + +last_octet() { + local ip="$1" + + case "$ip" in + *.*.*.*) echo "${ip##*.}" ;; + *) echo "" ;; + esac +} + +prefix24() { + local ip="$1" + + case "$ip" in + *.*.*.*) echo "${ip%.*}" ;; + *) echo "" ;; + esac +} + +valid_host_octet() { + local octet="$1" + + case "$octet" in + ''|*[!0-9]*) return 1 ;; + esac + [ "$octet" -ge 1 ] 2>/dev/null && [ "$octet" -le 254 ] 2>/dev/null +} + 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}' @@ -141,6 +196,70 @@ configure_dhcp() { fi } +set_config_value() { + local key="$1" + local value="$2" + local tmp_file + + if [ ! -f "$CONFIG_FILE" ]; then + mkdir -p "$(dirname "$CONFIG_FILE")" 2>/dev/null || true + : > "$CONFIG_FILE" 2>/dev/null || return 1 + fi + + tmp_file="${CONFIG_FILE}.tmp.$$" + if grep -q "^${key}=" "$CONFIG_FILE" 2>/dev/null; then + awk -v key="$key" -v value="$value" ' + BEGIN { q = sprintf("%c", 34) } + $0 ~ "^" key "=" { print key "=" q value q; next } + { print } + ' "$CONFIG_FILE" > "$tmp_file" && mv "$tmp_file" "$CONFIG_FILE" + else + printf '%s="%s"\n' "$key" "$value" >> "$CONFIG_FILE" + fi + chmod 600 "$CONFIG_FILE" 2>/dev/null || true +} + +wait_for_dhcp_network() { + local iface="$1" + local i=0 + local ip + local gateway + + while [ "$i" -lt "${REBASE_WAIT_SECONDS:-20}" ]; do + ip="$(current_ipv4_for_interface "$iface")" + gateway="$(current_gateway_for_interface "$iface")" + if [ -n "$ip" ] && [ -n "$gateway" ]; then + echo "$ip $gateway" + return 0 + fi + i=$((i + 1)) + sleep 1 + done + + return 1 +} + +candidate_ip_in_use() { + local iface="$1" + local candidate_ip="$2" + local current_ip="$3" + + [ "$candidate_ip" = "$current_ip" ] && return 1 + + if command -v arping >/dev/null 2>&1; then + # arping duplicate-address detection returns success when no reply is seen. + arping -D -I "$iface" -c 2 -w 3 "$candidate_ip" >/dev/null 2>&1 + [ "$?" != "0" ] && return 0 + return 1 + fi + + if ping -c 1 -W 1 "$candidate_ip" >/dev/null 2>&1; then + return 0 + fi + + return 1 +} + configure_static_nmcli() { local iface="$1" local conn @@ -243,6 +362,105 @@ configure_static() { fi } +rebase_static_after_dhcp() { + local iface="$1" + local dhcp_state + local dhcp_ip + local dhcp_gateway + local dhcp_prefix + local host_octet + local base_prefix + local candidate_ip + local old_static_ip="$STATIC_IP" + local old_static_gateway="$STATIC_GATEWAY" + local old_static_prefix="$STATIC_PREFIX" + local old_static_dns="$STATIC_DNS" + local current_dns + + [ "${REBASE_STATIC_ON_GATEWAY_CHANGE:-1}" = "1" ] || return 0 + + if [ -z "${STATIC_IP:-}" ] || [ -z "${STATIC_GATEWAY:-}" ]; then + log_msg "skip static rebase: STATIC_IP or STATIC_GATEWAY is empty" + return 0 + fi + + dhcp_state="$(wait_for_dhcp_network "$iface" 2>/dev/null || true)" + if [ -z "$dhcp_state" ]; then + log_msg "skip static rebase: DHCP did not provide an IP and gateway in time" + return 0 + fi + + dhcp_ip="$(echo "$dhcp_state" | awk '{print $1}')" + dhcp_gateway="$(echo "$dhcp_state" | awk '{print $2}')" + if [ "$dhcp_gateway" = "$STATIC_GATEWAY" ]; then + log_msg "DHCP gateway is still $dhcp_gateway; keep DHCP recovery without rebasing static IP" + return 0 + fi + + host_octet="${STATIC_HOST_OCTET:-$(last_octet "$STATIC_IP")}" + if ! valid_host_octet "$host_octet"; then + log_msg "skip static rebase: invalid STATIC_HOST_OCTET '$host_octet'" + return 0 + fi + + base_prefix="$(prefix24 "$dhcp_ip")" + [ -n "$base_prefix" ] || base_prefix="$(prefix24 "$dhcp_gateway")" + if [ -z "$base_prefix" ]; then + log_msg "skip static rebase: cannot derive IPv4 /24 prefix from DHCP state" + return 0 + fi + + candidate_ip="${base_prefix}.${host_octet}" + if candidate_ip_in_use "$iface" "$candidate_ip" "$dhcp_ip"; then + log_msg "skip static rebase: candidate $candidate_ip appears to be in use; staying on DHCP" + return 0 + fi + + dhcp_prefix="$(current_prefix_for_interface "$iface")" + STATIC_IP="$candidate_ip" + STATIC_GATEWAY="$dhcp_gateway" + STATIC_PREFIX="${dhcp_prefix:-$STATIC_PREFIX}" + current_dns="$(grep -E '^nameserver ' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n2 | tr '\n' ' ')" + STATIC_DNS="${current_dns:-$STATIC_DNS}" + + log_msg "gateway changed from $old_static_gateway to $STATIC_GATEWAY; rebasing static IP from $old_static_ip to $STATIC_IP/$STATIC_PREFIX" + configure_static + sleep 3 + + if connectivity_ok; then + set_config_value "STATIC_IP" "$STATIC_IP" || true + set_config_value "STATIC_GATEWAY" "$STATIC_GATEWAY" || true + set_config_value "STATIC_PREFIX" "$STATIC_PREFIX" || true + set_config_value "STATIC_DNS" "$STATIC_DNS" || true + set_config_value "STATIC_HOST_OCTET" "$host_octet" || true + set_config_value "PING_TARGETS" "$STATIC_GATEWAY 223.5.5.5 114.114.114.114" || true + log_msg "static rebase succeeded: $STATIC_IP via $STATIC_GATEWAY" + return 0 + fi + + STATIC_IP="$old_static_ip" + STATIC_GATEWAY="$old_static_gateway" + STATIC_PREFIX="$old_static_prefix" + STATIC_DNS="$old_static_dns" + log_msg "static rebase to $candidate_ip failed connectivity check; falling back to DHCP" + configure_dhcp +} + +recover_after_failure() { + local iface + local fallback_iface + + fallback_iface="$(detect_interface)" + configure_dhcp || return 1 + iface="$(detect_interface)" + [ -n "$iface" ] || iface="$fallback_iface" + if [ -z "$iface" ]; then + log_msg "skip static rebase: network interface not found after DHCP recovery" + return 1 + fi + rebase_static_after_dhcp "$iface" +} + connectivity_ok() { local iface local gateway @@ -288,7 +506,7 @@ check_network() { log_msg "connectivity failed ($fail_count/$FAIL_THRESHOLD)" if [ "$fail_count" -ge "$FAIL_THRESHOLD" ] && [ "$DHCP_AFTER_FAILURE" = "1" ]; then - configure_dhcp + recover_after_failure echo 0 > "$count_file" fi }