Add authenticated GitHub PAT for Forgejo mirror sync (#269)
## Summary - **mirror-create**: Auto-includes GitHub PAT from 1Password for authenticated upstream fetches at mirror creation time - **mirror-update-pats**: New mise task that SSHes into indri and rewrites the git remote URL in every GitHub mirror's bare repo config to embed the PAT. Idempotent, supports `--dry-run` - **app.ini.j2**: Explicit `[mirror]` section with `DEFAULT_INTERVAL = 8h` and `MIN_INTERVAL = 10m` (bakes in the defaults for visibility) - **manage-forgejo-mirrors**: New how-to doc covering mirror creation, PAT storage, the `mirror-update-pats` task, and the full 20-day PAT rotation procedure ## Context GitHub tightened unauthenticated rate limits for git clone/fetch in May 2025. With 23 GitHub mirrors syncing every 8 hours, authenticated fetches avoid throttling. The PAT is stored in 1Password (`Forgejo Secrets` → `github-mirror-pat`) and has been applied to all existing mirrors. ## Deployment and Testing - [x] `mirror-update-pats` dry-run verified (23 mirrors detected) - [x] `mirror-update-pats` applied to all 23 GitHub mirrors on indri - [x] Idempotency confirmed (re-run shows 0 updated, 23 skipped) - [ ] Provision indri with `--tags forgejo` to apply `[mirror]` config - [ ] Trigger a manual mirror sync and verify success in Forgejo UI Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/269
This commit is contained in:
parent
23dc79058e
commit
84338c32c2
7 changed files with 234 additions and 2 deletions
|
|
@ -9,6 +9,7 @@ set -euo pipefail
|
|||
FORGE_API="https://forge.ops.eblu.me/api/v1"
|
||||
ORG="mirrors"
|
||||
OP_TOKEN_REF="op://blumeops/w3663ffnvkewbftncqxtcpeavy/api-token"
|
||||
OP_GITHUB_PAT_REF="op://blumeops/w3663ffnvkewbftncqxtcpeavy/github-mirror-pat"
|
||||
|
||||
url="${usage_url:?}"
|
||||
|
||||
|
|
@ -41,9 +42,16 @@ if [[ "${usage_dry_run:-}" == "true" ]]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
echo "Reading Forgejo API token from 1Password..."
|
||||
echo "Reading secrets from 1Password..."
|
||||
token="$(op read "$OP_TOKEN_REF")"
|
||||
|
||||
# For GitHub upstreams, include the PAT for authenticated sync
|
||||
auth_token=""
|
||||
if [[ "$service" == "github" ]]; then
|
||||
auth_token="$(op read "$OP_GITHUB_PAT_REF")"
|
||||
echo "Using GitHub PAT for authenticated mirror sync"
|
||||
fi
|
||||
|
||||
payload=$(cat <<ENDJSON
|
||||
{
|
||||
"clone_addr": "$url",
|
||||
|
|
@ -51,7 +59,8 @@ payload=$(cat <<ENDJSON
|
|||
"repo_owner": "$ORG",
|
||||
"mirror": true,
|
||||
"service": "$service",
|
||||
"description": "$description"
|
||||
"description": "$description",
|
||||
"auth_token": "$auth_token"
|
||||
}
|
||||
ENDJSON
|
||||
)
|
||||
|
|
|
|||
69
mise-tasks/mirror-update-pats
Executable file
69
mise-tasks/mirror-update-pats
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
#MISE description="Update GitHub PAT on all mirror repos on indri"
|
||||
#USAGE flag "--dry-run" help="Show what would be done without changing anything"
|
||||
set -euo pipefail
|
||||
|
||||
OP_GITHUB_PAT_REF="op://blumeops/w3663ffnvkewbftncqxtcpeavy/github-mirror-pat"
|
||||
REPO_BASE="/opt/homebrew/var/forgejo/data/forgejo-repositories"
|
||||
DB_PATH="/opt/homebrew/var/forgejo/data/forgejo.db"
|
||||
DRY_RUN="${usage_dry_run:-false}"
|
||||
|
||||
echo "Reading GitHub PAT from 1Password..."
|
||||
pat="$(op read "$OP_GITHUB_PAT_REF")"
|
||||
|
||||
if [[ -z "$pat" ]]; then
|
||||
echo "Error: GitHub PAT is empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Querying mirrors on indri..."
|
||||
|
||||
# Get all GitHub mirrors: org_name, repo_name, upstream_url
|
||||
mirrors=$(ssh indri "sqlite3 '$DB_PATH' \"
|
||||
SELECT u.name, r.name, m.remote_address
|
||||
FROM mirror m
|
||||
JOIN repository r ON m.repo_id = r.id
|
||||
JOIN [user] u ON r.owner_id = u.id
|
||||
WHERE m.remote_address LIKE '%github.com%'
|
||||
\"" 2>/dev/null)
|
||||
|
||||
if [[ -z "$mirrors" ]]; then
|
||||
echo "No GitHub mirrors found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
updated=0
|
||||
skipped=0
|
||||
|
||||
while IFS='|' read -r org repo upstream_url; do
|
||||
bare_repo="${REPO_BASE}/${org}/${repo}.git"
|
||||
|
||||
# Build authenticated URL: https://eblume:<pat>@github.com/...
|
||||
auth_url="${upstream_url/https:\/\/github.com/https:\/\/eblume:${pat}@github.com}"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "[dry-run] ${org}/${repo}: would set origin to authenticated URL"
|
||||
((updated++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check current remote URL (< /dev/null prevents ssh from consuming loop stdin)
|
||||
current_url=$(ssh indri "git -C '${bare_repo}' config remote.origin.url" < /dev/null 2>/dev/null || echo "")
|
||||
|
||||
if [[ "$current_url" == "$auth_url" ]]; then
|
||||
echo " ${org}/${repo}: already up to date"
|
||||
((skipped++))
|
||||
continue
|
||||
fi
|
||||
|
||||
ssh indri "git -C '${bare_repo}' remote set-url origin '${auth_url}'" < /dev/null 2>/dev/null
|
||||
echo " ${org}/${repo}: updated"
|
||||
((updated++))
|
||||
done <<< "$mirrors"
|
||||
|
||||
echo
|
||||
echo "Done. Updated: ${updated}, Skipped: ${skipped}"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "(dry-run mode — no changes were made)"
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue