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 ​

⚠️ 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 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.

O que o cliente vê quando entra ​

O painel Server Admin na seção Monitoramento, com os cartões de CPU, memória e disco e o estado do Docker — a primeira tela de quem acabou de receber o servidor provisionado

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:

  1. Stripe — o pagamento consta como concluído e o webhook saiu com 200? Se o webhook falhou, nada depois disso aconteceu.
  2. Logs da provisionServer — ela recebeu o evento e chamou a Hetzner? O log traz o id da VM criada.
  3. Console da Hetzner — a VM existe e está ligada? VM criada e parada é falha de cloud-init, não de pagamento.
  4. /health da VM — responde 200? Enquanto não responder, o install.sh ainda está subindo os containers lá dentro.
  5. 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.
  6. Firestore — a assinatura virou active e as credenciais foram gravadas?
  7. 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):

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 ​