🌐 此页面尚未翻译为中文。显示葡萄牙语(BR)参考。帮助翻译。
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
1. Cliente clica "Assinar Cloud Pro" em /pricing
│
▼
2. Stripe Checkout (Brasil) processa cartão/Pix
│
▼
3. Stripe webhook → Cloud Function `provisionServer`
│
▼
4. Cloud Function chama Hetzner API (cria VM CX32)
+ gera token efêmero (TTL 30 min)
│
▼
5. VM boota com cloud-init que executa
`curl https://roqueos.com.br/install.sh | bash`
passando --customer-id, --tier, --provisioning-token,
--provisioning-callback
│
▼
6. install.sh roda dentro da VM:
- Pulla images Docker
- Cria volumes persistentes
- Sobe roqueos-server + sidecars (guacd, cloudflared)
- Aguarda /health responder 200
- POST /admin/setup local pra gerar admin key
- POSTa callback pro Firebase Functions com
{customerUid, token, apiKey, apiSecret}
│
▼
7. Cloud Function `provisioningCallback` valida o token,
marca subscription `active`, salva creds (TTL 24h),
dispara welcome email via SMTP
│
▼
8. Cliente recebe email com URL + credenciais
Login no /app já configurado pro server novoComponentes 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.
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)