Per operator: drop the hourly cc-ci-reap-dev-deploys systemd timer; instead run the dev-* reaper at the START (Step 0, alongside the orphan sweep) and END (new step 4b) of each /upgrade-all run, with THRESHOLD=0 (the run is quiescent then, so clear all dev-* unconditionally). The reaper keeps its safe default (4h) for ad-hoc use. Step-2b mandatory teardown is unchanged (primary mechanism); this is the backstop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.5 KiB
Bash
Executable File
51 lines
2.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Reap LEAKED step-2b dev deploys on the cc-ci server.
|
|
#
|
|
# /recipe-upgrade step 2b deploys a recipe under a `dev-<recipe>` domain to debug an upgrade with live
|
|
# logs, and REQUIRES the agent to tear it down when done. This is the backstop for a missed teardown
|
|
# (agent crashed / killed / abandoned mid-loop): it removes `dev-*` Swarm stacks (+ their dangling
|
|
# volumes). **Invoked at the START and END of an `/upgrade-all` run** (with `THRESHOLD=0` — by then the
|
|
# run is quiescent, so any `dev-*` is leftover and removed unconditionally).
|
|
#
|
|
# SAFE — scoped to the `dev-` naming convention only: CI per-run stacks (`<recipe[:4]>-<hash>`),
|
|
# `warm-*` canonicals, and infra are never `dev-*`, so never matched. Volume cleanup uses
|
|
# `dangling=true`, so a still-attached volume is never removed.
|
|
#
|
|
# `THRESHOLD` (seconds, default 14400=4h) only removes a `dev-*` stack whose newest service has been
|
|
# idle longer than it — so an ad-hoc run while a dev loop is ACTIVE won't kill it (an active loop keeps
|
|
# redeploying, refreshing UpdatedAt). `/upgrade-all` passes `THRESHOLD=0` at run start/end to clear ALL
|
|
# leftover dev deploys. Run ON the cc-ci host: ssh cc-ci 'THRESHOLD=0 bash -s' < reap-dev-deploys.sh
|
|
set -uo pipefail
|
|
export PATH=/run/current-system/sw/bin:$PATH
|
|
|
|
THRESHOLD="${THRESHOLD:-14400}" # default 4h (safe for ad-hoc use); /upgrade-all passes 0 at start/end
|
|
now=$(date +%s)
|
|
reaped=0
|
|
|
|
mapfile -t STACKS < <(docker stack ls --format '{{.Name}}' 2>/dev/null | grep -E '^dev-' || true)
|
|
for s in "${STACKS[@]}"; do
|
|
[ -z "$s" ] && continue
|
|
newest=0
|
|
for sid in $(docker service ls --filter "label=com.docker.stack.namespace=$s" -q 2>/dev/null); do
|
|
ua=$(docker service inspect "$sid" --format '{{.UpdatedAt}}' 2>/dev/null)
|
|
e=$(date -d "$ua" +%s 2>/dev/null || echo 0)
|
|
[ "$e" -gt "$newest" ] && newest="$e"
|
|
done
|
|
age=$(( now - newest ))
|
|
if [ "$newest" -gt 0 ] && [ "$age" -ge "$THRESHOLD" ]; then
|
|
echo "reap: dev stack '$s' idle ${age}s (>= ${THRESHOLD}s) — removing"
|
|
docker stack rm "$s" >/dev/null 2>&1 || true
|
|
reaped=$((reaped + 1))
|
|
else
|
|
echo "keep: dev stack '$s' active (last update ${age}s ago)"
|
|
fi
|
|
done
|
|
|
|
if [ "$reaped" -gt 0 ]; then
|
|
sleep 8 # let removed stacks' services drain so their volumes become dangling
|
|
for v in $(docker volume ls -qf dangling=true 2>/dev/null | grep -E '^dev-' || true); do
|
|
docker volume rm "$v" >/dev/null 2>&1 && echo "reap: removed leaked volume $v"
|
|
done
|
|
fi
|
|
echo "reap-dev-deploys: ${reaped} stale dev deploy(s) removed"
|