# ۲۸ — راهنمای استقرار (Docker + Caddy + TLS + بکاپ)

> موج ۱ پایدارسازی · ممیزی ۲۷ · نسخه‌ی ۱.۱۰.۰
> هدف: از «روی لپ‌تاپ با HTTP» به «روی VPS با HTTPSِ خودکار + بکاپ شبانه» در کمتر از ۳۰ دقیقه.

---


> 🎓 **نصب سریع؟** از نسخه 1.12.0 نصب‌گر داریم: ویزارد `/install` + CLI
> `node scripts/install.mjs` + بسته‌ی آماده‌ی cPanel — راهنمای کامل: **[سند ۳۴](34-install-cpanel.md)**.

## ۱. پیش‌نیازها

| چیزی | چرا |
|---|---|
| VPS با ۱GB RAM (مثلاً ۴-۶ یورو/ماه) | Node + SQLite + Caddy سبک‌اند |
| دامنه + رکورد A به IP سرور | TLS خودکار Let's Encrypt |
| Docker Engine 24+ با افزونه‌ی compose | اجرای دو کانتینر (app + caddy) |

> نکته: SQLite در این مقیاس (ده‌ها خانواده) کاملاً کافی است؛ نیازی به سرور DB جدا نیست.

---

## ۲. استقرار (۵ گام)

```bash
# ۱) گرفتن کد
git clone <repo> alphabet && cd alphabet

# ۲) رازها — .env در .gitignore است، هرگز کامیت نشود
cat > .env << 'EOF'
ALPHA_DOMAIN=alphabet.example.ir
ALPHA_ACME_EMAIL=you@example.ir
ALPHABET_JWT_SECRET=<خروجی: openssl rand -hex 32>
ALPHABET_ADMIN_EMAIL=admin@example.ir
ALPHABET_ADMIN_PASSWORD=<رمز قوی>
EOF
chmod 600 .env

# ۳) بالا آوردن (اولین بار: بیلد image + گرفتن گواهی)
docker compose up -d --build

# ۴) سلامت‌سنجی
curl -fsS https://$ALPHA_DOMAIN/api/v1/healthz   # {"ok":true}
docker compose ps                                 # هر دو healthy

# ۵) اولین بکاپ را همین حالا بگیر (منتظر ساعت ۳ بامداد نمان)
docker compose exec app node /app/scripts/backup.mjs
```

اگر `ALPHABET_JWT_SECRET` خالی بماند، سرور خودش راز می‌سازد — اما با هر
بازسازیِ کانتینر راز تازه می‌شود و همه‌ی نشست‌ها بی‌اعتبار می‌شوند؛ پس در تولید حتماً env بده.

---

## ۳. TLS — چه چیزی خودکار است؟

- **Caddy** در اولین بوت برای دامنه‌ی تنظیم‌شده گواهی Let's Encrypt می‌گیرد و
  ۶۰ روزه خودش تمدید می‌کند (`caddy-data` volume نگه‌دارنده‌ی گواهی‌هاست — پاکش نکن).
- **HSTS** (`max-age=31536000; includeSubDomains`) از Caddy می‌آید — Node عمداً نمی‌فرستد (تصمیم ممیزی).
- برای `localhost` گواهی داخلی Caddy ساخته می‌شود (بدون Let's Encrypt).
- پورت‌های ۸۰ و ۴۴۳ باید در فایروال باز باشند (۸۰ برای چالش ACME لازم است).
- CSP و بقیه‌ی هدرهای امنیتی را خودِ Node می‌فرستد (`server/csp.js`) — Caddy فقط لبه‌ی TLS/فشرده‌سازی است.

---

## ۴. بکاپ — چه چیزی، کی، چگونه رفع فاجعه؟

| | |
|---|---|
| چه چیزی | `alphabet.db` (اسنپ‌شات امن روی WAL زنده) + پوشه‌های `audio/`، `images/`، `words/` + `manifest.json` |
| کی | هر شب ساعت `ALPHABET_BACKUP_HOUR` (پیش‌فرض ۳) داخل کانتینر app |
| کجا | volume جدا `app-backups` → `/data-backups/alphabet-YYYYMMDD-HHmmss/` |
| چرخش | فقط ۱۴ نسخه‌ی آخر (`ALPHABET_BACKUP_KEEP`)؛ برخورد نام هم‌ثانیه → پسوند `-2` |
| صحت‌سنجی | بعد از هر بکاپ، manifest + شمار سطرها چک می‌شود؛ تست واحد (۱۰ مورد) بازیابی را «عملی» تمرین می‌کند |

**رفع فاجعه (سرور سوخت / DB خراب):**

```bash
# ۱) آخرین نسخه‌ی سالم را پیدا کن (صحت‌سنجی = چک manifest + سطرها)
docker compose exec app node /app/scripts/backup.mjs --verify

# ۲) بازیابی — سرورِ در حال اجرا را اول ببند
docker compose stop app
docker compose run --rm app node scripts/restore.mjs /data-backups/<نام‌نسخه> --yes
#   → DB فعلی با پسوند .pre-restore نگه داشته می‌شود (بازگشت اضطراری)
docker compose start app
```

> **قاعده‌ی ۳-۲-۱ (موج ۲ — خودکار شد):** بکاپ‌ها روی volume جدا هستند (۲)؛
> نسخه‌ی سوم با `ALPHABET_RESTIC_REPOSITORY` هر شب بعد از بکاپ محلی با restic
> به S3/R2 می‌رود (keep 14 روزانه/۸ هفتگی/۶ ماهانه + prune — `server/remoteBackup.js`).
> باینری restic باید در ایمیج باشد: در Dockerfile اضافه کن `apt-get install -y restic`.
> بدون restic: همان دستور دستی هفتگی:
> `docker run --rm -v alphabet_app-backups:/b -v $PWD:/out alpine tar czf /out/backups-$(date +%F).tgz -C /b .`

---

## ۵. به‌روزرسانی نسخه

```bash
git pull && docker compose up -d --build
# داده‌ها (app-data) و بکاپ‌ها (app-backups) جدا از imageاند — سالم می‌مانند
```

قبل از هر به‌روزرسانی یک بکاپ دستی بگیر (`step ۲ بالا`).

---

## ۶. متغیرهای محیطی (مرجع کامل)

| متغیر | پیش‌فرض | توضیح |
|---|---|---|
| `PORT` | ۸۰۸۰ | پورت داخلی Node |
| `ALPHABET_DATA_DIR` | `data` (در Docker: `/data`) | DB + صدای والدین + عکس‌ها |
| `ALPHABET_DB` | `{DATA_DIR}/alphabet.db` | مسیر DB |
| `ALPHABET_JWT_SECRET` | خودساز از فایل | راز نشست‌ها — در تولید حتماً env |
| `ALPHABET_ADMIN_EMAIL/PASSWORD` | — | حساب ادمین اول (یک‌بار در بوت) |
| `ALPHABET_FRAME_GUARD` | — | `deny` در تولید → X-Frame-Options DENY + frame-ancestors |
| `ALPHABET_BACKUP` | خاموش | `1` → زمان‌بند شبانه فعال |
| `ALPHABET_BACKUP_HOUR` | ۳ | ساعت بکاپ (محلی سرور) |
| `ALPHABET_BACKUP_KEEP` | ۱۴ | شمار نسخه‌های نگه‌داشتنی |
| `ALPHABET_BACKUP_DIR` | `{dataDir}/../data-backups` | مقصد بکاپ‌ها |
| `ALPHABET_RETENTION_DAYS` | ۱۸۰ | عمر رویدادهای خام (۰ = خاموش؛ همراه کار شبانه‌ی بکاپ) |
| `ALPHABET_SENTRY_DSN` | خاموش | DSN گلوچ‌تیپ/سنتری → فعال‌شدن پایش خطا (سرور+کلاینت) |
| `ALPHABET_ENV` | `development` | برچسب محیط در رویدادهای پایش (`production` بگذار) |
| `LOG_LEVEL` | `info` | سطح لاگ ساختاریافته (debug/info/warn/error) |
| `ALPHABET_MAIL_RESEND_KEY` | خاموش | کلید Resend → فعال‌شدن ایمیل (خوش‌آمد/ریست/گزارش) |
| `ALPHABET_MAIL_MAILGUN_KEY` + `_DOMAIN` | خاموش | جایگزین Mailgun |
| `ALPHABET_MAIL_FROM` | `no-reply@…` | فرستنده |
| `ALPHABET_MAIL_TRANSPORT=file` + `ALPHABET_MAIL_OUTBOX` | خاموش | حالت توسعه — ایمیل‌ها در JSONL |
| `ALPHABET_MAIL_WEEKLY` | خاموش | `1` → گزارش هفتگی والدین (روز: `ALPHABET_MAIL_WEEKLY_DAY`، پیش‌فرض شنبه) |
| `ALPHABET_RESET_TTL_MIN` | ۱۵ | عمر کد ریست خودخدمت |
| `FORGOT_RATE_MAX` / `RESET_RATE_MAX` | ۵ / ۱۰ | نرخ درخواست کد / تلاش ریست در ۱۵ دقیقه |
| `ALPHABET_RESTIC_REPOSITORY` | خاموش | `s3:…` → بکاپ ریموت شبانه با restic (۳-۲-۱) |
| `ALPHABET_RESTIC_PASSWORD_FILE` | — | رمز ریپازیتوری restic |

---

## ۷. چک‌لیست بعد از استقرار

- [ ] `https://دامنه` باز می‌شود و قفل سبز TLS هست
- [ ] `curl -I https://دامنه` → هدرهای `content-security-policy`، `permissions-policy`، `strict-transport-security` دیده می‌شوند
- [ ] ثبت‌نام والد + ساخت کودک + ضبط صدا کار می‌کند (میکروفون فقط روی HTTPS فعال است!)
- [ ] `docker compose exec app node /app/scripts/backup.mjs` اجرا شد و در `/data-backups` پوشه ساخت
- [ ] بازیابی یک‌بار در محیط آزمایشی تمرین شد (نه لزوماً روی سرور تولید)
- [ ] `.env` در `.gitignore` است و `git status` آن را نمی‌بیند

---

## ۸. پایش خطا و عملیات روزمره (موج ۱)

### ۸.۱ راه‌اندازی GlitchTip (سلف‌هاست — پیشنهاد ممیزی)
```bash
docker run -d --name glitchtip -p 8090:80 glitchtip/glitchtip
# در پنل: پروژه‌ی جدید (Node) → DSN را در .env بگذار:
#   ALPHABET_SENTRY_DSN=https://<key>@<host>/<project>
#   ALPHABET_ENV=production
docker compose up -d app   # فقط app ری‌استارت شود
```
از آن لحظه: خطاهای ۵xx و کرش‌های سرور + خطاهای JS کلاینت (از `Diagnostics`،
دسته‌ای و بدون PII — ایمیل/توکن سمت سرور پاک می‌شوند) در داشبورد می‌آیند.
لاگ ساختاریافته: `docker compose logs -f app` (هر خط = یک JSON).

### ۸.۲ ریست رمز والد (تا وقتی ایمیل نیست)
پنل ادمین → والدین → جزئیات والد → «🔑 ریست رمز این والد». رمز موقت
`AP-XXXXXXXX` فقط یک‌بار نشان داده می‌شود؛ همه‌ی نشست‌های والد بسته می‌شود.
نقش «پشتیبان» دسترسی دارد. ثبت در حسابرسی (`parent_reset`).

### ۸.۳ نگه‌داری داده
هر شب بعد از بکاپ: رویدادهای خام > ۱۸۰ روز و refreshهای مرده پاک می‌شوند
(`ALPHABET_RETENTION_DAYS`). گزارش والدین از تجمیع‌ها می‌آید — چیزی از دست نمی‌رود.

## ۹. محدودیت‌های شناخته‌شده (شفافیت)

- سندباکس توسعه docker ندارد — این فایل‌ها با بررسی ساختاری + تست محلی Node اعتبارسنجی
  شده‌اند؛ اولین اجرای واقعی روی VPS ممکن است نکته‌ی کوچکی بیرون بدهد (مثلاً انتخاب mirror بیلد).
- بکاپ داخل همان سرور است — کپی هفتگی بیرونی (بخش ۴) تا وقتی S3/ریموت اضافه شود (موج ۲) الزامی است.
- ریست رمز فعلاً از کانال ادمین است (نه ایمیل خودکار) — SMTP در موج ۲.
