# Repository Mirroring: GitHub -> Gitea Pull Mirror ## Overview GitHub is the primary repository. Gitea automatically pulls from GitHub every hour using its native pull mirror feature. No GitHub Actions, no manual dual-push, no cron jobs. ``` Developer -> git push -> GitHub (primary) | v (automatic, every 1h) Gitea (read-only mirror) ``` ## Current Configuration | Setting | Value | |---------|-------| | Primary | https://github.com/terraphim/gitea-infrastructure (private) | | Mirror | https://git.terraphim.cloud/terraphim/gitea-infrastructure | | Direction | GitHub -> Gitea (one-way pull) | | Interval | 1 hour | | Service | `github` (uses GitHub API for private repo access) | | Auth | GitHub token stored in Gitea migration config | ## Daily Workflow Push to GitHub only. Gitea syncs automatically. ```bash git push origin main ``` That is all. Gitea pulls within 1 hour. ## Manual Sync Trigger an immediate sync without waiting for the interval: ```bash source ~/op_zesticai_non_prod.sh GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential") curl -s -X POST \ "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure/mirror-sync" \ -H "Authorization: token $GITEA_TOKEN" ``` ## Check Mirror Status ```bash source ~/op_zesticai_non_prod.sh GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential") curl -s "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \ -H "Authorization: token $GITEA_TOKEN" | \ jq '{mirror, mirror_interval, mirror_updated, empty, size}' ``` ## Change Sync Interval ```bash curl -s -X PATCH \ "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"mirror_interval": "30m"}' ``` Minimum interval is 10 minutes (enforced by Gitea server config). ## Recreating the Mirror If the mirror breaks (e.g., GitHub token expires, repo gets corrupted): ```bash source ~/op_zesticai_non_prod.sh GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential") GH_TOKEN=$(gh auth token) # Step 1: Delete existing mirror curl -s -X DELETE \ "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \ -H "Authorization: token $GITEA_TOKEN" # Step 2: Wait for deletion to complete sleep 10 # Step 3: Verify deleted curl -s "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \ -H "Authorization: token $GITEA_TOKEN" -w "\nHTTP %{http_code}\n" # Should return HTTP 404 # Step 4: Recreate as pull mirror curl -s -X POST "https://git.terraphim.cloud/api/v1/repos/migrate" \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"clone_addr\": \"https://github.com/terraphim/gitea-infrastructure\", \"repo_name\": \"gitea-infrastructure\", \"repo_owner\": \"terraphim\", \"service\": \"github\", \"auth_token\": \"$GH_TOKEN\", \"mirror\": true, \"mirror_interval\": \"1h\" }" # Step 5: Verify (wait a few seconds for async clone) sleep 15 curl -s "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \ -H "Authorization: token $GITEA_TOKEN" | \ jq '{id, mirror, mirror_interval, mirror_updated, empty, size}' # Should show: mirror: true, empty: false ``` ## Critical Gotchas ### 1. Existing repos cannot be converted to mirrors Gitea only creates mirrors via the `POST /repos/migrate` endpoint. There is no way to convert an existing repo to a mirror. You must delete and recreate. ### 2. The `service` field matters for private repos | Service | Behavior | |---------|----------| | `github` | Uses GitHub API. Requires `auth_token` for private repos. | | `git` | Uses `git clone`. Fails with "terminal prompts disabled" for private repos. | Always use `service: "github"` with a valid GitHub token for private repositories. ### 3. Failed migrations leave empty repos If the async clone fails (bad token, network error), Gitea still creates the repo entry with `mirror: true, empty: true`. The `mirror-sync` endpoint returns "Repository is not a mirror" on these broken repos. Delete and recreate. ### 4. The `github.personal.token` in 1Password is expired As of 2026-02-18, the `op://TerraphimPlatform/github.personal.token/token` returns a token that GitHub rejects with HTTP 401. Use `gh auth token` instead for a working token. ### 5. Wait between delete and create Gitea needs time to fully delete a repo. Without a `sleep 10` between delete and create, you get `409 Conflict: The repository with the same name already exists`. ## Why Not GitHub Actions? The previous approach used two GitHub Actions workflows: - `sync-to-gitea.yml` -- pushed to Gitea on every GitHub push - `mirror-check.yml` -- checked sync status every 6 hours Problems: 1. GitHub Actions billing issues caused silent failures 2. Required storing a `GITEA_TOKEN` secret in GitHub 3. Consumed GitHub Actions minutes 4. Added unnecessary complexity The Gitea pull mirror replaces both workflows with zero configuration on the GitHub side. ## API Reference | Method | Endpoint | Purpose | |--------|----------|---------| | `POST` | `/api/v1/repos/migrate` | Create repo as pull mirror | | `POST` | `/api/v1/repos/{owner}/{repo}/mirror-sync` | Trigger immediate sync | | `PATCH` | `/api/v1/repos/{owner}/{repo}` | Change `mirror_interval` | | `GET` | `/api/v1/repos/{owner}/{repo}` | Check mirror status | | `DELETE` | `/api/v1/repos/{owner}/{repo}` | Delete (before recreating) |