#!/usr/bin/env bash # ensure-apt-deploy-test-containers.sh # # Four rootless podman containers on koopa (no nginx / no HTTPS): # # Fresh rebuild when repo fingerprint changes: # koopa-taler-deploy-test-apt-src-trixie # koopa-taler-deploy-test-apt-src-trixie-testing # # Persistent upgrade path (mytops upgrade.sh style): # koopa-taler-deploy-test-apt-src-trixie-upgrade # koopa-taler-deploy-test-apt-src-trixie-testing-upgrade # # Run as hernani on koopa. From elsewhere use run-aptdeploy.sh (ssh $DEPLOY_SSH or $APT_DEPLOY_SSH). # set -euo pipefail PODMAN_BIN="${APT_DEPLOY_PODMAN:-podman}" IMG="${APT_DEPLOY_BASE_IMAGE:-docker.io/library/debian:trixie}" STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/taler-apt-deploy" URI_BASE="${APT_DEPLOY_URI:-https://deb.taler.net/apt/debian}" mkdir -p "$STATE_DIR" # Packages we track for "new version → rebuild fresh containers" TRACK_PKGS="${APT_DEPLOY_TRACK_PKGS:-taler-merchant libtalermerchant libtalerexchange libdonau}" # Canonical four containers (keep). Everything else matching legacy names is removed. CANONICAL_CONTAINERS=( koopa-taler-deploy-test-apt-src-trixie koopa-taler-deploy-test-apt-src-trixie-testing koopa-taler-deploy-test-apt-src-trixie-upgrade koopa-taler-deploy-test-apt-src-trixie-testing-upgrade ) # Legacy / typo names from earlier iterations — always delete if present LEGACY_CONTAINERS=( koopa-taler-build-test-apt-trixie koopa-taler-build-test-apt-trixie-testing koopa-taler-deploy-test-apt-trixie koopa-taler-deploy-test-apt-trixie-testing koopa-taler-buidl-test-apt-trixie koopa-taler-buidl-test-apt-trixie-testing ) cleanup_legacy() { local n img echo "======== cleanup legacy containers / images ========" for n in "${LEGACY_CONTAINERS[@]}"; do if "$PODMAN_BIN" container exists "$n" 2>/dev/null; then echo "remove legacy container $n" "$PODMAN_BIN" rm -f "$n" 2>/dev/null || true fi # old setup images for that name if "$PODMAN_BIN" image exists "localhost/${n}:setup" 2>/dev/null; then echo "remove legacy image localhost/${n}:setup" "$PODMAN_BIN" rmi -f "localhost/${n}:setup" 2>/dev/null || true fi done # dangling setup images not in canonical set while read -r img; do [ -n "$img" ] || continue local base keep=0 base=${img#localhost/} base=${base%:setup} for n in "${CANONICAL_CONTAINERS[@]}"; do [ "$base" = "$n" ] && keep=1 && break done if [ "$keep" -eq 0 ] && [[ "$img" == localhost/koopa-taler-*-test-apt* ]]; then echo "remove non-canonical image $img" "$PODMAN_BIN" rmi -f "$img" 2>/dev/null || true fi done < <("$PODMAN_BIN" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -E 'localhost/koopa-taler-.*-test-apt' || true) } # Prefer single suite tree: ~/src/taler-monitoring vs ~/taler-monitoring # (legacy koopa-admin-log paths are not used). Keep the newer git clone; symlink the other. dedupe_suite_repos() { local a="$HOME/src/taler-monitoring" b="$HOME/taler-monitoring" local ta tb [ "${APT_DEPLOY_DEDUPE_REPOS:-1}" = "1" ] || return 0 if [ -d "$a/.git" ] && [ -d "$b/.git" ] && [ ! -L "$a" ] && [ ! -L "$b" ] \ && [ "$(readlink -f "$a" 2>/dev/null || echo "$a")" != "$(readlink -f "$b" 2>/dev/null || echo "$b")" ]; then ta=$(git -C "$a" log -1 --format=%ct 2>/dev/null || echo 0) tb=$(git -C "$b" log -1 --format=%ct 2>/dev/null || echo 0) echo "======== dedupe suite repos (both are git clones) ========" echo " $a HEAD_ts=$ta" echo " $b HEAD_ts=$tb" if [ "$ta" -ge "$tb" ]; then echo "keep $a · remove $b · symlink $b → $a" rm -rf "$b" ln -sfn "$a" "$b" else echo "keep $b · remove $a · symlink $a → $b" rm -rf "$a" mkdir -p "$(dirname "$a")" ln -sfn "$b" "$a" fi elif [ -d "$a/.git" ] && [ ! -e "$b" ]; then ln -sfn "$a" "$b" elif [ -d "$b/.git" ] && [ ! -e "$a" ]; then mkdir -p "$(dirname "$a")" ln -sfn "$b" "$a" fi } cleanup_legacy dedupe_suite_repos # --------------------------------------------------------------------------- # Repo fingerprint: Version: lines from suite Packages index # --------------------------------------------------------------------------- repo_pkg_version() { local suite=$1 pkg=$2 curl -fsS --max-time 45 \ "${URI_BASE}/dists/${suite}/main/binary-amd64/Packages" 2>/dev/null \ | awk -v p="$pkg" ' $0 == "Package: " p { want=1; next } /^Package: / { want=0 } want && /^Version: / { print $2; exit } ' } repo_fingerprint() { local suite=$1 pkg v line="" for pkg in $TRACK_PKGS; do v=$(repo_pkg_version "$suite" "$pkg" || true) [ -n "$v" ] || v="?" line="${line}${pkg}=${v};" done printf '%s' "$line" } container_fingerprint() { local name=$1 "$PODMAN_BIN" exec "$name" bash -lc ' for p in '"$TRACK_PKGS"'; do v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo missing) printf "%s=%s;" "$p" "$v" done ' 2>/dev/null || echo "missing" } # --------------------------------------------------------------------------- # Bootstrap: install base + taler source + packages (shared) # --------------------------------------------------------------------------- install_base_and_merchant() { local name=$1 suite=$2 srcfile=$3 "$PODMAN_BIN" exec -u root "$name" bash -lc ' set -euo pipefail export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq ca-certificates curl wget gnupg systemd systemd-sysv \ dbus procps iproute2 ' "$PODMAN_BIN" exec -u root "$name" bash -lc " set -euo pipefail export DEBIAN_FRONTEND=noninteractive mkdir -p /etc/apt/keyrings wget -q -O /etc/apt/keyrings/taler-systems.gpg https://taler.net/taler-systems.gpg cat >/etc/apt/sources.list.d/${srcfile} </dev/null systemctl enable taler-merchant.target 2>/dev/null systemctl start taler-merchant.target 2>/dev/null sleep 2 systemctl is-active taler-merchant-httpd.service 2>/dev/null || true ' || true } create_from_scratch() { local name=$1 suite=$2 srcfile=$3 echo "======== CREATE (fresh) $name Suites: $suite ========" "$PODMAN_BIN" pull "$IMG" >/dev/null "$PODMAN_BIN" rm -f "$name" 2>/dev/null || true # drop previous setup image so we do not accumulate duplicates "$PODMAN_BIN" rmi -f "localhost/${name}:setup" 2>/dev/null || true "$PODMAN_BIN" run -d --name "$name" --hostname "$name" "$IMG" sleep infinity install_base_and_merchant "$name" "$suite" "$srcfile" commit_and_systemd "$name" printf '%s\n' "$(repo_fingerprint "$suite")" >"$STATE_DIR/${name}.fp" printf '%s\n' "$(container_fingerprint "$name")" >"$STATE_DIR/${name}.installed" echo "created $name fp=$(cat "$STATE_DIR/${name}.fp")" } # --------------------------------------------------------------------------- # Upgrade path (mytops-admin-log upgrade.sh merchant packages, no restic/nginx) # Prints a markdown-ish table of before → after versions. # --------------------------------------------------------------------------- upgrade_inside() { local name=$1 local before after pkg v0 v1 echo "======== UPGRADE $name (mytops-style apt install + upgrade) ========" before=$("$PODMAN_BIN" exec "$name" bash -lc ' for p in taler-merchant libtalermerchant libtalerexchange libdonau libgnunet \ taler-merchant-webui taler-merchant-typst taler-terms-generator; do v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo "-") printf "%s %s\n" "$p" "$v" done ' 2>/dev/null || true) # capture upgrade output set +e "$PODMAN_BIN" exec -u root "$name" bash -lc ' set -euo pipefail export DEBIAN_FRONTEND=noninteractive systemctl stop taler-merchant.target 2>/dev/null || true systemctl stop taler-merchant-donaukeyupdate.service 2>/dev/null || true apt-get update -qq apt-get install -y \ taler-merchant \ taler-merchant-typst \ taler-merchant-webui \ taler-terms-generator \ || true if dpkg -l challenger-httpd 2>/dev/null | grep -q "^ii"; then apt-get remove -y challenger-httpd || true fi apt-get upgrade -y systemctl daemon-reload 2>/dev/null || true systemctl enable taler-merchant.target 2>/dev/null || true # try dbconfig if present (may no-op without full config) command -v taler-merchant-dbconfig >/dev/null && taler-merchant-dbconfig 2>/dev/null || true systemctl reset-failed "taler-merchant*" 2>/dev/null || true systemctl start taler-merchant.target 2>/dev/null || true sleep 2 ' 2>&1 | tee "$STATE_DIR/${name}.upgrade.log" | tail -30 set -e after=$("$PODMAN_BIN" exec "$name" bash -lc ' for p in taler-merchant libtalermerchant libtalerexchange libdonau libgnunet \ taler-merchant-webui taler-merchant-typst taler-terms-generator; do v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo "-") printf "%s %s\n" "$p" "$v" done ' 2>/dev/null || true) echo echo "| package | before | after | change |" echo "|---------|--------|-------|--------|" while read -r pkg v0; do [ -n "${pkg:-}" ] || continue v1=$(printf '%s\n' "$after" | awk -v p="$pkg" '$1==p {print $2; exit}') v1=${v1:--} if [ "$v0" = "$v1" ]; then ch="=" else ch="UPGRADED" fi echo "| $pkg | $v0 | $v1 | $ch |" done <<<"$before" printf '%s\n' "$(container_fingerprint "$name")" >"$STATE_DIR/${name}.installed" } # --------------------------------------------------------------------------- # Fresh mode: recreate if missing / not running / repo fp changed # --------------------------------------------------------------------------- ensure_fresh() { local name=$1 suite=$2 srcfile=$3 local want have running want=$(repo_fingerprint "$suite") echo "repo fingerprint [$suite]: $want" if ! "$PODMAN_BIN" container exists "$name" 2>/dev/null; then create_from_scratch "$name" "$suite" "$srcfile" return fi running=$("$PODMAN_BIN" inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo false) have=$(container_fingerprint "$name") echo "installed in $name: $have" if [ "$running" != "true" ] \ || ! "$PODMAN_BIN" exec "$name" bash -lc 'command -v taler-merchant-httpd' >/dev/null 2>&1 \ || [ "$want" != "$have" ]; then echo "rebuild $name (running=$running fp_match=$([ "$want" = "$have" ] && echo yes || echo NO))" create_from_scratch "$name" "$suite" "$srcfile" else echo "OK $name up-to-date (fresh container matches repo)" fi } # --------------------------------------------------------------------------- # Upgrade mode: create once, then always upgrade_inside # --------------------------------------------------------------------------- ensure_upgrade() { local name=$1 suite=$2 srcfile=$3 local running if ! "$PODMAN_BIN" container exists "$name" 2>/dev/null; then create_from_scratch "$name" "$suite" "$srcfile" echo "(initial install for upgrade-track container $name)" return fi running=$("$PODMAN_BIN" inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo false) if [ "$running" != "true" ]; then echo "start $name" "$PODMAN_BIN" start "$name" || { echo "recreate broken $name" create_from_scratch "$name" "$suite" "$srcfile" return } sleep 3 fi if ! "$PODMAN_BIN" exec "$name" bash -lc 'command -v taler-merchant-httpd' >/dev/null 2>&1; then echo "recreate $name (no httpd after start)" create_from_scratch "$name" "$suite" "$srcfile" return fi upgrade_inside "$name" } MODE="${1:-all}" # all | fresh | upgrade case "$MODE" in all|fresh) ensure_fresh koopa-taler-deploy-test-apt-src-trixie trixie taler.sources ensure_fresh koopa-taler-deploy-test-apt-src-trixie-testing trixie-testing taler-testing.sources ;;& all|upgrade) ensure_upgrade koopa-taler-deploy-test-apt-src-trixie-upgrade trixie taler.sources ensure_upgrade koopa-taler-deploy-test-apt-src-trixie-testing-upgrade trixie-testing taler-testing.sources ;; *) echo "usage: $0 [all|fresh|upgrade]" >&2 exit 2 ;; esac echo "done ensure-apt-deploy-test-containers mode=$MODE"