Автоматическая провизионинг (Cloud)
Этот документ описывает, как VM RoqueOS Cloud создаётся с нуля за ~3-5 минут после подписки клиента. Вам не нужно ничего из этого понимать для использования — это техническая справка для отладки, поддержки или для тех, кто хочет адаптировать поток под свой бизнес.
Диаграмма потока
1. Клиент кликает "Подписаться на Cloud Pro" в /pricing
│
▼
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 + sidecars (guacd, cloudflared)
- Ждёт, пока /health не вернёт 200
- Локальный POST /admin/setup для генерации admin key
- POST callback в Firebase Functions с
{customerUid, token, apiKey, apiSecret}
│
▼
7. Cloud Function `provisioningCallback` валидирует токен,
помечает подписку как `active`, сохраняет учётные данные (TTL 24ч),
запускает приветственное письмо через SMTP
│
▼
8. Клиент получает email с 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 — однострочный установщик с поддержкой callbacksrc/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 — checkout BRL через карту/Pix
Tier → spec Hetzner
| 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. Внутри это https.onCall, требующий context.auth.token.admin === true.
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 показывается admin только один раз (не сохраняется в логе). Его SHA-256 хеш хранится в Firestore в subscriptions/{uid}.provisioning.tokenHash с TTL 30 мин.
provisioningCallback (HTTP публичный)
Триггер: только что созданная VM вызывает POST этого эндпоинта через install.sh.
Публичный URL: https://southamerica-east1-roqueos.cloudfunctions.net/provisioningCallback
Почему публичный без Firebase auth: новая VM не имеет учётных данных Firebase. Аутентификация выполняется временным токеном (одноразовый, SHA-256 хеш сравнивается).
Input (POST JSON):
{
"customerUid": "abc123",
"token": "<64 hex chars>",
"apiKey": "ros_xxxx",
"apiSecret": "ros_secret_xxxx"
}Валидации:
- Форма payload (regex на token + apiKey + apiSecret)
tokenHashсовпадает с записанным в Firestore- Токен не просрочен
- Подписка всё ещё в
provisioning(не была завершена ранее)
Output ok: 200 { "ok": true }Output ошибка: HTTP 400/401/404/409/410 + { "error": "<code>" }
Возможные ошибки:
400 missing_fields— неполный payload400 invalid_*— невалидная форма401 token_mismatch— неправильный токен (потенциальная атака, логирует IP)404 subscription_not_found— customerUid не существует409 already_completed— callback уже был вызван успешно (dedup)409 no_pending_provisioning— подписка не в состоянии provisioning410 token_expired— прошло более 30 мин
install.sh флаги cloud-init
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 флага --customer-id, --tier, --provisioning-token, --provisioning-callback были добавлены в install.sh v1.1.0-cloud-callback (INSTALLER_VERSION в верху скрипта). Когда все четыре присутствуют, установщик запускает do_provisioning_callback() после того, как wait_for_server возвращает healthy.
Валидация со стороны оператора
Тестировать 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"}Устранение неполадок
| Симптом | Вероятная причина | Фикс |
|---|---|---|
| Приветственное письмо не пришло | SMTP_HOST/USER/PASS отсутствуют в Secret Manager | gcloud secrets list --project=roqueos | grep SMTP + переконфигурировать |
VM создана, но подписка осталась provisioning | install.sh упал ВНУТРИ VM (не вызвал callback) | SSH в VM: journalctl -u cloud-init -f + docker logs roqueos-server |
Callback возвращает token_expired | Provisioning занял >30 мин (VM медленная) | Запустить provisionServer снова для регенерации токена + cloud-init |
Callback возвращает token_mismatch | Баг или попытка атаки | Проверить IP источника в логе Function; если подозрительно, ротация и алерт |
vmIp: null в Firestore doc | Hetzner API timeout или ответ без public_net | Повторить provisionServer; логи в gcloud functions logs read provisionServer |
Не документировано здесь (но важно)
- Автоматическое продление подписки: Stripe обрабатывает сам через
customer.subscription.updatedwebhook. VM продолжает жить. - Отмена: webhook
customer.subscription.deleted→ будущая функцияdecommissionServerостанавливает VM + финальный snapshot + email "ваши данные сохранены на 30 дней". - Апгрейд tier (Pro → Power): планируется v1.2 — вероятно migration через
hcloud server change-type. - Cloudflare Tunnel per-customer: планируется — сегодня каждая VM экспонируется через публичный IP + порты 80/443. Tunnel добавляет TLS через edge SP (более низкая задержка для BR).
Перекрёстные ссылки
- Обзор RoqueOS Cloud — планы, цены, обоснование
- Self-host install — тот же
install.sh, что запускается внутри cloud-init - Server Admin Panel — для управления только что провизионированным сервером
- Техническая memory:
hetzner_architecture_decision.mdв репо (внутреннее)