refactor: Replace GitHub Actions mirroring with Gitea native pull mirror

- Remove sync-to-gitea.yml and mirror-check.yml GitHub Actions workflows
- Set up Gitea as pull mirror from GitHub (1h interval, via POST /repos/migrate)
- Rewrite MIRRORING-GUIDE.md with pull mirror setup and gotchas
- Update HANDOVER.md and lessons-learned.md with new approach

Gitea now automatically pulls from GitHub. No Actions, no dual-push,
no billing dependency. Push to GitHub only; Gitea syncs within 1 hour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 15:24:37 +01:00
parent a98941d74d
commit 1c8ab5939e
5 changed files with 253 additions and 511 deletions
-35
View File
@@ -1,35 +0,0 @@
name: Mirror Status Check
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch:
jobs:
check-sync:
runs-on: ubuntu-latest
steps:
- name: Check if repositories are in sync
run: |
# Get latest commit from GitHub
GITHUB_SHA=$(curl -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/terraphim/gitea-infrastructure/commits/main" | \
jq -r '.sha')
# Get latest commit from Gitea
GITEA_SHA=$(curl -s \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
"https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure/commits/main" | \
jq -r '.sha')
echo "GitHub: ${GITHUB_SHA:0:7}"
echo "Gitea: ${GITEA_SHA:0:7}"
if [ "$GITHUB_SHA" == "$GITEA_SHA" ]; then
echo "✅ Repositories are in sync"
exit 0
else
echo "⚠️ Repositories are OUT OF SYNC"
exit 1
fi
-27
View File
@@ -1,27 +0,0 @@
name: Sync to Gitea
on:
push:
branches: [main, master]
workflow_dispatch: # Allow manual trigger
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history
- name: Push to Gitea
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
git remote add gitea https://git.terraphim.cloud/terraphim/gitea-infrastructure.git
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
# Push to Gitea with token authentication
# If using HTTPS with token: https://token@url
git push https://oauth2:${GITEA_TOKEN}@git.terraphim.cloud/terraphim/gitea-infrastructure.git main
+73 -18
View File
@@ -199,9 +199,64 @@ To update Gitea to a new version:
docker compose up -d --build docker compose up -d --build
``` ```
## GitHub Mirroring ## GitHub -> Gitea Mirroring
See `MIRRORING-GUIDE.md` for bidirectional GitHub ↔ Gitea mirroring setup. Gitea is configured as a **pull mirror** from GitHub. No GitHub Actions or manual push needed.
**How it works:** Gitea polls GitHub every hour and pulls new commits automatically.
| Setting | Value |
|---------|-------|
| Source | https://github.com/terraphim/gitea-infrastructure (private) |
| Mirror | https://git.terraphim.cloud/terraphim/gitea-infrastructure |
| Interval | 1 hour |
| Auth | GitHub token stored in Gitea migration config |
### Manual Sync
```bash
source ~/op_zesticai_non_prod.sh
export 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"
```
### Recreating the Mirror
If the mirror breaks (e.g., GitHub token expires), delete and recreate:
```bash
source ~/op_zesticai_non_prod.sh
GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential")
GH_TOKEN=$(gh auth token)
# Delete existing
curl -s -X DELETE "https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure" \
-H "Authorization: token $GITEA_TOKEN"
sleep 10
# 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\"
}"
```
### Key Constraints
- The Gitea repo is **read-only** (mirror). All pushes go to GitHub only.
- An existing repo cannot be converted to a mirror -- must delete and recreate via `POST /repos/migrate`.
- `service: "github"` requires a valid GitHub token for private repos. `service: "git"` fails with "terminal prompts disabled".
- The 1Password item `github.personal.token` is expired. Use `gh auth token` instead.
## AI Agent Skill (Gitea Task Management) ## AI Agent Skill (Gitea Task Management)
@@ -272,7 +327,7 @@ Added `.pre-commit-config.yaml` with:
| `deploy-to-bigbox.sh` | Deployment script | | `deploy-to-bigbox.sh` | Deployment script |
| `s3_config.json.template` | SeaweedFS S3 config template | | `s3_config.json.template` | SeaweedFS S3 config template |
| `prometheus/prometheus.yml` | Prometheus scrape config | | `prometheus/prometheus.yml` | Prometheus scrape config |
| `.github/workflows/*.yml` | GitHub Actions for mirroring | | `MIRRORING-GUIDE.md` | Gitea pull mirror setup (replaced GitHub Actions) |
| `.agents/skills/gitea/SKILL.md` | AI agent Gitea skill | | `.agents/skills/gitea/SKILL.md` | AI agent Gitea skill |
| `.agents/skills/gitea/setup-labels.sh` | Label setup script | | `.agents/skills/gitea/setup-labels.sh` | Label setup script |
| `.agents/skills/gitea/test-gitea-skill.sh` | Integration tests | | `.agents/skills/gitea/test-gitea-skill.sh` | Integration tests |
@@ -294,14 +349,14 @@ Both cover: 1Password integration, Gitea + SeaweedFS infrastructure, all three A
## Gitea Mirror ## Gitea Mirror
The `gitea-infrastructure` repo is now published on both remotes: Gitea pulls from GitHub automatically (1-hour interval). No GitHub Actions needed.
| Remote | URL | | Role | URL |
|--------|-----| |------|-----|
| GitHub (origin) | https://github.com/terraphim/gitea-infrastructure | | Primary (push here) | https://github.com/terraphim/gitea-infrastructure |
| Gitea | https://git.terraphim.cloud/terraphim/gitea-infrastructure | | Mirror (auto-synced) | https://git.terraphim.cloud/terraphim/gitea-infrastructure |
**Note:** The `sync-to-gitea.yml` GitHub Actions workflow is failing due to a billing/spending limit issue. Direct push via `op` token was used as workaround. Check GitHub **Settings > Billing & plans** to restore automated sync. GitHub Actions workflows (`sync-to-gitea.yml`, `mirror-check.yml`) were removed -- Gitea's native pull mirror replaces them.
## Session Log (2026-02-18) ## Session Log (2026-02-18)
@@ -317,18 +372,18 @@ The `gitea-infrastructure` repo is now published on both remotes:
- Wrote two comparison articles (CTO voice + technical reference) comparing beads/br/agent-mail with Gitea replacement - Wrote two comparison articles (CTO voice + technical reference) comparing beads/br/agent-mail with Gitea replacement
- All code snippets, commands, and configurations verified against live Gitea instance - All code snippets, commands, and configurations verified against live Gitea instance
- Pushed all commits to GitHub origin/main - Pushed all commits to GitHub origin/main
- Created `gitea-infrastructure` repo on Gitea and pushed full history
- Discovered GitHub Actions billing issue blocking automated sync-to-gitea workflow
### All Commits (pushed) ### Session 3: Replace GitHub Actions with Gitea Pull Mirror
- `62e2275` docs: Add articles comparing beads/br/agent-mail with Gitea replacement - Removed `sync-to-gitea.yml` and `mirror-check.yml` GitHub Actions workflows
- `4ff1821` docs: Update handover and add lessons learned - Deleted manually-pushed Gitea repo, recreated as native pull mirror via `POST /repos/migrate`
- `b67284e` chore: Fix trailing whitespace across project files - Discovered `github.personal.token` in 1Password is expired (401); used `gh auth token` instead
- `66b305d` feat(gitea-skill): Add integration tests and fix workflow label swap - Discovered Gitea creates empty mirror repos on failed async clones (silent failure)
- `1fd202d` feat(gitea-skill): Rewrite Gitea agent skill with verified API patterns - Discovered `service: "git"` fails for private repos; `service: "github"` with valid token works
- Mirror interval set to 1 hour, manual sync available via API
- Updated HANDOVER.md, lessons-learned.md, and articles with new mirroring approach
### Next Steps ### Next Steps
1. Fix GitHub Actions billing to restore automated Gitea sync 1. Update `github.personal.token` in 1Password (currently expired)
2. Consider adding gitea-mcp or forgejo-mcp as MCP server for Claude Code (see SKILL.md MCP Integration section) 2. Consider adding gitea-mcp or forgejo-mcp as MCP server for Claude Code (see SKILL.md MCP Integration section)
3. Create first real milestone and tasks in `terraphim/agent-tasks` for a project 3. Create first real milestone and tasks in `terraphim/agent-tasks` for a project
4. Consider Phase 4 (disciplined-verification) if more rigorous validation is needed 4. Consider Phase 4 (disciplined-verification) if more rigorous validation is needed
+127 -430
View File
@@ -1,467 +1,164 @@
# Repository Mirroring: GitHub Gitea # Repository Mirroring: GitHub -> Gitea Pull Mirror
Complete guide for setting up mirroring between GitHub and self-hosted Gitea.
## Overview ## Overview
**Mirroring** keeps two repositories in sync automatically. Choose your direction: 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.
| Direction | Use Case | ```
|-----------|----------| Developer -> git push -> GitHub (primary)
| **GitHub → Gitea** | GitHub as primary, Gitea as backup/mirror | |
| **Gitea → GitHub** | Gitea as primary, GitHub for visibility/CI | v (automatic, every 1h)
| **Bidirectional** | Both accept commits (advanced) | Gitea (read-only mirror)
```
--- ## Current Configuration
## Method 1: Gitea as Mirror (GitHub → Gitea) | 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 |
Gitea periodically pulls from GitHub. Best for backup scenarios. ## Daily Workflow
### Step 1: Create Mirror in Gitea Push to GitHub only. Gitea syncs automatically.
**Via Web Interface:**
1. Go to https://git.terraphim.cloud
2. Click **"+"** → **"New Migration"**
3. Select **"GitHub"**
4. Enter URL: `https://github.com/terraphim/gitea-infrastructure`
5. **Authentication:**
- If private repo: Enter GitHub Personal Access Token
- If public: Leave empty
6. Check **"This repository will be a mirror"**
7. Click **"Migrate Repository"**
**Via Gitea API:**
```bash ```bash
# Get Gitea API token (Settings → Applications → Generate Token) git push origin main
GITEA_TOKEN="your_gitea_api_token" ```
# Create mirror That is all. Gitea pulls within 1 hour.
curl -X POST \
"https://git.terraphim.cloud/api/v1/repos/migrate" \ ## Manual Sync
-H "Authorization: token ${GITEA_TOKEN}" \
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" \ -H "Content-Type: application/json" \
-d '{ -d '{"mirror_interval": "30m"}'
"clone_addr": "https://github.com/terraphim/gitea-infrastructure",
"repo_name": "gitea-infrastructure-mirror",
"repo_owner": "terraphim",
"mirror": true,
"auth_token": "your_github_token_if_private"
}'
``` ```
### Step 2: Configure Mirror Settings Minimum interval is 10 minutes (enforced by Gitea server config).
1. Go to repository **Settings****Repository** ## Recreating the Mirror
2. Scroll to **Mirror Settings**
3. Set **Synchronization Interval**: `8h` (or your preference)
4. Click **"Update Mirror"**
### Step 3: Manual Sync (Optional) If the mirror breaks (e.g., GitHub token expires, repo gets corrupted):
```bash ```bash
# Trigger immediate sync source ~/op_zesticai_non_prod.sh
curl -X POST \ GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential")
"https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure-mirror/mirror-sync" \ GH_TOKEN=$(gh auth token)
-H "Authorization: token ${GITEA_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
## Method 2: GitHub as Mirror (Gitea → GitHub) ### 1. Existing repos cannot be converted to mirrors
Push from Gitea to GitHub. Best when Gitea is your primary. 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.
### Step 1: Add GitHub Remote to Local Repository ### 2. The `service` field matters for private repos
```bash | Service | Behavior |
cd /home/alex/infrastructure/gitea_config |---------|----------|
| `github` | Uses GitHub API. Requires `auth_token` for private repos. |
| `git` | Uses `git clone`. Fails with "terminal prompts disabled" for private repos. |
# Add GitHub as mirror remote Always use `service: "github"` with a valid GitHub token for private repositories.
git remote add github https://github.com/terraphim/gitea-infrastructure.git
# Or use SSH (recommended for automation) ### 3. Failed migrations leave empty repos
git remote add github git@github.com:terraphim/gitea-infrastructure.git
```
### Step 2: Configure Push Mirroring in Gitea 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.
**Option A: Gitea Push Mirror (Native)** ### 4. The `github.personal.token` in 1Password is expired
1. In Gitea, go to repository **Settings****Repository** 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.
2. Scroll to **Mirror Settings**
3. Check **"Push Mirror"**
4. Set **Git Remote URL**: `https://github.com/terraphim/gitea-infrastructure.git`
5. Add authentication:
- **Username**: Your GitHub username
- **Password/Token**: GitHub Personal Access Token
6. Set **Interval**: `8h`
7. Click **"Add Push Mirror"**
**Option B: Using Git Hooks (More Control)** ### 5. Wait between delete and create
Create a post-receive hook in Gitea: 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`.
1. Go to repository **Settings****Git Hooks** ## Why Not GitHub Actions?
2. Find **post-receive** hook
3. Edit and add:
```bash The previous approach used two GitHub Actions workflows:
#!/bin/bash - `sync-to-gitea.yml` -- pushed to Gitea on every GitHub push
# Push to GitHub after receiving commits - `mirror-check.yml` -- checked sync status every 6 hours
# Use deploy key or HTTPS with token Problems:
export GIT_SSH_COMMAND='ssh -i /path/to/deploy_key -o StrictHostKeyChecking=no' 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
git push github main --mirror The Gitea pull mirror replaces both workflows with zero configuration on the GitHub side.
echo "Mirrored to GitHub" ## API Reference
```
### Step 3: Test Push Mirroring | Method | Endpoint | Purpose |
|--------|----------|---------|
```bash | `POST` | `/api/v1/repos/migrate` | Create repo as pull mirror |
# Make a test commit | `POST` | `/api/v1/repos/{owner}/{repo}/mirror-sync` | Trigger immediate sync |
echo "# Test" >> README.md | `PATCH` | `/api/v1/repos/{owner}/{repo}` | Change `mirror_interval` |
git add README.md | `GET` | `/api/v1/repos/{owner}/{repo}` | Check mirror status |
git commit -m "test: Mirror sync" | `DELETE` | `/api/v1/repos/{owner}/{repo}` | Delete (before recreating) |
# Push to Gitea
git push origin main
# Check GitHub (should appear within seconds)
```
---
## Method 3: Bidirectional Mirroring
Both repositories accept commits. **Warning:** Can cause conflicts!
### Architecture
```
Developer A → Gitea ←→ GitHub ← Developer B
↑___________↓
Bidirectional sync
```
### Setup
1. **Set up both mirrors:**
- GitHub → Gitea (Method 1)
- Gitea → GitHub (Method 2)
2. **Conflict Resolution Strategy:**
- Always use `git merge` instead of `git push --force`
- Set up webhook for immediate sync
- Have clear contribution guidelines
3. **Webhook-based Immediate Sync:**
**In Gitea:**
- Settings → Webhooks → Add Webhook → Gitea
- Target URL: Your sync service
- Events: Push
**GitHub → Gitea sync service:**
```python
# sync-service.py (simplified)
from flask import Flask, request
import subprocess
app = Flask(__name__)
@app.route('/github-webhook', methods=['POST'])
def github_hook():
# Trigger Gitea pull
subprocess.run(['curl', '-X', 'POST',
'https://git.terraphim.cloud/api/v1/repos/terraphim/repo/mirror-sync'])
return 'OK'
@app.route('/gitea-webhook', methods=['POST'])
def gitea_hook():
# Trigger GitHub push
subprocess.run(['git', 'push', 'github', 'main'])
return 'OK'
```
---
## Method 4: Local Git Configuration (Simplest)
Push to both remotes simultaneously from your local machine.
### Setup
```bash
cd /home/alex/infrastructure/gitea_config
# Check current remotes
git remote -v
# origin: should be Gitea
# Add GitHub as additional remote
git remote add github https://github.com/terraphim/gitea-infrastructure.git
# Or use SSH:
git remote add github git@github.com:terraphim/gitea-infrastructure.git
```
### Push to Both
```bash
# Push to Gitea (origin)
git push origin main
# Push to GitHub
git push github main
```
### Automate with Git Alias
Add to `~/.gitconfig`:
```ini
[alias]
pushall = !git push origin $(git branch --show-current) && git push github $(git branch --show-current)
pa = pushall
```
Usage:
```bash
git pa # Pushes to both remotes
```
### Automate with Bash Function
Add to `~/.bashrc` or `~/.zshrc`:
```bash
git-push-all() {
local current_branch=$(git branch --show-current)
echo "Pushing to all remotes..."
git push origin "$current_branch"
git push github "$current_branch"
echo "✅ Pushed to Gitea and GitHub"
}
alias gpa='git-push-all'
```
---
## Authentication Setup
### GitHub Personal Access Token
1. Go to GitHub → Settings → Developer settings → Personal access tokens
2. Generate new token (classic)
3. Scopes needed:
- `repo` (full control of private repositories)
- `workflow` (if using GitHub Actions)
4. Copy token
### Use Token in Gitea
When setting up push mirror:
- **Username**: Your GitHub username
- **Password**: Your Personal Access Token (not your password!)
### SSH Key Setup (Recommended)
**For automated pushes:**
1. Generate deploy key:
```bash
ssh-keygen -t ed25519 -f ~/.ssh/gitea_github_deploy -C "gitea-mirror"
```
2. Add public key to GitHub:
- Repository Settings → Deploy Keys → Add deploy key
- Paste contents of `~/.ssh/gitea_github_deploy.pub`
- Allow write access
3. Add private key to Gitea server:
```bash
# Copy to Gitea server
scp ~/.ssh/gitea_github_deploy bigbox:~/.ssh/
ssh bigbox "chmod 600 ~/.ssh/gitea_github_deploy"
```
4. Configure SSH for GitHub in Gitea:
```bash
# Create SSH config
ssh bigbox "cat > ~/.ssh/config << 'EOF'
Host github-mirror
HostName github.com
User git
IdentityFile ~/.ssh/gitea_github_deploy
StrictHostKeyChecking no
EOF"
```
5. Update remote to use SSH:
```bash
ssh bigbox "cd ~/gitea-stack && git remote set-url github git@github-mirror:terraphim/gitea-infrastructure.git"
```
---
## Verification and Monitoring
### Check Mirror Status
**In Gitea:**
- Repository → Settings → Repository → Mirror Settings
- Shows last sync time and status
**Via API:**
```bash
# Check mirror status
curl -s \
"https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure-mirror" \
-H "Authorization: token ${GITEA_TOKEN}" | \
jq '.mirror_updated, .mirror_interval'
```
### Monitoring Script
Create `check-mirror.sh`:
```bash
#!/bin/bash
# Check if mirrors are in sync
echo "Checking repository sync status..."
# Get latest commit from GitHub
GITHUB_SHA=$(curl -s \
"https://api.github.com/repos/terraphim/gitea-infrastructure/commits/main" | \
jq -r '.sha')
# Get latest commit from Gitea
GITEA_SHA=$(curl -s \
"https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure/commits/main" | \
jq -r '.sha')
if [ "$GITHUB_SHA" == "$GITEA_SHA" ]; then
echo "✅ Repositories are in sync"
echo "Latest commit: ${GITHUB_SHA:0:7}"
else
echo "⚠️ Repositories are OUT OF SYNC"
echo "GitHub: ${GITHUB_SHA:0:7}"
echo "Gitea: ${GITEA_SHA:0:7}"
fi
```
---
## Troubleshooting
### Mirror Not Syncing
**Check Gitea logs:**
```bash
ssh bigbox "docker logs gitea | grep -i mirror"
```
**Test manually:**
```bash
ssh bigbox
cd ~/gitea-stack
git fetch github
git log HEAD..github/main # Check if behind
```
### Authentication Failures
**GitHub token expired:**
- Generate new token in GitHub
- Update in Gitea mirror settings
**SSH key issues:**
```bash
ssh bigbox "ssh -T github-mirror"
# Should show: Hi terraphim/gitea-infrastructure! You've successfully authenticated
```
### Conflict Resolution
If repositories diverge:
```bash
# On local machine
git fetch origin # Gitea
git fetch github # GitHub
# Check differences
git log --left-right --graph --cherry-pick --oneline origin/main...github/main
# Merge changes
git checkout main
git merge origin/main
git merge github/main
# Push to both
git push origin main
git push github main
```
---
## Best Practices
### 1. One-Way vs Two-Way
- **Use one-way** for most cases (clear primary/secondary)
- **Use two-way** only for specific workflows with clear contribution guidelines
### 2. Sync Frequency
- **Backup mirrors**: 24h interval
- **Active development**: 1h interval or webhook-triggered
- **CI/CD**: Immediate webhook sync
### 3. Notifications
Set up alerts for sync failures:
- Gitea webhook on sync failure
- GitHub Actions monitoring
- Email notifications
### 4. Documentation
Document your mirroring strategy:
- Which repository is primary?
- Where should PRs be opened?
- How to handle conflicts?
---
## Quick Reference
| Task | Command |
|------|---------|
| Check remotes | `git remote -v` |
| Add GitHub remote | `git remote add github https://github.com/terraphim/gitea-infrastructure.git` |
| Push to all | `git push origin main && git push github main` |
| Force sync | `curl -X POST "https://git.terraphim.cloud/api/v1/repos/terraphim/repo/mirror-sync"` |
| Check status | `git log origin/main..github/main` |
---
## Recommended Setup for Your Use Case
**Primary: Gitea (self-hosted, private)**
**Mirror: GitHub (backup, CI/CD)**
1. Use **Method 2 (Push Mirror)** from Gitea to GitHub
2. Set sync interval: 1 hour
3. Use SSH authentication with deploy key
4. Enable GitHub Actions for CI/CD on GitHub side
5. Keep Gitea as single source of truth for commits
This gives you:
- ✅ Full control on Gitea
- ✅ GitHub as backup
- ✅ Can use GitHub Actions for CI/CD
- ✅ Simple one-way sync (less conflicts)
+53 -1
View File
@@ -107,4 +107,56 @@
2. **Verify all code in articles against live systems** -- Both agents independently verified their code snippets against the actual codebase files. This caught zero errors because the source material (SKILL.md, test scripts) was already tested, but the verification step builds confidence. 2. **Verify all code in articles against live systems** -- Both agents independently verified their code snippets against the actual codebase files. This caught zero errors because the source material (SKILL.md, test scripts) was already tested, but the verification step builds confidence.
3. **Add gitea remote for direct push** -- Having both `origin` (GitHub) and `gitea` remotes allows manual sync when GitHub Actions is down. Command: `git remote add gitea https://git.terraphim.cloud/{org}/{repo}.git` 3. **Superseded: Add gitea remote for direct push** -- This was the session 2 approach. Session 3 replaced it with Gitea's native pull mirror (see below).
## Session: 2026-02-18 -- Replace GitHub Actions with Gitea Pull Mirror
### Technical Discoveries
1. **Gitea native pull mirror is the right approach**
- Gitea can poll GitHub and pull new commits automatically (configurable interval, minimum 10m)
- No GitHub Actions, no tokens in GitHub, no billing dependency
- Set up via `POST /api/v1/repos/migrate` with `"mirror": true`
- The Gitea repo becomes read-only; all pushes go to GitHub only
2. **Existing repos cannot be converted to pull mirrors**
- There is no API to add `mirror: true` to an existing repo
- Must delete the repo and recreate via `POST /repos/migrate`
- This is documented in Gitea's official docs
3. **The `service` field in migrate API matters for private repos**
- `service: "git"` tries `git clone` which fails with "terminal prompts disabled" for private repos
- `service: "github"` uses the GitHub API and handles auth correctly
- Always use `service: "github"` with `auth_token` for private GitHub repos
4. **Failed migrations leave broken empty repos**
- If the async clone fails (bad token, network), Gitea creates the repo entry anyway (`mirror: true, empty: true`)
- The `POST /mirror-sync` endpoint returns "Repository is not a mirror" on these broken repos
- Must delete and recreate; cannot repair in place
- The migrate API returns empty JSON `{}` (null fields) even on success -- check repo status separately
5. **The `github.personal.token` in 1Password is expired**
- `op://TerraphimPlatform/github.personal.token/token` returns a token rejected by GitHub (HTTP 401)
- `gh auth token` provides a working OAuth token as alternative
- Action item: update the 1Password item with a fresh GitHub PAT
6. **Race condition between delete and create on Gitea**
- `DELETE /repos/{owner}/{repo}` returns 204 immediately
- But the repo name may not be freed yet -- `POST /repos/migrate` returns 409
- **Fix:** Always `sleep 10` between delete and create, then verify 404 before proceeding
### Pitfalls to Avoid
1. **Do not use GitHub Actions for Gitea sync** -- it adds unnecessary dependency on GitHub billing, requires storing secrets in GitHub, and consumes Actions minutes. Use Gitea's native pull mirror instead.
2. **Do not use `service: "git"` for private GitHub repos** -- it cannot authenticate. Use `service: "github"` with `auth_token`.
3. **Do not trust the migrate API response body** -- it returns null/empty JSON even on success. Always check repo status via `GET /repos/{owner}/{repo}` after waiting for the async clone.
4. **Do not assume deleted repos are immediately gone** -- always wait and verify with a GET returning 404 before recreating.
### Best Practices Discovered
1. **Simplest mirroring wins** -- Gitea pull mirror is one API call to set up, zero maintenance, and replaces two GitHub Actions workflows plus a monitoring check.
2. **Use `gh auth token` as GitHub token source** -- more reliable than maintaining a separate PAT in 1Password. The `gh` CLI handles token refresh automatically.