Automatize o Deploy com Python e Git Post-Hooks

12 min de leitura Automação
Automatize o Deploy com Python e Git Post-Hooks

Introdução à Automação de Deploy com Git Hooks e Python

A automação é o pilar fundamental da engenharia de software moderna. Para sysadmins, desenvolvedores e profissionais de DevOps, eliminar tarefas manuais repetitivas não é apenas uma questão de conveniência, mas de confiabilidade e velocidade. Neste tutorial técnico, exploraremos como configurar um git post-hook para automatizar o deploy de aplicações utilizando scripts em Python.

O conceito central aqui é o uso de hooks do Git. Hooks são gatilhos executáveis que rodam automaticamente quando eventos específicos ocorrem no repositório, como a finalização de um commit ou uma push para uma branch específica. Ao combinar a precisão do Git com a flexibilidade e poder de script da linguagem Python, criamos um pipeline de ci/cd leve, sem a necessidade de ferramentas externas pesadas como Jenkins ou GitLab CI para projetos simples.

Neste guia, você aprenderá a estruturar seu repositório, criar o hook no lado do servidor (ou cliente) e escrever um script Python robusto que verifica mudanças, baixa as últimas versões do código e reinicia os serviços necessários. Este método é ideal para ambientes de hospedagem compartilhada, VPS ou servidores dedicados onde você mantém controle total sobre a infraestrutura.

Pré-requisitos e Estrutura do Projeto

Antes de começarmos a codificar, é essencial definir o ambiente. Vamos assumir que você possui acesso SSH a um servidor Linux (Ubuntu/Debian ou CentOS) onde sua aplicação web está hospedada. Você também precisa ter o Git instalado tanto na máquina local (para desenvolvimento) quanto no servidor.

A estrutura de diretórios recomendada para este tutorial segue o padrão "bare repository" no servidor, que é a maneira mais segura e limpa de gerenciar hooks em um ambiente de produção. Evite colocar arquivos de configuração ou código diretamente na raiz do repositório bare, pois isso pode causar conflitos.

Aqui está a estrutura lógica que construiremos:

  • /var/www/myapp.git: O repositório "bare" (sem working tree) no servidor. Este é o destino dos seus pushes.
  • /var/www/myapp: O diretório de trabalho onde a aplicação roda efetivamente.
  • /var/www/myapp.git/hooks/post-receive: O script shell que atua como ponte entre o Git e o Python.
  • /opt/scripts/deploy.py: O script Python principal que realiza a lógica complexa de deploy.

Crie esses diretórios no seu servidor usando os comandos abaixo. Note que é crucial definir as permissões corretas para que o usuário do servidor web (geralmente www-data ou nginx) possa acessar os arquivos, mas que o script de deploy tenha permissão de escrita se necessário.

# Criar diretórios
sudo mkdir -p /var/www/myapp.git
sudo mkdir -p /var/www/myapp
sudo mkdir -p /opt/scripts

# Inicializar repositório bare no servidor
cd /var/www/myapp.git
git init --bare

# Definir permissões básicas (ajuste conforme seu usuário)
sudo chown -R $USER:$USER /var/www/myapp.git
sudo chown -R www-data:www-data /var/www/myapp

Configurando o Repositório Local

Agora, vamos configurar seu repositório local para enviar pushes para este novo destino. Se você já tiver um projeto iniciado, pule a etapa de inicialização e vá direto para a configuração do remote.

# No seu computador local
cd /caminho/para/seu/projeto
git init
git add .
git commit -m "Initial commit"

# Adicionar o repositório remoto no servidor
git remote add production ssh://seu_usuario@seu_ip_servidor/var/www/myapp.git

Com o remote configurado, estamos prontos para criar a lógica de automação. O segredo deste processo reside em como o Git notifica o servidor sobre as mudanças.

Criando o Script Shell do Post-Receive Hook

O primeiro componente da nossa cadeia de automação é o hook post-receive. Este script é executado pelo Git no servidor imediatamente após uma operação de push ser concluída com sucesso. Sua função principal não é fazer o deploy em si, mas sim receber informações sobre quais branches foram atualizadas e passar esses dados para o script Python.

O Git passa informações sobre os refs recebidos via stdin (entrada padrão). Cada linha contém três campos: o hash antigo, o hash novo e o caminho da referência (ex: refs/heads/main). Precisamos extrair essas informações para saber exatamente o que foi alterado.

Crie o arquivo post-receive dentro do diretório hooks do seu repositório bare:

sudo nano /var/www/myapp.git/hooks/post-receive

Insira o seguinte conteúdo. Este script shell é simples e atua como um roteador:

#!/bin/bash

# Definir variáveis de ambiente
WORK_TREE=/var/www/myapp
GIT_DIR=/var/www/myapp.git

# Ler as linhas de entrada (stdin) fornecidas pelo Git
while read oldrev newrev refname
do
    # Se o branch for main, executamos o deploy
    if [[ "$refname" == "refs/heads/main" ]]; then
        echo "Branch main recebido. Iniciando deploy..."
        
        # Chamar o script Python com os parâmetros necessários
        /usr/bin/python3 /opt/scripts/deploy.py "$newrev" "$WORK_TREE"
    else
        echo "Branch $refname ignorado para deploy automático."
    fi
done

exit 0

Após salvar o arquivo, é **imperativo** torná-lo executável. Sem essa permissão, o Git ignorará o hook silenciosamente.

sudo chmod +x /var/www/myapp.git/hooks/post-receive

Desenvolvendo o Script Python de Automação

Agora chegamos ao coração da nossa solução: o script deploy.py. Este script será responsável por realizar o checkout do código atualizado no diretório de trabalho e, opcionalmente, reiniciar serviços como Gunicorn, Nginx ou processos em segundo plano.

Vamos utilizar a biblioteca padrão do Python para manipulação de arquivos e subprocessos. Não é necessário instalar dependências externas complexas, o que mantém o ambiente leve. No entanto, se sua aplicação Python depende de bibliotecas específicas, certifique-se de ativar seu virtualenv dentro do script ou garantir que o ambiente global esteja configurado.

Crie o arquivo /opt/scripts/deploy.py:

sudo nano /opt/scripts/deploy.py

O código abaixo implementa uma lógica robusta de deploy:

#!/usr/bin/env python3
import sys
import os
import subprocess
import shutil

def run_command(cmd, cwd=None):
    """Executa um comando shell e retorna o resultado."""
    print(f"Executando: {' '.join(cmd)}")
    try:
        result = subprocess.run(
            cmd, 
            cwd=cwd, 
            check=True, 
            capture_output=True, 
            text=True
        )
        if result.stdout:
            print(result.stdout)
        return True
    except subprocess.CalledProcessError as e:
        print(f"Erro ao executar comando: {e.stderr}")
        return False

def deploy(target_dir, new_commit_hash):
    """Lógica principal de deploy."""
    
    # 1. Verificar se o diretório de destino existe
    if not os.path.exists(target_dir):
        print(f"Diretório {target_dir} não encontrado. Criando...")
        os.makedirs(target_dir)

    # 2. Realizar o checkout do código atualizado
    # Usamos 'git checkout -f' para forçar a substituição dos arquivos locais
    # sem perguntar sobre conflitos (ideal para automação limpa)
    print("Baixando últimas alterações...")
    
    # Se for o primeiro deploy, fazemos um clone inicial. 
    # Caso contrário, atualizamos o repositório local e fazemos checkout.
    
    repo_path = "/var/www/myapp.git"
    
    # Verificar se já existe um .git no diretório de trabalho
    if os.path.exists(os.path.join(target_dir, '.git')):
        # Modo atualização: fetch + reset hard para garantir que fica idêntico ao HEAD
        run_command(['git', 'fetch', 'origin'], cwd=target_dir)
        run_command(['git', 'reset', '--hard', 'HEAD'], cwd=target_dir)
    else:
        # Modo inicial: clone do bare repo
        # Nota: Em produção, é melhor clonar de um remote público ou usar git archive.
        # Para este tutorial, assumimos que o servidor tem acesso ao repositório local.
        print("Primeiro deploy detectado. Clonando repositório...")
        run_command(['git', 'clone', repo_path, target_dir])

    # 3. Instalar dependências (ex: pip install -r requirements.txt)
    # Descomente e ajuste se estiver usando Python/Django/Flask
    # if os.path.exists(os.path.join(target_dir, 'requirements.txt')):
    #     run_command(['pip', 'install', '-r', 'requirements.txt'], cwd=target_dir)

    # 4. Aplicar migrações de banco de dados (se aplicável)
    # if os.path.exists(os.path.join(target_dir, 'manage.py')):
    #     run_command(['python3', 'manage.py', 'migrate'], cwd=target_dir)

    # 5. Coletar arquivos estáticos (para Django, por exemplo)
    # if os.path.exists(os.path.join(target_dir, 'manage.py')):
    #     run_command(['python3', 'manage.py', 'collectstatic', '--noinput'], cwd=target_dir)

    # 6. Reiniciar o serviço da aplicação
    # Exemplo para systemd (ajuste o nome do serviço)
    print("Reiniciando serviços...")
    run_command(['sudo', 'systemctl', 'restart', 'myapp.service'])
    
    # Se não usar systemd, você pode usar um script de wrapper ou kill -HUP
    # run_command(['/var/www/myapp/restart.sh'])

    print("Deploy concluído com sucesso! Commit: " + new_commit_hash[:7])

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Uso: python3 deploy.py  ")
        sys.exit(1)

    new_rev = sys.argv[1]
    target_directory = sys.argv[2]
    
    deploy(target_directory, new_rev)

Lembre-se de dar permissão de execução ao script Python:

sudo chmod +x /opt/scripts/deploy.py

Considerações de Segurança e Permissões

A automação via hooks é poderosa, mas introduz vetores de segurança que devem ser gerenciados. Ao permitir que o Git execute scripts em resposta a pushes, você está essencialmente permitindo que qualquer pessoa com acesso de push ao repositório execute código no servidor.

Controle de Acesso: Certifique-se de que apenas desenvolvedores confiáveis tenham permissão de push para a branch main. Se possível, utilize autenticação SSH com chaves específicas ou restrinja o acesso via git-daemon-export-ok se estiver usando protocolo git.

Permissões do Sistema de Arquivos: O script Python roda como o usuário que fez o push (se autenticado via SSH) ou como o usuário do serviço Git. Se você estiver usando um sistema mais complexo, pode ser necessário configurar o Sudoers para permitir que o script execute comandos específicos (como systemctl restart) sem pedir senha, mas restrito apenas a esses comandos.

Para configurar isso no sudoers, edite com sudo visudo e adicione uma linha como:

seu_usuario ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service

Isso garante que o script não tenha privilégios root completos, apenas a capacidade de reiniciar o serviço específico.

Testando o Pipeline de Deploy

Com tudo configurado, é hora de testar. Faça uma pequena alteração em um arquivo no seu projeto local:

# No computador local
echo "# Atualização automática" >> README.md
git add .
git commit -m "Teste do hook automatizado"
git push production main

Acompanhe o output no terminal. Você deve ver as mensagens de execução do script post-receive e, em seguida, os logs gerados pelo Python. Se tudo correr bem, acesse sua aplicação e verifique se as mudanças estão refletidas.

Se houver erros, verifique:

  1. As permissões de execução dos arquivos post-receive e deploy.py.
  2. O caminho absoluto para o interpretador Python no script shell.
  3. Os logs do sistema (/var/log/syslog ou journalctl -u git-daemon) para erros de permissão ou execução.

Vantagens e Limitações desta Abordagem

A utilização de git post-hook com Python oferece uma solução de devops minimalista. Não há necessidade de servidores CI dedicados, custos adicionais ou complexidade na configuração de tokens e segredos externos para projetos pequenos a médios.

No entanto, esta abordagem tem limitações. Ela é linear e síncrona. Se o deploy falhar, o script retorna um erro, mas não há rollback automático complexo nem notificações integradas (embora seja fácil adicionar envio de emails ou mensagens Slack ao script Python). Para equipes grandes com múltiplos desenvolvedores fazendo pushes simultâneos para a mesma branch, pode haver condições de corrida. Nesse caso, considere adicionar um arquivo de trava (lock file) no script Python para garantir que apenas um deploy ocorra por vez.

Conclusão

A automatização do processo de deploy é um passo crucial na maturidade técnica de qualquer equipe. Ao implementar este fluxo com Git Hooks e Python, você ganha agilidade e reduz erros humanos. Este tutorial forneceu a base para um sistema funcional que pode ser expandido conforme suas necessidades crescem.

A partir daqui, você pode evoluir este script para incluir testes unitários automáticos antes do deploy, envio de notificações para o Slack ou Discord, ou integração com sistemas de monitoramento. A flexibilidade do Python permite que você adapte este hook a praticamente qualquer stack tecnológica.

Lembre-se sempre de testar alterações no hook em um ambiente de staging antes de aplicar em produção. A estabilidade do seu servidor depende da robustez desses scripts automatizados. Com esta configuração, você está pronto para abraçar práticas modernas de ci/cd de forma simples e eficaz.

Compartilhar: Link copiado!
Esse tutorial foi útil?

Comentários (0)

Seja o primeiro a comentar.

Deixe seu comentário

Seu comentário será analisado antes de ser publicado.

0/2000