# Рассылка расписания — установка на VDS

Панель рассчитана на Ubuntu 22.04/24.04, постоянный VDS и домен с HTTPS.
Подойдёт российский или зарубежный сервер, если с него открываются API
Telegram, MAX и ВКонтакте.

## Что понадобится

1. Домен с A-записью на IP сервера.
2. Доступ к серверу по SSH с `sudo`.
3. Распакованный архив проекта или заранее клонированный репозиторий.
4. Почта для сертификата Let's Encrypt.

Токены Telegram, MAX и ВК установщик не спрашивает. Они вводятся позже в
защищённой панели.

Местоположение сервера само по себе не определяет доставку. Российский и
зарубежный VDS работают одинаково: панель делает исходящие HTTPS-запросы к API
трёх платформ. Выбирайте хостинг, с которого доступны все три API; если один из
них блокируется провайдером, смена кода это не обойдёт.

## Автоматическая установка

Перейдите в каталог, где лежат `package.json` и `ustanovit.sh`, затем:

```bash
sudo bash ustanovit.sh
```

Скрипт спросит домен, почту для сертификата и пароль Postgres. Пароль вводится
скрыто. В конце установщик покажет отдельный одноразовый код первого хозяина.
Сохраните его до успешного создания входа.

Установщик:

- ставит Node 22, Postgres, nginx и certbot;
- копирует приложение в `/opt/raspisanie`;
- создаёт production-сборку `.output/server/index.mjs`;
- применяет миграции без подавления ошибок;
- перед повторной установкой сохраняет код, дамп Postgres и env в
  `/var/backups/raspisanie/<UTC-время>`;
- при ошибке обновления автоматически возвращает прежний код приложения;
- сохраняет секреты только в `/etc/raspisanie/raspisanie.env`;
- запускает systemd-службу от отдельного пользователя;
- привязывает внутренний Node-порт только к `127.0.0.1`;
- настраивает nginx и HTTPS.

Если миграция или сборка не прошла, установка останавливается, возвращает
прежний код и показывает путь к резервной копии. Миграции только добавляют
совместимые поля; дамп базы остаётся для ручного аварийного восстановления.

## Первый вход

1. Откройте `https://ваш-домен`.
2. Нажмите «Первый раз — завести вход».
3. Введите свою почту, новый пароль и одноразовый код из установщика.
4. После входа откройте «Сотрудники» и «Ключи».

Открытой регистрации «кто первый, тот хозяин» больше нет. Без кода первого
хозяина создать owner-аккаунт нельзя. Если код потерян до первого входа, root
может посмотреть его на сервере:

```bash
sudo sed -n 's/^OWNER_SETUP_CODE=//p' /etc/raspisanie/raspisanie.env
```

## Сотрудники

Хозяин вводит почту коллеги на вкладке «Сотрудники». Панель показывает
одноразовый код на эту конкретную почту; код действует 72 часа. Передайте
коллеге ссылку на панель и код. В базе хранится только необратимый отпечаток
кода, поэтому старый код повторно посмотреть нельзя — при необходимости
выпустите новый.

## Почему задания работают после перезагрузки

Служба запускает production-сервер, а серверный plugin сразу включает
диспетчер очереди. Открытая вкладка браузера для расписания на 7:00 и приёма
фото через Telegram не нужна.

## Каналы и личные подписки

На главной странице Telegram и MAX имеют по два независимых переключателя:
«канал» и «личные сообщения». Можно включить оба режима или только один. ВК в
этой версии отправляет только личные сообщения по списку получателей.

### Telegram

1. Создайте отдельного бота в BotFather и сохраните токен на вкладке «Ключи».
2. Для публикации в канал добавьте бота администратором и укажите числовой ID
   канала или чата.
3. Для личной рассылки пользователь сам открывает бота и отправляет `/start`.
   Команда `/stop` отключает подписку и отменяет ещё не начатые сообщения этому
   пользователю.
4. Поле «Кто может кидать фото боту» относится только к сотрудникам, которые
   создают рассылки через Telegram. Подписчикам эти права не нужны.

Панель получает Telegram-события через `getUpdates`. Используйте отдельного
бота: если его webhook уже подключён к другому сервису, два обработчика будут
конфликтовать.

### MAX

1. Получите токен бота в кабинете MAX для партнёров и сохраните его на вкладке
   «Ключи».
2. Для публикации в канал добавьте бота администратором и укажите ID канала.
3. После размещения панели на рабочем HTTPS-домене нажмите «Подключить личные
   подписки». Панель зарегистрирует защищённый webhook.
4. Пользователь запускает бота — событие `bot_started` включает подписку.
   Остановка бота (`bot_stopped`) выключает её и отменяет ещё не начатые
   сообщения этому пользователю.

В базе Telegram/MAX хранятся только технические ID подписчиков, состояние
подписки и служебное состояние очереди. Имена, телефоны и username панель не
запрашивает. Для отложенной рассылки список активных подписчиков заново
собирается непосредственно перед первым отправлением.

Проверка:

```bash
systemctl status raspisanie
journalctl -u raspisanie -n 100 --no-pager
```

## Неопределённая отправка Telegram/MAX

Если процесс оборвался ровно между ответом платформы и записью результата,
панель не повторяет сообщение автоматически. В задании появится статус
«нужна проверка». Проверка блокирует только конкретный канал или получателя;
остальные направления продолжают работать. Проверьте сообщение и выберите:

- «Уже дошло» — продолжить без повтора;
- «Повторить с риском дубля» — повторить осознанно.

Так перезагрузка сервера не создаёт автоматический дубль.

## Редактирование отправленного текста

Для новых рассылок панель сохраняет идентификаторы постов в каналах Telegram и
MAX и каждого личного сообщения в Telegram, MAX и ВК. После завершения откройте
рассылку, исправьте текст (до 1024 символов) и нажмите «Изменить отправленные».
Работа идёт в фоновой очереди с прогрессом и ограниченными повторами. Фото пока
не заменяются. Старые рассылки, созданные до появления сохранённых ID,
отредактировать нельзя.

Токены ботов в Postgres защищены AES-256-GCM ключом, производным от
`BETTER_AUTH_SECRET`. Поэтому этот секрет нельзя менять и резервную копию env
нужно хранить вместе с дампом базы.

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

Проще всего повторно запустить `sudo bash ustanovit.sh`: он сам сделает
резервную копию. Для ручного обновления сначала сохраните Postgres, затем
замените исходники в `/opt/raspisanie` и выполните:

```bash
sudo -u raspisanie -H bash -lc 'cd /opt/raspisanie && npm ci'
sudo -u raspisanie -H bash -lc 'set -a; . /etc/raspisanie/raspisanie.env; set +a; cd /opt/raspisanie && npm run build:vds && npm run db:migrate'
sudo systemctl restart raspisanie
sudo systemctl --no-pager --full status raspisanie
```

Секрет `BETTER_AUTH_SECRET` нельзя менять при обновлении: иначе завершатся все
активные сеансы. Повторный запуск `ustanovit.sh` сохраняет уже существующий
секрет.

## Частые проблемы

- `Invalid origin`: `BETTER_AUTH_URL` должен точно совпадать с адресом в
  браузере, включая `https://`, без завершающего `/`.
- После перезагрузки ничего не отправляется: проверьте `systemctl status
raspisanie` и журнал службы.
- Не открывается HTTPS: проверьте A-запись домена и повторите `sudo certbot
--nginx -d ваш-домен`.
- Миграция упала: не запускайте приложение вручную через `vite`; исправьте
  причину по журналу и повторите `npm run db:migrate`.
- Telegram не видит `/start`: убедитесь, что у этого бота не подключён webhook
  другого сервиса, затем нажмите «Проверить ключ» и посмотрите вкладку «Бот».
- MAX не считает подписчиков: домен должен открываться по HTTPS; после
  сохранения токена повторно нажмите «Подключить личные подписки».

Файл `/etc/raspisanie/raspisanie.env` и дампы Postgres нельзя пересылать или
класть в архив проекта.
