diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c33bff --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# Migration Gogs → Gitea (figureslibres.io) + +Scripts utilisés pour migrer les dépôts d'une instance Gogs vers une instance Gitea +sur le même serveur (YunoHost), en conservant les propriétaires d'origine. + +**Portée volontairement limitée à :** +- les dépôts git (branches, tags, historique complet) ; +- les collaborateurs (contributeurs) de chaque dépôt ; +- les webhooks ; +- les dates de création/mise à jour affichées. + +**Explicitement hors scope** : issues, wiki, labels, milestones, releases, pull +requests. Les webhooks pointant vers des services internes avec secret non exposé +par l'API (ex: Kanboard) sont exclus par défaut. + +## Contexte + +Gogs et Gitea tournent tous les deux en local sur le même serveur (loopback, +`127.0.0.1`), ce qui permet de scripter la migration sans jamais passer par les +domaines publics ni par le SSO. Le script part du principe que les comptes +utilisateurs (individus) sont déjà identiques des deux côtés (ex: synchronisés via +un LDAP commun) — il ne crée jamais de compte utilisateur automatiquement. Les +organisations, en revanche, peuvent être créées automatiquement si absentes. + +### Pièges rencontrés (et déjà gérés par le script) + +- **L'authentification HTTP Basic Auth de l'API Gogs est cassée sur cette + instance** (401 identique avec de bons ou de mauvais identifiants) → l'API Gogs + s'utilise donc avec un **token**, jamais avec login/mot de passe. +- **`GET /api/v1/repos/search` de Gogs ne fait pas une vraie recherche + site-wide**, même avec un compte admin : il ne renvoie que les dépôts publics + + ceux du compte du token. La découverte se fait donc propriétaire par + propriétaire (`/users|orgs/{login}/repos`), en partant de la liste des comptes + connus côté Gitea. +- **Les dépôts privés d'un propriétaire n'apparaissent dans cette liste que si le + compte utilisé par le script a explicitement accès au dépôt** (ajouté comme + collaborateur) — l'admin ne "voit" pas tout par magie via cet endpoint. +- **`GET /api/v1/orgs/{login}` de Gogs ne vérifie pas le type** : il renvoie `200` + pour n'importe quel login existant, user ou org. La détection des vraies + organisations se fait donc en scrapant `/explore/organizations`, pas via cet + endpoint. +- **Le service `"gogs"` de l'API `/repos/migrate` de Gitea interroge d'abord + l'API Gogs et récupère son `clone_url` public**, qui repasse par le mur SSO et + fait échouer le clonage. Le script utilise le service générique `"git"` à la + place, qui clone brutalement l'adresse fournie (le loopback), sans ce détour. +- **`ALLOW_LOCALNETWORKS` doit être activé côté Gitea** (`app.ini`, section + `[migrations]`) pour autoriser une migration dont la source est une adresse + locale/loopback (protection anti-SSRF activée par défaut). +- **Un compte de service synchronisé LDAP se fait périodiquement réécraser son + statut admin** par la resynchronisation. Utiliser un compte **local** (pas + LDAP) pour le compte de service du script, des deux côtés (Gogs et Gitea). +- **L'API Gitea n'expose aucun moyen d'éditer `created_at`/`updated_at`** d'un + dépôt existant. `fix_creation_dates.py` compare les dates via les deux API + (lecture seule) et génère un script SQL à exécuter soi-même après vérification + du schéma et sauvegarde de la base. + +## Prérequis + +- Python 3 + `pip install requests` +- `git` installé +- Un **token API Gogs** admin (Gogs → Settings → Applications → Generate New Token) +- Un **compte Gitea admin local** (pas LDAP) avec mot de passe + +## Configuration + +```bash +cp config.env.example config.env +# éditer config.env et renseigner les valeurs -- ce fichier ne doit JAMAIS être commité +``` + +## Utilisation + +### `migrate_gogs_to_gitea.py` — dépôts, collaborateurs, webhooks + +Toujours en **dry-run par défaut** (rien n'est écrit) : + +```bash +python3 migrate_gogs_to_gitea.py # dry-run, tous les dépôts +python3 migrate_gogs_to_gitea.py --only owner/repo # dry-run, un seul dépôt +python3 migrate_gogs_to_gitea.py --limit 3 # dry-run, 3 premiers dépôts +python3 migrate_gogs_to_gitea.py --apply --only owner/repo # exécution réelle, ciblée +python3 migrate_gogs_to_gitea.py --apply # exécution réelle, tout +``` + +Un dépôt déjà présent côté Gitea n'est **jamais recréé ni écrasé** — seuls les +collaborateurs, webhooks et le topic `migrated-from-gogs` peuvent être complétés. + +### Vérifier l'historique git des dépôts déjà migrés à la main + +Compare les branches/tags Gogs vs Gitea des dépôts déjà présents (lecture seule) : + +```bash +python3 migrate_gogs_to_gitea.py --verify-existing +``` + +Ajoute les refs **totalement absentes** côté Gitea (jamais les branches +divergentes, jamais de `--force`) : + +```bash +python3 migrate_gogs_to_gitea.py --verify-existing --apply +``` + +### `fix_creation_dates.py` — dates de création/mise à jour + +Ne se connecte jamais à la base de données. Compare via les deux API (lecture +seule) et génère un script SQL à vérifier puis exécuter soi-même : + +```bash +python3 fix_creation_dates.py # tous les dépôts communs +python3 fix_creation_dates.py --only owner/repo # un seul dépôt (test) +``` + +Avant d'exécuter le `fix_dates.sql` généré : +1. Vérifier le nom des colonnes (`DESCRIBE repository;`) — `created_unix` / + `updated_unix` sont les noms standards des versions récentes de Gitea, mais à + confirmer sur la version en place. +2. Faire une sauvegarde de la table `repository` (ou de la base entière). +3. Exécuter avec le nom de la base en argument : + ```bash + mysql -u USER -pMOTDEPASSE NOM_BASE < fix_dates.sql + ``` + (le script valide lui-même avec un `COMMIT;` en fin de fichier) diff --git a/config.env.example b/config.env.example new file mode 100644 index 0000000..bd52a38 --- /dev/null +++ b/config.env.example @@ -0,0 +1,33 @@ +# Copier ce fichier en config.env puis renseigner les valeurs. +# config.env ne doit JAMAIS être partagé ni collé dans un chat/ticket. + +# URL locales (loopback) des deux instances -- valeurs déjà vérifiées sur ce serveur. +GOGS_URL=http://127.0.0.1:17750 +GITEA_URL=http://127.0.0.1:6002 + +# Token API Gogs (Gogs -> Settings -> Applications -> Generate New Token). +# Requis : sur cette instance, l'authentification HTTP Basic Auth de l'API Gogs est +# rejetée systématiquement (401 identique avec de bons ou de mauvais identifiants). +GOGS_TOKEN= + +# Compte admin Gogs -- utilisé UNIQUEMENT pour le clonage git (auth_username/password +# transmis à Gitea lors de /repos/migrate), pas pour les appels à l'API Gogs. +GOGS_ADMIN_USERNAME= +GOGS_ADMIN_PASSWORD= + +# Compte admin Gitea (authentification HTTP Basic, pas de token requis). +# Ce compte doit être admin du site : création d'orgs, ajout de collaborateurs sur +# n'importe quel dépôt, création de webhooks, etc. +GITEA_ADMIN_USERNAME= +GITEA_ADMIN_PASSWORD= + +# Topic Gitea posé sur chaque dépôt migré/complété, pour les retrouver facilement. +MIGRATION_TOPIC=migrated-from-gogs + +# Créer automatiquement les organisations Gogs absentes côté Gitea (true/false). +# Les comptes UTILISATEURS ne sont jamais créés automatiquement, quel que soit ce réglage. +AUTO_CREATE_ORGS=true + +# Webhooks dont l'URL contient cette sous-chaîne : jamais migrés (ex: kanboard interne, +# dont les secrets ne sont de toute façon pas exposés par l'API Gogs). +WEBHOOK_EXCLUDE_SUBSTR=figureslibres.io/kanboard