Guia Definitivo da API do HestiaCP: Automação em Bash, Integração em PHP e Troubleshooting em Produção
Voltar para blog

Guia Definitivo da API do HestiaCP: Automação em Bash, Integração em PHP e Troubleshooting em Produção

07/06/2026 · 5 min · Infraestrutura

Automatizar criação de contas no HestiaCP é etapa obrigatória quando o painel precisa operar acoplado a billing (WHMCS), ERP interno ou pipeline de onboarding. O ganho é direto: menos intervenção manual, menor tempo de entrega e trilha operacional auditável.

Neste artigo, documento exatamente como estruturei esse fluxo: validação local de comandos, chamada remota via API, troubleshooting de autenticação/conectividade e controles de segurança para produção.

1. Arquitetura da API do HestiaCP#

A API do HestiaCP atua como wrapper dos comandos CLI (v-*). Na prática, você envia um POST para o endpoint de administração e o painel executa o comando correspondente no backend.

Endpoint padrão:

https://SEU_SERVIDOR:8083/api/

Parâmetros essenciais:

Regra operacional: a API só é previsível quando você já validou o comando equivalente no shell.

2. Riscos de segurança críticos na automação de API#

A. O perigo da flag --insecure (ou -k)#

Desabilitar a verificação de certificados SSL/TLS no curl expõe a comunicação a ataques de Man-in-the-Middle (MITM). Um atacante posicionado na rede (ou um proxy intermediário) pode interceptar o tráfego e extrair as chaves e senhas administrativas em texto claro.

B. Senhas expostas nos argumentos da linha de comando#

Passar senhas diretamente nos parâmetros de comandos do shell faz com que a credencial fique visível no histórico (~/.bash_history), na listagem de processos ativos do sistema (ps aux | grep curl) e no diretório proc.


3. Gerenciamento seguro de chaves de acesso e variáveis#

O método mais recomendado de autenticação na API é o uso de Access Keys com privilégios limitados, evitando expor a senha principal do usuário admin.

Gerando a access key#

Você pode criar a chave pela interface web (Admin > Configure Server > API Access Key) ou diretamente pelo CLI do servidor:

# Adiciona uma chave de API com permissão para todos os comandos (dependendo da versão do HestiaCP, o comando CLI correspondente pode ser v-add-access-key ou v-generate-api-key)
v-add-access-key admin api-bot "HestiaCP API Token" all

Armazenando chaves com segurança#

Salve as chaves de acesso em um arquivo de configuração restrito ao usuário do script (normalmente root):

# Criar o diretório e arquivo de configuração
mkdir -p /root/.config
touch /root/.config/hestia-api.env
chmod 600 /root/.config/hestia-api.env

Edite o arquivo hestia-api.env e salve as variáveis:

HESTIA_HOST="https://seu-servidor.com:8083"
ACCESS_KEY="sua-access-key-gerada"
SECRET_KEY="sua-secret-key-gerada"

Exemplos avançados de chamadas cURL seguras de API segura#

Criando um usuário novo#

# Carrega as variáveis de ambiente com segurança
source /root/.config/hestia-api.env

# Lê a senha do usuário de forma segura sem expor no console
read -s -p "Digite a senha para o novo usuário: " NOVO_USER_PASS
echo ""

# Executa a chamada HTTPS com verificação de certificado ativa
curl -s -X POST "$HESTIA_HOST/api/v1/" \
  -d "hash=$ACCESS_KEY:$SECRET_KEY" \
  -d "returncode=json" \
  -d "cmd=v-add-user" \
  -d "arg1=cliente01" \
  -d "arg2=$NOVO_USER_PASS" \
  -d "[email protected]" \
  -d "arg4=default" \
  -d "arg5=Joao" \
  -d "arg6=Silva"

Adicionando um domínio web e configurando let's encrypt#

Diferente dos comandos básicos, o comando v-add-letsencrypt-domain requer o usuário, o domínio principal e a lista de aliases explícita (ex: www.dominio ou mail.dominio):

# 1. Adicionar o domínio
curl -s -X POST "$HESTIA_HOST/api/v1/" \
  -d "hash=$ACCESS_KEY:$SECRET_KEY" \
  -d "returncode=json" \
  -d "cmd=v-add-web-domain" \
  -d "arg1=cliente01" \
  -d "arg2=cliente.com.br"

# 2. Solicitar e aplicar SSL Let's Encrypt incluindo aliases
curl -s -X POST "$HESTIA_HOST/api/v1/" \
  -d "hash=$ACCESS_KEY:$SECRET_KEY" \
  -d "returncode=json" \
  -d "cmd=v-add-letsencrypt-domain" \
  -d "arg1=cliente01" \
  -d "arg2=cliente.com.br" \
  -d "arg3=www.cliente.com.br"

4. Pré-validação obrigatória no servidor (CLI primeiro)#

Antes de integrar PHP/WHMCS, valide diretamente no host se a sintaxe e permissões do comando estão corretas.

Exemplo de criação de usuário:

v-add-user novo_usuario 'SenhaForte123!' [email protected] default Nome Sobrenome
echo $?

Se o retorno (exit code) não for 0, não avance para API. Corrija no nível CLI primeiro.

Checklist mínimo no host:

which v-add-user
v-list-user admin json
v-list-packages json

Isso evita perder tempo depurando aplicação quando o problema está no próprio painel/pacote/permissão.

5. Testes isolados de API com cURL#

Com CLI validado, repliquei o mesmo fluxo via HTTP para eliminar variáveis do sistema chamador.

curl -k -X POST "https://seu-servidor.com:8083/api/" \
  -d "user=admin" \
  -d "password=SUA_SENHA" \
  -d "hash=SEU_HASH" \
  -d "cmd=v-add-user" \
  -d "arg1=novo_usuario" \
  -d "arg2=SenhaForte123" \
  -d "[email protected]" \
  -d "arg4=default" \
  -d "arg5=Nome" \
  -d "arg6=Sobrenome"

Durante debug, usei -k apenas para isolar TLS. Em produção, o correto é certificado válido + verificação habilitada.

Diagnóstico HTTP detalhado:

curl -vk -X POST "https://seu-servidor.com:8083/api/" -d "..."

Esse passo mostrou rapidamente se a falha era autenticação, payload malformado ou bloqueio de rede.

6. Implementação robusta de integração em PHP#

No integrador PHP, padronizei envio com http_build_query() para garantir encoding correto e evitar quebra de parâmetros com caracteres especiais.

<?php
$endpoint = 'https://seu-servidor.com:8083/api/';

$payload = [
  'user' => 'admin',
  'password' => 'SUA_SENHA',
  'hash' => 'SEU_HASH',
  'cmd' => 'v-add-user',
  'arg1' => 'novo_usuario',
  'arg2' => 'SenhaForte123',
  'arg3' => '[email protected]',
  'arg4' => 'default',
  'arg5' => 'Nome',
  'arg6' => 'Sobrenome',
];

$ch = curl_init($endpoint);
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => http_build_query($payload),
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CONNECTTIMEOUT => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_SSL_VERIFYPEER => true,
  CURLOPT_SSL_VERIFYHOST => 2,
  CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);

$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($errno) {
  throw new RuntimeException("cURL error [$errno]: $error");
}

if ($httpCode < 200 || $httpCode >= 300) {
  throw new RuntimeException("HTTP error [$httpCode] response: " . ($response ?? ''));
}

if ($response === false || trim($response) === '') {
  throw new RuntimeException('API response empty. Check auth/hash/firewall.');
}

echo $response;

Pontos que salvaram tempo de troubleshooting:

7. Script de provisionamento completo em Bash com retries e rollback#

O script a seguir foi desenhado para produção. Ele verifica conectividade e dependências, possui uma rotina de retentativas se o painel estiver temporariamente ocupado (ex: executando rotinas de backup), valida erros no retorno JSON e executa rollback automático de criação de usuário caso a adição do domínio falhe.

#!/usr/bin/env bash
# hestia-provision.sh - Provisionamento automatizado e seguro via API
# ⚠️ Teste exaustivamente em homologação antes de rodar em produção.

set -euo pipefail

# ==================== CONFIGURAÇÃO ====================
CONFIG_FILE="/root/.config/hestia-api.env"
LOG_FILE="/var/log/hestia-provision.log"

# Garantir que o arquivo de configuração existe e está protegido
if [ ! -f "$CONFIG_FILE" ]; then
  echo "❌ Arquivo de configuração ausente: $CONFIG_FILE" >&2
  exit 1
fi

source "$CONFIG_FILE"

# ==================== FUNÇÕES ====================

log() {
  echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] $1" | tee -a "$LOG_FILE"
}

error() {
  log "❌ ERRO: $1"
  exit 1
}

# Função principal com tratamento de conexões e validação de TLS
api_call() {
  local cmd="$1"; shift
  local i=1
  local args=()

  for arg in "$@"; do
    args+=("-d" "arg${i}=${arg}")
    ((i++))
  done

  # Realiza a chamada segura. Se o certificado do HestiaCP for auto-assinado em testes, 
  # substitua por --cacert /caminho/ca.crt, mas NUNCA use --insecure em produção.
  local response
  response=$(curl -s --connect-timeout 10 --max-time 60 \
    -X POST "$HESTIA_HOST/api/v1/" \
    -d "hash=$ACCESS_KEY:$SECRET_KEY" \
    -d "returncode=json" \
    -d "cmd=$cmd" \
    "${args[@]}") || error "Falha física de rede ao conectar à API HestiaCP"

  # Validar se a resposta retornou JSON válido e verificar se há erros reportados
  if ! echo "$response" | jq empty 2>/dev/null; then
    log "⚠️ Resposta da API não é um JSON válido: $response"
    return 1
  fi

  local api_error
  api_error=$(echo "$response" | jq -r '.error // empty' 2>/dev/null)
  if [ -n "$api_error" ] && [ "$api_error" != "null" ]; then
    log "⚠️ Chamada à API [$cmd] retornou erro: $api_error"
    return 1
  fi

  echo "$response"
}

# Wrapper de retentativas inteligentes com Backoff Exponencial
api_call_with_retry() {
  local max_retries=3
  local retry_count=0
  local response

  while [ $retry_count -lt $max_retries ]; do
    if response=$(api_call "$@"); then
      echo "$response"
      return 0
    fi

    retry_count=$((retry_count + 1))
    local wait_time=$((retry_count * 5))
    log "⏳ Chamada falhou ou o painel está ocupado. Tentativa $retry_count/$max_retries. Recuando e aguardando ${wait_time}s..."
    sleep $wait_time
  done

  error "Falha persistente após $max_retries tentativas no comando: $1"
}

# ==================== CONTROLE DE ERROS E CLEANUP (ROLLBACK) ====================
USUARIO_CRIADO=""

cleanup_on_error() {
  local exit_code=$?
  if [ $exit_code -ne 0 ]; then
    log "⚠️ Falha crítica detectada durante o provisionamento!"
    if [ -n "$USUARIO_CRIADO" ]; then
      log "🔄 Iniciando ROLLBACK: Removendo usuário parcial '$USUARIO_CRIADO'..."
      # Executa a remoção do usuário para evitar lixo órfão no sistema
      api_call v-delete-user "$USUARIO_CRIADO" >/dev/null || true
      log "✅ Rollback concluído. Conta parcial deletada."
    fi
  fi
}
trap cleanup_on_error EXIT

# ==================== PRÉ-REQUISITOS ====================
for dep in curl jq openssl; do
  command -v "$dep" >/dev/null 2>&1 || error "Dependência obrigatória ausente no sistema: $dep"
done

# Validar conectividade básica com a porta do painel
if ! curl -s --connect-timeout 5 "$HESTIA_HOST/api/v1/" > /dev/null 2>&1; then
  error "Endpoint da API do HestiaCP inacessível em: $HESTIA_HOST. Verifique o firewall."
fi

# ==================== RECEBIMENTO DE PARÂMETROS ====================
if [ $# -lt 3 ]; then
  echo "Uso: $0 <usuario> <email> <dominio>"
  exit 1
fi

USUARIO="$1"
EMAIL="$2"
DOMINIO="$3"

# Gerar credenciais e senhas randômicas fortes de forma programática
SENHA_USER=$(openssl rand -base64 16)
SENHA_DB=$(openssl rand -base64 16)

log "🚀 Iniciando provisionamento para o usuário: $USUARIO ($DOMINIO)"

# 1. Criação do Usuário
log "👤 Criando usuário '$USUARIO'..."
api_call_with_retry v-add-user "$USUARIO" "$SENHA_USER" "$EMAIL" "default"
USUARIO_CRIADO="$USUARIO" # Sinaliza que o usuário foi criado para controle do trap de rollback

# 2. Adição do Domínio Web
log "🌐 Adicionando domínio web '$DOMINIO'..."
api_call_with_retry v-add-web-domain "$USUARIO" "$DOMINIO"

# 3. Criação do Banco de Dados
log "🗄️ Configurando banco de dados MySQL para '$USUARIO'..."
api_call_with_retry v-add-database "$USUARIO" "db_${USUARIO}" "user_${USUARIO}" "$SENHA_DB" "mysql"

# 4. Emissão e aplicação do Certificado Let's Encrypt SSL
log "🔒 Solicitando certificado SSL Let's Encrypt para '$DOMINIO'..."
# Se falhar aqui (ex: DNS não propagado), registramos aviso mas não quebramos o script principal
api_call_with_retry v-add-letsencrypt-domain "$USUARIO" "$DOMINIO" "www.$DOMINIO" || \
  log "⚠️ SSL falhou (DNS pode não estar apontando para este host). Configure manualmente mais tarde."

# Salvar as credenciais geradas em local seguro com permissão restrita
SECRETS_DIR="/root/.secrets"
mkdir -p "$SECRETS_DIR"
touch "$SECRETS_DIR/${USUARIO}.info"
chmod 600 "$SECRETS_DIR/${USUARIO}.info"

cat <<EOF > "$SECRETS_DIR/${USUARIO}.info"
[CLIENTE: $USUARIO]
Data: $(date)
Domínio: $DOMINIO
Senha do Usuário: $SENHA_USER
---
Banco de Dados: db_${USUARIO}
Usuário do Banco: user_${USUARIO}
Senha do Banco: $SENHA_DB
EOF

log "✅ Provisionamento do cliente '$USUARIO' concluído com sucesso!"
log "🔑 Credenciais de acesso gravadas com segurança em: $SECRETS_DIR/${USUARIO}.info"

8. Diagnóstico de falhas recorrentes em produção#

9. Medidas de hardening e auditoria em produção#

Whitelisting de IP para API#

Mesmo com chaves de acesso fortes, restrinja o tráfego da API HestiaCP apenas para os servidores de controle (como o WHMCS). Configure o firewall do sistema e libere IPs na configuração de controle do painel:

# Adiciona acesso à API apenas para um IP específico
v-add-api-access-admin 192.168.1.50

Rotação periódica de chaves#

Chaves de API não devem ser eternas. Estabeleça uma rotina cron de substituição trimestral das chaves:

# Deleta a chave antiga (dependendo da versão do painel, use v-delete-access-key ou v-revoke-api-key)
v-delete-access-key ID_DA_CHAVE_ANTIGA

# Cria uma nova credencial temporária (use v-add-access-key ou v-generate-api-key)
v-add-access-key admin api-bot-$(date +%Y%m) "HestiaCP API Token" all

10. Runbook de validação pós-implementação e conclusão técnica#

Após deploy da automação, executei sequência de aceite:

  1. criar usuário via API
  2. criar domínio vinculado ao usuário
  3. validar conta no painel
  4. validar estrutura em disco
  5. validar serviço web/dns/mail conforme pacote

Comandos usados no pós-check:

v-list-user novo_usuario json
v-list-web-domains novo_usuario json
v-list-dns-domains novo_usuario json

Com isso, a automação ficou observável e previsível, sem "provisionamento fantasma".

Resultado técnico e considerações finais#

Com esse modelo, passei de criação manual para fluxo API com tempo de entrega de segundos, mantendo controle de segurança e trilha de auditoria. O principal ganho não foi só velocidade, foi consistência operacional.

Resumo prático:

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários