This commit is contained in:
eric
2026-05-19 06:47:07 +00:00
parent e1d7251545
commit 78e1308063
63 changed files with 2527 additions and 186 deletions

207
d2ypp2/scripts/linux/service_ctl.sh Normal file → Executable file
View File

@@ -290,20 +290,50 @@ resolve_neko_profile_dir() {
resolve_neko_profile_dir_for_instance() {
local idx="${1:-1}"
local base
local base override
base="${NEKO_PROFILE_HOST_DIR:-$(resolve_neko_profile_dir)}"
eval "override=\"\${NEKO_PROFILE_HOST_DIR_${idx}:-}\""
if [ -n "$override" ]; then
printf '%s' "$override"
return 0
fi
case "$idx" in
1) printf '%s' "$base" ;;
*) printf '%s-%s' "$base" "$idx" ;;
esac
}
seed_neko_tampermonkey_profile() {
local target="${1:-}" donor="${2:-}" rel src dst
[ -n "$target" ] || return 0
[ -n "$donor" ] || donor="$(resolve_neko_profile_dir_for_instance 1)"
[ "$target" = "$donor" ] && return 0
[ -d "$donor/Default" ] || return 0
mkdir -p "$target/Default"
for rel in \
"Extensions/dhdgffkkebhmkfjojejmpbldmpobfkfo" \
"Local Extension Settings/dhdgffkkebhmkfjojejmpbldmpobfkfo" \
"Managed Extension Settings/dhdgffkkebhmkfjojejmpbldmpobfkfo"; do
src="$donor/Default/$rel"
dst="$target/Default/$rel"
[ -e "$src" ] || continue
[ ! -e "$dst" ] || continue
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst" 2>/dev/null || true
done
}
resolve_neko_instance_count() {
local requested="${NEKO_INSTANCE_COUNT:-2}"
local cpu count
local cpu count max_count
max_count="${NEKO_MAX_INSTANCE_COUNT:-64}"
case "$max_count" in
''|*[!0-9]*) max_count=64 ;;
esac
[ "$max_count" -lt 2 ] && max_count=2
[ "$max_count" -gt 64 ] && max_count=64
cpu="$(nproc 2>/dev/null || echo 4)"
case "$requested" in
1|2|3) count="$requested" ;;
auto|"")
if [ "$cpu" -le 4 ]; then
count=2
@@ -311,19 +341,81 @@ resolve_neko_instance_count() {
count=3
fi
;;
*[!0-9]*) count=2 ;;
*)
count=2
count="$requested"
;;
esac
if [ "$count" -lt 1 ]; then
count=1
if [ "$count" -lt 2 ]; then
count=2
fi
if [ "$count" -gt 3 ]; then
count=3
if [ "$count" -gt "$max_count" ]; then
count="$max_count"
fi
printf '%s' "$count"
}
resolve_neko_http_port_for_instance() {
local idx="${1:-1}" value base
eval "value=\"\${NEKO_HTTP_PORT_${idx}:-}\""
if [ -n "$value" ]; then
printf '%s' "$value"
return 0
fi
base="${NEKO_HTTP_PORT:-9200}"
printf '%s' $((base + idx - 1))
}
resolve_neko_tcpmux_for_instance() {
local idx="${1:-1}" value base
eval "value=\"\${NEKO_WEBRTC_TCPMUX_${idx}:-}\""
if [ -n "$value" ]; then
printf '%s' "$value"
return 0
fi
base="${NEKO_WEBRTC_TCPMUX:-52300}"
printf '%s' $((base + idx - 1))
}
resolve_neko_udpmux_for_instance() {
local idx="${1:-1}" value base
eval "value=\"\${NEKO_WEBRTC_UDPMUX_${idx}:-}\""
if [ -n "$value" ]; then
printf '%s' "$value"
return 0
fi
base="${NEKO_WEBRTC_UDPMUX:-52300}"
printf '%s' $((base + idx - 1))
}
resolve_neko_udp_range_for_instance() {
local idx="${1:-1}" value start
eval "value=\"\${NEKO_WEBRTC_UDP_RANGE_${idx}:-}\""
if [ -n "$value" ]; then
printf '%s' "$value"
return 0
fi
if [ "$idx" -eq 1 ] && [ -n "${NEKO_WEBRTC_UDP_RANGE:-}" ]; then
printf '%s' "$NEKO_WEBRTC_UDP_RANGE"
return 0
fi
case "$idx" in
2) start=52101 ;;
3) start=52201 ;;
*) start=$((52400 + ((idx - 4) * 100))) ;;
esac
[ "$idx" -eq 1 ] && start=52000
printf '%s-%s' "$start" "$((start + 99))"
}
render_neko_compose_file() {
local count out
count="$(resolve_neko_instance_count)"
out="$ROOT_DIR/services/neko/docker-compose.generated.yml"
python3 "$ROOT_DIR/tools/render_neko_compose.py" --count "$count" --output "$out"
printf '%s' "$out"
}
remove_legacy_neko_container() {
if have docker && docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 'live-neko-chromium'; then
docker stop live-neko-chromium >/dev/null 2>&1 || true
@@ -884,20 +976,18 @@ service_neko_status() {
fi
kv available 1
local state
local running_count=0 total_count=0 states="" name instance_count
local running_count=0 total_count=0 states="" name instance_count idx ports="" profiles=""
instance_count="$(resolve_neko_instance_count)"
total_count="$instance_count"
for name in live-neko-1 live-neko-2 live-neko-3; do
case "$name" in
live-neko-1) [ "$instance_count" -ge 1 ] || continue ;;
live-neko-2) [ "$instance_count" -ge 2 ] || continue ;;
live-neko-3) [ "$instance_count" -ge 3 ] || continue ;;
esac
for idx in $(seq 1 "$instance_count"); do
name="live-neko-$idx"
state="$(docker_container_status "$name" || true)"
if echo "$state" | grep -qi running; then
running_count=$((running_count + 1))
fi
states="${states}${name}:${state:-missing};"
ports="${ports}$(resolve_neko_http_port_for_instance "$idx"),"
profiles="${profiles}$(resolve_neko_profile_dir_for_instance "$idx"),"
done
if [ "$running_count" -eq 0 ]; then
state="$(docker_container_status live-neko-chromium || true)"
@@ -915,7 +1005,7 @@ service_neko_status() {
if ! neko_nat1to1_is_usable "${NEKO_WEBRTC_NAT1TO1:-}"; then
NEKO_WEBRTC_NAT1TO1="$(detect_preferred_lan_ip || true)"
fi
kv detail "image=$(resolve_neko_image); supervisor_conf=$(resolve_neko_supervisor_conf); start_url=${NEKO_START_URL}; nat1to1=${NEKO_WEBRTC_NAT1TO1:-unset}; instances=${running_count}/${total_count}; ports=${NEKO_HTTP_PORT_1},${NEKO_HTTP_PORT_2},${NEKO_HTTP_PORT_3}; profiles=$(resolve_neko_profile_dir_for_instance 1),$(resolve_neko_profile_dir_for_instance 2),$(resolve_neko_profile_dir_for_instance 3); screen=${NEKO_DESKTOP_SCREEN}; shm=${NEKO_SHM_SIZE}; fps=${NEKO_MAX_FPS}; bitrate=${NEKO_VIDEO_BITRATE}; render=${NEKO_BROWSER_RENDER_MODE}; udp_ranges=${NEKO_WEBRTC_UDP_RANGE_1},${NEKO_WEBRTC_UDP_RANGE_2},${NEKO_WEBRTC_UDP_RANGE_3}; tcpmux=${NEKO_WEBRTC_TCPMUX_1},${NEKO_WEBRTC_TCPMUX_2},${NEKO_WEBRTC_TCPMUX_3}; udpmux=${NEKO_WEBRTC_UDPMUX_1},${NEKO_WEBRTC_UDPMUX_2},${NEKO_WEBRTC_UDPMUX_3}; containers=${states}"
kv detail "image=$(resolve_neko_image); supervisor_conf=$(resolve_neko_supervisor_conf); start_url=${NEKO_START_URL}; nat1to1=${NEKO_WEBRTC_NAT1TO1:-unset}; instances=${running_count}/${total_count}; ports=${ports%,}; profiles=${profiles%,}; screen=${NEKO_DESKTOP_SCREEN}; shm=${NEKO_SHM_SIZE}; fps=${NEKO_MAX_FPS}; bitrate=${NEKO_VIDEO_BITRATE}; render=${NEKO_BROWSER_RENDER_MODE}; containers=${states}"
}
service_neko_start() {
@@ -923,77 +1013,32 @@ service_neko_start() {
kv message "请先在 config/system-stack.env 设置 ENABLE_NEKO=1"
exit 1
fi
if [ ! -f "$NEKO_COMPOSE" ]; then
kv message "Neko compose missing"
if [ ! -f "$ROOT_DIR/tools/render_neko_compose.py" ]; then
kv message "Neko compose renderer missing"
return 0
fi
local prof
local prof idx port mux udp range compose_file service_names=""
local instance_count
local service_names="" extra_service_names="" idx_name
instance_count="$(resolve_neko_instance_count)"
for idx_name in 1 2 3; do
case "$idx_name" in
1)
if [ "$instance_count" -ge 1 ]; then
service_names="${service_names} neko1"
else
extra_service_names="${extra_service_names} neko1"
fi
;;
2)
if [ "$instance_count" -ge 2 ]; then
service_names="${service_names} neko2"
else
extra_service_names="${extra_service_names} neko2"
fi
;;
3)
if [ "$instance_count" -ge 3 ]; then
service_names="${service_names} neko3"
else
extra_service_names="${extra_service_names} neko3"
fi
;;
esac
done
for idx in 1 2 3; do
[ "$idx" -le "$instance_count" ] || break
prof="$(resolve_neko_profile_dir_for_instance "$idx")"
prune_neko_profile "$prof"
done
export_neko_env
for port in "$NEKO_HTTP_PORT_1" "$NEKO_HTTP_PORT_2" "$NEKO_HTTP_PORT_3" "$NEKO_WEBRTC_TCPMUX_1" "$NEKO_WEBRTC_TCPMUX_2" "$NEKO_WEBRTC_TCPMUX_3"; do
case "$port" in
"$NEKO_HTTP_PORT_1"|"$NEKO_WEBRTC_TCPMUX_1") [ "$instance_count" -ge 1 ] || continue ;;
"$NEKO_HTTP_PORT_2"|"$NEKO_WEBRTC_TCPMUX_2") [ "$instance_count" -ge 2 ] || continue ;;
"$NEKO_HTTP_PORT_3"|"$NEKO_WEBRTC_TCPMUX_3") [ "$instance_count" -ge 3 ] || continue ;;
esac
for idx in $(seq 1 "$instance_count"); do
prof="$(resolve_neko_profile_dir_for_instance "$idx")"
seed_neko_tampermonkey_profile "$prof" "$(resolve_neko_profile_dir_for_instance 1)"
prune_neko_profile "$prof"
port="$(resolve_neko_http_port_for_instance "$idx")"
mux="$(resolve_neko_tcpmux_for_instance "$idx")"
udp="$(resolve_neko_udpmux_for_instance "$idx")"
range="$(resolve_neko_udp_range_for_instance "$idx")"
allow_tcp_port_if_firewall_active "$port"
done
for port in "$NEKO_WEBRTC_UDPMUX_1" "$NEKO_WEBRTC_UDPMUX_2" "$NEKO_WEBRTC_UDPMUX_3"; do
case "$port" in
"$NEKO_WEBRTC_UDPMUX_1") [ "$instance_count" -ge 1 ] || continue ;;
"$NEKO_WEBRTC_UDPMUX_2") [ "$instance_count" -ge 2 ] || continue ;;
"$NEKO_WEBRTC_UDPMUX_3") [ "$instance_count" -ge 3 ] || continue ;;
esac
allow_udp_port_if_firewall_active "$port"
done
for port_range in "$NEKO_WEBRTC_UDP_RANGE_1" "$NEKO_WEBRTC_UDP_RANGE_2" "$NEKO_WEBRTC_UDP_RANGE_3"; do
case "$port_range" in
"$NEKO_WEBRTC_UDP_RANGE_1") [ "$instance_count" -ge 1 ] || continue ;;
"$NEKO_WEBRTC_UDP_RANGE_2") [ "$instance_count" -ge 2 ] || continue ;;
"$NEKO_WEBRTC_UDP_RANGE_3") [ "$instance_count" -ge 3 ] || continue ;;
esac
allow_udp_range_if_firewall_active "$port_range"
allow_tcp_port_if_firewall_active "$mux"
allow_udp_port_if_firewall_active "$udp"
allow_udp_range_if_firewall_active "$range"
service_names="${service_names} neko${idx}"
done
remove_legacy_neko_container
# 只启动实际需要的实例,避免多余的 Chrome/Xorg 进程继续吃 CPU
compose_file="$(render_neko_compose_file)"
# shellcheck disable=SC2086
compose_in_service_dir "$NEKO_COMPOSE" up -d $service_names
if [ -n "${extra_service_names# }" ]; then
# shellcheck disable=SC2086
compose_in_service_dir "$NEKO_COMPOSE" stop $extra_service_names >/dev/null 2>&1 || true
fi
compose_in_service_dir "$compose_file" up -d $service_names
kv message "Neko stack started (${instance_count} browser instances)"
}
@@ -1049,12 +1094,14 @@ export_neko_env() {
}
service_neko_stop() {
if [ ! -f "$NEKO_COMPOSE" ]; then
if [ ! -f "$ROOT_DIR/tools/render_neko_compose.py" ]; then
kv message "Neko compose missing"
return 0
fi
local compose_file
export_neko_env
compose_in_service_dir "$NEKO_COMPOSE" down
compose_file="$(render_neko_compose_file)"
compose_in_service_dir "$compose_file" down --remove-orphans
kv message "Neko stack stopped"
}