Le rôle gitea_repo ne savait viser qu'un propriétaire : l'organisation, des deux côtés à la fois. Les dépôts qui vivent sous le compte personnel `arcodange` ne pouvaient donc pas sortir du homelab — ni être balayés par gitea_sync, qui n'interroge que /orgs/<org>/repos. Trois séparations, toutes rétrocompatibles (les défauts reconduisent le comportement org-vers-org des dix dépôts déjà en miroir) : - le propriétaire côté Gitea (`gitea_repo_owner`) n'est plus le même objet que celui d'en face (`github_owner`, `gitlab_owner`) ; - un compte personnel n'est pas une organisation : GitHub ne crée pas le dépôt au même endroit, d'où `github_owner_is_org` qui route vers POST /user/repos ; - GitLab devient facultatif (`gitea_mirror_gitlab`). Il ne l'était pas : sa création attendait un 201 sans ignore_errors, si bien qu'un échec GitLab avortait l'itération — y compris la moitié GitHub, qui n'y était pour rien. Deux défauts corrigés au passage, tous deux silencieux : - les trois listages de gitea_sync ne paginaient pas (30 chez GitHub, 20 chez GitLab). Sous la taille d'une page tout va bien ; au-delà, la différence entre forges désigne de FAUX dépôts manquants et le rôle les « répare » ; - la migration entrante posait `repo_owner: github_organization` pour désigner le propriétaire DANS Gitea. Et un piège découvert en exécutant : un dépôt GitHub créé vide adopte comme branche par défaut la PREMIÈRE branche que le miroir lui pousse — `kadans` a atterri sur `arcodange/adr-ddd-front`. Le rôle réaligne désormais sur la branche par défaut de Gitea ; le miroir étant asynchrone, l'alignement échoue au run qui crée le dépôt et réussit au suivant, d'où le failed_when permissif. Ce qui sort du homelab reste un CHOIX : playbooks/07_mirrors.yml parcourt une liste explicite et relue (`gitea_mirrored_repos`) plutôt que la différence automatique entre forges, qui recréerait un dépôt supprimé exprès. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
4.9 KiB
vibe > Guidebooks > Factory provisioning > Ansible > 07 · Mirrors
07 · Mirrors — Gitea → GitHub / GitLab
Note
Status: ✅ active · Last Updated: 2026-07-27 Upstream: Ansible sub-hub · Factory provisioning hub Downstream: Roles reference —
gitea_repo,gitea_sync,gitea_tokenRelated: Inventory & variables · 03 · CI/CD
Gitea is the source of truth; GitHub and GitLab hold a pushed copy. playbooks/07_mirrors.yml walks the repos declared in gitea_mirrored_repos (inventory/group_vars/all/gitea.yml) and, for each, calls gitea_repo: create the counterpart repo private if it is missing, then attach a push mirror refreshed every 8 h and on every commit.
Nothing is pulled back. A mirror only ever pushes Gitea → forge, so a change made on GitHub is overwritten at the next sync.
uv run ansible-playbook -i ansible/arcodange/factory/inventory \
ansible/arcodange/factory/playbooks/07_mirrors.yml
# GitHub only — while the personal GitLab namespace is still unset:
… -e gitea_mirror_gitlab=false
The Gitea token is minted for the run by gitea_token and revoked in post_tasks. Everything is tagged gitea_mirrors.
Two ways to pick repos, and when each fits
gitea_sync |
gitea_mirrored_repos + 07_mirrors.yml |
|
|---|---|---|
| Selection | Automatic: diffs the three forges for one owner, reconciles whatever is missing somewhere | Explicit list, reviewed in the inventory |
| Fits | The organisation, where every repo is meant to exist everywhere | The personal account, where each repo leaving the homelab is a deliberate call |
| Blind spot | repos_incomplete = all − common says nothing about why a repo is missing — a repo deleted on purpose from GitHub is recreated |
Anything absent from the list is silently never mirrored |
Both drive the same gitea_repo role, so the mirror they produce is identical.
Owner mapping
A Gitea repo owned by the user arcodange does not belong on the GitHub organisation — and GitHub does not even create it the same way (POST /user/repos instead of POST /orgs/<org>/repos). Hence three knobs, all defaulting to the previous org-only behaviour:
| Var | Default | Meaning |
|---|---|---|
gitea_repo_owner |
gitea_organization |
Owner on Gitea |
github_owner / gitlab_owner |
github_organization / gitlab_root_group |
Owner on the far forge |
github_owner_is_org |
true |
false routes creation to POST /user/repos |
gitea_mirror_github / gitea_mirror_gitlab |
true |
Turn a forge off entirely |
Important
GitLab was not optional before. Its create call expected
201with noignore_errors, so a GitLab failure aborted the iteration — including the GitHub half that had nothing to do with it.gitea_mirror_gitlab: falseis the way out.
Warning
A GitHub repo created empty adopts as its default branch the first branch the mirror pushes, which is routinely a work branch rather than
main. The role realigns it against Gitea's default branch, but the mirror is asynchronous: the alignment fails on the run that creates the repo and succeeds on the next one. Run the playbook twice, or fix the branch by hand.
Current state (2026-07-27)
| Owner | Repos mirrored | Target |
|---|---|---|
arcodange-org |
10 (factory, tools, erp, cms, webapp, url-shortener, docker.tofu, docker-build-workflow, super-linter-workflow, vault-action) |
github.com/arcodange-org/* + GitLab |
arcodange (user) |
5 (kadans, kadans-api, kadans-dossier, kadans-jobs, video_analysis) — all private |
github.com/arcodange/* |
Not mirrored, deliberately left out of gitea_mirrored_repos: documents, studio, prospection, kissmetrics_contract_proposal (org) and .profile, DanceVideos, SecondBrain, dance-lessons-coach, frame-sdk, telegram-gateway (user).
Note
The personal repos have no GitLab mirror yet:
gitlab_personal_namespace_idis still~. Fill it with the numeric namespace ID of thearcodangeaccount on gitlab.com, otherwise creation would land the project in thearcodange-orggroup.
Reading the truth from Gitea
The push mirrors live in Gitea, not in this repo — the playbook is idempotent precisely because it asks first:
curl -s -H "Authorization: token $GITEA_TOKEN" \
https://gitea.arcodange.lab/api/v1/repos/arcodange/kadans/push_mirrors
last_update tells you when the mirror last pushed. A repo with no entry has no mirror, whatever this page claims.