mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-10-30 22:21:58 +01:00
docs(www): six new articles for sources, push destinations, API, reconcile and release assets (#389)
New use-case articles: mirror GitLab to Gitea, back up Codeberg and Forgejo repositories, mirror to GitHub or GitLab, automate mirrors with the API, adopt existing Gitea mirrors, and control release asset storage. Existing articles and the comparison page updated for the OCI Helm chart, the extra sources and destinations, and reconcile.
This commit is contained in:
1 parent
f7d830ac03
commit
c14473e3f8
13 files changed
+717
-14
No files matched your search
@@ -112,4 +112,52 @@ export const useCases: UseCase[] = [
|
||||
outcome: 'The CA template pre-wires ports, paths, and secrets so scheduled, auto-discovering backups run on the NAS you already manage.',
|
||||
tags: ['Unraid', 'Docker', 'Homelab'],
|
||||
},
|
||||
{
|
||||
slug: 'mirror-gitlab-to-gitea',
|
||||
title: 'Mirror GitLab Projects to Gitea',
|
||||
summary: 'Point the source at gitlab.com or your own GitLab (beta) and keep projects, groups and subgroups mirrored into Gitea on a schedule.',
|
||||
painPoint: 'Projects that live on GitLab were out of reach for a tool that only spoke GitHub, and hand-made pull mirrors do not notice new projects.',
|
||||
outcome: 'GitLab projects are discovered through the API, mirrored with branches, tags, wiki and LFS, and kept in sync like any GitHub repository.',
|
||||
tags: ['GitLab', 'Sources', 'Beta'],
|
||||
},
|
||||
{
|
||||
slug: 'backup-codeberg-forgejo-repositories',
|
||||
title: 'Back Up Codeberg and Forgejo Repositories',
|
||||
summary: 'Use a Gitea or Forgejo instance, Codeberg included, as the source (beta), and Forgejo as a named destination for the other direction.',
|
||||
painPoint: 'Code on Codeberg or on a second Gitea had no place in a GitHub-only backup routine, and Forgejo had to pretend to be Gitea.',
|
||||
outcome: 'Any Gitea or Forgejo host can be the source, Forgejo is a destination in its own right, and each row remembers where it was mirrored.',
|
||||
tags: ['Codeberg', 'Forgejo', 'Beta'],
|
||||
},
|
||||
{
|
||||
slug: 'mirror-to-github-or-gitlab',
|
||||
title: 'Mirror Repositories to GitHub or GitLab',
|
||||
summary: 'Make GitHub or GitLab the destination (beta): the push engine keeps a bare clone and pushes branches and tags with force and prune on every sync.',
|
||||
painPoint: 'GitHub has no pull mirror API and GitLab keeps its own behind a paid tier, so a copy on a hosted service meant scripts and cron.',
|
||||
outcome: 'Repositories are created on the target, overwritten to match the source every sync, and archived or deleted by cleanup through the host API.',
|
||||
tags: ['Push Engine', 'GitHub', 'GitLab'],
|
||||
},
|
||||
{
|
||||
slug: 'automate-mirrors-with-the-api',
|
||||
title: 'Automate Mirrors with the API',
|
||||
summary: 'Create an API key, add a repository and start a mirror from a script or a CI job, and poll status without opening the dashboard.',
|
||||
painPoint: 'New repositories waited for the next scheduled import, and pipelines had no way to ask the mirror to catch up.',
|
||||
outcome: 'A key in the x-api-key header drives every endpoint the UI uses, with expiry and revocation from the settings page.',
|
||||
tags: ['API', 'CI/CD', 'Automation'],
|
||||
},
|
||||
{
|
||||
slug: 'adopt-existing-gitea-mirrors',
|
||||
title: 'Adopt Existing Gitea Mirrors',
|
||||
summary: 'Reconcile compares Gitea with the database, adopts mirrors made by hand or by an older tool, and resets rows whose mirror is gone.',
|
||||
painPoint: 'A mirror without a database row is invisible to sync and cleanup, and a restored database lists repositories that are already there.',
|
||||
outcome: 'A dry run sorts the destination into four groups, and the two opt-in fixes bring rows and mirrors back in line without deleting anything.',
|
||||
tags: ['Reconcile', 'Recovery', 'Maintenance'],
|
||||
},
|
||||
{
|
||||
slug: 'control-release-asset-storage',
|
||||
title: 'Control Release Asset Storage',
|
||||
summary: 'Keep release notes for every mirrored release but upload binaries only for the newest few, globally or per organization and repository.',
|
||||
painPoint: 'A project that ships builds for five platforms every fortnight turns a release mirror into gigabytes of installers nobody will run.',
|
||||
outcome: 'Two independent limits decide how many releases exist on Gitea and how many of those carry assets, and nothing already uploaded is removed.',
|
||||
tags: ['Releases', 'Storage', 'Overrides'],
|
||||
},
|
||||
];
|
||||
@@ -43,7 +43,7 @@ The question isn't *if* you need backups—it's *which solution* fits your workf
|
||||
|
||||
**Best for**: Homelab enthusiasts, indie developers, privacy-conscious teams, cost-sensitive startups
|
||||
|
||||
**How it works**: Automatically mirrors your GitHub repositories to your own Gitea server using scheduled syncs. Preserves full history, metadata (issues, PRs as enriched issues, labels, milestones), releases, and wiki content.
|
||||
**How it works**: Automatically mirrors your GitHub repositories to your own Gitea server using scheduled syncs. Preserves full history, metadata (issues, PRs as enriched issues, labels, milestones), releases, and wiki content. GitLab and Gitea or Forgejo sources, and Forgejo, GitHub or GitLab destinations, are available as beta options; the metadata mirroring is GitHub to Gitea only.
|
||||
|
||||
**Pros**:
|
||||
- ✅ **Zero recurring costs** - Just your hosting/hardware
|
||||
@@ -53,6 +53,8 @@ The question isn't *if* you need backups—it's *which solution* fits your workf
|
||||
- ✅ **Docker-friendly** - One command deployment
|
||||
- ✅ **Multi-arch support** - AMD64 and ARM64 (Raspberry Pi compatible)
|
||||
- ✅ **No vendor lock-in** - Standard Git repos, portable
|
||||
- ✅ **More than one host** - GitLab, Codeberg and Forgejo as sources, Forgejo, GitHub and GitLab as destinations (beta)
|
||||
- ✅ **Scriptable** - API keys drive every endpoint the dashboard uses
|
||||
|
||||
**Cons**:
|
||||
- ❌ You manage the infrastructure (server, backups, updates)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Adopt Existing Gitea Mirrors with Reconcile"
|
||||
description: "Bring mirrors you created by hand or with an older tool under Gitea Mirror's management, and repair the database after a restore, without deleting anything on either side."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/adopt-existing-gitea-mirrors/"
|
||||
---
|
||||
|
||||
# Adopt Existing Gitea Mirrors with Reconcile
|
||||
|
||||
## The database is the only thing the app looks at
|
||||
|
||||
Gitea Mirror keeps a row for every repository it knows about. Scheduled sync, cleanup, the dashboard and the status counts all work from those rows. A mirror that exists on Gitea without a row is invisible to every one of those features, and a row whose mirror was deleted on Gitea looks healthy until the next sync fails.
|
||||
|
||||
That happens more often than you would think:
|
||||
|
||||
- You created pull mirrors by hand in Gitea before you found this app.
|
||||
- You migrated from a script or another tool that left mirrors behind.
|
||||
- You restored an older backup of `gitea-mirror.db`, or started with a fresh database on a new host, while Gitea kept everything.
|
||||
- Someone deleted a mirror in Gitea to free space, and the app still lists it as mirrored.
|
||||
|
||||
Since version 3.31 the **Reconcile with destination** action compares what Gitea holds with what the database holds and offers two opt-in fixes. It works for Gitea and Forgejo destinations.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Gitea Mirror 3.31 or newer with the destination URL, username and token configured
|
||||
- A source configured, because reconcile uses the source to decide which mirrors are yours
|
||||
- A Gitea token that can list the owners the app mirrors into
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Run the dry run
|
||||
|
||||
Open **Configuration → Automation** and find the **Repository Cleanup** card. Click **Reconcile with destination**. The dialog runs a dry run as soon as it opens and lists every repository under the destination owners this account mirrors into: the configured user and organization, the starred organization, organization overrides, and every owner the database already points at.
|
||||
|
||||
The result is sorted into four groups.
|
||||
|
||||
| Group | What it means | Fix offered |
|
||||
|---|---|---|
|
||||
| On the destination, not in the database | Mirrors of your source that the app does not know about | Adopt |
|
||||
| In the database, gone from the destination | Rows marked mirrored whose repository is no longer there | Reset |
|
||||
| Not managed by gitea-mirror | Native repositories and mirrors of other hosts | None, listed for information |
|
||||
| Could not be verified | The presence check itself failed | None, left alone |
|
||||
|
||||
A healthy count shows how many rows matched. If some rows were mirrored to a previous destination, the dialog says how many were left out of the comparison; they are expected to be absent here and are not treated as missing.
|
||||
|
||||
### 2. Decide what counts as yours
|
||||
|
||||
Only mirrors whose original URL points at the configured source count as this app's. The known hosts are the source itself, the API host it is reached through, and the host of every clone URL already stored, which covers GitHub Enterprise and setups where the clone host differs from the nominal source.
|
||||
|
||||
Everything else lands in the not managed group: repositories that are not mirrors at all, and mirrors of hosts you do not mirror from. Those are never touched by reconcile, and the cleanup service does not know about them either.
|
||||
|
||||
### 3. Adopt
|
||||
|
||||
Tick **Adopt** and apply. For each untracked mirror the app creates a row from the mirror's source URL, preferring the source's own metadata and falling back to what Gitea reports if the source lookup returns nothing (a private repository without a token, for example, takes its private flag from Gitea).
|
||||
|
||||
The new row records where the mirror already lives and pins it there, even when your mirror strategy would put a new mirror somewhere else. That is deliberate: the next sync updates the existing mirror instead of creating a second copy in the "right" organization. Adoption inserts with the existing unique index on user and full name, so it cannot create duplicates.
|
||||
|
||||
### 4. Reset
|
||||
|
||||
Tick **Reset** and apply. Each missing row goes back to `imported` with a note in its error field, and the next mirror run recreates the mirror on Gitea. A row is only called missing after a direct lookup of its expected location returned 404. A failed check leaves the row alone and lists it under could not be verified.
|
||||
|
||||
### 5. Run it again
|
||||
|
||||
A second dry run should come back clean: zero untracked, zero missing. Anything still listed is either not managed, which is fine, or unverified, which usually means a network or permission problem worth a look at the Gitea logs.
|
||||
|
||||
## What reconcile never does
|
||||
|
||||
- It does not delete or archive anything on Gitea.
|
||||
- It does not delete rows. Missing rows are reset, not removed.
|
||||
- It does not move mirrors between organizations.
|
||||
- It does not touch native repositories or mirrors of other hosts.
|
||||
|
||||
The cleanup service keeps its own rules for repositories deleted upstream. Reconcile is about the database matching the destination, not about the destination matching the source.
|
||||
|
||||
## From a script
|
||||
|
||||
The same action is `POST /api/cleanup/reconcile`. An empty body is a dry run; `{"dryRun": false, "adoptUntracked": true, "resetMissing": true}` applies both fixes. The response carries the four groups, the healthy and elsewhere counts, the owners that were scanned and the ones that were skipped because the destination does not have them. See [Automate mirrors with the API](/use-cases/automate-mirrors-with-the-api/) for keys and headers.
|
||||
|
||||
## FAQ
|
||||
|
||||
### I restored a database backup and the app now shows half my repositories. Is reconcile the fix?
|
||||
|
||||
Yes. Run the dry run, check that the untracked list is what you expect, adopt. Scheduled sync and cleanup include the adopted rows from then on.
|
||||
|
||||
### Will adoption pull issues and releases into the adopted mirror?
|
||||
|
||||
Adoption only creates the row. Metadata comes with the next sync according to your mirror settings, and only from GitHub sources.
|
||||
|
||||
### Why is reconcile not available for a GitHub or GitLab destination?
|
||||
|
||||
Push targets have no server side mirror state. The app's bare clone and the row are the state, so there is nothing on the target to compare against. See [Mirror to GitHub or GitLab](/use-cases/mirror-to-github-or-gitlab/).
|
||||
|
||||
## Related guides
|
||||
|
||||
- [Cleanup and reconcile reference](/docs/cleanup-and-reconcile/)
|
||||
- [Automate GitHub backups](/use-cases/github-backup-automation/)
|
||||
- [Migrate from GitHub to Gitea](/use-cases/migrate-github-to-gitea/)
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Automate Mirrors with the API and API Keys"
|
||||
description: "Create an API key, add a repository and start a mirror from a script or a GitHub Actions workflow, and watch status without opening the dashboard."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/automate-mirrors-with-the-api/"
|
||||
---
|
||||
|
||||
# Automate Mirrors with the API and API Keys
|
||||
|
||||
## The dashboard is not the only door
|
||||
|
||||
Everything the web interface does goes through JSON endpoints under `/api`. Since version 3.30 you can call them without a browser session: create an API key, put it in a header, and the same endpoints answer to a script, a CI job or a workflow tool such as n8n.
|
||||
|
||||
Typical reasons to bother:
|
||||
|
||||
- A template repository creates a new project on GitHub, and you want it mirrored the moment it exists rather than on the next scheduled import.
|
||||
- A deploy pipeline should not finish until the mirror is up to date.
|
||||
- A monitoring system should alert when a repository has been failing for a day.
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Create a key
|
||||
|
||||
Sign in, open **Configuration → Authentication** and find the **API Keys** card. Click **Create API key**, give it a name and pick an expiry: never, 30 days, 90 days or a year. The key is shown once. Copy it; afterwards only its first characters are visible in the list.
|
||||
|
||||
Keys start with `gm_`, belong to the user who created them, and carry the same rights as that user's session. They are stored hashed. Revoke a key from the same list when a script is retired or a secret leaks.
|
||||
|
||||
### 2. Send it
|
||||
|
||||
Put the key in the `x-api-key` header. Every endpoint that accepts a session cookie accepts the header instead.
|
||||
|
||||
```bash
|
||||
curl -sS "https://mirror.example.com/api/github/repositories" \
|
||||
-H "x-api-key: gm_..."
|
||||
```
|
||||
|
||||
A missing, malformed, expired or revoked key gets a `401` with `{"success": false, "error": "Unauthorized"}`. If the app is served under a base path, prefix every URL with it.
|
||||
|
||||
### 3. Add a repository and mirror it
|
||||
|
||||
Adding and mirroring are two calls. The first looks the repository up on the configured source and stores it with the status `imported`. The second starts the mirror in the background.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE="https://mirror.example.com"
|
||||
KEY="gm_..."
|
||||
|
||||
added=$(curl -sS -X POST "$BASE/api/sync/repository" \
|
||||
-H "x-api-key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"owner":"octocat","repo":"hello-world"}')
|
||||
|
||||
id=$(echo "$added" | jq -r '.repository.id')
|
||||
|
||||
curl -sS -X POST "$BASE/api/job/mirror-repo" \
|
||||
-H "x-api-key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repositoryIds\":[\"$id\"]}"
|
||||
```
|
||||
|
||||
The add call returns `409` when the repository is already tracked. Send `"force": true` to refresh its metadata instead, or skip straight to the mirror call with the id from the list endpoint. An optional `destinationOrg` field overrides the destination organization for this one repository.
|
||||
|
||||
`POST /api/job/sync-repo` takes the same body and re-syncs repositories that are already mirrored, which is the call a deploy pipeline wants.
|
||||
|
||||
### 4. Watch the status
|
||||
|
||||
`GET /api/github/repositories` lists every tracked repository whatever the source (the path kept its name for compatibility). Each entry carries `status`, `lastMirrored`, `errorMessage` and `mirroredLocation`. Status is one of `imported`, `mirroring`, `mirrored`, `failed`, `syncing`, `synced`, `skipped`, `deleted` or `archived`.
|
||||
|
||||
A small polling loop after the mirror call:
|
||||
|
||||
```bash
|
||||
until [ "$(curl -sS "$BASE/api/github/repositories" -H "x-api-key: $KEY" \
|
||||
| jq -r --arg id "$id" '.repositories[] | select(.id==$id) | .status')" != "mirroring" ]; do
|
||||
sleep 10
|
||||
done
|
||||
```
|
||||
|
||||
For a liveness check without a key, `GET /api/health` answers what the Helm chart's probes use.
|
||||
|
||||
## A GitHub Actions example
|
||||
|
||||
A workflow in the template repository that registers each new repository with the mirror on its first push:
|
||||
|
||||
```yaml
|
||||
name: Register with Gitea Mirror
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
register:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add and mirror
|
||||
env:
|
||||
BASE: ${{ secrets.MIRROR_URL }}
|
||||
KEY: ${{ secrets.MIRROR_API_KEY }}
|
||||
run: |
|
||||
owner="${GITHUB_REPOSITORY%%/*}"
|
||||
repo="${GITHUB_REPOSITORY##*/}"
|
||||
added=$(curl -sS -X POST "$BASE/api/sync/repository" \
|
||||
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
|
||||
-d "{\"owner\":\"$owner\",\"repo\":\"$repo\"}" || true)
|
||||
id=$(echo "$added" | jq -r '.repository.id // empty')
|
||||
if [ -n "$id" ]; then
|
||||
curl -sS -X POST "$BASE/api/job/mirror-repo" \
|
||||
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
|
||||
-d "{\"repositoryIds\":[\"$id\"]}"
|
||||
fi
|
||||
```
|
||||
|
||||
The mirror has to be reachable from the runner. For a homelab behind NAT that usually means a tunnel or a self-hosted runner on the same network.
|
||||
|
||||
## Organizations and maintenance
|
||||
|
||||
`POST /api/sync/organization` with `{"org": "my-org", "role": "member"}` adds an organization, and `POST /api/job/mirror-org` with `{"organizationIds": [...]}` mirrors it. `POST /api/cleanup/reconcile` compares a Gitea or Forgejo destination with the database and, with `dryRun: false`, adopts untracked mirrors or resets missing rows; the [reconcile playbook](/use-cases/adopt-existing-gitea-mirrors/) explains what it touches.
|
||||
|
||||
## What a key cannot do
|
||||
|
||||
- **Manage keys.** The key endpoints under `/api/auth/api-key/` need a browser session, not a key, so a leaked key cannot mint more keys. Scripts that must create keys need a session cookie and an `Origin` header.
|
||||
- **Act on another account.** A key sees the source, destination and repositories of the user who created it, nothing else.
|
||||
- **Bypass the host's limits.** Keys are not rate limited by Gitea Mirror. GitHub's hourly budget still applies and the app tracks it; a runaway script is stopped by revoking its key.
|
||||
|
||||
Deleting a user deletes their keys.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Is there a rate limit on the API itself?
|
||||
|
||||
No. The app trusts the key holder. If a script loops on the add call, it just gets `409` quickly. If it loops on the mirror call, it queues jobs, so put a poll between calls.
|
||||
|
||||
### Can I use a key with the SSE event stream?
|
||||
|
||||
The endpoints that accept a session accept a key, including the ones the dashboard uses for live updates. For most automation the list endpoint polled every few seconds is simpler.
|
||||
|
||||
### Where is the full endpoint list?
|
||||
|
||||
The [API reference](/docs/api/) has the endpoints with request and response shapes.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [API reference](/docs/api/)
|
||||
- [Automate GitHub backups](/use-cases/github-backup-automation/)
|
||||
- [Adopt existing Gitea mirrors](/use-cases/adopt-existing-gitea-mirrors/)
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Back Up Codeberg, Forgejo and Gitea Repositories"
|
||||
description: "Use a Gitea or Forgejo instance, Codeberg included, as the source for Gitea Mirror, and use Forgejo as a destination for the other direction."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/backup-codeberg-forgejo-repositories/"
|
||||
---
|
||||
|
||||
# Back Up Codeberg, Forgejo and Gitea Repositories
|
||||
|
||||
## Gitea to Gitea is a real use case
|
||||
|
||||
Plenty of projects live on Codeberg or on somebody else's Forgejo. Plenty of homelabs run two Gitea instances, one that people push to and one that only exists as a copy. Since version 3.30 Gitea Mirror covers both: the **Source** dropdown has a **Gitea / Forgejo** option, and the **Destination** dropdown has **Forgejo** next to Gitea.
|
||||
|
||||
Both are marked **beta**. Forgejo and Gitea share the same API, so everything the Gitea path supports works, but they have had less time in the field than the GitHub to Gitea default.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Gitea Mirror 3.30 or newer
|
||||
- The source instance URL, for example `https://codeberg.org` or `http://gitea.local:3000`
|
||||
- An access token on the source with `read:repository`, `read:user` and `read:organization`
|
||||
- A destination Gitea or Forgejo with an API token, as in the [Docker playbook](/use-cases/backup-github-repositories/)
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Select the source
|
||||
|
||||
Open **Configuration → Connections** and set **Source** to **Gitea / Forgejo**. The **Instance URL** defaults to Codeberg; replace it with your own instance's base URL if that is where the repositories are. If the instance is served under a path, include it, for example `http://gitea.local:3000/gitea`. Enter the username on that instance.
|
||||
|
||||
### 2. Create the token
|
||||
|
||||
On the source instance open **Settings → Applications** and generate a token with read access to repositories, user and organization. Paste it and click **Test**. For public repositories the token is optional: without it Gitea Mirror sees the configured user's public repositories and their stars, and adding a public repository by URL works as well.
|
||||
|
||||
### 3. Import and mirror
|
||||
|
||||
Use **Import Gitea / Forgejo Data** to discover the repositories the token can see, or paste a single repository URL into **Repositories → Add Repository**. A Codeberg link such as `https://codeberg.org/forgejo/forgejo` fills in owner and name.
|
||||
|
||||
Mirror from the Repositories page, then turn on **Automatic Syncing** on the **Automation** tab. Organizations on the source map to Gitea organizations on the destination with the **Preserve Structure** strategy, or into one organization with **Single Organization**, the same as for GitHub.
|
||||
|
||||
### 4. Decide what a "source" is for you
|
||||
|
||||
Because the source and destination are the same kind of software, it is easy to point the app at the wrong one. The source is the instance people push to. The destination is where Gitea Mirror creates read-only pull mirrors. If you point the source at your backup, you will mirror empty repositories.
|
||||
|
||||
## What is mirrored
|
||||
|
||||
For Gitea and Forgejo sources the git side is complete: code, branches, tags, the wiki when enabled, LFS objects, scheduled sync, auto import of new repositories, cleanup of repositories deleted upstream, starred and private repositories, and add by URL.
|
||||
|
||||
Issues, pull requests, labels, milestones and releases are still read from the GitHub API only, so those switches are disabled for a Gitea or Forgejo source. Force push detection is GitHub only as well. If issue history matters to you, keep that in mind when choosing which direction to mirror.
|
||||
|
||||
## Forgejo as the destination
|
||||
|
||||
The destination card's **Destination** dropdown offers **Forgejo** as its own kind. Under the hood it is the Gitea pull mirror path, because Forgejo kept Gitea's API, but naming it lets the app show the right icon and label, and it records on each repository row which host it was mirrored to.
|
||||
|
||||
That record matters when you change destinations. Once anything is mirrored, the destination locks and a change goes through a confirmation dialog. Rows remember where they were mirrored, and a row mirrored to one host is refused on another until it is removed and mirrored again, so a token never goes to the wrong server. Existing mirrors stay on the old server and are not moved; new mirrors go to the new server only.
|
||||
|
||||
`DESTINATION_PROVIDER=forgejo` sets it from the environment. The `GITEA_URL`, `GITEA_USERNAME` and `GITEA_TOKEN` names are kept for every destination kind.
|
||||
|
||||
## Two directions, two accounts
|
||||
|
||||
Gitea Mirror accounts have exactly one source and one destination each, and mirroring is one way. If you want Codeberg backed up to your Gitea and your Gitea pushed out to GitHub, that is two accounts in Gitea Mirror: one with a Gitea / Forgejo source and a Gitea destination, one with a Gitea source and a GitHub destination. The second one uses the push engine described in [Mirror to GitHub or GitLab](/use-cases/mirror-to-github-or-gitlab/).
|
||||
|
||||
Bidirectional mirroring is out of scope on purpose. A backup tool that writes back to the source has too many ways to go wrong.
|
||||
|
||||
## Environment variables
|
||||
|
||||
```env
|
||||
SOURCE_PROVIDER=gitea
|
||||
SOURCE_URL=https://codeberg.org
|
||||
GITHUB_USERNAME=your-codeberg-username
|
||||
GITHUB_TOKEN=your-codeberg-token
|
||||
|
||||
DESTINATION_PROVIDER=forgejo # or gitea
|
||||
GITEA_URL=https://forgejo.lab.local
|
||||
GITEA_USERNAME=mirror-bot
|
||||
GITEA_TOKEN=your-destination-token
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can the source and destination be the same instance?
|
||||
|
||||
Technically yes, with different owners, but the cleanup and reconcile features assume the destination holds mirrors and nothing else under the owners it scans. Use two instances if you can.
|
||||
|
||||
### Does Forgejo need anything special?
|
||||
|
||||
No. The migrate API, the mirror sync endpoint and the repository API are the same calls Gitea Mirror already makes against Gitea. If a future Forgejo release diverges, the separate kind is where a difference would be handled.
|
||||
|
||||
### What about Codeberg's rate limits?
|
||||
|
||||
Codeberg is a shared instance. Keep the sync interval sane, an hour or more for a large account, and use a token so your requests are attributed to you.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [Sources reference](/docs/sources/)
|
||||
- [Destinations reference](/docs/destinations/)
|
||||
- [Mirror GitLab projects to Gitea](/use-cases/mirror-gitlab-to-gitea/)
|
||||
@@ -11,6 +11,8 @@ canonical: "https://gitea-mirror.raylabs.io/use-cases/backup-github-repositories
|
||||
|
||||
GitHub is great—right up until an outage, SSO change, or account lockout strands your projects. Gitea Mirror keeps a self-hosted copy of everything (history, metadata, LFS) so you can keep working locally. This playbook walks through the minimal Docker setup the project ships with and shows how to prove your backups actually work.
|
||||
|
||||
This playbook covers the default path, GitHub into Gitea. Since 3.30 the source can also be [GitLab](/use-cases/mirror-gitlab-to-gitea/) or [Codeberg, Forgejo and Gitea](/use-cases/backup-codeberg-forgejo-repositories/), and since 3.31 the destination can be Forgejo, or [GitHub or GitLab](/use-cases/mirror-to-github-or-gitlab/) through a push engine. Those paths are marked beta in the settings.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker Engine and Compose on the host that will run the mirror
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Control Release Asset Storage"
|
||||
description: "Mirror release notes for every release but upload binaries only for the newest few, globally or per organization and repository, so a busy project does not fill your Gitea disk."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/control-release-asset-storage/"
|
||||
---
|
||||
|
||||
# Control Release Asset Storage
|
||||
|
||||
## Releases are cheap, assets are not
|
||||
|
||||
A GitHub release is a tag, some notes and, usually, a set of attached files: installers, tarballs, container bundles, model weights. The notes and the tag are a few kilobytes. The assets of a project that ships builds for five platforms every two weeks can run to gigabytes per year, and a mirror that copies all of them copies every historical build you will never install.
|
||||
|
||||
Gitea Mirror has had a release limit for a while: mirror the newest N releases and skip the rest. Since version 3.30 there is a second, independent limit for assets, so you can keep the notes of every release you mirror and the binaries of only the recent ones.
|
||||
|
||||
## The two limits
|
||||
|
||||
On the **Configuration** page, next to the **Releases** switch in the mirror settings, there are two small inputs.
|
||||
|
||||
| Input | Setting | Meaning |
|
||||
|---|---|---|
|
||||
| Latest | `releaseLimit` | How many of the newest releases exist on Gitea at all. Default 10, range 1 to 100. |
|
||||
| Assets for latest | `releaseAssetLimit` | How many of those also get their assets uploaded. Empty means all of them, `0` means notes and tag only, `N` means the newest N. |
|
||||
|
||||
Releases are ordered newest first. A release past the asset limit is still created on Gitea with its notes and tag; it just does not get the files. The two limits are independent: "Latest 50, Assets for latest 3" keeps three years of changelog browsable with the last three builds downloadable.
|
||||
|
||||
## Nothing already uploaded is deleted
|
||||
|
||||
The asset limit only decides whether a sync uploads. Lowering it later never removes assets that already reached Gitea. If you mirrored everything for a year and then set the limit to 2, the old files stay until you delete them in Gitea yourself. That is the safe direction for a backup tool, and it means the setting is cheap to experiment with.
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Set a global default
|
||||
|
||||
Open **Configuration → Connections**, scroll to the mirror settings and make sure **Releases** is on. Set **Latest** to how far back the notes should go and **Assets for latest** to how many builds you want on hand. For most homelabs, a handful of builds and a long tail of notes is the right shape.
|
||||
|
||||
### 2. Override per organization
|
||||
|
||||
Some organizations publish big artifacts, others publish none. Open the three-dot menu on an organization row and choose **Mirror options**. Both limits are there next to the switches, with **Inherit** meaning the global value. An organization that ships firmware images can get `0` assets while the rest of your account keeps the default.
|
||||
|
||||
### 3. Override per repository
|
||||
|
||||
The same dialog exists on repository rows, and a repository value wins over its organization, which wins over the global default. The dialog labels each row with what inherit resolves to right now, so you can see the effective value before you change it.
|
||||
|
||||
### 4. Environment
|
||||
|
||||
For deployments configured from the environment:
|
||||
|
||||
```env
|
||||
MIRROR_RELEASES=true
|
||||
RELEASE_LIMIT=25
|
||||
RELEASE_ASSET_LIMIT=3 # unset for all, 0 for notes only
|
||||
```
|
||||
|
||||
`RELEASE_ASSET_LIMIT` seeds the global setting on first boot, the same way the other mirror options do. The [environment variables page](/docs/environment-variables/) lists the rest.
|
||||
|
||||
## Doing the disk math
|
||||
|
||||
Take a project that publishes a release every two weeks with five platform builds of 40 MB each. That is 200 MB per release and about 5 GB per year of assets, for one repository. With the release limit at 25 and no asset limit, the first mirror pulls about 5 GB and every sync afterwards adds the newest release's 200 MB. With **Assets for latest** at 3, the first mirror pulls 600 MB. Each later release still adds 200 MB, because a new release is inside the limit and gets its files, and the files of the release that dropped out of the top three stay on disk until you remove them. The ongoing growth is the same; what you save is the backfill of every historical build.
|
||||
|
||||
Multiply by the number of repositories that ship binaries and compare with the free space on the Gitea host. Gitea stores release attachments in its own attachment storage, separate from the git objects and from LFS, so check that path when you plan capacity.
|
||||
|
||||
## Where releases apply
|
||||
|
||||
Releases are read from the GitHub API. For GitLab and Gitea or Forgejo sources the Releases switch is disabled, and for GitHub or GitLab destinations releases are not created at all because the push engine moves branches and tags only. Both limits therefore matter on the default GitHub to Gitea path and are ignored elsewhere.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Does lowering the release limit delete releases on Gitea?
|
||||
|
||||
No. Neither limit deletes anything. Releases and assets that already exist stay until you remove them in Gitea.
|
||||
|
||||
### Do assets sync again if I raise the limit?
|
||||
|
||||
Yes, for releases within the new limit that did not get their assets before. The next sync uploads what is missing.
|
||||
|
||||
### Can I skip releases for one repository entirely?
|
||||
|
||||
Turn the Releases switch off in that repository's **Mirror options**. The [LFS playbook](/use-cases/mirror-git-lfs-repositories/) uses the same dialog for the same reason.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [Mirror issues and pull requests](/use-cases/mirror-github-issues-pull-requests/)
|
||||
- [Mirror Git LFS repositories](/use-cases/mirror-git-lfs-repositories/)
|
||||
- [Configuration reference](/docs/configuration/)
|
||||
@@ -30,17 +30,12 @@ kubectl create namespace gitea-mirror
|
||||
|
||||
The [chart README](https://github.com/RayLabsHQ/gitea-mirror/blob/main/helm/gitea-mirror/README.md) documents multiple supported approaches. Choose the one that matches how you manage secrets.
|
||||
|
||||
The chart is published to GitHub Container Registry as an OCI package on every release, at `oci://ghcr.io/raylabshq/charts/gitea-mirror`. Each chart version deploys the application release with the same number, and the chart's default image tag is that release, so there is nothing to clone and no tag to look up. Installing from a clone still works: replace the OCI reference with `./helm/gitea-mirror` from the repository root.
|
||||
|
||||
**Inline quick start (no values file):**
|
||||
|
||||
First, clone the repository or download the chart:
|
||||
```bash
|
||||
git clone https://github.com/RayLabsHQ/gitea-mirror.git
|
||||
cd gitea-mirror
|
||||
```
|
||||
|
||||
Then install with credentials:
|
||||
```bash
|
||||
helm upgrade --install gitea-mirror ./helm/gitea-mirror \
|
||||
helm upgrade --install gitea-mirror oci://ghcr.io/raylabshq/charts/gitea-mirror \
|
||||
--namespace gitea-mirror \
|
||||
--set "gitea-mirror.github.username=<your-gh-username>" \
|
||||
--set "gitea-mirror.github.token=<your-gh-token>" \
|
||||
@@ -66,11 +61,13 @@ persistence:
|
||||
```
|
||||
|
||||
```bash
|
||||
helm upgrade --install gitea-mirror ./helm/gitea-mirror \
|
||||
helm upgrade --install gitea-mirror oci://ghcr.io/raylabshq/charts/gitea-mirror \
|
||||
--namespace gitea-mirror \
|
||||
--values values-gitea-mirror.yaml
|
||||
```
|
||||
|
||||
Add `--version 3.31.0` (or any published release) to pin the chart. Without it Helm installs the newest one.
|
||||
|
||||
**Bring your own Secret (recommended for production):**
|
||||
|
||||
```bash
|
||||
@@ -105,8 +102,8 @@ After the pod is healthy, open **Configuration → Connections** inside the UI t
|
||||
|
||||
### 4. Keep it updated
|
||||
|
||||
- Pull chart updates when you bump the repo: `git pull` then re-run the `helm upgrade` command.
|
||||
- Override the container image tag with `--set image.tag=v3.21.0` if you need to pin a release (defaults to `v{appVersion}` from Chart.yaml).
|
||||
- Re-run the `helm upgrade` command without `--version` to move to the newest chart, and with it the newest application image. Pass `--version` to stay on a specific release.
|
||||
- Leave `image.tag` empty to run the release the chart was published for. If you set it, use the tag as it appears on the registry, with the `v` prefix, for example `--set image.tag=v3.31.0`.
|
||||
- Use Helm rollbacks if a release misbehaves: `helm rollback gitea-mirror <REVISION> -n gitea-mirror`.
|
||||
|
||||
## Observability
|
||||
|
||||
@@ -72,7 +72,14 @@ Keep the GitHub repos around in archived state for a quarter. They cost nothing
|
||||
|
||||
## What about the reverse direction?
|
||||
|
||||
Teams that migrate off GitHub sometimes need to keep a public presence there. Gitea has built-in push mirroring for that: your Gitea repo pushes to a GitHub remote on every change. Gitea Mirror handles the pull direction, GitHub to Gitea; the push direction is native Gitea functionality and the two work fine together.
|
||||
Teams that migrate off GitHub sometimes need to keep a public presence there. Two options, both one way:
|
||||
|
||||
- Gitea's built-in push mirroring: your Gitea repository pushes to a GitHub remote on every change. It is native Gitea functionality and works fine next to Gitea Mirror.
|
||||
- Gitea Mirror's own push engine (beta since 3.31): a second Gitea Mirror account with your Gitea as the source and **GitHub** as the destination keeps a bare clone and pushes branches and tags with force and prune on a schedule. It creates the target repositories for you and applies cleanup through the GitHub API. See [Mirror to GitHub or GitLab](/use-cases/mirror-to-github-or-gitlab/).
|
||||
|
||||
Neither option pulls anything back. Bidirectional mirroring is out of scope on purpose.
|
||||
|
||||
If you already created some mirrors in Gitea by hand before setting the app up, run **Reconcile with destination** from the Repository Cleanup card once. It adopts those mirrors into the database so scheduled sync and cleanup cover them, without moving or deleting anything. See [Adopt existing Gitea mirrors](/use-cases/adopt-existing-gitea-mirrors/).
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Mirror GitLab Projects to Gitea"
|
||||
description: "Use GitLab as the source for Gitea Mirror: gitlab.com or a self-hosted instance, group and subgroup projects, token scopes, what comes across today and what does not yet."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/mirror-gitlab-to-gitea/"
|
||||
---
|
||||
|
||||
# Mirror GitLab Projects to Gitea
|
||||
|
||||
## GitLab is a source now
|
||||
|
||||
Gitea Mirror started as a GitHub to Gitea tool, and that is still the default and the best tested path. Since version 3.30 the connection card has a **Source** dropdown, and GitLab is one of the options. It is marked **beta**: it is tested end to end against gitlab.com and a self-hosted instance before every release, but it has had less time in the field than GitHub.
|
||||
|
||||
The mechanics are the same as for GitHub. Gitea does the actual mirroring through its pull mirror feature, which treats GitLab as a plain git remote. Gitea Mirror's job is to find your projects through the GitLab API, create the mirrors in the right place, keep them on a schedule, and notice when a project disappears upstream.
|
||||
|
||||
## Requirements
|
||||
|
||||
- A running Gitea Mirror (3.30 or newer) with a Gitea or Forgejo destination configured
|
||||
- A GitLab account on gitlab.com or your own instance
|
||||
- A personal access token with the `read_api` and `read_repository` scopes
|
||||
- Network access from Gitea itself to GitLab, since Gitea performs the clone
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Pick GitLab as the source
|
||||
|
||||
Open **Configuration → Connections**. At the top of the source card, change the **Source** dropdown from GitHub to **GitLab**. An **Instance URL** field appears. Leave it empty for gitlab.com, or enter the base URL of your own instance, for example `https://gitlab.example.com`. The username field takes your GitLab username.
|
||||
|
||||
If you already imported repositories from GitHub, the dropdown shows a lock note and a **Change** button instead. Changing the source is allowed, but only after a confirmation dialog, and the repositories you imported before stay tied to GitHub. More on that below.
|
||||
|
||||
### 2. Create the token
|
||||
|
||||
In GitLab go to **Preferences → Access tokens** and add a token with `read_api` and `read_repository`. Paste it into the token field and click **Test**. The test calls the selected instance with that token and tells you whether it can see your account.
|
||||
|
||||
You can skip the token for public projects. Without one, Gitea Mirror only sees the public projects of the configured user, plus their starred projects, and adding a single public project by URL still works.
|
||||
|
||||
### 3. Import projects
|
||||
|
||||
Click **Import GitLab Data** in the top right of the Configuration page, or wait for the scheduler if you turned on auto import. Discovery lists the projects the token can see, including private ones and projects in groups you belong to.
|
||||
|
||||
To add one project without importing everything, open **Repositories → Add Repository** and paste the project URL. Nested paths are fine: `https://gitlab.com/group/subgroup/project` fills the owner and name fields from the URL, and a deep link into a merge request or a file tree is trimmed automatically.
|
||||
|
||||
### 4. Mirror and schedule
|
||||
|
||||
Mirror the projects from the Repositories page as you would for GitHub. On the **Automation** tab, enable **Automatic Syncing** and pick an interval. Gitea's own pull mirror interval is not used; Gitea Mirror triggers the sync itself so the schedule you set is the schedule you get. Turn on **Auto-mirror new repositories** if new projects should be picked up without a manual import.
|
||||
|
||||
## How GitLab groups land in Gitea
|
||||
|
||||
GitLab nests groups, Gitea does not. A project at `group/subgroup/project` is mirrored into the Gitea organization `group` when the strategy is **Preserve Structure**, and the repository keeps `group/subgroup/project` as its full name in the Gitea Mirror table so you can still tell subgroups apart. The organization filter in the mirror settings matches the top level group.
|
||||
|
||||
Two smaller details:
|
||||
|
||||
- GitLab's **internal** visibility has no Gitea equivalent. Internal projects are mirrored as private, because they are not visible to anonymous users on GitLab either.
|
||||
- Repository names come from the URL path, not the display name. A project called "My Widget" with the path `my-widget` mirrors as `my-widget`.
|
||||
|
||||
## What comes across, and what does not yet
|
||||
|
||||
The git side is complete. Code, every branch and tag, the wiki when enabled, and LFS objects are mirrored for GitLab exactly as for GitHub, and scheduled sync, auto import, cleanup of projects deleted upstream, starred projects and private projects all work.
|
||||
|
||||
The metadata side is GitHub only for now. Issues, merge requests, labels, milestones and releases are not read from the GitLab API yet, so those switches are disabled on the Configuration page and the mirror step skips them. Force push detection also depends on the GitHub API and is unavailable for GitLab sources. The [issue and pull request playbook](/use-cases/mirror-github-issues-pull-requests/) only applies to GitHub sources.
|
||||
|
||||
## One source at a time
|
||||
|
||||
An account has one source. When you switch from GitHub to GitLab, the repositories imported from GitHub are not touched. They keep syncing, because Gitea already holds their clone configuration, but the cleanup service ignores them and mirroring one of them again is refused with an error that names both hosts. If you need a GitHub repository re-mirrored after the switch, remove it and add it again from the current source.
|
||||
|
||||
The same lock applies in the other direction. Once projects are imported from GitLab, the source and instance URL lock until you confirm a change.
|
||||
|
||||
## Environment variables
|
||||
|
||||
For a deployment that should come up configured, set:
|
||||
|
||||
```env
|
||||
SOURCE_PROVIDER=gitlab
|
||||
SOURCE_URL=https://gitlab.example.com # omit for gitlab.com
|
||||
GITHUB_USERNAME=your-gitlab-username
|
||||
GITHUB_TOKEN=glpat-...
|
||||
```
|
||||
|
||||
The `GITHUB_*` names are kept for every source so an existing deployment only has to add `SOURCE_PROVIDER`. The full list is on the [environment variables page](/docs/environment-variables/), and the [sources page](/docs/sources/) has the capability table.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Does this work with a self-hosted GitLab behind a private CA?
|
||||
|
||||
Yes, if both Gitea Mirror and Gitea trust the CA. Gitea Mirror talks to the GitLab API, Gitea does the clone. See [custom CA certificates](/docs/ca-certificates/).
|
||||
|
||||
### Can I mirror GitHub and GitLab into the same Gitea?
|
||||
|
||||
Not from one account. Each Gitea Mirror user has one source, so create a second user for the second source. Both can point at the same Gitea server.
|
||||
|
||||
### Will merge requests ever be mirrored?
|
||||
|
||||
Reading merge requests from the GitLab API is the natural next step after the code path settled. Watch the [GitHub issues](https://github.com/RayLabsHQ/gitea-mirror/issues) for progress.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [Sources reference](/docs/sources/)
|
||||
- [Back up Codeberg and Forgejo repositories](/use-cases/backup-codeberg-forgejo-repositories/)
|
||||
- [Sync GitHub to self-hosted Gitea](/use-cases/sync-github-to-self-hosted-gitea/)
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
layout: ../../layouts/UseCaseLayout.astro
|
||||
title: "Mirror Repositories to GitHub or GitLab"
|
||||
description: "Use GitHub or GitLab as the destination with the push engine: what it does on every sync, where repositories are created, which token scopes it needs, and what does not travel yet."
|
||||
canonical: "https://gitea-mirror.raylabs.io/use-cases/mirror-to-github-or-gitlab/"
|
||||
---
|
||||
|
||||
# Mirror Repositories to GitHub or GitLab
|
||||
|
||||
## When the copy should live on a hosted service
|
||||
|
||||
The usual Gitea Mirror setup copies GitHub into a Gitea you run. Sometimes the direction is the other way round: the repositories live on your own Gitea or Forgejo, and you want a copy on GitHub so people can find it, or your team lives on GitHub and you want a second copy in a GitLab group for good measure.
|
||||
|
||||
Since version 3.31 the **Destination** dropdown offers **GitHub** and **GitLab**, both marked **beta**. Neither host has a pull mirror API the app can use (GitHub has none, GitLab reserves it for paid tiers), so for these two the app runs a push engine instead of asking the destination to fetch.
|
||||
|
||||
## What the push engine does
|
||||
|
||||
For every repository whose destination is GitHub or GitLab, each sync does the following:
|
||||
|
||||
1. **Keeps a bare clone on disk.** It lives under `<data dir>/mirrors/<source host>/<owner>/<name>.git`, next to the SQLite database. The first run clones, later runs fetch with prune, so a branch or tag deleted at the source disappears from the clone.
|
||||
2. **Creates the target repository if it is missing.** On GitHub under the account from the connection card, or under an organization the mirror strategy picks. On GitLab under the user, or in a group; a missing top level group is created, a nested group is not.
|
||||
3. **Pushes branches and tags** with force and prune. The target ends up matching the source, including rewritten history and deleted branches.
|
||||
4. **Sets the default branch** on the target to match the source.
|
||||
|
||||
Statuses, mirror jobs and activity rows are the same as for a Gitea mirror, so the dashboard, the scheduler, recovery and cleanup treat both the same. The repository table shows the destination's icon and links to the target repository.
|
||||
|
||||
## The target is overwritten every sync
|
||||
|
||||
This is the sentence to remember. Every push uses force and prune. A commit made directly on the target, a branch created there, a tag moved there, is replaced by what the source has on the next sync. Treat a push target as read only, the same way a Gitea pull mirror is read only.
|
||||
|
||||
## What is not pushed
|
||||
|
||||
Push targets receive branches and tags only. These are disabled in the settings for push destinations, with a note saying why:
|
||||
|
||||
- issues, pull requests, labels and milestones
|
||||
- releases and release assets (the tags themselves do travel)
|
||||
- the wiki
|
||||
- LFS objects
|
||||
- force push protection and its backup strategies
|
||||
|
||||
Pull request refs on GitHub and merge request refs on GitLab are not pushed either, because the hosts refuse writes to their own namespaces.
|
||||
|
||||
Mirroring stays one way, and a repository has one destination. Nothing is pulled back from the target.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Gitea Mirror 3.31 or newer with `git` available in the container (the official image has it)
|
||||
- Disk space next to the database, roughly the size of the git objects of everything you mirror this way
|
||||
- A token on the target with the scopes below
|
||||
|
||||
| Target | Scopes | Notes |
|
||||
|---|---|---|
|
||||
| GitHub | `repo`, `workflow` | `workflow` is required as soon as a repository contains GitHub Actions files, or the push is rejected. Add `delete_repo` only if cleanup should delete repositories on GitHub. |
|
||||
| GitLab | `api`, `write_repository` | Pushes authenticate as the `oauth2` user with the token, as GitLab documents. |
|
||||
|
||||
Tokens never appear in a URL and are never written to disk. Each git process gets an inline credential helper that reads the token from an environment variable set for that process only: the source token for the fetch, the target token for the push.
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Configure the destination
|
||||
|
||||
Open **Configuration → Connections**. In the destination card set **Destination** to **GitHub** or **GitLab**. The instance URL is prefilled with github.com or gitlab.com; change it for GitHub Enterprise Server or a self-hosted GitLab. Enter the account the token belongs to, the token, and click **Test**.
|
||||
|
||||
For the single organization strategy, enter the organization or group name. GitHub organizations cannot be created through the API, so create the organization first and make the token's user a member with permission to create repositories. On GitLab a missing top level group is created for you.
|
||||
|
||||
### 2. Mirror a repository
|
||||
|
||||
Add and mirror repositories as usual. The first run for a repository clones the source and pushes everything. Large first pushes into GitHub can exceed its single push limit; the engine then retries in batches of 50 refs, branches first, then tags, then a prune. A freshly created GitLab project sometimes reports "not found" for a second or two after the create call returns, so the first push into a repository the run just created is retried a few times before it counts as a failure.
|
||||
|
||||
### 3. Check the result
|
||||
|
||||
Open the target repository from the link in the repository table and compare branches and tags with the source. A second sync of an unchanged repository reports that the target was already up to date.
|
||||
|
||||
### 4. Schedule
|
||||
|
||||
Turn on **Automatic Syncing** on the **Automation** tab. Each scheduled sync fetches and pushes again. Both steps are idempotent, so an interrupted run just runs again.
|
||||
|
||||
## Be careful with auto mirror
|
||||
|
||||
With a push destination, **Auto-mirror new repositories** creates a repository on the target for everything the import finds. If the source token can see a few hundred repositories, that is a few hundred new repositories on GitHub or in your GitLab group. Import first, look at the list, and only then turn auto mirror on.
|
||||
|
||||
## Cleanup and switching
|
||||
|
||||
When the source deletes a repository, the cleanup service applies the configured action through the target's API: archive, or delete. Deleting on GitHub needs the `delete_repo` scope; without it the cleanup run fails with a message naming the scope and the repository stays. On gitlab.com and instances with delayed deletion, a deleted project is renamed and kept for the configured number of days. The bare clone is removed with the row.
|
||||
|
||||
Once anything has been mirrored the destination locks, and changing it asks for confirmation. Rows remember which destination they were mirrored to, and a row mirrored to one host is refused on another until it is removed and mirrored again.
|
||||
|
||||
The **Reconcile with destination** action is for Gitea and Forgejo. Push targets have no server side mirror state to compare against, so the button is not offered for them.
|
||||
|
||||
## Tuning
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `MIRROR_CLONE_DIR` | `<data dir>/mirrors` | Where the bare clones live. Point it at a bigger disk if the data directory is small. |
|
||||
| `PUSH_CONCURRENCY` | `2` | How many git fetch or push operations run at the same time across all repositories. |
|
||||
| `DESTINATION_PROVIDER` | `gitea` | `github` or `gitlab` to select a push target from the environment. |
|
||||
|
||||
One git process per repository at a time is enforced with a lock file next to the clone. A lock older than an hour belongs to a run that died and is taken over; a half written clone is removed and cloned again.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can I push to GitHub and keep my Gitea mirror?
|
||||
|
||||
Not from one account: each Gitea Mirror account has one destination. Create a second account for the second destination. Both can share the same source.
|
||||
|
||||
### Does the source need to be Gitea for this?
|
||||
|
||||
No. Any source works: GitHub to a GitLab group, a GitLab project to GitHub, a Forgejo repository to GitHub. Every source and destination pair is exercised against real hosts before a release.
|
||||
|
||||
### Where do the release notes go?
|
||||
|
||||
Nowhere, for now. Tags are pushed, releases are not created on the target. If releases matter, keep a Gitea destination for that repository.
|
||||
|
||||
## Related guides
|
||||
|
||||
- [Destinations reference](/docs/destinations/)
|
||||
- [Back up Codeberg and Forgejo repositories](/use-cases/backup-codeberg-forgejo-repositories/)
|
||||
- [Stay ready to leave GitHub](/use-cases/vendor-lock-in-prevention/)
|
||||
@@ -11,6 +11,8 @@ canonical: "https://gitea-mirror.raylabs.io/use-cases/sync-github-to-self-hosted
|
||||
|
||||
You may still collaborate on GitHub every day, yet want a LAN Gitea copy you control. Gitea Mirror bridges the two: it tracks owners, auto-discovers repos, mirrors metadata, and keeps a local instance only minutes behind upstream without hand-written cron jobs.
|
||||
|
||||
GitHub to Gitea is the default and the best tested path. If your code lives elsewhere, the same setup works with a [GitLab source](/use-cases/mirror-gitlab-to-gitea/) or a [Codeberg, Forgejo or Gitea source](/use-cases/backup-codeberg-forgejo-repositories/), both beta, with the difference that issues, pull requests and releases are only read from GitHub for now.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Gitea Mirror deployed (Docker, [Proxmox LXC](/use-cases/proxmox-lxc-homelab/), [Unraid](/use-cases/unraid-github-backup/), or [Helm](/use-cases/deploy-with-helm-chart/))
|
||||
|
||||
@@ -44,11 +44,16 @@ GitHub can change pricing, authentication rules, or terms without notice. With G
|
||||
- Version-control the document inside the mirror so updates stay in sync.
|
||||
- Rehearse twice a year; include steps to roll back to GitHub if needed.
|
||||
|
||||
### 5. Watch for drift
|
||||
### 5. Keep a second copy on a different host
|
||||
|
||||
Since 3.31 the destination does not have to be Gitea. **Forgejo** is a named destination, and **GitHub** and **GitLab** are push destinations (all three beta). A second Gitea Mirror account with a GitLab group as its destination gives you a copy on a different company's infrastructure at no extra cost, and the push engine keeps it identical to the source on every sync. Each account has one source and one destination, so run one account per direction. See [Mirror to GitHub or GitLab](/use-cases/mirror-to-github-or-gitlab/).
|
||||
|
||||
### 6. Watch for drift
|
||||
|
||||
- Review the repositories dashboard weekly for new projects, teams, or permission mismatches.
|
||||
- Check the **Automation** tab—if the scheduler is enabled, auto-discovery keeps owners current.
|
||||
- Use the Activity Log and `/api/events` export to alert on failures before your recovery point objective is breached.
|
||||
- Run **Reconcile with destination** from the Repository Cleanup card now and then. It lists mirrors on Gitea the database does not know about and rows whose mirror is gone, and fixes both on request without deleting anything. See [Adopt existing Gitea mirrors](/use-cases/adopt-existing-gitea-mirrors/).
|
||||
|
||||
## Success criteria
|
||||
|
||||
|
||||
Reference in new issue
Block a user