mirror of
https://github.com/terraphim/gitea-infrastructure.git
synced 2026-07-15 22:50:34 +02:00
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:
@@ -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
|
||||
@@ -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
@@ -199,9 +199,64 @@ To update Gitea to a new version:
|
||||
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)
|
||||
|
||||
@@ -272,7 +327,7 @@ Added `.pre-commit-config.yaml` with:
|
||||
| `deploy-to-bigbox.sh` | Deployment script |
|
||||
| `s3_config.json.template` | SeaweedFS S3 config template |
|
||||
| `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/setup-labels.sh` | Label setup script |
|
||||
| `.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
|
||||
|
||||
The `gitea-infrastructure` repo is now published on both remotes:
|
||||
Gitea pulls from GitHub automatically (1-hour interval). No GitHub Actions needed.
|
||||
|
||||
| Remote | URL |
|
||||
|--------|-----|
|
||||
| GitHub (origin) | https://github.com/terraphim/gitea-infrastructure |
|
||||
| Gitea | https://git.terraphim.cloud/terraphim/gitea-infrastructure |
|
||||
| Role | URL |
|
||||
|------|-----|
|
||||
| Primary (push here) | https://github.com/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)
|
||||
|
||||
@@ -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
|
||||
- All code snippets, commands, and configurations verified against live Gitea instance
|
||||
- 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)
|
||||
- `62e2275` docs: Add articles comparing beads/br/agent-mail with Gitea replacement
|
||||
- `4ff1821` docs: Update handover and add lessons learned
|
||||
- `b67284e` chore: Fix trailing whitespace across project files
|
||||
- `66b305d` feat(gitea-skill): Add integration tests and fix workflow label swap
|
||||
- `1fd202d` feat(gitea-skill): Rewrite Gitea agent skill with verified API patterns
|
||||
### Session 3: Replace GitHub Actions with Gitea Pull Mirror
|
||||
- Removed `sync-to-gitea.yml` and `mirror-check.yml` GitHub Actions workflows
|
||||
- Deleted manually-pushed Gitea repo, recreated as native pull mirror via `POST /repos/migrate`
|
||||
- Discovered `github.personal.token` in 1Password is expired (401); used `gh auth token` instead
|
||||
- Discovered Gitea creates empty mirror repos on failed async clones (silent failure)
|
||||
- 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
|
||||
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)
|
||||
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
|
||||
|
||||
+127
-430
@@ -1,467 +1,164 @@
|
||||
# Repository Mirroring: GitHub ↔ Gitea
|
||||
|
||||
Complete guide for setting up mirroring between GitHub and self-hosted Gitea.
|
||||
# Repository Mirroring: GitHub -> Gitea Pull Mirror
|
||||
|
||||
## 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 |
|
||||
|-----------|----------|
|
||||
| **GitHub → Gitea** | GitHub as primary, Gitea as backup/mirror |
|
||||
| **Gitea → GitHub** | Gitea as primary, GitHub for visibility/CI |
|
||||
| **Bidirectional** | Both accept commits (advanced) |
|
||||
```
|
||||
Developer -> git push -> GitHub (primary)
|
||||
|
|
||||
v (automatic, every 1h)
|
||||
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
|
||||
|
||||
**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:**
|
||||
Push to GitHub only. Gitea syncs automatically.
|
||||
|
||||
```bash
|
||||
# Get Gitea API token (Settings → Applications → Generate Token)
|
||||
GITEA_TOKEN="your_gitea_api_token"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
# Create mirror
|
||||
curl -X POST \
|
||||
"https://git.terraphim.cloud/api/v1/repos/migrate" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
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 '{
|
||||
"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"
|
||||
}'
|
||||
-d '{"mirror_interval": "30m"}'
|
||||
```
|
||||
|
||||
### Step 2: Configure Mirror Settings
|
||||
Minimum interval is 10 minutes (enforced by Gitea server config).
|
||||
|
||||
1. Go to repository **Settings** → **Repository**
|
||||
2. Scroll to **Mirror Settings**
|
||||
3. Set **Synchronization Interval**: `8h` (or your preference)
|
||||
4. Click **"Update Mirror"**
|
||||
## Recreating the Mirror
|
||||
|
||||
### Step 3: Manual Sync (Optional)
|
||||
If the mirror breaks (e.g., GitHub token expires, repo gets corrupted):
|
||||
|
||||
```bash
|
||||
# Trigger immediate sync
|
||||
curl -X POST \
|
||||
"https://git.terraphim.cloud/api/v1/repos/terraphim/gitea-infrastructure-mirror/mirror-sync" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}"
|
||||
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
|
||||
|
||||
## 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
|
||||
cd /home/alex/infrastructure/gitea_config
|
||||
| Service | Behavior |
|
||||
|---------|----------|
|
||||
| `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
|
||||
git remote add github https://github.com/terraphim/gitea-infrastructure.git
|
||||
Always use `service: "github"` with a valid GitHub token for private repositories.
|
||||
|
||||
# Or use SSH (recommended for automation)
|
||||
git remote add github git@github.com:terraphim/gitea-infrastructure.git
|
||||
```
|
||||
### 3. Failed migrations leave empty repos
|
||||
|
||||
### 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**
|
||||
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"**
|
||||
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.
|
||||
|
||||
**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**
|
||||
2. Find **post-receive** hook
|
||||
3. Edit and add:
|
||||
## Why Not GitHub Actions?
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Push to GitHub after receiving commits
|
||||
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
|
||||
|
||||
# Use deploy key or HTTPS with token
|
||||
export GIT_SSH_COMMAND='ssh -i /path/to/deploy_key -o StrictHostKeyChecking=no'
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
# Make a test commit
|
||||
echo "# Test" >> README.md
|
||||
git add README.md
|
||||
git commit -m "test: Mirror sync"
|
||||
|
||||
# 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)
|
||||
| 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) |
|
||||
|
||||
+53
-1
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user