Double mirror de mes 28 repos GitHub : Gitea principal sur srv-a (accès VPN), Gitea backup sur le NAS (DR si srv-a down). Modèle PULL : chaque Gitea tire les commits de GitHub, jamais l’inverse. PAT fine-grained read-only. 3 pièges.
Piège 1 : gitea.db owned root au 1er start
Wizard d’install sur http://nas:3000, étape « Install » :
The database settings are invalid: migrate: sync: attempt to write a readonly database
Pas « readonly » : le fichier SQLite existe (0 byte) mais owned root, alors que Gitea tourne en git (uid 1000). La directive USER_UID/USER_GID: "1000" n’agit qu’à l’init du container (création du user interne) : elle ne chown pas le volume host. Fix :
chown -R 1000:1000 /srv/.../appdata/gitea/data/
docker restart gitea
Piège 2 : affiliation et type mutuellement exclusifs (GitHub API)
Pour ne lister que mes repos perso, mon script faisait /user/repos?affiliation=owner&type=owner :
$ curl -fsS ".../user/repos?per_page=100&affiliation=owner&type=owner"
curl: (22) ... error: 422
Le body (curl -v) :
{"message":"You can either filter by visibility or affiliation, not both."}
Exclusifs au runtime, pas documenté. Fix : garder affiliation=owner, filtrer côté script :
[r['name'] for r in repos if r['owner']['login'] == GH_USER]
Piège 3 : scope write:repository ≠ read:organization
Pour vérifier mes orgs sur Gitea srv-a :
$ curl -H "Authorization: token $GITEA_TOKEN" "http://10.8.0.1:3000/api/v1/orgs/github-mirror"
HTTP 403
Le PAT a write:repository (créer des mirrors), mais lire les métadonnées d’org demande read:organization. Pas bloquant : POST /repos/migrate avec repo_owner=github-mirror marche avec write:repository seul. Mais 5 min perdues à croire que les orgs n’existaient pas.
Architecture finale
Les 2 Gitea pull GitHub indépendamment (~2× bande passante, négligeable pour 28 repos en mirror_interval 24h). Vraie double source : une Gitea perd son disque → l’autre a tout, et GitHub aussi. Pas de cascade A→B. Le PC clone/push uniquement sur srv-a via WireGuard.
Lessons learned
USER_UID/USER_GIDGitea nechownpas le volume host :chown -R 1000:1000après le 1er crash.- GitHub API :
affiliationettypeexclusifs sur/user/repos. Combiner = filtrer côté script. - Gitea scopes granulaires :
write:repository≠read:organization. Tester avec l’endpoint exact qu’on va vraiment utiliser. - PAT fine-grained > classic pour un mirror :
Contents: Read-onlysuffit, aucune écriture possible si Gitea est compromise. - Chaque Gitea pull GitHub > « A pull GitHub, B pull A » : évite la cascade de SPOF.