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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 мин).
|
||||||
Reference in New Issue
Block a user