#!/usr/bin/env bash # Fill landing stack-bar package versions from live CTR dpkg (not hand-edited HTML). # # Reads: configs/shared/landing-stack.json (package lists + vanilla href) # Writes: configs/{bank,exchange,merchant}-landing/index.html ( block) # configs/shared/landing-stack-versions.json (last dpkg snapshot) # # Usage (from koopa-admin-log root, laptop or koopa): # ./scripts/taler-landing/stamp-landing-stack.sh # ./scripts/taler-landing/stamp-landing-stack.sh --ctr goa-regio-ng --ssh hernani@192.168.100.95 # ./scripts/taler-landing/stamp-landing-stack.sh bank exchange # # After this, bump footer dates with: # ./scripts/taler-landing/stamp-landing-version.sh --bump --all # Then deploy (unified vanilla CTR): # LANDING_CTR=goa-regio-ng ./scripts/taler-landing/deploy-landings.sh set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" STACK_FILE="$ROOT/configs/shared/landing-stack.json" VERS_OUT="$ROOT/configs/shared/landing-stack-versions.json" CTR="${LANDING_CTR:-goa-regio-ng}" SSH_HOST="${LANDING_SSH:-hernani@192.168.100.95}" USE_SSH=1 sites=() while [ $# -gt 0 ]; do case "$1" in --ctr) CTR="${2:-}"; shift 2 ;; --ssh) SSH_HOST="${2:-}"; USE_SSH=1; shift 2 ;; --local) USE_SSH=0; shift ;; bank|exchange|merchant) sites+=("$1"); shift ;; -h|--help) sed -n '2,20p' "$0" exit 0 ;; *) echo "unknown arg: $1" >&2; exit 2 ;; esac done if [ "${#sites[@]}" -eq 0 ]; then sites=(bank exchange merchant) fi [ -f "$STACK_FILE" ] || { echo "missing $STACK_FILE" >&2; exit 1; } # Collect unique package names from JSON for the selected sites mapfile -t PKGS < <(python3 - "$STACK_FILE" "${sites[@]}" <<'PY' import json, sys data = json.load(open(sys.argv[1], encoding="utf-8")) sites = sys.argv[2:] seen = [] for s in sites: for p in data["sites"][s]["packages"]: if p not in seen: seen.append(p) print("\n".join(seen)) PY ) if [ "${#PKGS[@]}" -eq 0 ]; then echo "no packages configured" >&2 exit 1 fi pkg_args=$(printf '%q ' "${PKGS[@]}") query_cmd="podman exec $(printf '%q' "$CTR") dpkg-query -W -f='\${Package}=\${Version}\n' ${pkg_args}" if [ "$USE_SSH" = 1 ]; then echo "dpkg ← ${SSH_HOST} ctr=${CTR}" # shellcheck disable=SC2086 raw=$(ssh -o BatchMode=yes -o ConnectTimeout=12 "$SSH_HOST" "podman exec $(printf '%q' "$CTR") dpkg-query -W -f='\${Package}=\${Version}\n' ${pkg_args}") else echo "dpkg ← local ctr=${CTR}" # shellcheck disable=SC2086 raw=$(podman exec "$CTR" dpkg-query -W -f='${Package}=${Version}\n' "${PKGS[@]}") fi python3 - "$STACK_FILE" "$VERS_OUT" "$ROOT" "$CTR" "$SSH_HOST" "$raw" "${sites[@]}" <<'PY' import json, re, sys from pathlib import Path from datetime import datetime from zoneinfo import ZoneInfo stack_path, vers_out, root, ctr, ssh_host, raw = sys.argv[1:7] sites = sys.argv[7:] stack = json.load(open(stack_path, encoding="utf-8")) tz = ZoneInfo("Europe/Zurich") now = datetime.now(tz) iso = now.isoformat(timespec="seconds") human = now.strftime("%Y-%m-%d %H:%M %Z") versions = {} for line in raw.splitlines(): line = line.strip() if not line or "=" not in line: continue pkg, ver = line.split("=", 1) # display upstream-ish: drop Debian revision (1.6.10-0+trixie → 1.6.10) short = re.sub(r"-[0-9].*$", "", ver) versions[pkg] = {"dpkg": ver, "display": short} missing = [] for site in sites: for pkg in stack["sites"][site]["packages"]: if pkg not in versions: missing.append(pkg) if missing: raise SystemExit(f"dpkg missing packages: {', '.join(sorted(set(missing)))}") vanilla_href = stack["vanilla_href"] vanilla_title = stack.get("vanilla_title", "vanilla") sep = '·' def render_block(site: str) -> str: meta = stack["sites"][site] parts = [ f' ', f'

', f' stack{sep}', f' vanilla', ] for pkg in meta["packages"]: disp = versions[pkg]["display"] parts.append(f" {sep}") parts.append(f" {pkg} {disp}") parts.append("

") parts.append(" ") return "\n".join(parts) + "\n" site_html = { "bank": Path(root) / "configs/bank-landing/index.html", "exchange": Path(root) / "configs/exchange-landing/index.html", "merchant": Path(root) / "configs/merchant-landing/index.html", } marker_re = re.compile(r"[ \t]*.*?\n?", re.S) bare_re = re.compile( r"[ \t]*

]*>.*?

\n?", re.S, ) for site in sites: path = site_html[site] text = path.read_text(encoding="utf-8") block = render_block(site) if marker_re.search(text): text = marker_re.sub(block, text, count=1) elif bare_re.search(text): text = bare_re.sub(block, text, count=1) else: raise SystemExit(f"no stack-bar in {path}") path.write_text(text, encoding="utf-8") print(f"stack → {path.relative_to(root)} ({site})") out = { "updated_iso": iso, "updated_human": human, "ctr": ctr, "ssh": ssh_host if ssh_host else None, "packages": versions, "sites": { site: { "packages": { pkg: versions[pkg]["display"] for pkg in stack["sites"][site]["packages"] } } for site in ("bank", "exchange", "merchant") if site in stack["sites"] }, } Path(vers_out).write_text(json.dumps(out, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") print(f"versions → {Path(vers_out).relative_to(root)}") print(f"OK stack stamped: {' '.join(sites)} @ {human}") PY echo "OK stamp-landing-stack: ${sites[*]} ← ${CTR}"