Compare commits

...

2 Commits

Author SHA1 Message Date
Eric Gullickson
82a543b250 Merge branch 'main' of 172.30.1.72:egullickson/motovaultpro
All checks were successful
Deploy to Staging / Build Images (push) Successful in 24s
Deploy to Staging / Deploy to Staging (push) Successful in 36s
Deploy to Staging / Verify Staging (push) Successful in 6s
Deploy to Staging / Notify Staging Ready (push) Successful in 5s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Mirror Base Images / Mirror Base Images (push) Successful in 24s
2026-01-04 20:05:22 -06:00
Eric Gullickson
4e43f63f4b feat: purge scripts for CI/CD artifacts 2026-01-04 20:05:17 -06:00
2 changed files with 282 additions and 0 deletions

91
scripts/ci/purge-action-runs.sh Executable file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -euo pipefail
# Defaults (override via CLI flags)
GITEA_BASE_URL="https://git.motovaultpro.com"
TOKEN=""
OWNER="egullickson"
REPO="motovaultpro"
PER_PAGE=50
usage() {
cat <<EOF
Usage:
$0 --token=YOUR_TOKEN [--base-url=https://git.motovaultpro.com] [--owner=egullickson] [--repo=motovaultpro] [--per-page=50]
Examples:
# Delete all Actions runs in egullickson/motovaultpro
$0 --token=XXXX
# Different repo
$0 --token=XXXX --owner=someone --repo=otherrepo
# Different server + page size
$0 --token=XXXX --base-url=https://git.example.com --per-page=100
EOF
}
# --- CLI parsing ---
for arg in "$@"; do
case "$arg" in
--token=*) TOKEN="${arg#*=}" ;;
--base-url=*) GITEA_BASE_URL="${arg#*=}" ;;
--owner=*) OWNER="${arg#*=}" ;;
--repo=*) REPO="${arg#*=}" ;;
--per-page=*) PER_PAGE="${arg#*=}" ;;
-h|--help) usage; exit 0 ;;
*)
echo "ERROR: Unknown argument: $arg"
usage
exit 1
;;
esac
done
if [[ -z "$TOKEN" ]]; then
echo "ERROR: --token= is required"
usage
exit 1
fi
# Basic validation
if ! [[ "$PER_PAGE" =~ ^[0-9]+$ ]]; then
echo "ERROR: --per-page must be a number"
exit 1
fi
api() {
curl -fsS \
-H "Authorization: token ${TOKEN}" \
-H "Accept: application/json" \
"$@"
}
page=1
deleted=0
while :; do
echo "Fetching page ${page}..."
json="$(api "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/actions/runs?limit=${PER_PAGE}&page=${page}")"
# Try both common shapes:
# - {"workflow_runs":[...]}
# - {"runs":[...]}
ids="$(echo "$json" | jq -r '(.workflow_runs // .runs // []) | .[].id')"
if [[ -z "${ids}" ]]; then
echo "No more runs found."
break
fi
while read -r id; do
[[ -z "$id" ]] && continue
echo "Deleting run id=${id}"
api -X DELETE "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/actions/runs/${id}" >/dev/null
deleted=$((deleted + 1))
done <<< "${ids}"
page=$((page + 1))
done
echo "Done. Deleted ${deleted} runs."

View File

@@ -0,0 +1,191 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# Defaults (override via CLI flags)
# =============================================================================
REGISTRY="https://git.motovaultpro.com"
USERNAME="egullickson"
TOKEN=""
KEEP_TAG="latest"
DRY_RUN=1
REPOS=(
"egullickson/frontend"
"egullickson/backend"
)
# =============================================================================
need() { command -v "$1" >/dev/null || { echo "ERROR: missing dependency: $1"; exit 1; }; }
need curl
need jq
usage() {
cat <<EOF
Usage:
$0 --token=YOUR_PAT [--user=egullickson] [--registry=https://git.motovaultpro.com]
[--keep-tag=latest] [--dry-run|--no-dry-run]
Examples:
# Preview
$0 --token=XXXX --dry-run
# Actually delete
$0 --token=XXXX --no-dry-run
Notes:
- Deletes all tags except KEEP_TAG for repos:
${REPOS[*]}
- Protects KEEP_TAG by digest (won't delete anything pointing to KEEP_TAG digest).
EOF
}
# --- CLI parsing -------------------------------------------------------------
for arg in "$@"; do
case "$arg" in
--token=*) TOKEN="${arg#*=}" ;;
--user=*) USERNAME="${arg#*=}" ;;
--registry=*) REGISTRY="${arg#*=}" ;;
--keep-tag=*) KEEP_TAG="${arg#*=}" ;;
--dry-run) DRY_RUN=1 ;;
--no-dry-run) DRY_RUN=0 ;;
-h|--help) usage; exit 0 ;;
*)
echo "ERROR: Unknown argument: $arg"
usage
exit 1
;;
esac
done
if [[ -z "$TOKEN" ]]; then
echo "ERROR: --token= is required"
usage
exit 1
fi
ACCEPT_MANIFEST="application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json"
get_bearer_token() {
local repo="$1"
local scope="repository:${repo}:pull,delete"
curl -fsS -u "${USERNAME}:${TOKEN}" \
"${REGISTRY}/v2/token?service=container_registry&scope=${scope}" \
| jq -r '.token // .access_token // empty'
}
auth_header_for_repo() {
local repo="$1"
local t
t="$(get_bearer_token "$repo")"
if [[ -z "$t" ]]; then
echo "ERROR: Could not obtain bearer token for ${repo} (check PAT permissions)."
exit 1
fi
echo "Authorization: Bearer ${t}"
}
tags_list() {
local repo="$1" auth="$2"
curl -fsS -H "$auth" "${REGISTRY}/v2/${repo}/tags/list"
}
digest_for_tag() {
local repo="$1" tag="$2" auth="$3"
curl -fsSI \
-H "$auth" \
-H "Accept: ${ACCEPT_MANIFEST}" \
"${REGISTRY}/v2/${repo}/manifests/${tag}" \
| tr -d '\r' \
| awk 'BEGIN{IGNORECASE=1} /^docker-content-digest:/ {print $2; exit}'
}
delete_digest() {
local repo="$1" digest="$2" auth="$3"
if [[ "$DRY_RUN" == "1" ]]; then
echo "DRY_RUN: DELETE ${REGISTRY}/v2/${repo}/manifests/${digest}"
return 0
fi
curl -fsS -X DELETE -H "$auth" \
"${REGISTRY}/v2/${repo}/manifests/${digest}" >/dev/null
}
purge_repo_keep_tag() {
local repo="$1"
echo "== Purging ${repo} (keep ${KEEP_TAG}) =="
local auth
auth="$(auth_header_for_repo "$repo")"
local json tags
json="$(tags_list "$repo" "$auth")"
tags="$(echo "$json" | jq -r '.tags[]?' | sort || true)"
if [[ -z "$tags" ]]; then
echo "No tags found."
echo
return 0
fi
if ! echo "$tags" | grep -qx "${KEEP_TAG}"; then
echo "WARN: No '${KEEP_TAG}' tag found; refusing to purge ${repo}."
echo "$tags" | sed 's/^/ - /'
echo
return 0
fi
local keep_digest
keep_digest="$(digest_for_tag "$repo" "$KEEP_TAG" "$auth" || true)"
if [[ -z "$keep_digest" ]]; then
echo "ERROR: Could not resolve digest for ${repo}:${KEEP_TAG}"
echo "Tip: curl -vI -H \"$auth\" ${REGISTRY}/v2/${repo}/manifests/${KEEP_TAG}"
echo
return 1
fi
echo "${KEEP_TAG} digest: ${keep_digest}"
# Collect digests referenced by non-KEEP_TAG tags (skipping those equal to keep_digest)
local digests=()
while read -r tag; do
[[ -z "$tag" || "$tag" == "$KEEP_TAG" ]] && continue
local d
d="$(digest_for_tag "$repo" "$tag" "$auth" || true)"
if [[ -z "$d" ]]; then
echo "WARN: couldn't resolve digest for tag=${tag} (skipping)"
continue
fi
if [[ "$d" == "$keep_digest" ]]; then
echo "SKIP: tag=${tag} points to ${KEEP_TAG} digest (deleting would remove ${KEEP_TAG} too)"
continue
fi
digests+=("$d")
done <<< "$tags"
if [[ "${#digests[@]}" -eq 0 ]]; then
echo "Nothing to delete."
echo
return 0
fi
echo "Will delete (deduped) manifest(s) (and all tags pointing to them)."
printf '%s\n' "${digests[@]}" | sort -u | while read -r digest; do
[[ -z "$digest" ]] && continue
echo "Deleting digest: $digest"
if ! delete_digest "$repo" "$digest" "$auth"; then
echo "WARN: delete failed for $digest"
fi
done
echo "Done."
echo
}
for repo in "${REPOS[@]}"; do
purge_repo_keep_tag "$repo"
done
echo "Complete."