Projeto que mantém repositórios do GitHub espelhados no GitLab de forma automática.
- Lista repositórios próprios do GitHub via API.
- Processa repositórios em ordem crescente de último push, deixando os mais recentes por último.
- Permite incluir ou ignorar forks.
- Permite incluir ou ignorar repositórios arquivados.
- Cria projetos no GitLab automaticamente, preservando a visibilidade pública ou privada do repositório de origem.
- Evita sobrescrever projetos GitLab existentes quando as refs não batem com o mirror esperado.
- Mantém clones bare locais em
data/mirrorspara acelerar sincronizações futuras. - Continua processando os demais repositórios quando um mirror individual falha.
- Registra logs de execução em
data/logs. - Pode rodar localmente com
uvou como serviço agendado em Docker Swarm.
- Python 3.14+
uvgitdisponível noPATH- Token do GitHub com acesso aos repositórios que serão espelhados
- Token do GitLab com permissão para criar projetos e fazer push no namespace de destino
Para deploy agendado, também é necessário Docker com Swarm habilitado.
Crie o arquivo .env a partir do exemplo:
cp .env.example .envDepois preencha as variáveis:
| Variável | Descrição |
|---|---|
TZ |
Fuso horário usado pela aplicação e pelo agendador. |
CRON |
Expressão cron usada pelo deploy em Docker Swarm. |
GITHUB_USERNAME |
Nome do usuário GitHub dono dos repositórios. Mantido na configuração para identificação do ambiente. |
GITHUB_TOKEN |
Token usado para consultar a API e clonar repositórios do GitHub. |
GITLAB_TOKEN |
Token usado para autenticar no GitLab, criar projetos e enviar mirrors. |
GITLAB_URL |
URL da instância GitLab. O padrão é https://gitlab.com. |
GITLAB_NAMESPACE_ID |
ID numérico do namespace de destino no GitLab. |
MIRRORS_INCLUDE_FORKS |
Define se forks também serão espelhados. |
MIRRORS_INCLUDE_ARCHIVED |
Define se repositórios arquivados também serão espelhados. |
MIRRORS_OVERRIDE |
Desativa a checagem de segurança das refs do GitLab e força a sobrescrita do destino. O padrão é false. Use com extremo cuidado. |
Crie tokens separados para GitHub e GitLab. Salve o valor exibido na criação, porque os serviços normalmente não mostram o token novamente depois que a tela é fechada.
Use um fine-grained personal access token sempre que possível:
- Acesse
GitHub > Settings > Developer settings > Personal access tokens > Fine-grained tokens. - Clique em
Generate new token. - Em
Resource owner, selecione o usuário dono dos repositórios. - Em
Repository access, escolha:All repositories, para espelhar todos os repositórios próprios.Only select repositories, para limitar o mirror a uma lista específica.
- Em
Repository permissions, conceda:Contents: Read-only, necessário para clonar os repositórios por HTTPS.Metadata: Read-only, normalmente obrigatório e usado para consultar os metadados dos repositórios.
- Gere o token e copie o valor para
GITHUB_TOKEN.
Se usar um classic personal access token:
- Para espelhar repositórios privados, use o escopo
repo. - Para espelhar apenas repositórios públicos,
public_repoé suficiente.
O token do GitHub não precisa de permissão de escrita, porque este projeto só lê e clona os repositórios de origem.
Crie um personal access token no GitLab:
- Acesse
GitLab > Preferences > Access tokens. - Informe um nome, por exemplo
mirro-world. - Defina uma data de expiração compatível com sua política de segurança.
- Marque os escopos:
api, necessário para autenticar na API, localizar o namespace e criar projetos.write_repository, necessário para enviar o mirror comgit push --mirror.
- Gere o token e copie o valor para
GITLAB_TOKEN.
Preencha GITLAB_NAMESPACE_ID com o ID numérico do usuário ou grupo onde os
projetos espelhados serão criados. Para encontrar esse valor no GitLab, abra a
página do usuário ou grupo de destino; o ID aparece nos detalhes do namespace.
Em grupos, ele também aparece em Settings > General.
A conta dona do token precisa ter permissão nesse namespace para criar projetos e
fazer push nos projetos criados. Em grupos do GitLab, isso normalmente significa
ser Owner ou Maintainer, ou ter uma configuração do grupo que permita criação
de projetos por membros.
Exemplo de agendamento para rodar toda segunda-feira à meia-noite:
CRON='0 0 * * 1'Instale as dependências:
make installOu diretamente com uv:
uv sync --all-groups --all-packagesCom o .env configurado, rode:
uv run python -m mirro_world.mainO comando acima também inicializa o logging em arquivo. Durante a execução, a aplicação:
- Valida se o
gitestá instalado. - Busca os repositórios próprios no GitHub.
- Resolve o namespace configurado no GitLab.
- Cria o projeto de destino quando necessário.
- Se o projeto já existir no GitLab, só continua quando ele estiver vazio ou quando suas refs baterem com o mirror esperado.
- Clona ou atualiza o mirror local em
data/mirrors. - Envia o mirror para o GitLab com
git push --mirror.
Para evitar sobrescrita acidental, um projeto GitLab não vazio só é aceito se
suas refs forem iguais ao último clone local mantido em data/mirrors. Isso
permite atualizar o GitLab depois de um git push --force no GitHub, porque o
GitLab ainda estará igual ao último mirror conhecido antes da atualização. Em
uma instalação nova, quando ainda não existe clone local, o projeto GitLab só é
aceito se suas refs já forem iguais às refs atuais do GitHub.
MIRRORS_OVERRIDE=false é o padrão e deve continuar assim na operação normal.
MIRRORS_OVERRIDE=true ignora a proteção de refs e envia git push --mirror
mesmo quando o projeto GitLab não parece ser o mirror esperado. Essa configuração
é extremamente perigosa: ela pode apagar, mover ou sobrescrever branches e tags
de um projeto GitLab totalmente diferente que tenha o mesmo nome. Na prática, é
uma confirmação de que você aceita substituir o conteúdo do destino pelo estado
do GitHub.
Ative MIRRORS_OVERRIDE=true apenas temporariamente, em uma execução controlada,
e volte para false assim que terminar.
Os logs ficam em:
data/logs/
A imagem é publicada no GitHub Container Registry pelo workflow de CI:
ghcr.io/nayetdet/mirro-world:latest
O docker-compose.yml foi preparado para Docker Swarm usando
crazy-max/swarm-cronjob. O serviço principal começa com replicas: 0; o
cronjob cria execuções conforme a expressão definida em CRON.
Faça o deploy:
./deploy.shTambém é possível informar arquivos customizados:
./deploy.sh --docker-compose docker-compose.yml --env-file .envO script:
- Verifica se o arquivo Compose existe.
- Verifica se o arquivo de ambiente existe.
- Inicializa o Docker Swarm caso ele ainda não esteja ativo.
- Publica a stack
mirro-world.
Por padrão, a aplicação usa o diretório data na raiz do projeto:
data/
├── logs/ # Arquivos de log por execução
└── mirrors/ # Clones bare usados para sincronização incremental
Esse diretório não deve ser versionado.
src/mirro_world/
├── clients/ # Clientes GitHub e GitLab
├── core/ # Lógica de sincronização do mirror
├── logging.py # Configuração de logs
├── utils/ # Helpers para URLs e nomes de repositórios
├── main.py # Fluxo principal
└── settings.py # Configuração via variáveis de ambiente
O workflow em .github/workflows/ci.yml constrói e publica a imagem Docker no
GitHub Container Registry quando há push na branch main com mudanças em código,
Dockerfile, dependências ou workflow.
Este projeto está licenciado sob GPL-3.0-or-later. Consulte o arquivo
LICENSE para mais detalhes.