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