Sem FTP: Subindo sites WordPress com GitHub Actions
Você ainda abre o FileZilla para enviar arquivo por arquivo para o servidor? Chegou a hora de mudar! Depois deste post bastará subir os arquivos para um repositório no GitHub e eles serão enviados para o servidor automaticamente.
Se você está com pressa... tudo bem!
Não tem problema se você está com pressa. De forma bem resumida isso é o que vamos fazer:
- Criar um par de chaves SSH que se conectem ao seu servidor;
- Criar os segredos
DEPLOY_SSH_HOST
,DEPLOY_SSH_USER
eDEPLOY_SSH_KEY
no GitHub; - Criar o arquivo
bin/rsync-excludes.txt
no seu repositório; - Criar o arquivo
.github/workflows/deploy.yml
no seu repositório; - Fazer qualquer modificação na branch
trunk
.
O que você precisa
A lista do que vamos precisar para esse post é bem simples:
- Acesso SSH ao seu servidor: Disponível em quase todas as hospedagens atualmente;
- Um repositório no GitHub: Grátis e ilimitados, inclusive os privados.
Não se esqueça de fazer um backup do conteúdo do seu servidor antes de continuar. Assim, se algo der errado, será mais fácil reverter.
O que a GitHub Action vai fazer
Basicamente, o que vai acontecer é que ao atualizar os arquivos em uma determinada branch do repositório, uma GitHub Action se conectará via SSH com o servidor e enviará os arquivos usando o comando rsync
.
Acesso SSH
A sua GitHub Action precisará se conectar através do SSH (Secure SHell) com o seu servidor para enviar os arquivos. Para que isso seja possível, vamos criar um par de chaves SSH e configurar o servidor para aceitá-lo.
1. Criar um par de chaves SSH
A criptografia usada pelo protocolo SSH usa duas chaves: uma pública e uma privada. Para que a sua GitHub Action consiga enviar os arquivos para o seu servidor, você precisará de um novo par de chaves. Embora possível, não recomendo usar a mesma chave que você usa normalmente.
O comando para criar uma nova chave é o seguinte:
ssh-keygen -t ed25519 -C "usuario@dominio"
ssh-keygen
: O comando que gera uma nova chave;-t ed25519
: O algoritmo usado na criação da chave;-C "usuario@dominio"
: Não precisa ser um e-mail de verdade. Só é usado para diferenciar as chaves, como um comentário. Algo como[email protected]
já é o suficiente.
O comando pedirá:
- Uma senha: deixe em branco para facilitar o processo;
- Um nome de arquivo: não use o padrão, coloque um novo como
deploy
ou o nome do projeto. Não use nenhuma extensão.
2. Autorizar a chave a conectar com o seu servidor
Cada serviço de hospedagem cuida disso de uma forma diferente. Em hospedagens com algum tipo de painel de controle isso pode estar em Minha conta > SSH ou similar.
Na Cloudways, por exemplo, as chaves SSH são gerenciadas por aplicação. Na seção Access Details, é possível gerenciar as chaves SSH em Application Credentials.
Na imagem vemos setas para três elementos:
- Public IP: Será usado como Host na nossa configuração. Em muitos casos é a URL do próprio site.
- Username: A Cloudways tem um username diferente por aplicação. Algumas hospedagens usam o mesmo usuário para acessar tanto o painel quanto o servidor via SSH.
- SSH Keys: Este é o lugar onde é possível configurar novas chaves para essa aplicação. Lá você deve fornecer a chave pública (
arquivo.pub
) do par gerado.
3. Testar a chave SSH (Opcional)
Do seu computador é possível testar o acesso com a nova chave. Para isso basta executar o seguinte comando:
ssh -i <caminho-chave-privada> <usuario>@<servidor>
<caminho-chave-privada>
: O caminho do arquivo da chave privada (sem.pub
) no seu computador<usuario>@<servidor>
: Varia de serviço para serviço. No caso da Cloudways teríamos[email protected]
.
Se usássemos a chave gerada no exemplo, a chamada ficaria assim:
ssh -i /home/elia/.ssh/exemplopost [email protected]
Se tudo deu certo você vai conseguir acessar o terminal do seu servidor.
GitHub Actions
GitHub Actions é a ferramenta de automação do GitHub. Basicamente, o GitHub vai analisar qualquer arquivo .yml
na pasta .github/workflows
do seu repositório e tentar executá-lo cada vez que alguma coisa acontecer no seu repositório.
No nosso caso vamos criar um arquivo chamado .github/workflows/deploy.yml
. Você pode dar o nome que quiser, desde que mantenha a extensão .yml
e a localização do arquivo.
ATENÇÃO: Arquivos .yml não permitem tabs, só espaços (2 ou 4).
O conteúdo do nosso arquivo .github/workflows/deploy.yml
será o seguinte:
name: Deploy
env:
SSH_USER: ${{ secrets.DEPLOY_SSH_USER }}
SSH_HOST: ${{ secrets.DEPLOY_SSH_HOST }}
on:
push:
branches:
- trunk
jobs:
deploy_cloudways:
name: Deploy to Cloudways
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure SSH
run: |
mkdir -p ~/.ssh/
echo "$SSH_KEY" > ~/.ssh/deploy.key
chmod 600 ~/.ssh/deploy.key
cat >>~/.ssh/config <<END
Host cloudways
HostName $SSH_HOST
User $SSH_USER
IdentityFile ~/.ssh/deploy.key
StrictHostKeyChecking no
END
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
- name: Send files
run: "rsync --delete -avO ${{ env.RSYNC_FLAGS }} --exclude-from=${{ env.EXCLUDES }} ./ ${{ env.SSH_USER }}@${{ env.SSH_HOST }}:${{ env.DESTINATION }}"
env:
RSYNC_FLAGS: '' #--dry-run
EXCLUDES: bin/rsync-excludes.txt
SSH_HOST: cloudways
DESTINATION: "~/public_html/wp-content/"
Nome do fluxo de trabalho
A primeira linha do nosso arquivo simplesmente dá nome ao nosso workflow. O nome é usado simplesmente para visualizações e não interfere no funcionamento da automação.
name: Deploy
Segredos no GitHub Action
A segunda seção do nosso arquivo configura algumas variáveis de ambiente compartilhadas por alguns "passos" mais adiante.
env:
SSH_USER: ${{ secrets.DEPLOY_SSH_USER }}
SSH_HOST: ${{ secrets.DEPLOY_SSH_HOST }}
Como a descrição do fluxo de trabalho é feita em um arquivo visível para qualquer um que tenha acesso ao repositório, o GitHub fornece um jeito de criar segredos, editáveis apenas por administradores do repositório ou organização.
Como criar um segredo no GitHub Actions
Vamos relembrar o comando de acesso SSH que usamos para testar anteriormente:
ssh -i <caminho-chave-privada> <usuario>@<servidor>
Com esse comando em mente, acesse o repositório e vá até Settings > Secrets and Variables > Actions. Vamos criar três segredos:
DEPLOY_SSH_KEY
: O conteúdo da chave privada que você criou.DEPLOY_SSH_USER
: O que você usou como<usuario>
;DEPLOY_SSH_HOST
: O que você usou como<servidor>
;
Repare que DEPLOY_SSH_KEY
é diferente das outras: passamos o caminho do arquivo no comando, mas o segredo tem o conteúdo do arquivo. Isso acontece porque criaremos o arquivo durante a execução da action.
O segredo DEPLOY_SSH_KEY
não está nessa seção env
de propósito. Ele será usado mais adiante.
Gatilhos e eventos
on:
push:
branches:
- trunk
O conteúdo dessa seção é bem auto-explicativo: os passos neste arquivo só devem ser executados quando o conteúdo da branch trunk
for modificado.
Ao invés de push
, poderia ser pull_request
ou schedule
, por exemplo.
Jobs
Um conjunto de passos que serão executados. Nosso arquivo de workflow só tem um job, mas é possível ter mais de um. Diferentes jobs são executados em paralelo.
jobs:
deploy_cloudways:
name: Deploy to Cloudways
runs-on: ubuntu-latest
No nosso caso deploy_cloudways
poderia ser qualquer slug, assim como Deploy to Cloudways
. Esses são só nomes.
A string ubuntu-latest
especifica em que sistema operacional o job deve ser executado. É possível especificar versões de Linux, Windows e MacOS. Veja a lista completa.
Etapas ou steps
Steps são subdivisões do nosso job, executadas em ordem.
steps:
- name: Checkout
uses: actions/checkout@v2
O nosso primeiro passo é o checkout do repositório. Essa é, normalmente, a primeira etapa de todos os workflows que você verá por aí. Simplesmente traz o conteúdo do repositório para dentro do ambiente da action.
Configuração do SSH
Esta seção configura o SSH da máquina executando a nossa ação no GitHub.
- name: Configure SSH
run: |
mkdir -p ~/.ssh/
echo "$SSH_KEY" > ~/.ssh/deploy.key
chmod 600 ~/.ssh/deploy.key
cat >>~/.ssh/config <<END
Host cloudways
HostName $SSH_HOST
User $SSH_USER
IdentityFile ~/.ssh/deploy.key
StrictHostKeyChecking no
END
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
mkdir -p ~/.ssh/
: Cria o diretório.ssh
dentro da pasta$HOME
do servidor.echo "$SSH_KEY" > ~/.ssh/deploy.key
: Coloca o conteúdo da variávelSSH_KEY
dentro do arquivodeploy.key
.chmod 600 ~/.ssh/deploy.key
: Configura as permissões do arquivodeploy.key
. Nessa caso,600
significa que o dono do arquivo tem permissão de leitura e escrita. Nenhum outro usuário pode acessá-lo.cat >>~/.ssh/config <<END
: O que estiver entre<<END
eEND
será inserido no arquivoconfig
da pasta.ssh
.Host cloudways
: Cria um nome para a configuração a seguir. Ao chamar o SSH passandocloudways
, os dados seguintes serão usados;HostName $SSH_HOST
: O endereço do servidor. Se você lembrar do que falamos na seçãoenv
, essa variável receberá o valor do segredoDEPLOY_SSH_HOST
;User $SSH_USER
: Similar aoHostName
, mas aplicado ao usuário;IdentityFile ~/.ssh/deploy.key
: Especifica o caminho da chave a ser usada para se conectar com oHost
;StrictHostKeyChecking no
: Pula a verificação da identificação do servidor com o conteúdo do arquivoknown_hosts
.
Logo abaixo vemos uma seção env
, similar àquela global, declarada no começo do nosso arquivo. Repare que nesse caso a variável é criada apenas para esse passo. No caso, estamos passando o conteúdo do segredo DEPLOY_SSH_KEY
para uma variável de ambiente chamada SSH_KEY
. O conteúdo dessa variável será adicionado ao arquivo deploy.key
como vimos acima.
Enviar os arquivos com rsync
No último passo vamos finalmente alterar o conteúdo do servidor. Para isso vamos usar o comando rsync
, que sincroniza os arquivos de um lugar com outro. Nesse caso, os arquivos da máquina do GitHub (origem) com os arquivos no seu servidor (destino).
- name: Send files
run: "rsync --delete -avO ${{ env.RSYNC_FLAGS }} --exclude-from=${{ env.EXCLUDES }} ./ ${{ env.SSH_USER }}@${{ env.SSH_HOST }}:${{ env.DESTINATION }}"
env:
RSYNC_FLAGS: '' #--dry-run
EXCLUDES: bin/rsync-excludes.txt
SSH_HOST: cloudways
DESTINATION: "~/public_html/wp-content/"
Se o comando rsync
é novidade para você, vamos ver cada um dos parâmetros passados:
--delete
: Se um arquivo não está na origem, ele deve ser excluído do destino.-avO
:-a
significa sincronizar todos os arquivos, pastas e links simbólicos preservando suas permissões. Ov
é paraverbose
, ou seja, imprime no terminal a lista de modificações sendo feitas.O
(um "o" maiúsculo) configurarsync
para não se preocupar com datas de modificações de pastas.${{ env.RSYNC_FLAGS }}
: Como você já deve imaginar, será substituído pelo conteúdo da variávelRSYNC_FLAGS
configurada mais abaixo. Normalmente, não recebe nada, mas você pode, por exemplo, passar--dry-run
que só simula o chamado ao comando, sem fazer nenhuma mudança.--exclude-from=${{ env.EXCLUDES }}
: Você pode passar o endereço de um arquivo com a lista de tudo o que deve ser ignorado porrsync
. Veja a próxima seção para saber mais sobre isso../
: o endereço da pasta local que deve ser sincronizada.${{ env.SSH_USER }}@${{ env.SSH_HOST }}:${{ env.DESTINATION }}
: O endereço de destino. A última parte será substituída pela pasta no endereço de destino. No caso aqui o repositório contém os arquivos da pastawp-content
e o site está localizado na pasta$HOME/public_html
do servidor. No seu caso a pasta pode estar em outro lugar, como/var/www/html
. Acesse seu servidor ou procure nas configurações da sua hospedagem para saber a pasta em que seu site está localizado.
Sobre exclusões no rsync
No exemplo acima estamos usando um arquivo chamado rsync-excludes.txt
localizado na pasta bin
do repositório. Esse arquivo tem uma lista de arquivos e pastas que devem ser ignorados pelo comando rsync
. Por exemplo:
*.gitignore *.gitmodules *.git *.gitkeep *.github
/bin
*rsync-excludes.txt
/uploads
/upgrade
/themes/index.php
/plugins/index.php
Ao invés de usar --exclude-from=<arquivo>
você também pode alterar a chamada a rsync
e usar --exclude .git --exclude README.md
, por exemplo.
Fazendo mais com GitHub Actions
Se você quiser, também é possível chamar comandos como composer install
ou npm install
durante o processo, evitando que você tenha que versionar arquivos de terceiros no seu repositório. Além disso, o processo também pode ser iniciado pela criação de um Pull Request ou por períodos (uma vez a cada dia, semana, etc.).
Para exemplos reais, veja o repositório do ElasticPress. Lá tem ações executadas uma vez por dia, testes automatizados, deploy para o repositório do WordPress.org e mais.
Por que abandonar o SFTP
Para começar, você deve usar um controle de versão como o git, mesmo que trabalhe sozinho. Ter seus projetos em um repositório git serve tanto para o controle das mudanças quanto como backup, além de ser requisito mínimo para a maioria das vagas atualmente.
O processo descrito aqui tem várias vantagens sobre o método antigo de subir arquivo por arquivo em um programa de SFTP como FileZilla, por exemplo. O meu favorito é que com esse método somente os arquivos alterados serão enviados, tornando o processo mais rápido e simples. Também evitamos que pessoas diferentes sobrescrevam as mudanças umas das outras.
Usei GitHub e GitHub Actions neste post, mas existem outras alternativas disponíveis se achar necessário. O processo de enviar arquivos para um servidor através do repositório é chamado de Continuous Delivery e faz parte do conjunto Continuous Integration, Delivery e Deployment, comumente abreviados como CI/CD.
Conclusão
Nesse post vimos um método para enviar arquivos para um servidor a partir de um repositório git.
Para acessar o servidor é preciso um par de chaves SSH. A chave pública é armazenada no servidor, a privada é guardada em um segredo no GitHub.
Também vimos como é fácil criar um workflow no GitHub: basta criar um arquivo .yml
na pasta .github/workflows
. Para restringir o acesso a informações sensíveis usamos os segredos do GitHub.
A sincronização dos arquivos é feita pelo comando rsync
, que aceita vários parâmetros. Também é possível excluir arquivos da sincronização de formas diferentes: uma lista em um arquivo com o parâmetro --exclude-from
ou várias chamadas para --exclude
.
Este post é só o começo do que é possível com GitHub Actions. Também é possível usar ferramentas como o Composer ou NPM durante o processo.
Por fim, vimos porque essa forma de deployment é melhor que enviar arquivos individuais via SFTP.