🌐 Esta página aún no está traducida al español. Mostrando referencia en portugués (BR). Ayúdanos a traducir.
Provisioning automatizado (Cloud)
Este documento descreve como uma VM RoqueOS Cloud é criada do zero em ~3-5 min após o cliente assinar. Você não precisa entender nada disso pra usar — é referência técnica pra debug, suporte, ou pra quem quer adaptar o fluxo pro próprio negócio.
Diagrama do fluxo
⚠️ O token dos passos 4 e 7 é o que impede qualquer um de registrar um servidor em nome de outro cliente. Ele vale 30 minutos: uma VM que demore mais que isso para chamar de volta cai no ramo da direita e não conclui.
Componentes envolvidos
Frontend (roqueos-front)
src/pages/PricingPage.vue— botão "Assinar" leva pro Stripe Checkoutfunctions/index.js— Cloud FunctionsprovisionServereprovisioningCallbackpublic/install.sh— mirror do instalador canonical doroqueos-server
Backend (roqueos-server)
install/install.shv1.1.0-cloud-callback — instalador one-liner com suporte a callbacksrc/modules/admin/admin.controller.ts— endpointPOST /admin/setupidempotente (gera admin key)
Infra externa
- Hetzner Cloud API (
https://api.hetzner.cloud/v1) — criação de servers - Cloudflare API — criação de subdomain
<id>.cloud.roqueos.com.br(planejado v1.2) - Firebase Secret Manager — guarda
HCLOUD_TOKEN,PROVISIONER_SSH_KEY_ID,STANDARD_FIREWALL_ID - Stripe Brasil — checkout BRL via cartão/Pix
Tier → spec Hetzner
| Tier RoqueOS | Hetzner SKU | vCPU | RAM | Disco SSD | COGS €/mês |
|---|---|---|---|---|---|
| Starter | cx22 | 2 shared | 4 GB | 40 GB | 4.51 |
| Pro | cx32 | 4 shared | 8 GB | 80 GB | 7.55 |
| Power | ccx23 | 4 dedicated | 16 GB | 160 GB | 31.20 |
| Enterprise | ad-hoc | varia | varia | varia | varia |
Localização: Falkenstein, Alemanha (fsn1). Imagem: ubuntu-22.04.
Endpoints Cloud Function
provisionServer (admin onCall)
Trigger: Stripe webhook customer.subscription.created. Internamente é uma https.onCall que requer context.auth.token.admin === true.
Input:
{
"customerUid": "abc123",
"tier": "pro"
}Output:
{
"ok": true,
"vmId": "12345678",
"vmIp": "65.108.x.x",
"provisioningToken": "<64 hex chars>",
"callbackUrl": "https://southamerica-east1-roqueos.cloudfunctions.net/provisioningCallback",
"tokenExpiresAt": 1716800000000
}O provisioningToken é mostrado uma única vez pro admin (não persiste em log). O hash SHA-256 dele fica em subscriptions/{uid}.provisioning.tokenHash no Firestore, com TTL 30 min.
provisioningCallback (HTTP público)
Trigger: VM recém-criada chama POST desse endpoint via install.sh.
URL pública: https://southamerica-east1-roqueos.cloudfunctions.net/provisioningCallback
Por que público sem auth Firebase: a VM novinha não tem credenciais Firebase. Autenticação é feita pelo token efêmero (one-time, SHA-256 hash comparado).
Input (POST JSON):
{
"customerUid": "abc123",
"token": "<64 hex chars>",
"apiKey": "ros_xxxx",
"apiSecret": "ros_secret_xxxx"
}Validações:
- Shape do payload (regex em token + apiKey + apiSecret)
tokenHashconfere com o gravado no Firestore- Token não expirado
- Subscription ainda em
provisioning(não foi completada antes)
Output ok: 200 { "ok": true }Output erro: HTTP 400/401/404/409/410 + { "error": "<code>" }
Erros possíveis:
400 missing_fields— payload incompleto400 invalid_*— shape inválido401 token_mismatch— token errado (potencial ataque, loga IP)404 subscription_not_found— customerUid não existe409 already_completed— callback já foi chamado com sucesso (dedup)409 no_pending_provisioning— subscription não está em estado provisioning410 token_expired— passou dos 30 min
install.sh flags do cloud-init
A Cloud Function provisionServer gera um user_data (cloud-init) que executa:
#!/bin/bash
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y curl ca-certificates
curl -fsSL https://roqueos.com.br/install.sh | bash -s -- \
--customer-id "${CUSTOMER_UID}" \
--tier "${TIER}" \
--provisioning-token "${TOKEN}" \
--provisioning-callback "${CALLBACK_URL}" \
--skip-promptsAs 4 flags --customer-id, --tier, --provisioning-token, --provisioning-callback foram adicionadas no install.sh v1.1.0-cloud-callback (INSTALLER_VERSION no topo do script). Quando todas as quatro estão presentes, o instalador dispara do_provisioning_callback() após wait_for_server retornar healthy.
O que o cliente vê quando entra

Acompanhar um provisionamento, passo a passo
Quando um cliente assina e o servidor não chega, é esta a ordem para conferir — cada passo elimina uma etapa do fluxo acima:
- Stripe — o pagamento consta como concluído e o webhook saiu com 200? Se o webhook falhou, nada depois disso aconteceu.
- Logs da
provisionServer— ela recebeu o evento e chamou a Hetzner? O log traz o id da VM criada. - Console da Hetzner — a VM existe e está ligada? VM criada e parada é falha de cloud-init, não de pagamento.
/healthda VM — responde 200? Enquanto não responder, oinstall.shainda está subindo os containers lá dentro.- Logs da
provisioningCallback— o callback chegou e o token era válido? Token expirado (30 min) é o motivo mais comum de VM de pé sem assinatura ativa. - Firestore — a assinatura virou
activee as credenciais foram gravadas? - E-mail — o SMTP entregou? Se tudo acima está certo e o cliente não recebeu, o problema é só o e-mail, e as credenciais já existem para reenvio.
Validar do lado do operador
Testar callback manualmente (substituindo customerUid/token fakes):
curl -sS -X POST \
"https://southamerica-east1-roqueos.cloudfunctions.net/provisioningCallback" \
-H 'Content-Type: application/json' \
-d '{
"customerUid":"smoke-test",
"token":"0000000000000000000000000000000000000000000000000000000000000000",
"apiKey":"ros_smoketestkey",
"apiSecret":"ros_secret_smoketest_secret_xyz"
}'
# Esperado: HTTP 404 {"error":"subscription_not_found"}Troubleshooting
| Sintoma | Causa provável | Fix |
|---|---|---|
| Email de welcome não chegou | SMTP_HOST/USER/PASS faltando no Secret Manager | gcloud secrets list --project=roqueos | grep SMTP + reconfigurar |
VM criada mas subscription ficou provisioning | install.sh falhou DENTRO da VM (não chamou callback) | SSH na VM: journalctl -u cloud-init -f + docker logs roqueos-server |
Callback retorna token_expired | Provisioning levou >30 min (VM lenta) | Rodar provisionServer de novo pra regenerar token + cloud-init |
Callback retorna token_mismatch | Bug ou tentativa de ataque | Conferir IP origem no log da Function; se suspeito, rotacionar e alertar |
vmIp: null no doc Firestore | Hetzner API timeout ou resposta sem public_net | Retentar provisionServer; logs em gcloud functions logs read provisionServer |
Não documentado aqui (mas relevante)
- Renovação automática de assinatura: Stripe lida sozinho via
customer.subscription.updatedwebhook. VM continua viva. - Cancelamento: webhook
customer.subscription.deleted→ futura funçãodecommissionServerque para VM + snapshot final + email "seus dados estão preservados por 30 dias". - Upgrade de tier (Pro → Power): planejado v1.2 — provavelmente migration via
hcloud server change-type. - Cloudflare Tunnel per-customer: planejado — hoje cada VM expõe via IP público + portas 80/443. Tunnel adiciona TLS via edge SP (latência mais baixa pra BR).
Referências cruzadas
- RoqueOS Cloud overview — planos, preços, racional
- Instalação self-host — mesmo
install.shque roda dentro do cloud-init - Server Admin Panel — pra gerenciar o servidor recém-provisionado
- Memory técnica:
hetzner_architecture_decision.mdno repo (interna)