cPanel Deploy
Pega uma pasta de arquivos estáticos e põe num cPanel. Python puro, sem
pip install, sem FTP, sem CI.
No fim ele busca o endereço público e confere se a página no ar é a que
subiu, comparando o <title>. Deploy que "deu certo" e serve o site velho é
o modo de falha mais comum aqui, e o script existe em boa parte para pegá-lo.
Config: duas camadas
O caso normal não é um site: é uma conta de hospedagem atendendo vários clientes. Então a credencial fica num lugar e o destino em outro.
projeto/
├── .env <- A CONTA. Uma vez, para todos.
│ CPANEL_HOST, CPANEL_USER, CPANEL_TOKEN
└── clientes/
├── padaria/
│ ├── .env <- DEPLOY_PATH, SITE_URL
│ └── dist/
└── oficina/
├── .env
└── dist/
O script sobe até 4 pastas juntando os .env, do mais distante para o mais
próximo, então o do cliente ganha. Rode de dentro da pasta do cliente.
| Chave | Onde | O que é |
|---|---|---|
CPANEL_HOST |
conta | o hostname do painel. Pode colar a URL inteira, o script limpa |
CPANEL_USER |
conta | usuário do cPanel (ande9564), não o e-mail da conta |
CPANEL_TOKEN |
conta | cPanel → Segurança → Gerenciar tokens de API |
CPANEL_PORT |
conta | 2083, o padrão |
DEPLOY_PATH |
cliente | pasta remota. Uma por cliente |
SOURCE_DIR |
cliente | pasta local, dist por padrão |
SITE_URL |
cliente | endereço público, para a conferência final |
--env arquivo força um arquivo só e desliga a busca para cima.
Modelo em .env.example. Detalhes de HostGator, limites de plano e
segurança de conta compartilhada em references/hostgator.md — leia antes
do primeiro deploy.
Como usar
cd clientes/padaria
python <SKILL>/scripts/deploy-cpanel.py --dry-run # lista, sem conexão
python <SKILL>/scripts/deploy-cpanel.py # publica
Sempre --dry-run primeiro. Ele não abre conexão nenhuma e por isso nem pede
credencial: serve para conferir o lote e o destino antes de tocar no servidor.
Não pergunte o DEPLOY_PATH: o servidor sabe
Cliente novo ainda não tem .env, e é tentador perguntar ao usuário qual é a
pasta. Não pergunte antes de olhar. O cPanel tem o document root exato de
cada domínio da conta:
python <SKILL>/scripts/deploy-cpanel.py dominios
python <SKILL>/scripts/deploy-cpanel.py dominios --dominio clientedele.com.br
Três respostas possíveis, e a terceira é a que importa:
| Resposta | O que fazer |
|---|---|
| O domínio está na conta | use o DEPLOY_PATH que ele imprimiu, sem perguntar nada |
| Não está, e o cliente ainda não tem domínio | subpasta do principal: public_html/<cliente> |
| Não está, mas o cliente tem domínio | ele ainda não aponta para cá. Adicione como addon domain antes, com o document root fora do public_html |
Uma sessão real perguntou "public_html ou addon domain?" e ofereceu as duas para um domínio que não estava na conta. As duas respostas estavam erradas, e o servidor teria dito isso em dois segundos.
Depois de saber o caminho, aí sim pergunte o que é decisão de negócio: subpasta agora ou domínio próprio já.
O que ele faz sozinho
| Checagem | Por quê |
|---|---|
Recusa se o .env estiver no git |
token commitado é irreversível: quem clonou já tem |
Lê só as chaves do cPanel, nunca o .env inteiro |
o .env do cliente costuma ter credencial de outra coisa |
| Para se a pasta remota já tiver o site de outro negócio | DEPLOY_PATH copiado de outro cliente sobrescreve o site dele em silêncio. Compara o <title> remoto com o que vai subir. --forcar passa por cima |
Avisa se houver index.php na pasta |
o Apache serve ele antes do index.html novo, e a home velha fica no ar |
| Avisa imagem acima de 500 KB | o site abre no 4G |
| Lê o JSON de verdade, topo e por arquivo separados | ver abaixo |
Busca a URL publicada e compara o <title> |
é a única checagem que responde "subiu mesmo?" |
O token nunca é impresso, e não vai por linha de comando: só no header da
requisição, porque argumento de processo aparece em ps aux.
Se a conferência final falhar, o script sai com código 1. Não anuncie "site no ar" antes do ✅.
Coisas que só se descobre publicando
Tudo abaixo foi encontrado contra uma HostGator real, e cada uma tem teste.
O Cloudflare recusa requisição sem User-Agent. Devolve 403 com
error code: 1010, antes de olhar o token — requisição sem autenticação
nenhuma dá o mesmo erro. Sem esse header o deploy nunca funciona, e o 403
parece problema de permissão. O script manda o header e, se o 1010 aparecer
mesmo assim, diz explicitamente que não é o token.
Fileman::mkdir não existe nesta versão do cPanel (nem create_directory,
nem makedir). Não faz falta: o upload_files cria a árvore sozinho, aninhada
inclusive.
upload_files recusa sobrescrever sem overwrite=1. Sem ele, o segundo
deploy de um cliente falha em todos os arquivos, que é a operação mais comum.
O status do UAPI não pode ser lido por busca de texto. upload_files
devolve um status por arquivo dentro de data, então uma resposta de erro
pode conter "status":1 aninhado:
{"errors":["token invalido"],"status":0,"data":[{"file":"a.jpg","status":1}]}
Ler isso com grep '"status":1' reporta sucesso sem ter subido nada. Os dois
status importam e são coisas diferentes: o de cima diz se a chamada foi aceita,
o de baixo se aquele arquivo entrou.
O mod_security devolve 406 para User-Agent curto. Navegador nunca cai nisso, mas monitor de uptime e crawler simples caem, e acusam o site como fora do ar. A conferência final usa UA de navegador por causa disso.
Antes do deploy
.envexiste, está no.gitignoree não foi commitadoDEPLOY_PATHé a pasta deste cliente- o conteúdo atual é sobrescrito arquivo a arquivo. O script não apaga nada: lixo de instalador antigo continua lá até você limpar pelo cPanel
- em addon domain,
DEPLOY_PATHé o document root que o cPanel mostrar, nãopublic_html
Teste
python scripts/deploy-cpanel.py --autoteste
Sem rede, sem chave, sem tocar em servidor. Roda no CI a cada push.