The main sweep — ~260 call sites across ~100 files move from string
concatenation on a single root to appDir/storageAppDirs/storageAppConfigs.
On a single-root install the resolved paths are identical, so this is a
no-op until a location is registered.
Enumerators were the interesting half. `for d in "$containers_dir"/*/`
appears in the menus, the registry/artifact scanners and the DNS setup —
and a shell glob cannot list a rootless 751 tree at all, which is the
same bug config_find_file.sh already documents in a comment. Routing them
through storageAppDirs (which enumerates as the owning user) fixes that
alongside the multi-root work.
Three places needed judgement rather than substitution:
db_app_scan.sh deletes database rows and port allocations for apps whose
folder is missing, and reaps "empty" app dirs. With a storage location
unmounted, every app on it looks exactly like that. Each of those
branches now gates on appStorageAvailable first — an app on an unplugged
drive is skipped with a notice, never deleted.
instance_create.sh rewrites cloned hooks so an instance touches its own
directory instead of the base app's. Its sed matched ${containers_dir}<type>,
which this sweep just replaced with $(appDir <type>) — so it would have
silently stopped redirecting, and an instance would have written to the
original's files (the adguard auth adapter case its own comment warns
about). Now matches both appDir forms, verified against bare, quoted,
unrelated-app, legacy and prose cases.
peer_shell/peer_pull streamed and extracted relative to the primary root.
Both now use the app's own root, and peer_shell keeps a single-root
fallback since it runs as a restricted SSH shell with no LibrePortal env.
Also fixes a pre-existing bug found on the way: webui_app_config.sh
tested "$containers_dir/frontend/data/last_update", one level short of the
real tree under the libreportal app dir, so the WebUI refresh trigger
after a config update has never once fired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
4.8 KiB
Bash
117 lines
4.8 KiB
Bash
#!/bin/bash
|
|
|
|
# Gluetun network-routing provider hooks. An app routes through gluetun by setting
|
|
# CFG_<APP>_NETWORK=gluetun; the central compose templater + uninstall flow call
|
|
# these by convention (appNetworkApplyMode_<provider> / appNetworkRegisterPorts_
|
|
# <provider>) with no provider name hardcoded centrally — so this lives with the
|
|
# app that owns it.
|
|
|
|
# Switch a routed app's compose between default and gluetun networking by editing
|
|
# its marker regions (GLUETUN_OFF_* / GLUETUN_ON_*) and forcing traefik off (the
|
|
# app is reached via gluetun's published ports instead).
|
|
appNetworkApplyMode_gluetun()
|
|
{
|
|
local file="$1"
|
|
[[ -z "$file" || ! -f "$file" ]] && return 0
|
|
|
|
local tmp="${file}.netmode.$$"
|
|
runFileOp awk '
|
|
BEGIN { in_off=0; in_on=0 }
|
|
/# *GLUETUN_OFF_BEGIN/ { in_off=1; print; next }
|
|
/# *GLUETUN_OFF_END/ { in_off=0; print; next }
|
|
/# *GLUETUN_ON_BEGIN/ { in_on=1; print; next }
|
|
/# *GLUETUN_ON_END/ { in_on=0; print; next }
|
|
{
|
|
if (in_off) {
|
|
if ($0 ~ /^[[:space:]]*#/) { print; next }
|
|
match($0, /^[[:space:]]*/)
|
|
indent = substr($0, RSTART, RLENGTH)
|
|
rest = substr($0, RLENGTH + 1)
|
|
print indent "# " rest
|
|
next
|
|
}
|
|
if (in_on) {
|
|
match($0, /^[[:space:]]*/)
|
|
indent = substr($0, 1, RLENGTH)
|
|
rest = substr($0, RLENGTH + 1)
|
|
sub(/^#[[:space:]]?/, "", rest)
|
|
print indent rest
|
|
next
|
|
}
|
|
print
|
|
}
|
|
' "$file" | runFileWrite "$tmp"
|
|
|
|
runFileOp mv "$tmp" "$file"
|
|
|
|
tagsManagerUpdateUniversalTag "$file" "TRAEFIK_ENABLE_TAG" "false"
|
|
}
|
|
|
|
# Rebuild gluetun's GLUETUN_FORWARDED_PORTS region from every installed app whose
|
|
# CFG_<APP>_NETWORK is gluetun. Region is fully rewritten on every call so removed
|
|
# apps drop out automatically. Self-skips if gluetun isn't installed.
|
|
appNetworkRegisterPorts_gluetun()
|
|
{
|
|
local gluetun_compose="$(appDir gluetun)/docker-compose.yml"
|
|
if [[ ! -f "$gluetun_compose" ]]; then return 0; fi
|
|
if ! command -v sqlite3 >/dev/null 2>&1; then return 0; fi
|
|
if [[ ! -f "$docker_dir/$db_file" ]]; then return 0; fi
|
|
|
|
local installed_apps
|
|
installed_apps=$(runInstallOp sqlite3 "$docker_dir/$db_file" \
|
|
"SELECT name FROM apps WHERE status = 1 ORDER BY name;" 2>/dev/null)
|
|
|
|
local routed_lines=""
|
|
local app # local: bash is dynamically scoped, and a while-read loop
|
|
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
|
while IFS= read -r app; do
|
|
[[ -z "$app" || "$app" == "gluetun" ]] && continue
|
|
local app_config_file="$(appDir "$app")/${app}.config"
|
|
[[ -f "$app_config_file" ]] || continue
|
|
local net
|
|
net=$(grep -E "^CFG_${app^^}_NETWORK=" "$app_config_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"')
|
|
[[ "$net" != "gluetun" ]] && continue
|
|
|
|
local ports
|
|
ports=$(runInstallOp sqlite3 "$docker_dir/$db_file" \
|
|
"SELECT resource_value FROM network_resources WHERE app_name = '$app' AND resource_type = 'port' AND status = 'active';" 2>/dev/null)
|
|
while IFS= read -r p; do
|
|
[[ -z "$p" ]] && continue
|
|
local ext_port int_port access
|
|
ext_port=$(echo "$p" | cut -d':' -f1)
|
|
int_port=$(echo "$p" | cut -d':' -f2)
|
|
access=$(echo "$p" | cut -d':' -f3)
|
|
[[ "$access" == "disabled" ]] && continue
|
|
[[ -z "$ext_port" || "$ext_port" == "random" ]] && continue
|
|
routed_lines+=" - \"${ext_port}:${int_port}\" # gluetun-routed: ${app}"$'\n'
|
|
done <<< "$ports"
|
|
done <<< "$installed_apps"
|
|
|
|
local tmp="${gluetun_compose}.fwd.$$"
|
|
runFileOp awk -v block="$routed_lines" '
|
|
BEGIN { in_region=0 }
|
|
/# *GLUETUN_FORWARDED_PORTS_BEGIN/ {
|
|
print
|
|
printf "%s", block
|
|
in_region=1
|
|
next
|
|
}
|
|
/# *GLUETUN_FORWARDED_PORTS_END/ { in_region=0; print; next }
|
|
{ if (!in_region) print }
|
|
' "$gluetun_compose" | runFileWrite "$tmp"
|
|
|
|
if ! runFileOp cmp -s "$tmp" "$gluetun_compose"; then
|
|
runFileOp mv "$tmp" "$gluetun_compose"
|
|
if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^gluetun-service$'; then
|
|
isNotice "Gluetun forwarded ports changed; recreating gluetun-service to apply."
|
|
(cd "$(appDir gluetun)" && runFileOp docker compose up -d --force-recreate gluetun-service >/dev/null 2>&1) || true
|
|
# Recreating gluetun gives it a new container ID, which orphans every
|
|
# `network_mode: container:gluetun-service` reference. Re-attach all
|
|
# routed apps so they share the new netns instead of getting their own.
|
|
appGluetunRecreateRouted
|
|
fi
|
|
else
|
|
runFileOp rm -f "$tmp"
|
|
fi
|
|
}
|