@@ -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_ru n 3 5 git clone " $ANDROID_PANEL_GIT_URL " " $ANDROID_PANEL_DIR " ; then
local clone_ok = 0 attempt
for attempt i n 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