Skip to content

自动配置(Cloud)

本文档描述 客户订阅后 RoqueOS Cloud VM 如何在 ~3-5 分钟内从零创建。您不需要了解任何这些就可以使用 — 它是用于调试、支持或想要为自己的业务调整流程的人的技术参考。

流程图

1. 客户在 /pricing 上点击"订阅 Cloud Pro"


2. Stripe Checkout(巴西)处理卡/Pix


3. Stripe webhook → Cloud Function `provisionServer`


4. Cloud Function 调用 Hetzner API(创建 CX32 VM)
   + 生成短期令牌(TTL 30 分钟)


5. VM 使用 cloud-init 启动,执行
   `curl https://roqueos.com.br/install.sh | bash`
   传递 --customer-id、--tier、--provisioning-token、
   --provisioning-callback


6. install.sh 在 VM 内运行:
   - 拉取 Docker 镜像
   - 创建持久卷
   - 启动 roqueos-server + 边车(guacd、cloudflared)
   - 等待 /health 返回 200
   - 在本地 POST /admin/setup 以生成 admin key
   - 使用 {customerUid, token, apiKey, apiSecret} POST 回调到 Firebase Functions


7. Cloud Function `provisioningCallback` 验证令牌,
   将订阅标记为 `active`,保存凭证(TTL 24 小时),
   通过 SMTP 触发欢迎邮件


8. 客户收到带 URL + 凭证的邮件
   登录 /app 已为新服务器配置

涉及的组件

Frontend (roqueos-front)

  • src/pages/PricingPage.vue — "订阅"按钮通向 Stripe Checkout
  • functions/index.js — Cloud Functions provisionServerprovisioningCallback
  • public/install.shroqueos-server 规范安装程序的镜像

Backend (roqueos-server)

  • install/install.sh v1.1.0-cloud-callback — 支持回调的一键安装程序
  • src/modules/admin/admin.controller.ts — 幂等端点 POST /admin/setup(生成 admin key)

外部基础设施

  • Hetzner Cloud API (https://api.hetzner.cloud/v1) — 服务器创建
  • Cloudflare API — 子域名 <id>.cloud.roqueos.com.br 创建(计划 v1.2)
  • Firebase Secret Manager — 保存 HCLOUD_TOKENPROVISIONER_SSH_KEY_IDSTANDARD_FIREWALL_ID
  • Stripe Brasil — 通过卡/Pix 的 BRL 结账

Tier → Hetzner spec

RoqueOS TierHetzner SKUvCPURAMSSD 磁盘COGS €/月
Startercx222 shared4 GB40 GB4.51
Procx324 shared8 GB80 GB7.55
Powerccx234 dedicated16 GB160 GB31.20
Enterprisead-hocvariavariavariavaria

位置: 法尔肯施泰因,德国 (fsn1)。镜像: ubuntu-22.04

Cloud Function 端点

provisionServer(admin onCall)

触发器: Stripe webhook customer.subscription.created。在内部是需要 context.auth.token.admin === truehttps.onCall

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
}

provisioningToken 对管理员 只显示一次(不保留在日志中)。其 SHA-256 哈希保存在 Firestore 中 subscriptions/{uid}.provisioning.tokenHash,TTL 30 分钟。

provisioningCallback(HTTP 公开)

触发器: 新创建的 VM 通过 install.sh 调用此端点的 POST

公开 URL: https://southamerica-east1-roqueos.cloudfunctions.net/provisioningCallback

为什么没有 Firebase 认证就公开: 全新的 VM 没有 Firebase 凭证。认证是通过短期令牌(一次性,SHA-256 哈希比较)完成的。

Input(POST JSON):

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

验证:

  • 负载形状(token + apiKey + apiSecret 的 regex)
  • tokenHash 与 Firestore 中记录的匹配
  • 令牌未过期
  • 订阅仍在 provisioning(之前没有完成)

Output ok: 200 { "ok": true }Output 错误: HTTP 400/401/404/409/410 + { "error": "<code>" }

可能的错误:

  • 400 missing_fields — 负载不完整
  • 400 invalid_* — 形状无效
  • 401 token_mismatch — 令牌错误(潜在攻击,记录 IP)
  • 404 subscription_not_found — customerUid 不存在
  • 409 already_completed — 回调已成功调用(dedup)
  • 409 no_pending_provisioning — 订阅不在 provisioning 状态
  • 410 token_expired — 超过 30 分钟

cloud-init 的 install.sh flags

Cloud Function provisionServer 生成执行以下的 user_data(cloud-init):

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

4 个 flags --customer-id--tier--provisioning-token--provisioning-callbackinstall.sh v1.1.0-cloud-callback 中添加(脚本顶部的 INSTALLER_VERSION)。当所有四个都存在时,安装程序在 wait_for_server 返回 healthy 后触发 do_provisioning_callback()

在操作员端验证

手动测试回调(用假的 customerUid/token 替换):

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"
  }'
# 预期: HTTP 404 {"error":"subscription_not_found"}

故障排除

症状可能原因修复
欢迎邮件未到达Secret Manager 中缺少 SMTP_HOST/USER/PASSgcloud secrets list --project=roqueos | grep SMTP + 重新配置
VM 已创建但订阅仍为 provisioninginstall.sh 在 VM 内失败(未调用回调)SSH 到 VM: journalctl -u cloud-init -f + docker logs roqueos-server
回调返回 token_expired配置花费 >30 分钟(VM 缓慢)再次运行 provisionServer 以重新生成令牌 + cloud-init
回调返回 token_mismatch错误或攻击尝试在 Function 日志中检查源 IP;如果可疑,轮换并发出警报
Firestore 文档中 vmIp: nullHetzner API 超时或无 public_net 的响应重试 provisionServer;日志在 gcloud functions logs read provisionServer

此处未记录(但相关)

  • 订阅自动续订: Stripe 通过 customer.subscription.updated webhook 单独处理。VM 继续运行。
  • 取消: webhook customer.subscription.deleted → 未来函数 decommissionServer 停止 VM + 最终 snapshot + "您的数据保留 30 天" 邮件。
  • Tier 升级(Pro → Power): 计划 v1.2 — 可能通过 hcloud server change-type 迁移。
  • 每个客户的 Cloudflare Tunnel: 计划 — 今天每个 VM 通过公共 IP + 端口 80/443 公开。Tunnel 通过 edge SP 添加 TLS(对 BR 更低延迟)。

交叉引用