自动配置(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 Checkoutfunctions/index.js— Cloud FunctionsprovisionServer和provisioningCallbackpublic/install.sh—roqueos-server规范安装程序的镜像
Backend (roqueos-server)
install/install.shv1.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_TOKEN、PROVISIONER_SSH_KEY_ID、STANDARD_FIREWALL_ID - Stripe Brasil — 通过卡/Pix 的 BRL 结账
Tier → Hetzner spec
| RoqueOS Tier | Hetzner SKU | vCPU | RAM | SSD 磁盘 | COGS €/月 |
|---|---|---|---|---|---|
| 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 |
位置: 法尔肯施泰因,德国 (fsn1)。镜像: ubuntu-22.04。
Cloud Function 端点
provisionServer(admin onCall)
触发器: Stripe webhook customer.subscription.created。在内部是需要 context.auth.token.admin === true 的 https.onCall。
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
}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):
{
"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):
#!/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-prompts4 个 flags --customer-id、--tier、--provisioning-token、--provisioning-callback 在 install.sh v1.1.0-cloud-callback 中添加(脚本顶部的 INSTALLER_VERSION)。当所有四个都存在时,安装程序在 wait_for_server 返回 healthy 后触发 do_provisioning_callback()。
在操作员端验证
手动测试回调(用假的 customerUid/token 替换):
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/PASS | gcloud secrets list --project=roqueos | grep SMTP + 重新配置 |
VM 已创建但订阅仍为 provisioning | install.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: null | Hetzner API 超时或无 public_net 的响应 | 重试 provisionServer;日志在 gcloud functions logs read provisionServer |
此处未记录(但相关)
- 订阅自动续订: Stripe 通过
customer.subscription.updatedwebhook 单独处理。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 更低延迟)。
交叉引用
- RoqueOS Cloud 概述 — 计划、价格、理由
- 自托管安装 — 与 cloud-init 内运行的相同
install.sh - Server Admin Panel — 管理新配置的服务器
- 技术记忆:
hetzner_architecture_decision.md在仓库中(内部)