Refuse instancing an app that pins a fixed host port
Audit of the per-app install hooks for singleton assumptions. The naming work
so far made identities unique, but a second copy still has to bind its own
ports, and `8201:80` is the same 8201 for every instance — the second container
simply fails at compose-up. `random:<internal>` is what makes an app
instanceable, since portAllocate then hands each instance its own host port.
Seven apps are caught: pihole (53 tcp+udp), stalwart (25/465/587/993), unbound
(5335 tcp+udp), traefik (443), searxng (8083), vaultwarden (8201), stoat
(7881). The message distinguishes the two cases, because they need opposite
fixes: an arbitrary pin like vaultwarden's 8201 should just become random,
while a DNS server on 53 or a mail server on 25 is genuinely one-per-host and
should never be instanced.
Runs before anything is cloned — this is a property of the app, not of the
instance. Bookstack is unaffected (all its ports are already random).
The rest of the hook audit found nothing further to fix:
- No hook writes to another app's config or deployed directory. The three that
reference ${containers_dir}traefik / headscale only test [[ -d ]] to detect
whether those are installed.
- Only two hooks read a foreign CFG_ namespace, and both are system-wide
settings (CFG_DOCKER_INSTALL_TYPE, CFG_ENABLE_VIDEO), not another app's.
- No app declares a fixed container IP; all come from IP_TAG allocation.
- Host-level writes are limited to wireguard's sysctl IPv4-forwarding drop-in
(global and idempotent) and its /etc/wireguard/params conflict probe. Traefik
writes only under $containers_dir$app_name. Stalwart's /etc/stalwart path is
inside its container.
Not mechanically checkable, so left as maintainer judgement: gluetun is a
network provider other apps join via network_mode container:gluetun-service,
and it plus wireguard hold NET_ADMIN and /dev/net/tun. Both are one-per-host
for reasons no guard can see.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e9bbe44601
commit
ef02b48966
@ -136,6 +136,46 @@ _instanceSetLocalOnly() {
|
||||
done
|
||||
}
|
||||
|
||||
# Refuse an app whose ports are pinned to a fixed host port.
|
||||
#
|
||||
# The identity checks below make names unique, but a second instance still has to
|
||||
# bind its own ports, and `8201:80` is the same 8201 for every copy — the second
|
||||
# container just fails to start at compose-up. `random:80` is what makes an app
|
||||
# instanceable: portAllocate hands each instance its own host port from the pool.
|
||||
#
|
||||
# This is a property of the app, not of the instance, so it runs before anything
|
||||
# is cloned. Ports the maintainer marked `disabled` are skipped — they publish
|
||||
# nothing, so they cannot collide.
|
||||
_instanceCheckPortsInstanceable() {
|
||||
local type="$1"
|
||||
local cfg="${install_containers_dir%/}/$type/$type.config"
|
||||
[[ -f "$cfg" ]] || return 0
|
||||
|
||||
local line val ext access
|
||||
local -a fixed=()
|
||||
while IFS= read -r line; do
|
||||
val="${line#*=}"; val="${val//$'\r'/}"; val="${val#\"}"; val="${val%\"}"
|
||||
local IFS='|'
|
||||
local -a f=($val)
|
||||
unset IFS
|
||||
ext="${f[2]%%:*}"
|
||||
access="${f[3]}"
|
||||
[[ "$access" == "disabled" ]] && continue
|
||||
[[ "$ext" == "random" ]] && continue
|
||||
[[ -z "$ext" ]] && continue
|
||||
fixed+=("${f[1]:-port} -> ${f[2]}")
|
||||
done < <(grep -E "^CFG_${type^^}_PORT_[0-9]+=" "$cfg" 2>/dev/null)
|
||||
|
||||
if [[ ${#fixed[@]} -gt 0 ]]; then
|
||||
isError "Instance create: '$type' pins ${#fixed[@]} port(s) to a fixed host port, so a second copy cannot start:"
|
||||
local p
|
||||
for p in "${fixed[@]}"; do echo " $p"; done
|
||||
isNotice "Change those to 'random:<internal>' in ${type}.config if the host port is arbitrary. If it is not — a DNS server on 53, a mail server on 25 — the app is genuinely one-per-host and should not be instanced."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Every container identity a compose declares: its service keys (from the
|
||||
# SERVICE_TAG_<n> markers) plus its container_name values, deduped.
|
||||
#
|
||||
@ -371,6 +411,10 @@ instanceCreate() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Fail before cloning: a fixed host port is a property of the app, and no
|
||||
# amount of renaming makes a second copy able to bind it.
|
||||
_instanceCheckPortsInstanceable "$type" || return 1
|
||||
|
||||
local id
|
||||
id=$(instanceIdPart "$rawname")
|
||||
if [[ -z "$id" ]]; then
|
||||
|
||||
@ -578,6 +578,7 @@ declare -gA LP_FN_MAP=(
|
||||
[installSwapfile]="install/install_swapfile.sh"
|
||||
[installUFW]="install/install_ufw.sh"
|
||||
[installUFWDocker]="install/install_ufwd.sh"
|
||||
[_instanceCheckPortsInstanceable]="instance/instance_create.sh"
|
||||
[_instanceComposeIdentities]="instance/instance_create.sh"
|
||||
[instanceCreate]="instance/instance_create.sh"
|
||||
[instanceIdPart]="instance/instance_create.sh"
|
||||
@ -1733,6 +1734,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[installSwapfile]="scripts"
|
||||
[installUFW]="scripts"
|
||||
[installUFWDocker]="scripts"
|
||||
[_instanceCheckPortsInstanceable]="scripts"
|
||||
[_instanceComposeIdentities]="scripts"
|
||||
[instanceCreate]="scripts"
|
||||
[instanceIdPart]="scripts"
|
||||
@ -2924,6 +2926,7 @@ installSSLCertificate() { unset -f installSSLCertificate; __lpAutoload "${instal
|
||||
installSwapfile() { unset -f installSwapfile; __lpAutoload "${install_scripts_dir}install/install_swapfile.sh"; installSwapfile "$@"; }
|
||||
installUFW() { unset -f installUFW; __lpAutoload "${install_scripts_dir}install/install_ufw.sh"; installUFW "$@"; }
|
||||
installUFWDocker() { unset -f installUFWDocker; __lpAutoload "${install_scripts_dir}install/install_ufwd.sh"; installUFWDocker "$@"; }
|
||||
_instanceCheckPortsInstanceable() { unset -f _instanceCheckPortsInstanceable; __lpAutoload "${install_scripts_dir}instance/instance_create.sh"; _instanceCheckPortsInstanceable "$@"; }
|
||||
_instanceComposeIdentities() { unset -f _instanceComposeIdentities; __lpAutoload "${install_scripts_dir}instance/instance_create.sh"; _instanceComposeIdentities "$@"; }
|
||||
instanceCreate() { unset -f instanceCreate; __lpAutoload "${install_scripts_dir}instance/instance_create.sh"; instanceCreate "$@"; }
|
||||
instanceIdPart() { unset -f instanceIdPart; __lpAutoload "${install_scripts_dir}instance/instance_create.sh"; instanceIdPart "$@"; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user