Skip to content

🌐 यह पृष्ठ अभी तक हिंदी में अनुवादित नहीं किया गया है। पुर्तगाली (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 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