Refuse to auto-detect a container when the prefix is ambiguous
On this host "authelia" matches both Authelia and Authelia-Secondary, and taking the first match writes the wrong instance into the conf every other script then trusts.
This commit is contained in:
+124
-3
@@ -55,6 +55,97 @@
|
||||
# GITEA_CONTAINER fuzzy match from docker ps
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# 1. Resolve this host's conf from MY_ID
|
||||
# 2. Per service, locate its container and read its own config file:
|
||||
# arrs → config.xml via the container's /config volume mount
|
||||
# SABnzbd → sabnzbd.ini
|
||||
# slskd → config.yml
|
||||
# qBit → qBittorrent.conf (plaintext WebUI credentials only)
|
||||
# Emby/JF → docker port bindings and /transcode mount
|
||||
# 3. Write each value only if the conf field is EMPTY, unless --overwrite
|
||||
# 4. Push the updated conf to partners via conf_sync.sh, unless --no-push
|
||||
#
|
||||
# Container names are resolved by _resolve_container(): an exact name match wins, otherwise
|
||||
# a prefix match must be unambiguous or the field is skipped.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Never Overwrite a Human's Value
|
||||
# Only empty fields are populated. A value already in the conf was either set deliberately
|
||||
# or populated from a service that has since changed — either way, the file wins over
|
||||
# detection. --overwrite exists for deliberate re-sync after a key rotation.
|
||||
#
|
||||
# Read From the Service, Not From Assumption
|
||||
# Every value comes out of the service's own config file or docker metadata — ports from
|
||||
# actual port bindings, paths from actual volume mounts. Nothing is derived from naming
|
||||
# convention where the real value is readable.
|
||||
#
|
||||
# Refuse to Guess a Container
|
||||
# An ambiguous prefix skips the field rather than picking one. Writing the wrong container
|
||||
# name is worse than writing nothing: an empty field is visibly incomplete and gets fixed,
|
||||
# while a wrong one silently points the whole stack at the wrong instance. This host has a
|
||||
# live example — "authelia" prefix-matches both Authelia and Authelia-Secondary.
|
||||
#
|
||||
# Push Immediately After Populating
|
||||
# Fresh credentials go to partners right away rather than waiting for the next scheduled
|
||||
# conf sync, so a partner is never authenticating with a key this host has already rotated.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Root Enforcement
|
||||
# Reads service config files owned by container users and writes the host conf.
|
||||
#
|
||||
# Host Detection
|
||||
# detect_hosts() resolves MY_ID, which selects which host conf is written. Populating the
|
||||
# wrong host's conf would write this machine's credentials into a partner's file.
|
||||
#
|
||||
# Conf Existence Guard
|
||||
# Aborts if the resolved host conf does not exist, rather than creating a partial one.
|
||||
#
|
||||
# Empty-Field-Only Writes
|
||||
# Existing values are preserved unless --overwrite is passed explicitly.
|
||||
#
|
||||
# Container Ambiguity Guard
|
||||
# _resolve_container() refuses a prefix matching more than one container, warning with the
|
||||
# full match list. Exact name matches short-circuit and are never treated as ambiguous.
|
||||
#
|
||||
# Missing Service Tolerance
|
||||
# A service that is not installed on this host is skipped with a log line. Absence is a
|
||||
# valid configuration, not a failure.
|
||||
#
|
||||
# Map Block Validation
|
||||
# Associative-array entries are only inserted when the target map actually exists in the
|
||||
# conf; a missing map warns and skips instead of appending an orphaned entry.
|
||||
#
|
||||
# Dry Run Support
|
||||
# --dry-run reports every value it would write, truncated, and writes nothing.
|
||||
#
|
||||
# Credential Truncation in Output
|
||||
# Detected secrets are printed truncated (first 8 chars) so a populate run can be pasted
|
||||
# into a log or issue without leaking full API keys.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Reads and writes: Configurations/<hostid>.conf and Configurations/master.conf
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# DOCKER_APPDATA_BASE
|
||||
# Fallback appdata root when a container exposes no /config mount to read from.
|
||||
#
|
||||
# Everything else this script touches is a field it populates rather than one it consumes —
|
||||
# see AUTO-DETECTED FIELDS above for the full list.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
@@ -174,11 +265,41 @@ _set_conf_map_entry() {
|
||||
(( UPDATED++ ))
|
||||
}
|
||||
|
||||
# ── Helper: resolve a container name from a prefix, refusing ambiguity ────────
|
||||
# grep -m1 silently returns whichever name docker happens to list first. On this very host
|
||||
# "^authelia" matches both Authelia (9091, primary) and Authelia-Secondary (9092), and -m1
|
||||
# picks the secondary — writing the wrong container into the conf that every other script
|
||||
# then trusts. An exact match wins outright; otherwise a prefix match must be unambiguous.
|
||||
# Same rule detect_hosts() applies to host identity: exactly one candidate, or none.
|
||||
_resolve_container() {
|
||||
local pattern="$1"
|
||||
local all exact matches count
|
||||
|
||||
all=$(docker ps -a --format '{{.Names}}' 2>/dev/null)
|
||||
[[ -z "$all" ]] && return 1
|
||||
|
||||
# Exact name match short-circuits — "Authelia" is not ambiguous with "Authelia-Secondary"
|
||||
exact=$(printf '%s\n' "$all" | grep -ixm1 -- "$pattern")
|
||||
[[ -n "$exact" ]] && { echo "$exact"; return 0; }
|
||||
|
||||
matches=$(printf '%s\n' "$all" | grep -i -- "^${pattern}")
|
||||
count=$(printf '%s\n' "$matches" | grep -c .)
|
||||
|
||||
if [[ "$count" -gt 1 ]]; then
|
||||
warn "Container prefix '${pattern}' is ambiguous — matches: $(printf '%s' "$matches" | tr '\n' ' ')"
|
||||
warn " Refusing to guess. Set the container name manually in host*.conf."
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ "$count" -eq 1 ]] && { printf '%s\n' "$matches"; return 0; }
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Helper: find arr config dir via docker volume mount ───────────────────────
|
||||
_arr_config_dir() {
|
||||
local pattern="$1"
|
||||
local container_name
|
||||
container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}")
|
||||
container_name=$(_resolve_container "$pattern") || return 1
|
||||
[[ -z "$container_name" ]] && return 1
|
||||
|
||||
local config_path
|
||||
@@ -239,7 +360,7 @@ _set_conf_var "${MY_ID}_SSH_KEY" "/root/.ssh/${owner}_rsync_automation" "
|
||||
|
||||
for arr in radarr sonarr lidarr; do
|
||||
arr_upper="${arr^^}"
|
||||
arr_container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${arr}")
|
||||
arr_container=$(_resolve_container "$arr")
|
||||
config_dir=$(_arr_config_dir "$arr") || {
|
||||
log "${arr_upper}: no running container found — skipping"
|
||||
continue
|
||||
@@ -341,7 +462,7 @@ qbit_dir=$(_arr_config_dir "qbittorrent") && {
|
||||
transcode_dir=""
|
||||
|
||||
for pattern in "emby" "jellyfin"; do
|
||||
container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}")
|
||||
container=$(_resolve_container "$pattern") || continue
|
||||
[[ -z "$container" ]] && continue
|
||||
|
||||
case "$pattern" in
|
||||
|
||||
Reference in New Issue
Block a user