Skip to content

🌐 Cette page n'est pas encore traduite en français. Affichage de la référence portugaise (BR). Aidez-nous à traduire.

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 novo

Componentes envolvidos

Frontend (roqueos-front)

  • src/pages/PricingPage.vue — botão "Assinar" leva pro Stripe Checkout
  • functions/index.js — Cloud Functions provisionServer e provisioningCallback
  • public/install.sh — mirror do instalador canonical do roqueos-server

Backend (roqueos-server)

  • install/install.sh v1.1.0-cloud-callback — instalador one-liner com suporte a callback
  • src/modules/admin/admin.controller.ts — endpoint POST /admin/setup idempotente (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 RoqueOSHetzner SKUvCPURAMDisco SSDCOGS €/mês
Startercx222 shared4 GB40 GB4.51
Procx324 shared8 GB80 GB7.55
Powerccx234 dedicated16 GB160 GB31.20
Enterprisead-hocvariavariavariavaria

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:

json
{
  "customerUid": "abc123",
  "tier": "pro"
}

Output:

json
{
  "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):

json
{
  "customerUid": "abc123",
  "token": "<64 hex chars>",
  "apiKey": "ros_xxxx",
  "apiSecret": "ros_secret_xxxx"
}

Validações:

  • Shape do payload (regex em token + apiKey + apiSecret)
  • tokenHash confere 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 incompleto
  • 400 invalid_* — shape inválido
  • 401 token_mismatch — token errado (potencial ataque, loga IP)
  • 404 subscription_not_found — customerUid não existe
  • 409 already_completed — callback já foi chamado com sucesso (dedup)
  • 409 no_pending_provisioning — subscription não está em estado provisioning
  • 410 token_expired — passou dos 30 min

install.sh flags do cloud-init

A Cloud Function provisionServer gera um user_data (cloud-init) que executa:

bash
#!/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-prompts

As 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):

bash
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

SintomaCausa provávelFix
Email de welcome não chegouSMTP_HOST/USER/PASS faltando no Secret Managergcloud secrets list --project=roqueos | grep SMTP + reconfigurar
VM criada mas subscription ficou provisioninginstall.sh falhou DENTRO da VM (não chamou callback)SSH na VM: journalctl -u cloud-init -f + docker logs roqueos-server
Callback retorna token_expiredProvisioning levou >30 min (VM lenta)Rodar provisionServer de novo pra regenerar token + cloud-init
Callback retorna token_mismatchBug ou tentativa de ataqueConferir IP origem no log da Function; se suspeito, rotacionar e alertar
vmIp: null no doc FirestoreHetzner API timeout ou resposta sem public_netRetentar 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.updated webhook. VM continua viva.
  • Cancelamento: webhook customer.subscription.deleted → futura função decommissionServer que 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