Gitea 28: novidades e cuidados no upgrade

Mascote LinuxPro abre uma caixa com o logo do Gitea e símbolos de auditoria, bots e segurança, acompanhado pelo caramelo ciborgue.

O Gitea pulou da 1.27 para a 28 — e não, não foram 27 versões perdidas. A 28.0.0, anunciada em 30 de setembro de 2026, é a versão que se chamaria 1.28.0: o projeto simplesmente abandonou o “1.” que nunca saía do lugar. Por trás do número novo há uma release grande, com log de auditoria, contas de bot, deploy tokens, impersonação pelo administrador e várias melhorias nas Actions — e mudanças incompatíveis que exigem revisar segurança, retenção e workflows antes de atualizar. Este post resume o que mudou e conta o que aprendemos atualizando uma instância de produção.

Por que 28 e não 1.28

Desde o fork do Gogs, em 2016, o Gitea numerava as versões como 1.x. O primeiro número nunca mudou; quem carregava a informação era o segundo. A partir desta release, esse segundo número passou para a frente: 1.27 → 28. Não há reescrita nem quebra geral de compatibilidade por causa do número — há mudanças incompatíveis que precisam ser avaliadas, descritas mais abaixo.

O efeito prático é em automação: scripts que procuram tags 1.*, comparam versões como texto ou montam a URL de download na mão. Junto com a mudança, a 28 deixou de publicar binários x86 de 32 bits e as variantes gogit, e os nomes de arquivo perderam o sufixo de versão do sistema operacional. Para Linux amd64, o binário continua em https://dl.gitea.com/gitea/28.0.0/gitea-28.0.0-linux-amd64, com .sha256, assinatura GPG e pacote Sigstore ao lado.

Log de auditoria

Um recurso importante para quem administra Gitea em empresa. Eventos relevantes de segurança passam a ser registrados no estilo do GitHub: ação, autor, escopo, origem (interface, API, CLI ou sistema) e metadados. Os eventos aparecem nas configurações do administrador, da organização, do repositório e do usuário, com filtros. Ações feitas durante uma impersonação registram as duas pessoas.

O detalhe que importa: a gravação vem desligada. Para ligar, no app.ini:

[audit]
RECORD_OUTPUT  = database
RETENTION_DAYS = 90   ; padrão 30; 0 guarda para sempre

A limpeza dos eventos antigos é feita pela tarefa cron.delete_old_audit_events.

Contas de bot e deploy tokens

Automação no Gitea costumava significar criar um usuário comum, gerar um token e torcer para ninguém logar com ele. A 28 traz contas de bot de verdade: usuários sem senha, que só autenticam por token, não recebem notificações nem e-mails e não conseguem abrir sessão interativa — nem por login, nem por proxy reverso, nem por fonte externa. Elas podem ser administradas pela interface, pela API ou pela CLI. O comando gitea admin user change-type converte uma conta local existente em bot ou faz a conversão inversa.

Os deploy tokens são o par das deploy keys para HTTPS: uma credencial limitada a um repositório, com leitura ou leitura e escrita, usada como senha numa operação Git — inclusive LFS. Servem para o servidor de produção puxar um repositório sem chave SSH e sem a conta de uma pessoa.

Completam o pacote os tokens pessoais regeneráveis: dá para trocar o valor de um token mantendo nome e permissões, útil quando ele vazou ou foi entregue a terceiros — o próprio PR cita o caso de um token passado a um agente de IA.

Administração e revisão de código

  • Impersonação: o administrador pode ver a instância como um usuário específico para investigar um problema de permissão. A ação fica registrada quando o log de auditoria está habilitado.
  • Code owners obrigatórios: uma nova regra de proteção de branch exige a aprovação de um proprietário ou integrante do time por regra correspondente no CODEOWNERS.
  • Diff mais navegável: busca e filtro por extensão na barra lateral de arquivos, e linhas longas truncadas, mas visíveis.
  • Notificações por WebSocket: o canal de eventos em tempo real (contador de notificações, cronômetro, logout) trocou SSE por WebSocket em /-/ws. Se o WebSocket não conectar, a interface cai para consulta periódica.

Gitea Actions

As Actions — o CI/CD embutido, com YAML compatível com o da GitHub Actions — ganharam:

  • Fila de builds: uma visão somente leitura dos jobs, com os em execução primeiro e depois os que estão aguardando, na ordem em que um runner vai pegá-los. Aparece na administração para a instância inteira e em cada repositório.
  • Matriz dinâmica: o strategy.matrix de um job pode ser montado a partir das saídas de jobs anteriores.
  • max-parallel na matriz, cancelamento forçado de execuções pela API e mais endpoints para gerenciar execuções e logs.
  • Prévia de artefatos direto na página da execução.

No lado do executor, o runner chegou à 4.1.0 em 1º de outubro, com um backend para rodar os jobs no Kubernetes.

Retenção e egress: dois cuidados antes do upgrade

Comece pela retenção dos dados e pelas permissões de saída. Há outros requisitos de compatibilidade na seção seguinte.

1. O histórico de Actions passa a expirar

Até a 1.27, execuções concluídas ficavam no banco para sempre. A 28 cria o RUN_RETENTION_DAYS, com padrão de 400 dias: na limpeza da meia-noite seguinte ao upgrade, execuções mais antigas que isso são apagadas, junto com jobs, logs e artefatos. Sem um backup, não há restauração automática. Para manter o comportamento antigo, defina antes de atualizar:

[actions]
RUN_RETENTION_DAYS = 0   ; 0 = guardar para sempre

Atenção ao 0: agora ele significa “guardar para sempre” também em LOG_RETENTION_DAYS e ARTIFACT_RETENTION_DAYS. Até a 1.27, quem pusesse 0 nessas duas chaves tinha logs e artefatos apagados na limpeza seguinte.

2. O tráfego Git de saída passa por um proxy interno

Migrações, espelhos e outras operações de rede do Git agora passam por um proxy interno, que aplica as regras de egress às conexões diretas. As regras ganharam um modo:

  • EGRESS_MODE = lax (padrão): permite hosts públicos em qualquer porta, salvo bloqueios explícitos; endereços privados e de loopback exigem permissão.
  • EGRESS_MODE = strict: só libera o que estiver na lista de permitidos, respeitando os bloqueios; entradas sem porta valem apenas para 80 e 443.
Fluxo das operações Git de migração e espelhamento pelo proxy interno, comparando permissões de saída nos modos lax e strict; a lista de bloqueio prevalece
Conexões Git diretas: o modo strict restringe os destinos à lista de permitidos. Bloqueios explícitos prevalecem nos dois modos.

O alerta das notas de versão é específico: em [security], uma ALLOWED_HOST_LIST que antes restringia os destinos públicos deixa de ser exclusiva no modo padrão lax. Para manter essa restrição em webhooks e OAuth2, configure EGRESS_MODE = strict. Não copie essa configuração sem listar os destinos realmente necessários.

Há uma exceção importante na migração de configuração: em [migrations], se a lista nova estiver ausente, uma ALLOWED_DOMAINS legada mantém o modo estrito por compatibilidade e permite todas as portas dos hosts listados. Ao adotar a lista nova, revise as portas explicitamente. Consulte a referência de migrações, em vez de presumir equivalência entre chaves antigas e novas.

As listas controlam conexões diretas. Se houver um proxy de saída configurado, o bloqueio dos destinos encaminhados passa a ser responsabilidade desse proxy.

Além disso:

  • O preset external deixou de existir.
  • Curingas em endereços IP e a entrada * não são mais aceitos.
  • Domínios seguem a sintaxe do curl: example.com vale para o domínio e todos os subdomínios; *.example.com, só para os subdomínios.
  • Uma entrada inválida em [migrations] BLOCKED_HOST_LIST impede o Gitea de subir.
  • ALLOWED_DOMAINS, BLOCKED_DOMAINS e ALLOW_LOCALNETWORKS ficam obsoletas em favor de ALLOWED_HOST_LIST e BLOCKED_HOST_LIST.
  • O [migrations] controla migrações e espelhos; o [security], webhooks e OAuth2.

Outras mudanças incompatíveis

  • Git mínimo: a versão 28 exige Git 2.25.0 ou posterior. Confira git --version no ambiente que executa o Gitea.
  • Cadastro: o autorregistro fica desativado por padrão. Para permiti-lo deliberadamente, configure [service] DISABLE_REGISTRATION = false.
  • URL da instância: [server] DOMAIN deixa de ser lido; revise ROOT_URL, usado também para derivar o domínio SSH padrão.
  • Workflows: o if: do job é avaliado antes da expansão da matriz. Condições com matrix precisam ser movidas para etapas ou para a configuração da matriz.
  • Falha na matriz: strategy.fail-fast passa a ser aplicado; configure false quando todas as combinações precisarem terminar.
  • Workflows reutilizáveis: repositórios públicos não podem chamar workflows privados, e chamadas aninhadas não podem elevar as permissões do token do chamador.

Esses pontos fazem parte das mudanças incompatíveis documentadas pelo projeto.

Na prática: atualizando uma instância de produção

Na atualização de uma instância instalada como binário com systemd e MySQL, com runner próprio e espelhos, três pontos mereceram atenção.

A configuração legada de migrações merecia revisão. O app.ini tinha:

[migrations]
ALLOW_LOCALNETWORKS = true
ALLOWED_DOMAINS     = github.com,api.github.com,gitlab.com

Uma configuração explícita com a lista nova pode usar o modo estrito. Não é uma equivalência exata: entradas sem porta passam a permitir apenas 80 e 443. Além disso, github.com já cobre api.github.com:

[migrations]
EGRESS_MODE       = strict
ALLOWED_HOST_LIST = github.com,gitlab.com,private,loopback

O exemplo permite redes privadas e loopback: remova esses presets se não forem necessários e prefira destinos específicos. Confira também de onde vêm os espelhos — no modo estrito, um Git interno numa porta como 3000 precisa de uma entrada com a porta, como 10.0.0.15/32:3000 (substitua pelo IP real do seu ambiente). Valide a configuração e o acesso aos destinos antes da virada.

O histórico de Actions seria parcialmente apagado. Com o padrão de 400 dias, execuções acima desse prazo seriam removidas na limpeza agendada. Definimos RUN_RETENTION_DAYS = 0 antes do upgrade.

O nginx não repassava o WebSocket. Depois do upgrade, o handshake em /-/ws respondia 101 Switching Protocols direto no Gitea, mas 426 Upgrade Required pelo nginx. O bloco de proxy até repassava os cabeçalhos Upgrade e Connection, mas faltava a versão do protocolo — no nginx 1.18.0 verificado, o padrão para o upstream é HTTP/1.0, que não permite esse upgrade:

Para uma configuração nova, o exemplo abaixo usa o map recomendado na documentação do nginx. O map pertence ao contexto http; o location, ao bloco server existente:

# Dentro de http {}, fora de server {}
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# Dentro do server {} existente
location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;              # a linha que faltava
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Para testar, sem navegador:

curl -s --max-time 5 -o /dev/null -w '%{http_code}\n' --http1.1 \
  -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
  -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  https://git.exemplo.com.br/-/ws
# 101 = handshake concluído; 426 = investigar o upgrade no proxy

Depois de receber 101, a conexão permanece aberta; o timeout e o código de saída 28 do curl ao fim dos cinco segundos são esperados neste teste. Valide a configuração com nginx -t antes de recarregar o serviço.

As notificações continuam funcionando se você esquecer — a interface cai para consulta periódica —, mas as notificações deixam de ser instantâneas. Quem usa o Caddy na frente não precisa fazer nada: o reverse_proxy trata WebSocket sem configuração extra.

No proxy nginx 1.18.0 verificado, acrescentar proxy_http_version 1.1, mantendo os cabeçalhos de upgrade já existentes, mudou a resposta de 426 para 101. A página inicial continuou respondendo 200 depois da recarga.

Roteiro de upgrade

  1. Backup consistente: interrompa as gravações conforme a documentação de backup. Preserve banco, repositórios, configuração e arquivos de dados no mesmo ponto consistente; use o dump nativo do banco quando indicado e teste a restauração. A migração do banco não tem rollback automático.
  2. Compatibilidade: confira Git, ROOT_URL, cadastro e workflows antes de reiniciar.
  3. Retenção: decida o RUN_RETENTION_DAYS antes de subir a 28.
  4. Egress: se você usa ALLOWED_DOMAINS, BLOCKED_DOMAINS, ALLOW_LOCALNETWORKS ou o preset external, reescreva com ALLOWED_HOST_LIST, BLOCKED_HOST_LIST e EGRESS_MODE. Se a lista precisa ser exclusiva, use strict.
  5. Scripts: ajuste o que monta URLs de download ou compara versões com “1.”.
  6. Troca: substitua o binário ou a tag da imagem (docker.gitea.com/gitea:28.0.0) e reinicie. Confira a soma SHA-256 antes.
  7. Verificação: /api/healthz, a versão em /api/v1/version, os avisos no log de inicialização e o teste de WebSocket acima.

O passo a passo completo de instalação — Docker Compose com MariaDB, binário com systemd, runner e tea CLI — está no guia Gitea: Git self-hosted com Actions, runner e tea CLI, já atualizado para a 28.

Segurança

A 28 traz correções de segurança, entre elas a rejeição de objetos Git inválidos ou duplicados no push, a identificação de chaves SSH por impressão digital, a garantia de que execuções de PR vindas de fork continuem esperando aprovação mesmo depois de canceladas, uma correção de negação de serviço no SSH e a verificação de autorização por repositório em acessos de times, exclusões e pacotes. Os detalhes completos — com identificadores e gravidade — ficaram para cerca de uma semana depois do lançamento e, até 5 de outubro de 2026, ainda não tinham sido publicados. Isso, por si só, já é motivo para não adiar o upgrade.

Vale atualizar?

Sim, depois de revisar as mudanças incompatíveis e ter um backup restaurável. A 28 é uma release de amadurecimento: auditoria, bots, deploy tokens e aprovação por code owners ampliam os controles disponíveis para equipes, sem perder o que sempre foi sua vantagem — um binário, um banco e pouca memória. O número novo assusta mais do que a atualização.

Leia também: a história do GitLab, o Gogs, de onde o Gitea saiu e DevOps: o que é CI/CD?.

Referências: release 28.0.0, configurações do Gitea e runner 4.1.0. Revisado em 5 de outubro de 2026.