mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
Matrix-deploy variant transitions wipe a consumer's compose stack and
its named DB volume (`compose down -v` in
scripts/container/purge/entity/compose.sh) but `databases.csv` at
$FILE_DATABASE_SECRETS survives the purge. When the next round drops
the consumer from its inventory, the stale baudolo-seed row remains
behind. The subsequent backup run reads that row, attempts
`mariadb-dump -u<entity> -p<old_pw>` against the freshly initialised
RDBMS user that no longer exists, and the systemd unit fails with
"Access denied for user 'matomo'@'127.0.0.1' (using password: YES)"
(job 77797356000 on run 26428080957: matomo dump in
svc-bkp-container-2-local variant 1, where round 1 seeded matomo but
round 2 only deploys postgres + mariadb + svc-bkp itself).
Add `utils.cleanup.databases_csv` mirroring the existing
`utils.cleanup.tokens` pattern: parse the baudolo-seed CSV format
(';'-separated, four columns instance/database/username/password, with
header — verified against baudolo.seed.__main__:check_and_add_entry)
and strip every row whose `database` or `username` column matches an
entity derived from the supplied app ids via
utils.roles.entity_name.get_entity_name. Header and blank lines are
preserved verbatim; the file is rewritten only when at least one row
was removed.
Wire the helper into scripts/container/purge/apps.sh right after the
token-store wipe so every matrix-deploy inter-round purge invokes it
alongside the other cleanups. Operator-triggered purges
(`INFINITO_APPS=<app> bash scripts/container/purge/apps.sh`) also get
the cleanup — that is a behaviour change relative to before, where
the row would survive an operator-triggered entity teardown.
Coverage: 12 unit tests in tests/unit/utils/cleanup/test_databases_csv.py
covering the happy path, the multi-app case, the
`database='*'`/username-match case, the unknown-app no-op, header and
blank-line preservation, the all-rows-removed case, malformed short
rows, and the legacy-header-less file. `make test` green across all 5
targets.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
3.0 KiB
Bash
Executable File
90 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# App-keyed purge orchestrator. Maps each app id in $INFINITO_APPS to its
|
|
# compose entity, then for each entity runs the entity-keyed primitives
|
|
# (db, compose, dir) and finally wipes the matching token-store entries
|
|
# via utils.data.tokens. Tokens live outside /opt/compose and would
|
|
# otherwise survive entity purges, breaking matrix-deploy variant
|
|
# transitions (stale token slips past the empty-token guard in
|
|
# sys-front-inj-matomo and the next round 502s on the public matomo URL).
|
|
#
|
|
# Expects:
|
|
# INFINITO_APPS (required) e.g. "web-app-nextcloud" or "web-app-keycloak,web-app-matomo"
|
|
#
|
|
# Designed to be invoked from the host via:
|
|
# docker exec -e INFINITO_APPS="${INFINITO_APPS}" <container> \
|
|
# bash ${INFINITO_SRC_DIR}/scripts/container/purge/apps.sh # nocheck: self-path-reference
|
|
|
|
set -euo pipefail
|
|
: "${INFINITO_SRC_DIR:?INFINITO_SRC_DIR must be set by the container environment}"
|
|
cd "${INFINITO_SRC_DIR}"
|
|
|
|
: "${INFINITO_APPS:?INFINITO_APPS is not set (e.g. INFINITO_APPS=web-app-nextcloud)}"
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
ENTITY_DIR="${SCRIPT_DIR}/entity"
|
|
|
|
apps_raw="${INFINITO_APPS//,/ }"
|
|
read -r -a apps <<<"${apps_raw}"
|
|
|
|
if [[ "${#apps[@]}" -lt 1 ]]; then
|
|
echo "!!! WARNING: INFINITO_APPS is empty after parsing: skipping purge"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -f "scripts/meta/env/load.sh" ]]; then
|
|
# shellcheck source=scripts/meta/env/load.sh
|
|
source "scripts/meta/env/load.sh"
|
|
fi
|
|
python_bin="${PYTHON:-python3}"
|
|
|
|
declare -A seen_entities=()
|
|
entities=()
|
|
|
|
for app in "${apps[@]}"; do
|
|
[[ -n "${app}" ]] || continue
|
|
|
|
entity="$("${python_bin}" -c 'from utils.roles.entity_name import get_entity_name; import sys; print(get_entity_name(sys.argv[1]) or "")' "${app}")"
|
|
|
|
if [[ -z "${entity}" ]]; then
|
|
echo "!!! WARNING: could not derive entity from INFINITO_APPS=${app}: skipping"
|
|
continue
|
|
fi
|
|
|
|
if [[ -z "${seen_entities[${entity}]:-}" ]]; then
|
|
seen_entities[${entity}]=1
|
|
entities+=("${entity}")
|
|
echo ">>> Derived entity from INFINITO_APPS=${app}: ${entity}"
|
|
fi
|
|
done
|
|
|
|
echo ">>> Wiping token-store entries for: ${apps[*]}"
|
|
"${python_bin}" -m utils.cleanup.tokens "${apps[@]}" || true
|
|
|
|
echo ">>> Wiping databases.csv entries for: ${apps[*]}"
|
|
"${python_bin}" -m utils.cleanup.databases_csv "${apps[@]}" || true
|
|
|
|
if [[ "${#entities[@]}" -lt 1 ]]; then
|
|
echo "!!! WARNING: no valid entities found: skipping entity purge"
|
|
exit 0
|
|
fi
|
|
|
|
for entity in "${entities[@]}"; do
|
|
echo
|
|
echo ">>> Purging entity: ${entity}"
|
|
bash "${ENTITY_DIR}/nginx.sh" "${entity}" || true
|
|
if [[ -d "/opt/compose/${entity}" ]]; then
|
|
bash "${ENTITY_DIR}/db.sh" "${entity}" || true
|
|
bash "${ENTITY_DIR}/compose.sh" "${entity}" || true
|
|
bash "${ENTITY_DIR}/dir.sh" "${entity}" || true
|
|
bash "${ENTITY_DIR}/network.sh" "${entity}" || true
|
|
else
|
|
echo ">>> /opt/compose/${entity} not present — skipping compose/db/dir/network purges"
|
|
fi
|
|
done
|
|
|
|
if docker info >/dev/null 2>&1; then
|
|
docker network prune -f
|
|
else
|
|
echo ">>> docker daemon not reachable — skipping 'docker network prune'"
|
|
fi
|