diff --git a/scripts/instance/instance_create.sh b/scripts/instance/instance_create.sh index 47533f0..471dce3 100644 --- a/scripts/instance/instance_create.sh +++ b/scripts/instance/instance_create.sh @@ -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:' 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_ 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 diff --git a/scripts/source/files/arrays/function_manifest.sh b/scripts/source/files/arrays/function_manifest.sh index 373182f..d08376d 100644 --- a/scripts/source/files/arrays/function_manifest.sh +++ b/scripts/source/files/arrays/function_manifest.sh @@ -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 "$@"; }