From 38267fa432fdbddc170dcb967f49ab3da0c655d8 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Mon, 25 May 2026 15:13:26 +0500 Subject: [PATCH] Add staging environment plan and deploy script Staging runs at staging.school.second-brain.ru on Hetzner port 3011. Deploy script: ~/Documents/Claude/scripts/deploy-staging.sh Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-05-25-lms-staging.md | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-25-lms-staging.md diff --git a/docs/superpowers/plans/2026-05-25-lms-staging.md b/docs/superpowers/plans/2026-05-25-lms-staging.md new file mode 100644 index 0000000..75f4db5 --- /dev/null +++ b/docs/superpowers/plans/2026-05-25-lms-staging.md @@ -0,0 +1,451 @@ +# LMS Staging Environment Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Развернуть полноценный staging-стенд LMS на `staging.school.second-brain.ru` — изолированный от prod, с автоматическим применением миграций и отключёнными email-уведомлениями. + +**Architecture:** Hetzner VPS (178.104.27.196, 16 GB RAM), отдельный Docker Compose стек в `/root/digital-household/lms-staging/` с собственным postgres-контейнером. Caddy на том же сервере роутит `staging.school.second-brain.ru → localhost:3011`. Сборочный репо — `/root/lms-staging-build/`, тег образа — `lms-staging-app:latest`. Деплой в staging производится отдельным скриптом `deploy-staging.sh` независимо от prod. + +**Tech Stack:** Docker Compose, Next.js 16 (App Router), PostgreSQL 16, Caddy, Gitea (git.second-brain.ru/admins/lms-sb) + +--- + +## Файловая структура + +``` +Hetzner VPS (root@178.104.27.196): +├── /root/lms-staging-build/ ← git clone (сборочная директория) +│ └── [полный репо lms-sb] +├── /root/digital-household/lms-staging/ +│ ├── docker-compose.yml ← создаём (app + db) +│ └── .env ← создаём (staging-специфичный) +└── /root/digital-household/caddy-proxy/ + └── Caddyfile ← добавляем блок staging + +Mac: +└── ~/Documents/Claude/scripts/deploy-staging.sh ← создаём +``` + +--- + +## Task 1: Клонировать репо на Hetzner + +**Files:** +- Создаём: `/root/lms-staging-build/` на сервере + +- [ ] **Шаг 1.1: SSH на Hetzner и клонировать репо** + + Нужен токен Gitea. Он хранится в уже существующем git remote на сервере. Достанем его: + + ```bash + ssh root@178.104.27.196 "git -C /root/digital-household/lms-sb remote get-url origin 2>/dev/null || echo 'no-remote'" + ``` + + Если remote есть — берём URL из него. Если нет — токен в `~/.config/secrets/` на Mac (найти файл с `GITEA_TOKEN` или `git.second-brain.ru`). + +- [ ] **Шаг 1.2: Клонировать** + + ```bash + GITEA_URL=$(ssh root@178.104.27.196 "git -C /root/digital-household/lms-sb remote get-url origin 2>/dev/null") + # Пример: https://admins:TOKEN@git.second-brain.ru/admins/lms-sb.git + + ssh root@178.104.27.196 "git clone ${GITEA_URL} /root/lms-staging-build" + ``` + +- [ ] **Шаг 1.3: Проверить** + + ```bash + ssh root@178.104.27.196 "ls /root/lms-staging-build/src && git -C /root/lms-staging-build log --oneline -3" + ``` + + Ожидаем: список файлов и последние 3 коммита. + +--- + +## Task 2: Создать staging docker-compose.yml + +**Files:** +- Создаём: `/root/digital-household/lms-staging/docker-compose.yml` + +- [ ] **Шаг 2.1: Создать директорию** + + ```bash + ssh root@178.104.27.196 "mkdir -p /root/digital-household/lms-staging" + ``` + +- [ ] **Шаг 2.2: Записать docker-compose.yml** + + ```bash + ssh root@178.104.27.196 "cat > /root/digital-household/lms-staging/docker-compose.yml" << 'COMPOSE' + services: + app: + image: lms-staging-app:latest + restart: unless-stopped + ports: + - "3011:3000" + env_file: .env + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: lms_staging_user + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: lms_staging_db + volumes: + - staging_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U lms_staging_user -d lms_staging_db"] + interval: 5s + timeout: 5s + retries: 10 + + volumes: + staging_postgres_data: + COMPOSE + ``` + +- [ ] **Шаг 2.3: Проверить** + + ```bash + ssh root@178.104.27.196 "cat /root/digital-household/lms-staging/docker-compose.yml" + ``` + + Ожидаем: полный YAML без артефактов heredoc. + +--- + +## Task 3: Залить staging .env на сервер + +**Files:** +- Создаём: `/root/digital-household/lms-staging/.env` + +Все креды уже сгенерированы и хранятся в `~/.config/secrets/lms-staging.env` на Mac. + +- [ ] **Шаг 3.1: Скопировать .env на сервер** + + ```bash + scp ~/.config/secrets/lms-staging.env root@178.104.27.196:/root/digital-household/lms-staging/.env + ``` + +- [ ] **Шаг 3.2: Установить права** + + ```bash + ssh root@178.104.27.196 "chmod 600 /root/digital-household/lms-staging/.env" + ``` + +- [ ] **Шаг 3.3: Проверить** + + ```bash + ssh root@178.104.27.196 "grep -E '^BETTER_AUTH_URL|^RESEND_API_KEY|^DB_PASSWORD' /root/digital-household/lms-staging/.env" + ``` + + Ожидаем: + ``` + BETTER_AUTH_URL=https://staging.school.second-brain.ru + RESEND_API_KEY= + DB_PASSWORD=38002343e67efd057559d8f073d45892 + ``` + +--- + +## Task 4: Собрать Docker-образ и запустить staging + +**Files:** +- Используем `/root/lms-staging-build/` для сборки + +- [ ] **Шаг 4.1: Собрать образ** + + ```bash + ssh root@178.104.27.196 "cd /root/lms-staging-build && docker build -t lms-staging-app:latest . 2>&1 | tail -5" + ``` + + Ожидаем: `Successfully built ...` или `naming to docker.io/library/lms-staging-app:latest done`. + + Время: ~2–4 минуты. + +- [ ] **Шаг 4.2: Запустить контейнеры** + + ```bash + ssh root@178.104.27.196 "cd /root/digital-household/lms-staging && docker compose up -d" + ``` + + Ожидаем: `Container lms-staging-db-1 Started`, `Container lms-staging-app-1 Started`. + +- [ ] **Шаг 4.3: Проверить логи (через 10 сек)** + + ```bash + ssh root@178.104.27.196 "sleep 10 && docker logs lms-staging-app-1 --tail 20" + ``` + + Ожидаем в логах: + - `Running database migrations...` + - `No pending migrations to apply.` (или список применённых) + - `Starting Next.js...` + - `✓ Ready in 0ms` + + Если вместо этого ошибка `DATABASE_URL` или `connection refused` — проверить `.env`: `docker exec lms-staging-app-1 env | grep DATABASE`. + +--- + +## Task 5: Настроить Caddy + +**Files:** +- Изменяем: `/root/digital-household/caddy-proxy/Caddyfile` + +- [ ] **Шаг 5.1: Посмотреть текущий конец Caddyfile** + + ```bash + ssh root@178.104.27.196 "tail -10 /root/digital-household/caddy-proxy/Caddyfile" + ``` + +- [ ] **Шаг 5.2: Добавить блок для staging** + + ```bash + ssh root@178.104.27.196 "cat >> /root/digital-household/caddy-proxy/Caddyfile" << 'CADDY' + + staging.school.second-brain.ru { + reverse_proxy http://localhost:3011 + } + CADDY + ``` + +- [ ] **Шаг 5.3: Перезагрузить Caddy** + + ```bash + ssh root@178.104.27.196 "docker exec caddy-proxy caddy reload --config /etc/caddy/Caddyfile 2>&1" + ``` + + Ожидаем: пустой вывод (успех) или `Successfully loaded new configuration`. + + Если ошибка синтаксиса — проверить: `docker exec caddy-proxy caddy validate --config /etc/caddy/Caddyfile`. + +--- + +## Task 6: Добавить DNS-запись + +DNS для `*.school.second-brain.ru` и `school.second-brain.ru` нужно проверить — где управляется зона. + +- [ ] **Шаг 6.1: Проверить текущий DNS** + + ```bash + dig +short staging.school.second-brain.ru + dig +short school.second-brain.ru + ``` + + Если `staging.school.second-brain.ru` не резолвится — нужно добавить A-запись. + +- [ ] **Шаг 6.2: Добавить A-запись** + + В панели DNS (Hetzner DNS Console или CloudFlare — где управляется зона `second-brain.ru`): + + ``` + Тип: A + Имя: staging.school + Значение: 178.104.27.196 + TTL: 300 + ``` + +- [ ] **Шаг 6.3: Дождаться распространения** + + ```bash + # Через 1-2 минуты: + dig +short staging.school.second-brain.ru + ``` + + Ожидаем: `178.104.27.196` + +--- + +## Task 7: Проверить работу staging end-to-end + +- [ ] **Шаг 7.1: HTTP проверка** + + ```bash + curl -s -o /dev/null -w "%{http_code}" https://staging.school.second-brain.ru/login + ``` + + Ожидаем: `200` + +- [ ] **Шаг 7.2: Войти под тестовым аккаунтом** + + Открыть `https://staging.school.second-brain.ru/login` в браузере. + + БД пустая, но seed создаёт admin-аккаунт. Запустить seed: + + ```bash + ssh root@178.104.27.196 "cd /root/lms-staging-build && docker compose -f /root/digital-household/lms-staging/docker-compose.yml exec app npx prisma db seed 2>&1 | tail -5" + ``` + + После seed входить: `admin@second-brain.ru` / `Password123!` + +- [ ] **Шаг 7.3: Проверить что email не уходит** + + В staging при регистрации `/api/auth/sign-up` не должно быть ошибок Resend (только `RESEND_API_KEY` пустой — письмо тихо не отправляется, регистрация всё равно работает). + + Убедиться в логах: + ```bash + ssh root@178.104.27.196 "docker logs lms-staging-app-1 --since 5m 2>&1 | grep -i 'resend\|email\|error' | head -10" + ``` + +--- + +## Task 8: Скрипт deploy-to-staging и документация + +**Files:** +- Создаём: `~/Documents/Claude/scripts/deploy-staging.sh` +- Создаём: `SBT/02-Стандарты/Процессы/lms-staging-deploy.md` + +- [ ] **Шаг 8.1: Написать скрипт deploy-staging.sh** + + ```bash + cat > ~/Documents/Claude/scripts/deploy-staging.sh << 'SCRIPT' + #!/usr/bin/env bash + # Deploy LMS to staging (staging.school.second-brain.ru) + # Usage: bash deploy-staging.sh + set -euo pipefail + + STAGING_SERVER="root@178.104.27.196" + BUILD_DIR="/root/lms-staging-build" + COMPOSE_DIR="/root/digital-household/lms-staging" + LOG_LINES=15 + + echo "=== LMS Staging Deploy ===" + + echo "→ Pulling latest code..." + ssh "$STAGING_SERVER" "cd ${BUILD_DIR} && git pull 2>&1 | tail -5" + + echo "→ Building image..." + ssh "$STAGING_SERVER" "cd ${BUILD_DIR} && docker build -t lms-staging-app:latest . 2>&1 | tail -5" + + echo "→ Restarting app..." + ssh "$STAGING_SERVER" "cd ${COMPOSE_DIR} && docker compose up -d app" + + echo "→ Waiting 10s for startup..." + sleep 10 + + echo "→ Logs:" + ssh "$STAGING_SERVER" "docker logs lms-staging-app-1 --tail ${LOG_LINES} 2>&1" + + echo "→ Health check..." + CODE=$(curl -s -o /dev/null -w "%{http_code}" https://staging.school.second-brain.ru/login) + if [ "$CODE" = "200" ]; then + echo "✅ Staging OK — https://staging.school.second-brain.ru (HTTP $CODE)" + else + echo "❌ Staging returned HTTP $CODE" + exit 1 + fi + SCRIPT + chmod +x ~/Documents/Claude/scripts/deploy-staging.sh + ``` + +- [ ] **Шаг 8.2: Тестовый запуск скрипта** + + ```bash + bash ~/Documents/Claude/scripts/deploy-staging.sh + ``` + + Ожидаем в конце: `✅ Staging OK — https://staging.school.second-brain.ru (HTTP 200)` + +- [ ] **Шаг 8.3: Создать SBT-заметку** + + Создать файл `SBT/02-Стандарты/Процессы/lms-staging-deploy.md` со следующим содержимым: + + ```markdown + --- + type: process + title: "LMS — Деплой в staging" + status: active + created: 20260525 + updated: 20260525 + tags: [lms, staging, deploy, docker] + related: ["[[Процессы/lms-deploy]]", "[[Сервисы/hoster-kz]]", "[[Сервисы/vps-hetzner]]"] + --- + + # Процесс: Деплой LMS в staging + + ## Когда применяется + + Перед выкаткой любых изменений в prod — сначала деплоим в staging и проверяем. + Также: тестирование миграций БД, проверка новых фич, отладка. + + ## Ключевые факты + + | Что | Где | + |---|---| + | URL | https://staging.school.second-brain.ru | + | Сервер | Hetzner Antigravity-1 (178.104.27.196) | + | Compose | `/root/digital-household/lms-staging/` | + | Build dir | `/root/lms-staging-build/` | + | Порт | 3011 | + | БД | `lms_staging_db` (отдельный контейнер) | + | Email | Отключён (RESEND_API_KEY пустой) | + | S3 | Тот же бакет что и prod | + + ## Деплой одной командой (с Mac) + + ```bash + bash ~/Documents/Claude/scripts/deploy-staging.sh + ``` + + ## Ручные шаги (если нужно) + + ```bash + # На сервере + cd /root/lms-staging-build && git pull + docker build -t lms-staging-app:latest . + cd /root/digital-household/lms-staging && docker compose up -d app + docker logs lms-staging-app-1 --tail 20 + ``` + + ## Откат + + ```bash + ssh root@178.104.27.196 "cd /root/digital-household/lms-staging && docker compose down" + ``` + + ## Восстановить prod-данные в staging (для отладки) + + ```bash + # 1. Взять свежий дамп с 3TB или Storage Box + # 2. Залить в staging-базу + DUMP="/Volumes/3TB/Second Brain Production/LMS Backups/lms-db-YYYYMMDD-HHMM.sql.gz" + scp "$DUMP" root@178.104.27.196:/tmp/staging-restore.sql.gz + ssh root@178.104.27.196 \ + "zcat /tmp/staging-restore.sql.gz | docker exec -i lms-staging-db-1 psql -U lms_staging_user lms_staging_db" + ``` + + ## Связанные процессы + + - [[Процессы/lms-deploy]] — деплой в prod (после проверки на staging) + - [[Процессы/lms-disaster-recovery]] + ``` + +- [ ] **Шаг 8.4: Коммит скрипта** + + ```bash + cd ~/Documents/Claude/lms-system + git add docs/ + git commit -m "Add LMS staging environment plan and deploy script" + git push origin HEAD + ``` + +--- + +## Чек-лист финальной проверки + +- [ ] `https://staging.school.second-brain.ru/login` → 200 +- [ ] Войти под тестовым аккаунтом +- [ ] Курс открывается +- [ ] Email НЕ уходит при регистрации (проверить логи) +- [ ] `bash deploy-staging.sh` завершается с `✅ Staging OK` +- [ ] SBT-заметка создана + +--- + +## Ожидаемое время + +~30–40 минут (основное время — сборка Docker-образа ~3–5 мин).