This commit is contained in:
eric
2026-05-28 15:59:03 -05:00
parent 1d89aa29e0
commit 08155500fc
47 changed files with 1126 additions and 122 deletions

View File

@@ -57,14 +57,18 @@ apt_update_once() {
apt_install() {
apt_update_once
if apt-get install -y --no-install-recommends "$@"; then
export DEBIAN_FRONTEND=noninteractive
local -a dpkg_opts=(
-o Dpkg::Options::=--force-confdef
-o Dpkg::Options::=--force-confold
)
if apt-get install -y --no-install-recommends "${dpkg_opts[@]}" "$@"; then
return 0
fi
log "apt install failed; trying dpkg --configure -a and apt -f install"
export DEBIAN_FRONTEND=noninteractive
dpkg --configure -a 2>/dev/null || true
apt-get -f install -y || true
apt-get install -y --no-install-recommends "$@"
dpkg --force-confdef --force-confold --configure -a 2>/dev/null || true
apt-get -f install -y "${dpkg_opts[@]}" || true
apt-get install -y --no-install-recommends "${dpkg_opts[@]}" "$@"
}
allow_tcp_port_if_firewall_active() {
@@ -159,6 +163,37 @@ compose_in_service_dir() {
)
}
run_optional_with_timeout() {
local seconds="$1"
shift
if [ -z "$seconds" ] || [ "$seconds" = "0" ] || ! have timeout; then
"$@"
else
timeout "$seconds" "$@"
fi
}
compose_in_service_dir_timed() {
local seconds="$1"
local compose_file="$2"
shift 2
if [ -z "$seconds" ] || [ "$seconds" = "0" ] || ! have timeout; then
compose_in_service_dir "$compose_file" "$@"
return
fi
local d base
d="$(cd "$(dirname "$compose_file")" && pwd)"
base="$(basename "$compose_file")"
(
cd "$d"
if docker compose version >/dev/null 2>&1; then
timeout "$seconds" docker compose -f "$base" "$@"
else
timeout "$seconds" docker-compose -f "$base" "$@"
fi
)
}
as_live() {
runuser -u live -- bash -lc "cd '$ROOT_DIR' && $*"
}
@@ -326,6 +361,10 @@ load_env() {
}
resolve_neko_image() {
if [ -n "${NEKO_IMAGE:-}" ]; then
printf '%s' "$NEKO_IMAGE"
return 0
fi
case "$(uname -m)" in
x86_64|amd64)
printf '%s' "$NEKO_IMAGE_X86"
@@ -797,9 +836,14 @@ install_base_packages() {
git curl wget jq unzip xz-utils ca-certificates sudo \
python3 python3-venv python3-pip build-essential \
avahi-daemon avahi-utils libnss-mdns \
network-manager dnsmasq net-tools \
network-manager dnsmasq-base net-tools \
bluez usbutils v4l-utils
systemctl enable --now NetworkManager 2>/dev/null || true
if systemctl is-active --quiet systemd-resolved 2>/dev/null && systemctl is-enabled dnsmasq >/dev/null 2>&1; then
log "Disabling dnsmasq.service because systemd-resolved already owns port 53"
systemctl disable --now dnsmasq >/dev/null 2>&1 || true
systemctl reset-failed dnsmasq >/dev/null 2>&1 || true
fi
}
install_media_packages() {
@@ -862,6 +906,8 @@ install_docker_stack() {
return 0
fi
if have docker; then
systemctl enable --now docker >/dev/null 2>&1 || true
usermod -aG docker live 2>/dev/null || true
if docker compose version >/dev/null 2>&1; then
return 0
fi
@@ -1256,12 +1302,67 @@ configure_wifi_profile() {
fi
}
shellcrash_cpu_core() {
case "$(uname -m)" in
aarch64|arm64) printf '%s\n' arm64 ;;
armv7l|armv7*) printf '%s\n' armv7 ;;
x86_64|amd64) printf '%s\n' amd64 ;;
*) printf '%s\n' "$(uname -m)" ;;
esac
}
shellcrash_set_cfg_default() {
local cfg="$1" key="$2" value="$3"
if ! grep -q "^${key}=" "$cfg" 2>/dev/null; then
printf '%s=%s\n' "$key" "$value" >>"$cfg"
fi
}
harden_shellcrash_runtime() {
if [ ! -d "$SHELLCRASH_DIR" ]; then
return 0
fi
log "Hardening ShellCrash runtime defaults"
mkdir -p "$SHELLCRASH_DIR/yamls" "$SHELLCRASH_DIR/configs" /tmp/ShellCrash
local cfg="$SHELLCRASH_DIR/configs/ShellCrash.cfg"
[ -f "$cfg" ] || printf '%s\n' "#ShellCrash配置文件不明勿动" >"$cfg"
shellcrash_set_cfg_default "$cfg" crashcore meta
shellcrash_set_cfg_default "$cfg" cpucore "$(shellcrash_cpu_core)"
shellcrash_set_cfg_default "$cfg" redir_mod "Redir模式"
shellcrash_set_cfg_default "$cfg" firewall_area 3
shellcrash_set_cfg_default "$cfg" dns_mod redir_host
shellcrash_set_cfg_default "$cfg" mix_port 7890
shellcrash_set_cfg_default "$cfg" redir_port 7892
shellcrash_set_cfg_default "$cfg" tproxy_port 7893
shellcrash_set_cfg_default "$cfg" db_port 9999
shellcrash_set_cfg_default "$cfg" dns_port 1053
shellcrash_set_cfg_default "$cfg" network_check OFF
if ! grep -q '^crashcore=.*singbox' "$cfg" 2>/dev/null; then
cat >"$SHELLCRASH_DIR/configs/command.env" <<EOF
TMPDIR=/tmp/ShellCrash
BINDIR=$SHELLCRASH_DIR
COMMAND="\$TMPDIR/CrashCore -d \$BINDIR -f \$TMPDIR/config.yaml"
EOF
if systemctl list-unit-files shellcrash.service --no-legend 2>/dev/null | grep -q '^shellcrash.service'; then
local unit_path
unit_path="$(systemctl show -p FragmentPath shellcrash.service 2>/dev/null | sed 's/^FragmentPath=//')"
if [ -n "$unit_path" ] && [ -f "$unit_path" ]; then
sed -i "s#^ExecStart=.*#ExecStart=/tmp/ShellCrash/CrashCore -d $SHELLCRASH_DIR -f /tmp/ShellCrash/config.yaml#" "$unit_path"
systemctl daemon-reload
fi
fi
fi
}
install_shellcrash() {
if [ "${ENABLE_SHELLCRASH:-1}" != "1" ]; then
return 0
fi
if [ -x "$SHELLCRASH_DIR/start.sh" ]; then
log "ShellCrash already installed"
harden_shellcrash_runtime
return 0
fi
log "Installing ShellCrash"
@@ -1284,6 +1385,7 @@ install_shellcrash() {
log "ShellCrash install script returned error — continuing; configure manually under $SHELLCRASH_DIR if needed"
return 0
fi
harden_shellcrash_runtime
}
install_browser_stack() {
@@ -1292,6 +1394,10 @@ install_browser_stack() {
fi
log "Installing browser stack"
export DEBIAN_FRONTEND=noninteractive
local chromium_browser_is_snap=0
if apt-cache show chromium-browser 2>/dev/null | grep -qiE 'snap|transitional'; then
chromium_browser_is_snap=1
fi
case "$(uname -m)" in
x86_64|amd64)
if ! have google-chrome && ! have google-chrome-stable; then
@@ -1307,7 +1413,13 @@ install_browser_stack() {
fi
;;
*)
apt_install chromium || apt_install chromium-browser || true
if apt-cache policy chromium 2>/dev/null | awk '/Candidate:/ && $2 != "(none)" { found = 1 } END { exit(found ? 0 : 1) }'; then
apt_install chromium || true
elif [ "$chromium_browser_is_snap" != 1 ] && apt-cache policy chromium-browser 2>/dev/null | awk '/Candidate:/ && $2 != "(none)" { found = 1 } END { exit(found ? 0 : 1) }'; then
apt_install chromium-browser || true
else
log "Host Chromium skipped: apt package unavailable or snap-only on this architecture; Neko Docker browsers remain available"
fi
;;
esac
mkdir -p /etc/chromium/policies/managed /etc/chromium-browser/policies/managed /etc/opt/chrome/policies/managed
@@ -1341,7 +1453,7 @@ install_neko_browser() {
set -a
[ -f "$STACK_ENV" ] && . "$STACK_ENV"
set +a
local idx neko_prof instance_count port mux udp range compose_file
local idx neko_prof instance_count port mux udp range compose_file neko_timeout
instance_count="$(resolve_neko_instance_count)"
export NEKO_HTTP_PORT="${NEKO_HTTP_PORT:-9200}"
export NEKO_HTTP_PORT_1="${NEKO_HTTP_PORT_1:-$NEKO_HTTP_PORT}"
@@ -1402,7 +1514,10 @@ install_neko_browser() {
done
remove_legacy_neko_container
compose_file="$(render_neko_compose_file)"
compose_in_service_dir "$compose_file" up -d
neko_timeout="${NEKO_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}"
if ! compose_in_service_dir_timed "$neko_timeout" "$compose_file" up -d; then
log "WARN: Neko start timed out or failed after ${neko_timeout}s; continuing. Run: sudo bash $ROOT_DIR/scripts/linux/service_ctl.sh neko start"
fi
}
install_redroid() {
@@ -1413,7 +1528,7 @@ install_redroid() {
log "Redroid skipped: docker unavailable"
return 0
fi
local image name data_dir
local image name data_dir redroid_timeout
case "$(uname -m)" in
x86_64|amd64) image="${REDROID_IMAGE:-${REDROID_IMAGE_X86_ALT:-$REDROID_IMAGE_X86}}" ;;
aarch64|arm64) image="${REDROID_IMAGE:-${REDROID_IMAGE_ARM64_ALT:-$REDROID_IMAGE_ARM64}}" ;;
@@ -1428,6 +1543,8 @@ install_redroid() {
log "Removing incompatible Redroid container (${name})"
docker rm -f "$name" >/dev/null 2>&1 || true
fi
systemctl disable --now live-redroid-host.service >/dev/null 2>&1 || true
systemctl reset-failed live-redroid-host.service >/dev/null 2>&1 || true
return 0
fi
mkdir -p "$data_dir"
@@ -1451,7 +1568,8 @@ install_redroid() {
fi
fi
log "Installing Redroid container (${image})"
docker run -d \
redroid_timeout="${REDROID_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}"
run_optional_with_timeout "$redroid_timeout" docker run -d \
--name "$name" \
--privileged \
--pull always \
@@ -1468,7 +1586,7 @@ install_redroid() {
$( redroid_legacy_binder_devices_ready && printf '%s ' '--device' '/dev/binder' '--device' '/dev/hwbinder' '--device' '/dev/vndbinder' ) \
$( [ -e /dev/rga ] && printf '%s ' '--device /dev/rga' ) \
$( [ -e /dev/mpp_service ] && printf '%s ' '--device /dev/mpp_service' ) \
"$image" ${REDROID_BOOT_ARGS:-} >/dev/null 2>&1 || log "WARN: Redroid create failed; check /dev/kvm, binderfs, and image availability"
"$image" ${REDROID_BOOT_ARGS:-} >/dev/null 2>&1 || log "WARN: Redroid create timed out or failed after ${redroid_timeout}s; check /dev/kvm, binderfs, and image availability"
}
configure_adb_udev() {
@@ -1488,17 +1606,27 @@ install_android_panel() {
log "Installing Android web panel (ws-scrcpy)"
mkdir -p "$(dirname "$ANDROID_PANEL_DIR")"
ANDROID_PANEL_GIT_URL="${ANDROID_PANEL_GIT_URL:-https://github.com/baixin1228/web-scrcpy.git}"
ANDROID_PANEL_GIT_TIMEOUT="${ANDROID_PANEL_GIT_TIMEOUT:-60}"
if [ "$(id -u)" -eq 0 ] && ! git config --global --get-all safe.directory 2>/dev/null | grep -qxF "$ANDROID_PANEL_DIR"; then
git config --global --add safe.directory "$ANDROID_PANEL_DIR"
fi
if [ ! -d "$ANDROID_PANEL_DIR/.git" ]; then
rm -rf "$ANDROID_PANEL_DIR"
if ! retry_run 3 5 git clone "$ANDROID_PANEL_GIT_URL" "$ANDROID_PANEL_DIR"; then
local clone_ok=0 attempt
for attempt in 1 2 3; do
rm -rf "$ANDROID_PANEL_DIR"
if timeout "$ANDROID_PANEL_GIT_TIMEOUT" git -c http.version=HTTP/1.1 clone --depth 1 "$ANDROID_PANEL_GIT_URL" "$ANDROID_PANEL_DIR"; then
clone_ok=1
break
fi
log "retry ${attempt}/3 failed command: git clone $ANDROID_PANEL_GIT_URL $ANDROID_PANEL_DIR"
sleep 5
done
if [ "$clone_ok" != 1 ]; then
log "WARN: Android web panel clone failed; skipping ws-scrcpy install for now"
return 0
fi
else
if ! retry_run 3 5 git -C "$ANDROID_PANEL_DIR" pull --ff-only; then
if ! retry_run 3 5 timeout "$ANDROID_PANEL_GIT_TIMEOUT" git -C "$ANDROID_PANEL_DIR" -c http.version=HTTP/1.1 pull --ff-only; then
log "WARN: Android web panel update failed; keeping previous ws-scrcpy checkout"
return 0
fi
@@ -1836,7 +1964,8 @@ prepare_homepage() {
fi
fi
render_homepage_services
compose_in_service_dir "$ROOT_DIR/services/homepage/docker-compose.yml" up -d
compose_in_service_dir_timed "${HOMEPAGE_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/homepage/docker-compose.yml" up -d || \
log "WARN: Homepage failed to start before timeout; continuing"
}
prepare_filebrowser() {
@@ -1849,20 +1978,24 @@ prepare_filebrowser() {
local fb_cfg_dir="$ROOT_DIR/services/filebrowser/config"
local fb_db="$fb_db_dir/filebrowser.db"
local fb_settings="$fb_cfg_dir/settings.json"
local fb_tmp_json fb_hash
local fb_tmp_json fb_hash fb_timeout
fb_timeout="${FILEBROWSER_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}"
mkdir -p "$ROOT_DIR/services/filebrowser/database" "$ROOT_DIR/services/filebrowser/config"
rm -f "$fb_db"
touch "$fb_db"
chmod -R 0777 "$ROOT_DIR/services/filebrowser/database" "$ROOT_DIR/services/filebrowser/config"
compose_in_service_dir "$ROOT_DIR/services/filebrowser/docker-compose.yml" down >/dev/null 2>&1 || true
docker run --rm \
if ! run_optional_with_timeout "$fb_timeout" docker run --rm \
-v "$fb_db_dir:/database" \
-v "$fb_cfg_dir:/config" \
"$fb_image" \
config init --config /config/settings.json --database /database/filebrowser.db >/dev/null 2>&1 || true
config init --config /config/settings.json --database /database/filebrowser.db >/dev/null 2>&1; then
log "WARN: File Browser config init failed before timeout; skipping File Browser startup"
return 0
fi
docker run --rm \
if ! run_optional_with_timeout "$fb_timeout" docker run --rm \
-v "$fb_db_dir:/database" \
-v "$fb_cfg_dir:/config" \
"$fb_image" \
@@ -1875,9 +2008,15 @@ prepare_filebrowser() {
--hideLoginButton=false \
--branding.name "Live File Browser" \
--config /config/settings.json \
--database /database/filebrowser.db >/dev/null
--database /database/filebrowser.db >/dev/null; then
log "WARN: File Browser config set failed before timeout; skipping File Browser startup"
return 0
fi
fb_hash="$(docker run --rm "$fb_image" hash "$FILEBROWSER_PASS" | tail -n 1)"
fb_hash="$(run_optional_with_timeout "$fb_timeout" docker run --rm "$fb_image" hash "$FILEBROWSER_PASS" | tail -n 1)" || {
log "WARN: File Browser password hash failed before timeout; skipping File Browser startup"
return 0
}
fb_tmp_json="$fb_cfg_dir/filebrowser-users.import.json"
FILEBROWSER_HASH="$fb_hash" FILEBROWSER_USER="$FILEBROWSER_USER" python3 - <<'PY' >"$fb_tmp_json"
import json
@@ -1921,16 +2060,20 @@ print(
PY
chmod 0666 "$fb_tmp_json"
docker run --rm \
if ! run_optional_with_timeout "$fb_timeout" docker run --rm \
-w /config \
-v "$fb_db_dir:/database" \
-v "$fb_cfg_dir:/config" \
"$fb_image" \
users import $(basename "$fb_tmp_json") --overwrite --config /config/settings.json --database /database/filebrowser.db >/dev/null
users import $(basename "$fb_tmp_json") --overwrite --config /config/settings.json --database /database/filebrowser.db >/dev/null; then
log "WARN: File Browser user import failed before timeout; skipping File Browser startup"
return 0
fi
rm -f "$fb_cfg_dir/users.backup.json"
rm -f "$fb_tmp_json"
compose_in_service_dir "$ROOT_DIR/services/filebrowser/docker-compose.yml" up -d
compose_in_service_dir_timed "${FILEBROWSER_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/filebrowser/docker-compose.yml" up -d || \
log "WARN: File Browser failed to start before timeout; continuing"
}
prepare_mitmproxy() {
@@ -1941,7 +2084,7 @@ prepare_mitmproxy() {
mkdir -p "$ROOT_DIR/services/mitmproxy/data"
allow_tcp_port_if_firewall_active "$MITMPROXY_WEB_PORT"
allow_tcp_port_if_firewall_active "$MITMPROXY_PROXY_PORT"
compose_in_service_dir "$ROOT_DIR/services/mitmproxy/docker-compose.yml" up -d || \
compose_in_service_dir_timed "${MITMPROXY_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/mitmproxy/docker-compose.yml" up -d || \
log "WARN: mitmproxy failed to start"
}
@@ -1952,7 +2095,7 @@ prepare_vscode_server() {
log "Preparing code-server"
mkdir -p "$ROOT_DIR/services/vscode-server/config"
allow_tcp_port_if_firewall_active "$VSCODE_SERVER_PORT"
compose_in_service_dir "$ROOT_DIR/services/vscode-server/docker-compose.yml" up -d || \
compose_in_service_dir_timed "${VSCODE_SERVER_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/vscode-server/docker-compose.yml" up -d || \
log "WARN: code-server failed to start"
}
@@ -1963,7 +2106,7 @@ prepare_uptime_kuma() {
log "Preparing Uptime Kuma"
mkdir -p "$ROOT_DIR/services/uptime-kuma/data"
allow_tcp_port_if_firewall_active "$UPTIME_KUMA_PORT"
compose_in_service_dir "$ROOT_DIR/services/uptime-kuma/docker-compose.yml" up -d || \
compose_in_service_dir_timed "${UPTIME_KUMA_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/uptime-kuma/docker-compose.yml" up -d || \
log "WARN: Uptime Kuma failed to start"
}
@@ -1976,7 +2119,7 @@ prepare_openclaw() {
chmod -R 0777 "$OPENCLAW_CONFIG_DIR" "$OPENCLAW_WORKSPACE_DIR" 2>/dev/null || true
allow_tcp_port_if_firewall_active "$OPENCLAW_GATEWAY_PORT"
allow_tcp_port_if_firewall_active "$OPENCLAW_BRIDGE_PORT"
compose_in_service_dir "$ROOT_DIR/services/openclaw/docker-compose.yml" up -d || \
compose_in_service_dir_timed "${OPENCLAW_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/openclaw/docker-compose.yml" up -d || \
log "WARN: OpenClaw failed to start"
}
@@ -2056,7 +2199,8 @@ prepare_srs() {
fi
fi
mkdir -p "$ROOT_DIR/services/srs/data"
compose_in_service_dir "$ROOT_DIR/services/srs/docker-compose.yml" up -d
compose_in_service_dir_timed "${SRS_INSTALL_TIMEOUT:-${DOCKER_OPTIONAL_INSTALL_TIMEOUT:-900}}" "$ROOT_DIR/services/srs/docker-compose.yml" up -d || \
log "WARN: SRS failed to start before timeout; continuing"
}
run_hardware_probe() {
@@ -2121,7 +2265,18 @@ install_live_edge_proxy() {
esac
local ver="2.8.4"
local tmp="/tmp/caddy-${ver}-linux-${arch}.tar.gz"
curl -fsSL "https://github.com/caddyserver/caddy/releases/download/v${ver}/caddy_${ver}_linux_${arch}.tar.gz" -o "$tmp"
local url="https://github.com/caddyserver/caddy/releases/download/v${ver}/caddy_${ver}_linux_${arch}.tar.gz"
rm -f "$tmp" /tmp/caddy
if ! retry_run 4 10 curl -fL --connect-timeout 20 --max-time 240 --retry 3 --retry-delay 3 --retry-all-errors "$url" -o "$tmp"; then
if have wget; then
retry_run 4 10 wget -O "$tmp" "$url" || true
fi
fi
if [ ! -s "$tmp" ] || ! tar -tzf "$tmp" caddy >/dev/null 2>&1; then
log "WARN: Caddy download failed or archive is invalid; skipping edge proxy install for now"
rm -f "$tmp" /tmp/caddy
return 0
fi
tar -xzf "$tmp" -C /tmp caddy
install -m0755 /tmp/caddy /usr/local/bin/caddy
rm -f "$tmp" /tmp/caddy