#!/usr/bin/env bash # aptly-mirror-refresh — periodic content refresh for mirrors declared in # state.yaml: fetch the mirror, snapshot it, switch every publish target that # references it, then prune old snapshots. Intended to be run by an external # scheduler (cron, a Compose --profile, a k8s CronJob you wire up yourself — # this chart does not ship one, see docs/versioning.md "Nicht im Scope"). # # Only touches mirrors listed in --mirrors (or all mirrors in state.yaml if # omitted). Safe to run concurrently with aptly-reconcile against the same # instance (both go through the REST API); NOT safe to run two instances of # this script concurrently against the same mirror, since both would try to # update+snapshot it at once — use your scheduler's concurrency policy # (Gitea/k8s CronJob: concurrencyPolicy=Forbid) to prevent that. # # Env: # APTLY_STATE_FILE path to state.yaml (required) # APTLY_MIRRORS comma-separated mirror names to refresh (default: all) # APTLY_KEEP_SNAPSHOTS how many historical snapshots to retain per mirror (default: 5) # APTLY_SNAPSHOT_PREFIX snapshot name template prefix (default: mirror name) # plus lib/common.sh's APTLY_URL / auth vars set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=rootfs/usr/local/bin/lib/common.sh source "${SCRIPT_DIR}/lib/common.sh" require_cmd curl jq yq date : "${APTLY_STATE_FILE:?APTLY_STATE_FILE must point at a state.yaml}" KEEP="${APTLY_KEEP_SNAPSHOTS:-5}" STATE_JSON="$(yq -o=json '.' "$APTLY_STATE_FILE")" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" wanted_mirrors=() if [[ -n "${APTLY_MIRRORS:-}" ]]; then IFS=',' read -ra wanted_mirrors <<< "$APTLY_MIRRORS" fi wants() { local name="$1" [[ "${#wanted_mirrors[@]}" -eq 0 ]] && return 0 for m in "${wanted_mirrors[@]}"; do [[ "$m" == "$name" ]] && return 0; done return 1 } api_ready failures=0 mirror_count="$(jq '(.mirrors // []) | length' <<<"$STATE_JSON")" for ((i=0; i '${snap_name}'" resp="$(api POST "/api/mirrors/${name}/snapshots" -H 'Content-Type: application/json' \ -d "$(jq -n --arg n "$snap_name" '{Name: $n}')")" \ || { warn "snapshot failed for ${name}"; failures=$((failures+1)); continue; } # Switch every publish target in state.yaml whose sources reference this mirror's snapshots. publish_count="$(jq '(.publish // []) | length' <<<"$STATE_JSON")" for ((j=0; j 0' <<<"$p")" [[ "$refs_mirror" == "true" ]] || continue prefix="$(jq -r '.prefix // ""' <<<"$p")" dist="$(jq -r '.distribution' <<<"$p")" escaped_prefix="$(api_prefix "$prefix")" component="$(jq -r '.sources[0].component // "main"' <<<"$p")" log "switching publish prefix='${prefix}' distribution='${dist}' -> snapshot '${snap_name}'" resp="$(api PUT "/api/publish/${escaped_prefix}/${dist}" -H 'Content-Type: application/json' \ -d "$(jq -n --arg n "$snap_name" --arg c "$component" '{Snapshots: [{Name: $n, Component: $c}]}')")" \ || { warn "publish switch failed for ${prefix}/${dist}"; failures=$((failures+1)); continue; } task_id="$(jq -r '.ID // empty' <<<"$resp")" wait_task "$task_id" || { warn "publish switch task failed for ${prefix}/${dist}"; failures=$((failures+1)); } done # Prune old snapshots for this mirror beyond APTLY_KEEP_SNAPSHOTS, oldest first. all_snaps="$(api GET "/api/snapshots" | jq -r --arg m "$name" \ '[.[] | select(.Name | startswith($m + "-"))] | sort_by(.CreatedAt) | .[].Name')" total="$(wc -l <<<"$all_snaps" | tr -d ' ')" to_prune=$(( total > KEEP ? total - KEEP : 0 )) if [[ "$to_prune" -gt 0 ]]; then log "pruning ${to_prune} old snapshot(s) for '${name}' (keeping ${KEEP})" head -n "$to_prune" <<<"$all_snaps" | while IFS= read -r s; do [[ -z "$s" ]] && continue api DELETE "/api/snapshots/${s}" >/dev/null 2>&1 \ || warn "could not drop snapshot ${s} (likely still referenced by a publish — will retry next run)" done fi done if [[ "$failures" -gt 0 ]]; then die "${failures} mirror(s) failed to refresh — see warnings above" fi log "mirror refresh complete"