This commit is contained in:
root
2026-05-18 13:49:53 +00:00
parent 088a5a6429
commit fa422fb7b8
9 changed files with 160 additions and 2 deletions

View File

@@ -95,6 +95,8 @@ NEKO_WEBRTC_UDPMUX_1=52300
NEKO_WEBRTC_UDPMUX_2=52301
NEKO_WEBRTC_UDPMUX_3=52302
NEKO_WEBRTC_NAT1TO1=
# 后台 watcher 会定期核对当前 LAN IP若板子切换 WiFi / DHCP 后 IP 变化,会自动重启 Neko 刷新 WebRTC NAT 地址
NEKO_IP_WATCH_INTERVAL=15
NEKO_PLUGINS_ENABLED=true
# Redroid Android 13需要 binderfs 或 /dev/binder*;安装脚本会在支持的内核上自动准备)

View File

@@ -30,6 +30,7 @@ configure_sudoers
configure_mdns
install_browser_stack
install_neko_browser
install_neko_ip_watch_service
install_redroid
prepare_mitmproxy
prepare_vscode_server

View File

@@ -29,6 +29,7 @@ configure_wifi_profile
install_shellcrash
install_browser_stack
install_neko_browser
install_neko_ip_watch_service
install_redroid
prepare_mitmproxy
prepare_vscode_server

View File

@@ -777,6 +777,36 @@ UNIT
systemctl enable --now live-console.service
}
install_neko_ip_watch_service() {
if [ "${ENABLE_NEKO:-1}" != "1" ]; then
systemctl disable --now live-neko-ip-watch.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/live-neko-ip-watch.service
systemctl daemon-reload
return 0
fi
log "Installing Neko LAN IP watcher"
chmod +x "$ROOT_DIR/scripts/linux/neko_ip_watch.sh"
cat >/etc/systemd/system/live-neko-ip-watch.service <<UNIT
[Unit]
Description=Live Neko LAN IP watcher
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=$ROOT_DIR
Environment=NEKO_IP_WATCH_INTERVAL=${NEKO_IP_WATCH_INTERVAL:-15}
ExecStart=/bin/bash $ROOT_DIR/scripts/linux/neko_ip_watch.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now live-neko-ip-watch.service
}
configure_sudoers() {
log "Configuring sudoers for live control plane"
cat >/etc/sudoers.d/live-control-plane <<SUDOERS

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
set -euo pipefail
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
INSTALL_TAG="${INSTALL_TAG:-neko-ip-watch}"
. "$SELF_DIR/lib/common.sh"
reload_watch_env() {
unset ENABLE_NEKO NEKO_IP_WATCH_INTERVAL
if [ -f "$STACK_ENV" ]; then
set -a
. "$STACK_ENV"
set +a
fi
ENABLE_NEKO="${ENABLE_NEKO:-1}"
NEKO_IP_WATCH_INTERVAL="${NEKO_IP_WATCH_INTERVAL:-15}"
}
normalize_interval() {
local value="${1:-15}"
case "$value" in
''|*[!0-9]*)
value=15
;;
esac
if [ "$value" -lt 5 ]; then
value=5
fi
printf '%s\n' "$value"
}
running_neko_nat1to1_values() {
local name value
for name in live-neko-1 live-neko-2 live-neko-3; do
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$name"; then
continue
fi
value="$(
docker inspect "$name" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null |
awk -F= '/^NEKO_WEBRTC_NAT1TO1=/{print $2; exit}'
)"
if [ -n "$value" ]; then
printf '%s\n' "$value"
fi
done
}
maybe_refresh_neko_for_ip_change() {
local preferred active interval cooldown active_value
if [ "${ENABLE_NEKO:-1}" != "1" ] || ! have docker; then
return 0
fi
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -Eq '^live-neko-[123]$'; then
return 0
fi
preferred="$(detect_preferred_lan_ip || true)"
if [ -z "$preferred" ]; then
return 0
fi
active=""
for active_value in $(running_neko_nat1to1_values || true); do
[ -n "$active_value" ] || continue
if [ "$active_value" != "$preferred" ]; then
active="$active_value"
break
fi
done
if [ -z "$active" ]; then
return 0
fi
interval="$(normalize_interval "${NEKO_IP_WATCH_INTERVAL:-15}")"
cooldown="$interval"
if [ "$cooldown" -lt 15 ]; then
cooldown=15
fi
log "Detected Neko LAN IP drift: container=${active} current=${preferred}; restarting Neko"
if bash "$SERVICE_CTL" neko restart; then
log "Neko restart finished; refreshed NAT1TO1=${preferred}"
else
log "WARN: Neko restart failed after LAN IP drift"
fi
sleep "$cooldown"
}
main() {
load_env
reload_watch_env
log "Watching Neko LAN IP changes"
while true; do
reload_watch_env
maybe_refresh_neko_for_ip_change
sleep "$(normalize_interval "${NEKO_IP_WATCH_INTERVAL:-15}")"
done
}
main "$@"

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# git pull 后若改了 systemd 模板(如 StartLimit执行本脚本重写 live-console.service 并 enable --now
# git pull 后若改了 systemd 模板(如 live-console 或 Neko watcher执行本脚本重写对应 unit 并 enable --now
set -euo pipefail
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -10,4 +10,6 @@ ensure_root "$@"
load_env
log "Rewriting /etc/systemd/system/live-console.service from repo"
install_live_console_service
log "Rewriting /etc/systemd/system/live-neko-ip-watch.service from repo"
install_neko_ip_watch_service
log "Done. Check: systemctl status live-console.service --no-pager"

View File

@@ -9,7 +9,7 @@ ensure_root "$@"
load_env
log "Stopping systemd services"
for svc in live-edge.service live-console.service live-webtty.service live-android-panel.service live-pulseaudio.service live-kmscube.service live-redroid-host.service netdata cockpit.socket shellcrash.service avahi-daemon; do
for svc in live-edge.service live-console.service live-neko-ip-watch.service live-webtty.service live-android-panel.service live-pulseaudio.service live-kmscube.service live-redroid-host.service netdata cockpit.socket shellcrash.service avahi-daemon; do
systemctl stop "$svc" >/dev/null 2>&1 || true
systemctl disable "$svc" >/dev/null 2>&1 || true
done
@@ -42,6 +42,7 @@ fi
log "Removing systemd unit files"
rm -f /etc/systemd/system/live-edge.service
rm -f /etc/systemd/system/live-console.service
rm -f /etc/systemd/system/live-neko-ip-watch.service
rm -f /etc/systemd/system/live-webtty.service
rm -f /etc/systemd/system/live-android-panel.service
rm -f /etc/systemd/system/live-pulseaudio.service

View File

@@ -20,6 +20,9 @@ prepare_python_runtime
log "Rebuilding web-console (npm)"
prepare_web_console
log "Refreshing Neko LAN IP watcher unit"
install_neko_ip_watch_service
log "Restarting live-console.service"
systemctl daemon-reload
systemctl restart live-console.service

View File

@@ -86,6 +86,23 @@ class NekoConfigTests(unittest.TestCase):
self.assertIn("NEKO_HTTP_PORT_2", common)
self.assertIn("NEKO_WEBRTC_UDP_RANGE_3", common)
def test_neko_ip_watcher_is_installed_and_restarts_on_drift(self) -> None:
common = (ROOT / "scripts" / "linux" / "lib" / "common.sh").read_text(encoding="utf-8")
watcher = (ROOT / "scripts" / "linux" / "neko_ip_watch.sh").read_text(encoding="utf-8")
upgrade = (ROOT / "scripts" / "linux" / "upgrade_live_console.sh").read_text(encoding="utf-8")
uninstall = (ROOT / "scripts" / "linux" / "uninstall_stack.sh").read_text(encoding="utf-8")
stack_env = (ROOT / "config" / "system-stack.env.example").read_text(encoding="utf-8")
self.assertIn("install_neko_ip_watch_service()", common)
self.assertIn("live-neko-ip-watch.service", common)
self.assertIn("ExecStart=/bin/bash", common)
self.assertIn("scripts/linux/neko_ip_watch.sh", common)
self.assertIn("detect_preferred_lan_ip", watcher)
self.assertIn('bash "$SERVICE_CTL" neko restart', watcher)
self.assertIn("NEKO_IP_WATCH_INTERVAL", watcher)
self.assertIn("install_neko_ip_watch_service", upgrade)
self.assertIn("live-neko-ip-watch.service", uninstall)
self.assertIn("NEKO_IP_WATCH_INTERVAL=15", stack_env)
def test_local_browser_prefers_chrome_on_x86_and_chromium_elsewhere(self) -> None:
script = (ROOT / "scripts" / "linux" / "launch_browser.sh").read_text(encoding="utf-8")
self.assertIn("x86_64|amd64", script)