gluetun_providers.sh writes its working files ($raw, $headers) next to $output_file, which lives at containers_dir/libreportal/frontend/data/apps/generated/gluetun-providers.json — dockerinstall-owned in rootless. The five rm -f calls on those paths were unwrapped, so the manager running the script (e.g. from the task processor) would get Permission denied — same class as the updateConfigOption sed -i bug that was just fixed. $tmp comes from mktemp (/tmp), so the rm -f for it stays unwrapped. Audit context: this was the only remaining raw filesystem op against container-tree paths in any containers/*/*.sh. The rest of the container .sh files are clean — every sed -i / chmod / chown / cp / mv is already routed through runFileOp or runFileWrite, and the per-app install bodies delegate fs work to high-level helpers (dockerConfigSetupToContainer, copyResource, dockerComposeSetupFile) which themselves use the wrappers. Hooks (<app>_migrate_pre/_post, restoreAppRunHook pre/post) are present in the framework but unused by any app today — that's by design (opt-in per-app). If a future app needs federation-key rotation post-migrate, or a hostname rewrite that the generic URL-rewrite layer doesn't cover, those slots are ready. Signed-off-by: librelad <librelad@digitalangels.vip>
92 lines
3.8 KiB
Bash
92 lines
3.8 KiB
Bash
#!/bin/bash
|
|
|
|
# Per-app routine WebUI-refresh hook (appWebuiRefresh_<app>): webui_updater calls
|
|
# it on every WebUI update while gluetun is installed; the installer (gluetun.sh)
|
|
# and the 'gluetun_refresh_providers' tool call it directly too.
|
|
#
|
|
# Fetches gluetun's upstream servers.json, slims it down to a
|
|
# { providers: { <name>: { vpnTypes, countries } } } shape, and writes it
|
|
# to the WebUI data dir so the per-app config dropdowns stay honest as
|
|
# gluetun adds/removes providers and protocols. Falls back silently to the
|
|
# previous snapshot (or the bundled default) on network failure.
|
|
appWebuiRefresh_gluetun() {
|
|
local output_file="${containers_dir}libreportal/frontend/data/apps/generated/gluetun-providers.json"
|
|
local upstream="https://raw.githubusercontent.com/qdm12/gluetun/master/internal/storage/servers.json"
|
|
local tmp="$(mktemp)"
|
|
local raw="${output_file}.raw.$$"
|
|
|
|
runFileOp mkdir -p "$(dirname "$output_file")"
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
isNotice "jq not installed; skipping gluetun provider refresh."
|
|
return 0
|
|
fi
|
|
|
|
# GitHub raw only sends ETag (no Last-Modified), so use If-None-Match
|
|
# via a sidecar to skip the 7MB body when nothing has changed upstream.
|
|
local etag_file="${output_file}.etag"
|
|
local etag=""
|
|
[[ -s "$etag_file" ]] && etag=$(<"$etag_file")
|
|
local headers="${output_file}.hdr.$$"
|
|
local http_code
|
|
http_code=$(curl -sSL \
|
|
${etag:+-H "If-None-Match: $etag"} \
|
|
--speed-limit 1000 --speed-time 30 \
|
|
--retry 2 --retry-delay 2 --retry-all-errors \
|
|
-D "$headers" -o "$raw" \
|
|
-w '%{http_code}' "$upstream") || http_code=""
|
|
|
|
# $raw and $headers live next to $output_file (under containers_dir/
|
|
# libreportal/frontend/data/, dockerinstall-owned in rootless). The
|
|
# manager can't `rm` them directly without a Permission denied — same
|
|
# class of bug as the updateConfigOption sed-i issue. runFileOp routes
|
|
# the rm through the right user. $tmp is from mktemp (/tmp), so
|
|
# `rm -f $tmp` stays unwrapped.
|
|
if [[ "$http_code" == "304" ]]; then
|
|
runFileOp rm -f "$raw" "$headers"
|
|
return 0
|
|
fi
|
|
if [[ "$http_code" != "200" ]]; then
|
|
isNotice "Upstream fetch failed (${http_code:-no response}); keeping existing snapshot."
|
|
runFileOp rm -f "$raw" "$headers"
|
|
return 0
|
|
fi
|
|
local new_etag
|
|
new_etag=$(awk 'tolower($1)=="etag:"{print $2}' "$headers" | tr -d '\r')
|
|
runFileOp rm -f "$headers"
|
|
|
|
# servers.json is a top-level object keyed by provider; each provider
|
|
# entry has a `servers` array whose items have `vpn` (wireguard|openvpn),
|
|
# `country`, `city`, etc. We collapse that into per-provider unique
|
|
# vpn-type and country lists. Drop the `version` key (it's not a provider).
|
|
if ! jq '
|
|
[ to_entries[]
|
|
| select(.key != "version")
|
|
| { key: .key,
|
|
value: {
|
|
vpnTypes: ((.value.servers // []) | map(.vpn) | unique | map(select(. != null and . != ""))),
|
|
countries: ((.value.servers // []) | map(.country) | unique | map(select(. != null and . != "")))
|
|
}
|
|
}
|
|
]
|
|
| from_entries
|
|
| { providers: . }
|
|
' "$raw" > "$tmp" 2>/dev/null; then
|
|
isNotice "Failed to parse gluetun servers.json; keeping existing provider snapshot."
|
|
runFileOp rm -f "$raw"
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
|
|
runFileOp rm -f "$raw"
|
|
|
|
if [ -s "$tmp" ]; then
|
|
runFileWrite "$output_file" < "$tmp"; rm -f "$tmp"
|
|
[[ -n "$new_etag" ]] && echo "$new_etag" | tee "$etag_file" >/dev/null
|
|
isSuccessful "Refreshed gluetun provider snapshot ($(jq '.providers | length' "$output_file") providers)."
|
|
else
|
|
rm -f "$tmp"
|
|
isNotice "Empty gluetun snapshot generated; ignoring."
|
|
fi
|
|
}
|