# Установка

Страница для того, кто ставит коробку на сервер. Она честнее обычной инструкции
в одном: установка **дважды проходила на живом сервере**, и всё, что там сломалось,
описано здесь, а не сглажено.

Главное правило: **чек-лист ниже проходится до установки.** Каждый его пункт стоит
десяти минут, а найденный после установки — нескольких часов.

## Что нужно от сервера

| | Минимум | Комфортно |
|---|---|---|
| Ядра | 2 | 4 |
| Память | 4 ГБ | 8 ГБ |
| Свободный диск | 20 ГБ | 40 ГБ |
| ОС | Ubuntu 22.04 / 24.04, Debian 12 | — |
| Docker | 24+ с плагином compose v2 | 29+ |

**Память — про сборку, а не про работу.** Работающая установка укладывается примерно
в 1,5 ГБ. Сборка образа съедает 2–3 ГБ разом: если на сервере уже что-то живёт,
сборка на нём — самый быстрый способ уронить соседей.

## Чек-лист до установки

### 1. Время синхронизировано

```bash
timedatectl
```

Нужно `System clock synchronized: yes`. Это не формальность: одноразовые коды
двухфакторной защиты ломаются при расхождении больше 30 секунд — **владелец
не сможет войти в собственную админку**. Плюс от часов сервера считаются сроки
доступа и отложенные события, а Let's Encrypt не выпустит сертификат при заметном
расхождении.

**Синхронизация может быть настроена и при этом не работать.** На сервере первой
установки служба была активна, а стандартные серверы времени с этой машины
не отвечали вовсе. Лечится указанием доступного:

```bash
mkdir -p /etc/systemd/timesyncd.conf.d
printf '[Time]\nNTP=time.cloudflare.com\n' > /etc/systemd/timesyncd.conf.d/10-reachable-ntp.conf
systemctl restart systemd-timesyncd
timedatectl timesync-status
```

Часовой пояс сервера при этом значения не имеет: приложение работает в UTC
и показывает даты в поясе школы. Обязательна именно синхронность.

### 2. Место и память

```bash
df -h /
free -h
```

20 ГБ свободных. И решите здесь же, собирается образ на этом сервере (нужно ещё
3 ГБ памяти) или переносится готовым с другой машины.

### 3. Порты

```bash
ss -ltnp | grep -E ':(80|443|5432|6379|3000)\b'
```

- **80 и 443** нужны, если установка ставит свой прокси. Заняты чужим прокси —
  это отдельный сценарий, см. ниже.
- **5432 и 6379** наружу не нужны вообще: PostgreSQL и Redis живут во внутренней
  сети Docker и портов на хост не публикуют. Если 5432 занят чужой базой — это
  нам не мешает.

### 4. Домен

A-запись домена должна указывать на этот сервер **до** установки: без этого
сертификат не выпустится.

```bash
getent hosts school.example.com
curl -s https://ifconfig.me
```

Два адреса должны совпасть. Домен за проксирующим DNS или CDN — выпуск сертификата
пойдёт иначе, и это надо решить заранее, а не в момент установки.

### 5. Исходящая связь

```bash
curl -s -o /dev/null -w '%{http_code}\n' https://api.resend.com
timeout 6 bash -c '</dev/tcp/smtp.yandex.ru/587' && echo "587 открыт" || echo "587 закрыт"
```

**Закрытый исходящий SMTP — норма, а не поломка.** У многих хостеров порты 25, 465,
587 и 2525 закрыты по умолчанию и открываются по заявке. На сервере первой установки
закрыты были все четыре, до всех проверенных провайдеров, при работающем исходящем
HTTPS. Если письма нужны — либо открывайте порт заявкой хостеру, либо берите
провайдера, который принимает письма по HTTPS.

### 6. Откуда ставится

**Из чистого клона репозитория**, а не из папки, где кто-то пишет код. Рабочая копия
содержит свой `.env`, свои `node_modules` и незакоммиченные правки: установка
перепишет первое и споткнётся о третьем. Проверено дорогой ценой на первой же
установке.

## Установка за уже работающим прокси

Отдельный сценарий, и он **не автоматизирован**. Если 80 и 443 уже занял чужой
Traefik, Nginx или Caddy, автоматический установщик неприменим: он рассчитывает,
что порты его. Порядок в этом случае ручной — коробка поднимается без своего
прокси, а маршрут к ней добавляется в уже стоящий.

Это ровно тот случай, который встретился на первой установке, и он оказался
не редким: сервер, где живёт только школа и больше ничего, — исключение, а не правило.

## Первая проверка

Убедиться, что встало верно, можно не заводя ни одного ученика:

- страница входа открывается по вашему домену, сертификат настоящий;
- `http://` отдаёт перенаправление на `https://`, а не страницу;
- владелец вошёл и настроил двухфакторную защиту **сам** — см. предупреждение ниже;
- в админке видно состояние очередей и фоновых задач;
- создался тестовый курс с одним уроком, и урок открывается.

## Предупреждение, которое дороже остальных

**Кто первым вошёл — тот и владеет школой.** Первый вход настраивает двухфакторную
защиту на себя, и перенастроить её на другого человека средствами продукта сейчас
нельзя: войти по паролю можно, а перепривязать второй фактор — нет.

Практически это значит: **первый вход делает тот, кто останется владельцем школы**,
а не подрядчик, который ставит. Школа, у которой уволился техспециалист,
настроивший двухфакторку на себя, останется без доступа к собственной админке.

Это находка первой установки, она известна и чинится. Пока не починена — порядок
входа решает всё.

## Обновление

**Раздел не написан, и это не забывчивость.**

Порядок обновления работающей установки с данными **ещё не проверен**: второй заход
установки — обновление поверх непустой базы — в паузе. Описывать желаемый порядок
вместо проверенного нельзя: обновление важнее установки, потому что ставят один раз,
а обновляют десятки.

Что известно точно уже сейчас:

- обновление — ваше событие, а не наше: оно не приходит само;
- миграции базы генерируются, а не пишутся руками, и применяются отдельным шагом;
- перед обновлением снимается резервная копия, и её восстановимость надо
  **проверить**, а не предположить.

Когда порядок будет пройден на живой установке с данными, здесь появятся шаги
с замерами простоя и поведением при упавшей на середине миграции.
