gitea-image-prune.sh: auto-discover credentials from git
When GITEA_TOKEN/--token-file aren't set and the script runs inside a Gitea-host clone, reuse git's stored credential: the token embedded in the remote URL, else a configured credential helper (git credential fill, no prompting). Switch to HTTP Basic auth (user:token) when a username is known so both API tokens and the embedded-URL/helper credential work; keep the token header for a bare GITEA_TOKEN. Banner reports the source. Live-verified: env token + git credential helper (as kisfenyo) both list and resolve OCI sizes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+53
-7
@@ -42,6 +42,12 @@
|
||||
# and their blobs are freed by the daily "@midnight" run (or on the next Gitea
|
||||
# restart — RUN_AT_START was enabled in app.ini on 2026-06-17).
|
||||
#
|
||||
# CREDENTIALS (in order: GITEA_TOKEN env -> --token-file -> git's stored
|
||||
# credential for the Gitea host: the remote URL's embedded token, else a
|
||||
# configured credential helper). Auto-discovery lets you just run the script
|
||||
# inside a clone with no token fuss — but a *git* credential may only have repo
|
||||
# scope; if so, a 403 will name the missing package/admin scope.
|
||||
#
|
||||
# REQUIRED TOKEN (env GITEA_TOKEN, or --token-file). Must belong to a Gitea
|
||||
# site-admin user. Minimal fine-grained scopes (Gitea 1.26):
|
||||
# read:package — list packages / versions / files (list, prune planning)
|
||||
@@ -149,16 +155,55 @@ for bin in curl jq; do
|
||||
done
|
||||
|
||||
# Token: --token-file beats env. Never printed.
|
||||
GITEA_USER="${GITEA_USER:-}" # set when credentials come with a username (-> Basic auth)
|
||||
CRED_SRC="GITEA_TOKEN env"
|
||||
|
||||
if [[ -n "$TOKEN_FILE" ]]; then
|
||||
[[ -r "$TOKEN_FILE" ]] || { error "--token-file not readable: $TOKEN_FILE"; exit 1; }
|
||||
GITEA_TOKEN="$(tr -d ' \t\r\n' < "$TOKEN_FILE")"
|
||||
CRED_SRC="--token-file"
|
||||
fi
|
||||
|
||||
# Auto-discover from git when no explicit token: reuse the credential git already
|
||||
# has for the Gitea host (embedded remote URL, or a configured credential helper).
|
||||
# Convenient, but a *git* token may lack package/admin scopes — a 403 will say so.
|
||||
discover_git_credential() {
|
||||
command -v git &>/dev/null || return 1
|
||||
git rev-parse --is-inside-work-tree &>/dev/null || return 1
|
||||
local host="${GITEA_URL#*://}"; host="${host%%/*}"
|
||||
# (1) credentials embedded in the remote URL (https://user:token@host/...)
|
||||
local url; url="$(git remote get-url origin 2>/dev/null || true)"
|
||||
if [[ "$url" == *"$host"* && "$url" =~ ^https?://([^:/@]+):([^@/]+)@ ]]; then
|
||||
GITEA_USER="${BASH_REMATCH[1]}"; GITEA_TOKEN="${BASH_REMATCH[2]}"; CRED_SRC="git remote URL"; return 0
|
||||
fi
|
||||
# (2) a configured credential helper (store / cache / manager). Never prompt.
|
||||
if git config --get credential.helper &>/dev/null; then
|
||||
local out user pass
|
||||
out="$(printf 'protocol=https\nhost=%s\n\n' "$host" | GIT_TERMINAL_PROMPT=0 git credential fill 2>/dev/null || true)"
|
||||
pass="$(printf '%s\n' "$out" | sed -n 's/^password=//p' | head -1)"
|
||||
user="$(printf '%s\n' "$out" | sed -n 's/^username=//p' | head -1)"
|
||||
if [[ -n "$pass" ]]; then GITEA_TOKEN="$pass"; GITEA_USER="${user:-$OWNER}"; CRED_SRC="git credential helper"; return 0; fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ -z "$GITEA_TOKEN" ]]; then
|
||||
error "No token. Set GITEA_TOKEN env or pass --token-file <path>."
|
||||
error "Needs a site-admin token with read/write:package + read/write:admin."
|
||||
discover_git_credential || true
|
||||
fi
|
||||
|
||||
if [[ -z "$GITEA_TOKEN" ]]; then
|
||||
error "No credentials. Set GITEA_TOKEN env, pass --token-file <path>, or run inside"
|
||||
error "a clone of a ${GITEA_URL#*://} repo whose git credentials are configured."
|
||||
error "Needs a site-admin token with read/write:package + read/write:admin scope."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Auth method: Basic (user:token) when a username is known — works for both API
|
||||
# tokens and passwords; bare 'token' header otherwise. OCI /v2/ always uses Basic.
|
||||
declare -a AUTH
|
||||
if [[ -n "$GITEA_USER" ]]; then AUTH=(-u "${GITEA_USER}:${GITEA_TOKEN}"); else AUTH=(-H "Authorization: token ${GITEA_TOKEN}"); fi
|
||||
OCI_USER="${GITEA_USER:-$OWNER}"
|
||||
|
||||
# Default audit log
|
||||
if [[ -z "$LOG_FILE" ]]; then
|
||||
LOG_FILE="${SCRIPT_DIR}/logs/gitea-prune-$(date +%Y-%m-%d).log"
|
||||
@@ -182,7 +227,7 @@ audit() { # free-form line -> audit log (token-free by construction)
|
||||
api_get() {
|
||||
local path="$1" out code
|
||||
out="$(mktemp)"
|
||||
code="$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
code="$(curl -sS "${AUTH[@]}" \
|
||||
-o "$out" -w '%{http_code}' "${GITEA_URL}${path}" || true)"
|
||||
if [[ ! "$code" =~ ^2[0-9][0-9]$ ]]; then
|
||||
error "GET ${path} -> HTTP ${code}"
|
||||
@@ -194,7 +239,7 @@ api_get() {
|
||||
|
||||
api_delete() {
|
||||
local path="$1" code
|
||||
code="$(curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
code="$(curl -sS -X DELETE "${AUTH[@]}" \
|
||||
-o "$TMP/del.body" -w '%{http_code}' "${GITEA_URL}${path}" || true)"
|
||||
echo "$code"
|
||||
[[ "$code" == "204" ]]
|
||||
@@ -202,7 +247,7 @@ api_delete() {
|
||||
|
||||
api_post() {
|
||||
local path="$1" code
|
||||
code="$(curl -sS -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
code="$(curl -sS -X POST "${AUTH[@]}" \
|
||||
-o "$TMP/post.body" -w '%{http_code}' "${GITEA_URL}${path}" || true)"
|
||||
if [[ ! "$code" =~ ^2[0-9][0-9]$ ]]; then
|
||||
error "POST ${path} -> HTTP ${code}"
|
||||
@@ -215,7 +260,7 @@ api_post() {
|
||||
oci_get() {
|
||||
local name="$1" ref="$2" out code
|
||||
out="$(mktemp)"
|
||||
code="$(curl -sS -u "${OWNER}:${GITEA_TOKEN}" \
|
||||
code="$(curl -sS -u "${OCI_USER}:${GITEA_TOKEN}" \
|
||||
-H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json' \
|
||||
-o "$out" -w '%{http_code}' "${GITEA_URL}/v2/${OWNER}/${name}/manifests/${ref}" || true)"
|
||||
if [[ ! "$code" =~ ^2[0-9][0-9]$ ]]; then rm -f "$out"; return 1; fi
|
||||
@@ -348,7 +393,7 @@ find_orphans() {
|
||||
# echoes "ok <code>" on 2xx, "denied" on 403, "err <code>" otherwise; rc reflects.
|
||||
trigger_cleanup_cron() {
|
||||
local code
|
||||
code="$(curl -sS -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
code="$(curl -sS -X POST "${AUTH[@]}" \
|
||||
-o "$TMP/cron.body" -w '%{http_code}' \
|
||||
"${GITEA_URL}/api/v1/admin/cron/${CLEANUP_CRON}" || true)"
|
||||
if [[ "$code" =~ ^2[0-9][0-9]$ ]]; then echo "ok ${code}"; return 0; fi
|
||||
@@ -617,6 +662,7 @@ info "╔═══════════════════════
|
||||
info "║ Gitea container-image prune ║"
|
||||
info "╚══════════════════════════════════════════╝"
|
||||
info "Server: ${GITEA_URL} Owner: ${OWNER} Log: ${LOG_FILE}"
|
||||
info "Auth: ${CRED_SRC}$( [[ -n "$GITEA_USER" ]] && echo " (user: ${GITEA_USER})")"
|
||||
|
||||
# Sanity: Gitea version (public, no auth)
|
||||
GV="$(curl -fsS "${GITEA_URL}/api/v1/version" 2>/dev/null | jq -r '.version' 2>/dev/null || echo '?')"
|
||||
|
||||
Reference in New Issue
Block a user