619 lines
18 KiB
Bash
619 lines
18 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
SERVICE_ID="${1:-}"
|
||
ACTION="${2:-status}"
|
||
|
||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||
CONFIG_DIR="$ROOT_DIR/config"
|
||
STACK_ENV="$CONFIG_DIR/system-stack.env"
|
||
HW_ENV="$CONFIG_DIR/hardware.env"
|
||
SRS_COMPOSE="$ROOT_DIR/services/srs/docker-compose.yml"
|
||
HOMEPAGE_COMPOSE="$ROOT_DIR/services/homepage/docker-compose.yml"
|
||
FILEBROWSER_COMPOSE="$ROOT_DIR/services/filebrowser/docker-compose.yml"
|
||
NEKO_COMPOSE="$ROOT_DIR/services/neko/docker-compose.yml"
|
||
BROWSER_LAUNCHER="$ROOT_DIR/scripts/linux/launch_browser.sh"
|
||
|
||
[ -f "$STACK_ENV" ] && set -a && . "$STACK_ENV" && set +a
|
||
[ -f "$HW_ENV" ] && set -a && . "$HW_ENV" && set +a
|
||
|
||
HOSTNAME_ALIAS="${HOSTNAME_ALIAS:-live}"
|
||
PORT="${PORT:-8001}"
|
||
WEBTTY_PORT="${WEBTTY_PORT:-7681}"
|
||
HOMEPAGE_PORT="${HOMEPAGE_PORT:-80}"
|
||
FILEBROWSER_PORT="${FILEBROWSER_PORT:-8082}"
|
||
NETDATA_PORT="${NETDATA_PORT:-19999}"
|
||
SRS_HTTP_PORT="${SRS_HTTP_PORT:-8080}"
|
||
SRS_RTMP_PORT="${SRS_RTMP_PORT:-1935}"
|
||
SRS_API_PORT="${SRS_API_PORT:-1985}"
|
||
SRS_SRT_PORT="${SRS_SRT_PORT:-10080}"
|
||
SRS_WEBRTC_PORT="${SRS_WEBRTC_PORT:-8088}"
|
||
SHELLCRASH_DIR="${SHELLCRASH_DIR:-/etc/ShellCrash}"
|
||
WEBTTY_USER="${WEBTTY_USER:-live}"
|
||
WEBTTY_PASS="${WEBTTY_PASS:-12345678}"
|
||
FILEBROWSER_USER="${FILEBROWSER_USER:-live}"
|
||
FILEBROWSER_PASS="${FILEBROWSER_PASS:-12345678}"
|
||
WIFI_CONNECTION_NAME="${WIFI_CONNECTION_NAME:-live-wifi}"
|
||
WIFI_SSID="${WIFI_SSID:-live}"
|
||
ANDROID_GATEWAY_NAME="${ANDROID_GATEWAY_NAME:-live-android-gateway}"
|
||
ANDROID_UPLINK_IF="${ANDROID_UPLINK_IF:-eth0}"
|
||
ANDROID_DOWNLINK_IF="${ANDROID_DOWNLINK_IF:-}"
|
||
ANDROID_GATEWAY_CIDR="${ANDROID_GATEWAY_CIDR:-192.168.51.1/24}"
|
||
ANDROID_PANEL_PORT="${ANDROID_PANEL_PORT:-5000}"
|
||
NEKO_HTTP_PORT="${NEKO_HTTP_PORT:-9200}"
|
||
CONSOLE_BASE_URL="http://${HOSTNAME_ALIAS}.local:${PORT}"
|
||
|
||
kv() {
|
||
printf '%s=%s\n' "$1" "$2"
|
||
}
|
||
|
||
have() {
|
||
command -v "$1" >/dev/null 2>&1
|
||
}
|
||
|
||
filtered_pgrep() {
|
||
local pattern="$1"
|
||
pgrep -fa "$pattern" 2>/dev/null | grep -v -F "$0" | grep -v -F "pgrep -fa" || true
|
||
}
|
||
|
||
unit_exists() {
|
||
systemctl list-unit-files "$1" --no-legend 2>/dev/null | grep -q "^$1"
|
||
}
|
||
|
||
unit_active() {
|
||
systemctl is-active --quiet "$1"
|
||
}
|
||
|
||
compose_bin() {
|
||
if have docker && docker compose version >/dev/null 2>&1; then
|
||
echo "docker compose"
|
||
return 0
|
||
fi
|
||
if have docker-compose; then
|
||
echo "docker-compose"
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
compose_run() {
|
||
local compose_file="$1"
|
||
shift
|
||
local cmd
|
||
cmd="$(compose_bin)" || return 127
|
||
if [ "$cmd" = "docker compose" ]; then
|
||
docker compose -f "$compose_file" "$@"
|
||
else
|
||
docker-compose -f "$compose_file" "$@"
|
||
fi
|
||
}
|
||
|
||
docker_container_status() {
|
||
local name="$1"
|
||
if ! have docker; then
|
||
return 1
|
||
fi
|
||
docker ps -a --filter "name=^/${name}$" --format '{{.State}}|{{.Status}}' | head -n 1
|
||
}
|
||
|
||
service_mdns_status() {
|
||
local detail hostname ipv6
|
||
hostname="$(hostname)"
|
||
ipv6="unknown"
|
||
if grep -Eq '^[[:space:]]*use-ipv6=no' /etc/avahi/avahi-daemon.conf 2>/dev/null; then
|
||
ipv6="disabled"
|
||
fi
|
||
detail="hostname=${hostname}; avahi_ipv6=${ipv6}"
|
||
kv available 1
|
||
kv running "$(unit_active avahi-daemon && echo 1 || echo 0)"
|
||
kv status "$(unit_active avahi-daemon && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port 22
|
||
kv url "ssh ${WEBTTY_USER}@${HOSTNAME_ALIAS}.local"
|
||
kv detail "$detail"
|
||
}
|
||
|
||
service_mdns_start() {
|
||
hostnamectl set-hostname "$HOSTNAME_ALIAS"
|
||
systemctl restart avahi-daemon
|
||
kv message "mDNS hostname refreshed"
|
||
}
|
||
|
||
service_mdns_stop() {
|
||
systemctl stop avahi-daemon
|
||
kv message "avahi-daemon stopped"
|
||
}
|
||
|
||
service_wifi_status() {
|
||
if ! have nmcli || [ ! -d /sys/class/net/wlan0 ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "NetworkManager or wlan0 is not available"
|
||
return 0
|
||
fi
|
||
local active detail
|
||
active="$(nmcli -t -f NAME,DEVICE connection show --active 2>/dev/null | grep "^${WIFI_CONNECTION_NAME}:" || true)"
|
||
detail="$(nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT connection show "$WIFI_CONNECTION_NAME" 2>/dev/null | paste -sd ';' - || true)"
|
||
kv available 1
|
||
kv running "$([ -n "$active" ] && echo 1 || echo 0)"
|
||
kv status "$([ -n "$active" ] && echo online || echo configured)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port ""
|
||
kv url ""
|
||
kv detail "${detail:-ssid=${WIFI_SSID}}"
|
||
}
|
||
|
||
service_wifi_start() {
|
||
if ! have nmcli || [ ! -d /sys/class/net/wlan0 ]; then
|
||
kv message "Wi-Fi interface is not available"
|
||
exit 1
|
||
fi
|
||
if ! nmcli connection show "$WIFI_CONNECTION_NAME" >/dev/null 2>&1; then
|
||
nmcli connection add type wifi ifname wlan0 con-name "$WIFI_CONNECTION_NAME" ssid "$WIFI_SSID" \
|
||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "${WIFI_PSK:-12345678}" ipv4.method auto connection.autoconnect yes
|
||
fi
|
||
nmcli connection up "$WIFI_CONNECTION_NAME" >/dev/null 2>&1 || true
|
||
kv message "Wi-Fi profile ensured"
|
||
}
|
||
|
||
service_wifi_stop() {
|
||
nmcli connection down "$WIFI_CONNECTION_NAME" >/dev/null 2>&1 || true
|
||
kv message "Wi-Fi profile disconnected"
|
||
}
|
||
|
||
service_shellcrash_status() {
|
||
local running detail
|
||
if [ ! -x "$SHELLCRASH_DIR/start.sh" ] && ! unit_exists shellcrash.service; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "ShellCrash is not installed"
|
||
return 0
|
||
fi
|
||
running=0
|
||
detail="$(filtered_pgrep 'CrashCore|shellcrash' | tr '\n' ';' || true)"
|
||
if unit_active shellcrash.service || [ -n "$detail" ]; then
|
||
running=1
|
||
fi
|
||
[ -z "$detail" ] && detail="dir=$SHELLCRASH_DIR"
|
||
kv available 1
|
||
kv running "$running"
|
||
kv status "$([ "$running" = 1 ] && echo online || echo stopped)"
|
||
kv url "${CONSOLE_BASE_URL}/shellcrash"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port ""
|
||
kv detail "$detail"
|
||
}
|
||
|
||
service_shellcrash_start() {
|
||
if unit_exists shellcrash.service; then
|
||
systemctl enable --now shellcrash.service
|
||
elif [ -x "$SHELLCRASH_DIR/start.sh" ]; then
|
||
"$SHELLCRASH_DIR/start.sh" start
|
||
else
|
||
kv message "ShellCrash is not installed"
|
||
exit 1
|
||
fi
|
||
kv message "ShellCrash started"
|
||
}
|
||
|
||
service_shellcrash_stop() {
|
||
if unit_exists shellcrash.service; then
|
||
systemctl stop shellcrash.service
|
||
elif [ -x "$SHELLCRASH_DIR/start.sh" ]; then
|
||
"$SHELLCRASH_DIR/start.sh" stop
|
||
fi
|
||
kv message "ShellCrash stopped"
|
||
}
|
||
|
||
service_srs_status() {
|
||
if ! have docker || [ ! -f "$SRS_COMPOSE" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Docker or SRS compose file is missing"
|
||
return 0
|
||
fi
|
||
local state
|
||
state="$(docker_container_status live-srs || true)"
|
||
kv available 1
|
||
kv running "$([[ "$state" == running* ]] && echo 1 || echo 0)"
|
||
kv status "$([[ "$state" == running* ]] && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$SRS_HTTP_PORT"
|
||
kv url "http://${HOSTNAME_ALIAS}.local:${SRS_HTTP_PORT}"
|
||
kv detail "${state:-container=missing};rtmp=${SRS_RTMP_PORT};api=${SRS_API_PORT};srt=${SRS_SRT_PORT};rtc=${SRS_WEBRTC_PORT}"
|
||
}
|
||
|
||
service_srs_start() {
|
||
compose_run "$SRS_COMPOSE" up -d
|
||
kv message "SRS started"
|
||
}
|
||
|
||
service_srs_stop() {
|
||
compose_run "$SRS_COMPOSE" down
|
||
kv message "SRS stopped"
|
||
}
|
||
|
||
service_webtty_status() {
|
||
if ! unit_exists live-webtty.service && ! have ttyd; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "ttyd is not installed"
|
||
return 0
|
||
fi
|
||
kv available 1
|
||
kv running "$(unit_active live-webtty.service && echo 1 || echo 0)"
|
||
kv status "$(unit_active live-webtty.service && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$WEBTTY_PORT"
|
||
kv url "http://${HOSTNAME_ALIAS}.local:${WEBTTY_PORT}"
|
||
kv detail "web user=${WEBTTY_USER}"
|
||
}
|
||
|
||
service_webtty_start() {
|
||
systemctl enable --now live-webtty.service
|
||
kv message "WebTTY started"
|
||
}
|
||
|
||
service_webtty_stop() {
|
||
systemctl stop live-webtty.service
|
||
kv message "WebTTY stopped"
|
||
}
|
||
|
||
service_cockpit_status() {
|
||
if ! unit_exists cockpit.socket; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Cockpit is not installed"
|
||
return 0
|
||
fi
|
||
kv available 1
|
||
kv running "$(unit_active cockpit.socket && echo 1 || echo 0)"
|
||
kv status "$(unit_active cockpit.socket && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port 9090
|
||
kv url "https://${HOSTNAME_ALIAS}.local:9090"
|
||
kv detail "login with live or any enabled system account"
|
||
}
|
||
|
||
service_cockpit_start() {
|
||
systemctl enable --now cockpit.socket
|
||
kv message "Cockpit started"
|
||
}
|
||
|
||
service_cockpit_stop() {
|
||
systemctl stop cockpit.socket
|
||
kv message "Cockpit stopped"
|
||
}
|
||
|
||
service_filebrowser_status() {
|
||
if ! have docker || [ ! -f "$FILEBROWSER_COMPOSE" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Docker or File Browser compose file is missing"
|
||
return 0
|
||
fi
|
||
local state
|
||
state="$(docker_container_status live-filebrowser || true)"
|
||
kv available 1
|
||
kv running "$([[ "$state" == running* ]] && echo 1 || echo 0)"
|
||
kv status "$([[ "$state" == running* ]] && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$FILEBROWSER_PORT"
|
||
kv url "http://${HOSTNAME_ALIAS}.local:${FILEBROWSER_PORT}"
|
||
kv detail "${state:-container=missing};user=${FILEBROWSER_USER:-live}"
|
||
}
|
||
|
||
service_filebrowser_start() {
|
||
compose_run "$FILEBROWSER_COMPOSE" up -d
|
||
kv message "File Browser started"
|
||
}
|
||
|
||
service_filebrowser_stop() {
|
||
compose_run "$FILEBROWSER_COMPOSE" down
|
||
kv message "File Browser stopped"
|
||
}
|
||
|
||
service_homepage_status() {
|
||
if ! have docker || [ ! -f "$HOMEPAGE_COMPOSE" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Docker or Homepage compose file is missing"
|
||
return 0
|
||
fi
|
||
local state
|
||
state="$(docker_container_status live-homepage || true)"
|
||
kv available 1
|
||
kv running "$([[ "$state" == running* ]] && echo 1 || echo 0)"
|
||
kv status "$([[ "$state" == running* ]] && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$HOMEPAGE_PORT"
|
||
kv url "$([ "$HOMEPAGE_PORT" = 80 ] && echo "http://${HOSTNAME_ALIAS}.local" || echo "http://${HOSTNAME_ALIAS}.local:${HOMEPAGE_PORT}")"
|
||
kv detail "${state:-container=missing}"
|
||
}
|
||
|
||
service_homepage_start() {
|
||
compose_run "$HOMEPAGE_COMPOSE" up -d
|
||
kv message "Homepage started"
|
||
}
|
||
|
||
service_homepage_stop() {
|
||
compose_run "$HOMEPAGE_COMPOSE" down
|
||
kv message "Homepage stopped"
|
||
}
|
||
|
||
service_netdata_status() {
|
||
if ! unit_exists netdata.service; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Netdata is not installed"
|
||
return 0
|
||
fi
|
||
kv available 1
|
||
kv running "$(unit_active netdata.service && echo 1 || echo 0)"
|
||
kv status "$(unit_active netdata.service && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$NETDATA_PORT"
|
||
kv url "http://${HOSTNAME_ALIAS}.local:${NETDATA_PORT}"
|
||
kv detail "metrics and alarms API available at /api/v1"
|
||
}
|
||
|
||
service_netdata_start() {
|
||
systemctl enable --now netdata
|
||
kv message "Netdata started"
|
||
}
|
||
|
||
service_netdata_stop() {
|
||
systemctl stop netdata
|
||
kv message "Netdata stopped"
|
||
}
|
||
|
||
service_adb_status() {
|
||
if ! have adb; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "adb is not installed"
|
||
return 0
|
||
fi
|
||
local details
|
||
details="$(adb devices -l 2>/dev/null | tr '\n' ';' || true)"
|
||
kv available 1
|
||
kv running "$(filtered_pgrep 'adb -L tcp:5037|[ /]adb([ ]|$)' >/dev/null 2>&1 && echo 1 || echo 0)"
|
||
kv status "$(filtered_pgrep 'adb -L tcp:5037|[ /]adb([ ]|$)' >/dev/null 2>&1 && echo online || echo idle)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port ""
|
||
kv url ""
|
||
kv detail "${details:-adb server ready}"
|
||
}
|
||
|
||
service_adb_start() {
|
||
adb start-server >/dev/null
|
||
kv message "ADB server started"
|
||
}
|
||
|
||
service_adb_stop() {
|
||
adb kill-server >/dev/null 2>&1 || true
|
||
kv message "ADB server stopped"
|
||
}
|
||
|
||
service_android_panel_status() {
|
||
if ! unit_exists live-android-panel.service; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Android web panel is not installed"
|
||
return 0
|
||
fi
|
||
kv available 1
|
||
kv running "$(unit_active live-android-panel.service && echo 1 || echo 0)"
|
||
kv status "$(unit_active live-android-panel.service && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$ANDROID_PANEL_PORT"
|
||
kv url "${CONSOLE_BASE_URL}/android"
|
||
kv detail "android-center=${CONSOLE_BASE_URL}/android;ws-scrcpy=http://${HOSTNAME_ALIAS}.local:${ANDROID_PANEL_PORT};git=ANDROID_PANEL_GIT_URL"
|
||
}
|
||
|
||
service_android_panel_start() {
|
||
systemctl enable --now live-android-panel.service
|
||
kv message "Android web panel started"
|
||
}
|
||
|
||
service_android_panel_stop() {
|
||
systemctl stop live-android-panel.service
|
||
kv message "Android web panel stopped"
|
||
}
|
||
|
||
service_chromium_status() {
|
||
if ! have chromium && ! have chromium-browser && ! have google-chrome; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Chromium or Google Chrome is not installed"
|
||
return 0
|
||
fi
|
||
local details
|
||
details="$(filtered_pgrep 'chromium|chrome' | tr '\n' ';' || true)"
|
||
kv available 1
|
||
kv running "$([ -n "$details" ] && echo 1 || echo 0)"
|
||
kv status "$([ -n "$details" ] && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "${BROWSER_REMOTE_DEBUG_PORT:-9222}"
|
||
kv url ""
|
||
kv detail "${details:-profile=${BROWSER_PROFILE_DIR:-$HOME/.local/share/live-browser/profile}}"
|
||
}
|
||
|
||
service_chromium_start() {
|
||
nohup "$BROWSER_LAUNCHER" >/tmp/live-browser.log 2>&1 &
|
||
disown || true
|
||
kv message "Chromium launched"
|
||
}
|
||
|
||
service_chromium_stop() {
|
||
pkill -f 'chromium|chrome' || true
|
||
kv message "Chromium stopped"
|
||
}
|
||
|
||
service_neko_status() {
|
||
if [ "${ENABLE_NEKO:-0}" != "1" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "在 config/system-stack.env 设置 ENABLE_NEKO=1 后执行安装脚本或在此 Start 拉取容器"
|
||
return 0
|
||
fi
|
||
if [ ! -f "$NEKO_COMPOSE" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "services/neko/docker-compose.yml missing"
|
||
return 0
|
||
fi
|
||
if ! have docker; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "docker not installed"
|
||
return 0
|
||
fi
|
||
kv available 1
|
||
local state
|
||
state="$(docker_container_status live-neko-chromium || true)"
|
||
kv running "$(echo "$state" | grep -qi running && echo 1 || echo 0)"
|
||
kv status "$(echo "$state" | grep -qi running && echo online || echo stopped)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port "$NEKO_HTTP_PORT"
|
||
kv url "http://${HOSTNAME_ALIAS}.local:${NEKO_HTTP_PORT}"
|
||
kv detail "m1k1o/neko Chromium in Docker; ENABLE_NEKO=1 + install_neko_browser or Modules start"
|
||
}
|
||
|
||
service_neko_start() {
|
||
if [ "${ENABLE_NEKO:-0}" != "1" ]; then
|
||
kv message "请先在 config/system-stack.env 设置 ENABLE_NEKO=1"
|
||
exit 1
|
||
fi
|
||
if [ ! -f "$NEKO_COMPOSE" ]; then
|
||
kv message "Neko compose missing"
|
||
return 0
|
||
fi
|
||
local neko_dir
|
||
neko_dir="$(cd "$(dirname "$NEKO_COMPOSE")" && pwd)"
|
||
local prof="${NEKO_PROFILE_HOST_DIR:-$ROOT_DIR/services/neko/data/chromium-profile}"
|
||
mkdir -p "$prof"
|
||
chmod 777 "$prof" 2>/dev/null || true
|
||
export NEKO_HTTP_PORT="${NEKO_HTTP_PORT:-9200}"
|
||
export NEKO_USER_PASS="${NEKO_USER_PASS:-live}"
|
||
export NEKO_ADMIN_PASS="${NEKO_ADMIN_PASS:-admin}"
|
||
export NEKO_PROFILE_HOST_DIR="$prof"
|
||
export NEKO_DESKTOP_SCREEN="${NEKO_DESKTOP_SCREEN:-1920x1080@30}"
|
||
export NEKO_WEBRTC_EPR="${NEKO_WEBRTC_EPR:-52000-52100}"
|
||
export NEKO_WEBRTC_UDP_RANGE="${NEKO_WEBRTC_UDP_RANGE:-52000-52100}"
|
||
export NEKO_WEBRTC_ICELITE="${NEKO_WEBRTC_ICELITE:-1}"
|
||
if [ -z "${NEKO_WEBRTC_NAT1TO1:-}" ]; then
|
||
export NEKO_WEBRTC_NAT1TO1="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||
fi
|
||
export NEKO_PLUGINS_ENABLED="${NEKO_PLUGINS_ENABLED:-true}"
|
||
cmd="$(compose_bin)" || {
|
||
kv message "docker compose unavailable"
|
||
return 0
|
||
}
|
||
(
|
||
cd "$neko_dir" || exit 1
|
||
if [ "$cmd" = "docker compose" ]; then
|
||
docker compose -f docker-compose.yml up -d
|
||
else
|
||
docker-compose -f docker-compose.yml up -d
|
||
fi
|
||
)
|
||
kv message "Neko stack started"
|
||
}
|
||
|
||
service_neko_stop() {
|
||
if [ ! -f "$NEKO_COMPOSE" ]; then
|
||
kv message "Neko compose missing"
|
||
return 0
|
||
fi
|
||
local neko_dir
|
||
neko_dir="$(cd "$(dirname "$NEKO_COMPOSE")" && pwd)"
|
||
cmd="$(compose_bin)" || {
|
||
kv message "docker compose unavailable"
|
||
return 0
|
||
}
|
||
(
|
||
cd "$neko_dir" || exit 1
|
||
if [ "$cmd" = "docker compose" ]; then
|
||
docker compose -f docker-compose.yml down
|
||
else
|
||
docker-compose -f docker-compose.yml down
|
||
fi
|
||
)
|
||
kv message "Neko stack stopped"
|
||
}
|
||
|
||
service_android_gateway_status() {
|
||
if ! have nmcli || [ -z "$ANDROID_DOWNLINK_IF" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "透明网关需第二块网卡:在 system-stack.env 设置 ANDROID_DOWNLINK_IF(如 eth1),并安装 NetworkManager(nmcli)"
|
||
return 0
|
||
fi
|
||
if [ ! -d "/sys/class/net/$ANDROID_DOWNLINK_IF" ]; then
|
||
kv available 0
|
||
kv running 0
|
||
kv status unavailable
|
||
kv detail "Downlink interface $ANDROID_DOWNLINK_IF is missing"
|
||
return 0
|
||
fi
|
||
local active
|
||
active="$(nmcli -t -f NAME connection show --active 2>/dev/null | grep "^${ANDROID_GATEWAY_NAME}$" || true)"
|
||
kv available 1
|
||
kv running "$([ -n "$active" ] && echo 1 || echo 0)"
|
||
kv status "$([ -n "$active" ] && echo online || echo configured)"
|
||
kv host "${HOSTNAME_ALIAS}.local"
|
||
kv port ""
|
||
kv url ""
|
||
kv detail "uplink=${ANDROID_UPLINK_IF};downlink=${ANDROID_DOWNLINK_IF};gateway=${ANDROID_GATEWAY_CIDR}"
|
||
}
|
||
|
||
service_android_gateway_start() {
|
||
if [ -z "$ANDROID_DOWNLINK_IF" ]; then
|
||
kv message "ANDROID_DOWNLINK_IF is not configured"
|
||
exit 1
|
||
fi
|
||
if ! nmcli connection show "$ANDROID_GATEWAY_NAME" >/dev/null 2>&1; then
|
||
nmcli connection add type ethernet ifname "$ANDROID_DOWNLINK_IF" con-name "$ANDROID_GATEWAY_NAME" \
|
||
ipv4.method shared ipv4.addresses "$ANDROID_GATEWAY_CIDR" connection.autoconnect yes
|
||
fi
|
||
nmcli connection up "$ANDROID_GATEWAY_NAME"
|
||
kv message "Android gateway started"
|
||
}
|
||
|
||
service_android_gateway_stop() {
|
||
nmcli connection down "$ANDROID_GATEWAY_NAME" >/dev/null 2>&1 || true
|
||
kv message "Android gateway stopped"
|
||
}
|
||
|
||
dispatch() {
|
||
local fn="service_${SERVICE_ID}_${ACTION}"
|
||
if ! declare -F "$fn" >/dev/null 2>&1; then
|
||
echo "MESSAGE=Unsupported service/action: ${SERVICE_ID}/${ACTION}"
|
||
exit 1
|
||
fi
|
||
"$fn"
|
||
}
|
||
|
||
if [ -z "$SERVICE_ID" ]; then
|
||
echo "MESSAGE=Missing service id"
|
||
exit 1
|
||
fi
|
||
|
||
dispatch
|