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:
@@ -2,6 +2,19 @@
|
||||
|
||||
All notable changes to the operator helper scripts. Newest on top.
|
||||
|
||||
## 2026-06-17 (later)
|
||||
|
||||
### Changed — `gitea-image-prune.sh`
|
||||
- Credential auto-discovery: when `GITEA_TOKEN`/`--token-file` are not set and the
|
||||
script runs inside a Gitea-host clone, it reuses git's stored credential — the
|
||||
token embedded in the remote URL, else a configured credential helper
|
||||
(`git credential fill`, never prompting). Lets you run it from a configured
|
||||
clone with no token. Startup banner reports the credential source + user.
|
||||
- Auth now uses HTTP Basic (`user:token`) when a username is known (so both API
|
||||
tokens and the embedded-URL/helper credential work), falling back to the
|
||||
`Authorization: token` header for a bare `GITEA_TOKEN`. Live-verified both paths
|
||||
(env token; git credential helper as `kisfenyo`).
|
||||
|
||||
## 2026-06-17
|
||||
|
||||
### Added — `gitea-image-prune.sh`
|
||||
|
||||
@@ -47,7 +47,23 @@ Confirmed live on Gitea 1.26.2 (2026-06-17), the reclaim path is **three steps**
|
||||
> accumulated orphan manifests on `felhom-hub` then freed **86 MiB**. After
|
||||
> reclaim, surviving tags (`latest`, `0.1.3`, …) still `docker pull` cleanly.
|
||||
|
||||
### Token & required scopes
|
||||
### Credentials
|
||||
|
||||
The script resolves credentials in this order:
|
||||
|
||||
1. `GITEA_TOKEN` env var
|
||||
2. `--token-file <path>`
|
||||
3. **git's stored credential for the Gitea host** — auto-discovered when you run
|
||||
the script inside a clone: first the token embedded in the remote URL
|
||||
(`https://user:token@host/…`), else a configured **credential helper**
|
||||
(`git credential fill`). No prompting.
|
||||
|
||||
So from a configured clone you can just run `./gitea-image-prune.sh --repo … list`
|
||||
with no token at all. The startup banner prints which source was used. Caveat: a
|
||||
*git* credential may only carry repo scope — if it lacks package/admin scope, the
|
||||
call returns a 403 naming the missing scope (see below).
|
||||
|
||||
### Required scopes
|
||||
|
||||
Pass an admin-user token via `GITEA_TOKEN` (env) or `--token-file <path>`. The
|
||||
token must belong to a Gitea **site-admin** user. Minimal fine-grained scopes
|
||||
|
||||
+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