250 lines
16 KiB
Markdown
250 lines
16 KiB
Markdown
# WhatIDo
|
||
|
||
Учётная система для образовательного центра: журнал посещений, проектные работы воспитанников, галерея групп, откреплённые файлы и публичные страницы-витрины (share-ссылки).
|
||
|
||
## Возможности
|
||
|
||
- **Журнал записей** — отметки о занятиях с фото и прикреплёнными файлами (проектные работы), мягкое удаление и корзина
|
||
- **Файлы** — централизованный раздел со всеми загруженными файлами, фильтры (имя воспитанника, группа, даты, поиск) и вкладка «Откреплённые»
|
||
- **Группы** — учебные группы, расписание (день недели, время), фотохроника группы
|
||
- **Воспитанники** — справочник с привязкой к группам
|
||
- **Share-ссылки** — публичные страницы-витрины с выбором группы / воспитанника / диапазона дат
|
||
- **Дашборд** — статистика, активные группы, активность за 14 дней, последние записи, топ воспитанников
|
||
- **Резервное копирование** — экспорт/импорт полного дампа (БД + файлы) в `tar.gz`
|
||
- **Настройки** — тексты футера, анти-спам интервал
|
||
- **Публикация через Tailscale** — приложение открывается по постоянному адресу `https://whatido.<tailnet>.ts.net` без проброса портов, внешнего IP и reverse-proxy
|
||
|
||
## Технологии
|
||
|
||
- Node.js + Express
|
||
- PostgreSQL (pg)
|
||
- Multer (загрузка файлов), Tar (бэкапы)
|
||
- Docker / Docker Compose
|
||
- Tailscale (Serve / Funnel) — публикация по HTTPS
|
||
|
||
## Быстрый старт
|
||
|
||
### Требования
|
||
|
||
- Linux-хост с Docker и плагином `docker compose`
|
||
- Free порта `443` на хосте (его займёт tailscale для Funnel)
|
||
- Аккаунт Tailscale (для публикации по ссылке)
|
||
|
||
### Запуск
|
||
|
||
```bash
|
||
# 1) создайте .env из примера и задайте свои пароли
|
||
cp .env.example .env
|
||
$EDITOR .env
|
||
|
||
# 2) соберите и поднимите стек
|
||
docker compose up -d --build
|
||
```
|
||
|
||
После старта (без публикации через tailscale):
|
||
|
||
- **HTTP** `http://localhost:3000` — редирект на HTTPS
|
||
- **HTTPS** `https://localhost:3443` — приложение (самоподписанный сертификат, примите предупреждение браузера)
|
||
- **PostgreSQL** — доступен только внутри docker-сети (наружу не публикуется)
|
||
|
||
Управление:
|
||
|
||
```bash
|
||
docker compose ps # статус
|
||
docker compose logs -f app # логи приложения
|
||
docker compose down # остановка (данные сохраняются)
|
||
```
|
||
|
||
> Приложение **не запустится** без `ADMIN_PASSWORD` (защита от пароля по умолчанию).
|
||
> `DB_PASSWORD` задаёт пароль пользователя `app` в PostgreSQL.
|
||
|
||
## Конфигурация
|
||
|
||
Переменные окружения (`.env`):
|
||
|
||
| Переменная | По умолчанию | Назначение |
|
||
|------------------|--------------------|-------------------------------------|
|
||
| `ADMIN_PASSWORD` | — (обязательно) | Пароль администратора (X-Admin-Token). Без него сервер не стартует |
|
||
| `DB_PASSWORD` | — (обязательно) | Пароль пользователя `app` в PostgreSQL |
|
||
|
||
Пример `.env` (в репозитории — `.env.example`):
|
||
|
||
```
|
||
ADMIN_PASSWORD=сложный-пароль
|
||
DB_PASSWORD=случайная-длинная-строка
|
||
```
|
||
|
||
`DB_PASSWORD` подставляется в `docker-compose.yml` в `POSTGRES_PASSWORD` и `DATABASE_URL`. Если БД уже была инициализирована ранее, значение `DB_PASSWORD` должно совпадать с фактическим паролем пользователя `app` в БД (иначе приложение не подключится).
|
||
|
||
Имя узла Tailscale задаётся в `docker-compose.yml` (`tailscale.hostname`, по умолчанию `whatido`).
|
||
|
||
## Публичный доступ через Tailscale
|
||
|
||
Стек не требует внешнего IP и проброса портов: контейнер `tailscale` запускается с `network_mode: host`, входит в вашу tailnet-сеть и через **Serve** открывает приложение внутри tailnet, а через **Funnel** — в публичном интернете.
|
||
|
||
Цепочка:
|
||
|
||
```
|
||
Интернет / tailnet → https://whatido.<tailnet>.ts.net (TLS от Tailscale)
|
||
→ localhost:443 контейнера tailscale
|
||
→ https://127.0.0.1:3443 (приложение, самоподписанный cert)
|
||
```
|
||
|
||
Контейнер доверяет самоподписанному сертификату приложения через `SSL_CERT_FILE=/etc/tailscale/app-certs/cert.pem` (файл монтируется из `./certs/cert.pem`).
|
||
|
||
### 1. Вход в tailnet при первом запуске
|
||
|
||
При первом старте контейнер автоматически выполняет `tailscale up` и печатает ссылку для авторизации — откройте её в браузере и добавьте устройство в аккаунт. Ввести устройство вручную там не нужно: `tailscale status` покажет статус:
|
||
|
||
```bash
|
||
docker exec -it whatido-tailscale-1 tailscale status
|
||
```
|
||
|
||
Если авторизация по какой-то причине не прошла, выполните вход вручную:
|
||
|
||
```bash
|
||
docker exec -it whatido-tailscale-1 tailscale up --hostname=whatido
|
||
# откроется ссылка вида https://login.tailscale.com/a/... — войдите в браузер
|
||
```
|
||
|
||
### 2. Включите функции и найдите адрес
|
||
|
||
После авторизации узел получит имя вида `whatido` и адрес:
|
||
|
||
```bash
|
||
docker exec -it whatido-tailscale-1 tailscale status
|
||
# что-то вроде: whatido.taile47725.ts.net 100.x.x.x receiver online
|
||
```
|
||
|
||
Чтобы публиковать сайт, для tailnet должны быть включены:
|
||
|
||
- **HTTPS Certificates** — автоматически выдается при первом Serve/Funnel
|
||
- **Serve** (доступ из tailnet) и **Funnel** (доступ из интернета) — включаются в админ-консоли Tailscale, например по прямой ссылке на узел:
|
||
`https://login.tailscale.com/f/serve?node=<node-id>` и `https://login.tailscale.com/funnel?node=<node-id>`
|
||
(id узла берётся из `tailscale status`)
|
||
|
||
### 3. Запуск Serve / Funnel
|
||
|
||
Контейнер при старте сам выполняет:
|
||
|
||
```bash
|
||
tailscale funnel --bg --yes https://127.0.0.1:3443
|
||
```
|
||
|
||
(`funnel` включает и serve-часть; флаг `--bg` — работа в фоне, `--yes` — не спрашивать подтверждения.)
|
||
|
||
Если сервис уже запущен и команды в compose не отработали (например, функции только что включили в консоли), выполните вручную:
|
||
|
||
```bash
|
||
docker exec whatido-tailscale-1 tailscale funnel --bg --yes https://127.0.0.1:3443
|
||
```
|
||
|
||
Проверить состояние:
|
||
|
||
```bash
|
||
docker exec whatido-tailscale-1 tailscale funnel status
|
||
# https://whatido.<tailnet>.ts.net/ (Funnel on)
|
||
```
|
||
|
||
### 4. Готово
|
||
|
||
- Из любого устройства вашей tailnet: `https://whatido.<tailnet>.ts.net/`
|
||
- Из интернета (при включённом Funnel): тот же адрес
|
||
- Админка: `https://whatido.<tailnet>.ts.net/admin`
|
||
|
||
### Важные замечания
|
||
|
||
- **Порт 443 на хосте должен быть свободен** — tailscale слушает его напрямую (поэтому у сервиса `network_mode: host`, а приложение опубликовано на `127.0.0.1:3000/3443`).
|
||
- **Сертификат приложения**: контейнер приложения генерирует self-signed `cert.pem` при сборке образа (это не секрет — публичный сертификат). Он монтируется в tailscale через `./certs/cert.pem`. Если образ приложения пересобирали впервые на новом хосте — скопируйте сертификат и перезапустите tailscale:
|
||
|
||
```bash
|
||
docker compose up -d --build app
|
||
docker cp $(docker compose ps -q app):/app/certs/cert.pem certs/cert.pem
|
||
docker compose up -d tailscale
|
||
```
|
||
|
||
- **`/lib/modules`** монтируется в контейнер tailscale, чтобы на LC/дистрибутивах без авто-загрузки модулей корректно инициализировался netfilter (иначе `tailscaled` падает с `Table does not exist` и узел мигает online/offline).
|
||
- **Ограничения сети**: если у провайдера нет глобального IPv6 и соединения к ПК-ядрам Tailscale нестабильные (CGNAT), отвечать наружу узел может с перебоями — классический признак: `tailscale status` показывает `online`, а из интернета URL не открывается. Внутри tailnet сайт работает всегда.
|
||
|
||
## Альтернативная публикация (Caddy)
|
||
|
||
Исторически проект публиковался через reverse-proxy Caddy + Let's Encrypt (файлы `Caddyfile.example`, закомментированный сервис в старых версиях compose). Если нужно классическое публичное HTTPS на собственном домене с проброшенными портами 80/443 — этот вариант остаётся возможным: раскомментируйте/восстановите сервис `caddy`, укажите домен в `Caddyfile` и уберите сетевые блокировки. По умолчанию сейчас рекомендуется Tailscale-схема выше.
|
||
|
||
## Структура данных
|
||
|
||
- `groups` — группы, расписание (`day_of_week`, `time_start`, `time_end`)
|
||
- `students` — воспитанники
|
||
- `entries` — записи журнала (фото `photo_path`, описание, мягкое удаление `deleted_at`)
|
||
- `project_files` — файлы записей (`token`, `path`, привязка `entry_id`, отметка `detached_at`)
|
||
- `group_photos` — фотохроника групп
|
||
- `share_links` — публичные ссылки-витрины
|
||
- `settings` — пары ключ/значение (анти-спам интервал, футер)
|
||
|
||
Схема инициализируется при первом запуске из `db/init.sql`; миграции существующей БД — в `db/migration.sql`.
|
||
|
||
## Хранилище файлов
|
||
|
||
Загруженные фото и файлы хранятся в каталоге `uploads/` на хосте и монтируются в контейнер (`./uploads:/app/uploads`). Это даёт прямой доступ к данным из-под хост-системы. Данные БД хранятся в именованном томе `pgdata`.
|
||
|
||
## Бэкапы
|
||
|
||
В админке (Настройки → Бэкап) можно:
|
||
|
||
- Скачать полный бэкап — `tar.gz`, содержащий `data.json` (все таблицы) и `uploads/`
|
||
- Восстановить из файла бэкапа
|
||
|
||
Также доступны скрипты на хосте:
|
||
|
||
```bash
|
||
./scripts/backup.sh # дамп БД + фото в backups/whatido-backup-<дата>.tar.gz
|
||
./scripts/restore.sh # восстановление из архива
|
||
```
|
||
|
||
## Безопасность
|
||
|
||
- **Пароль администратора** обязателен (`ADMIN_PASSWORD`); фолбэка на `admin` нет.
|
||
- **CORS отключён** — кросс-доменные запросы к API запрещены.
|
||
- **Rate limiting** по IP на публичные роуты: `POST /api/entries` — 10 запросов / 15 мин, загрузка файлов и share-ссылки — 300 / 15 мин.
|
||
- **Загрузки** ограничены: 30 МБ суммарно на запись, 10 МБ на файл; заблокированы опасные расширения (`.html`, `.js`, `.svg`, `.xml`, `.exe` и др.); SVG не отдаётся inline.
|
||
- **Restore** проходит полную валидацию данных бэкапа; удаление файлов ограничено каталогом `uploads/`.
|
||
- **Заголовки**: `helmet` — `X-Frame-Options`, `nosniff`, HSTS, `Referrer-Policy`.
|
||
- **Порт БД** 5432 наружу не публикуется (доступ только внутри docker-сети).
|
||
- **TLS**: снаружи HTTPS терминируется Tailscale (сертификат Let's Encrypt для `*.ts.net`); между tailscale и приложением используется самоподписанный сертификат приложения.
|
||
|
||
## Основные API
|
||
|
||
| Метод | Путь | Назначение |
|
||
|-------|------|------------|
|
||
| `GET` | `/api/entries` | Записи журнала (с фильтрами) |
|
||
| `POST` | `/api/entries` | Создать запись (photo + files) |
|
||
| `PUT/DELETE` | `/api/entries/:id` | Обновить / мягко удалить |
|
||
| `GET` | `/api/files` | Все файлы (фильтры) |
|
||
| `GET` | `/api/files/detached` | Откреплённые файлы |
|
||
| `POST` | `/api/files/:id/detach` | Открепить файл от записи |
|
||
| `GET` | `/api/files/:token` | Скачать/показать файл по токену (публично) |
|
||
| `GET` | `/api/groups` | Список групп |
|
||
| `POST/PUT/DELETE` | `/api/groups/:id(?)` | CRUD групп |
|
||
| `GET/POST` | `/api/groups/:id/photos` | Фотохроника группы |
|
||
| `GET/POST/PUT/DELETE` | `/api/share/...`, `/api/links` | Публичные ссылки |
|
||
| `GET` | `/api/backup` | Скачать бэкап |
|
||
| `POST` | `/api/restore` | Восстановить из бэкапа |
|
||
| `GET` | `/api/dashboard`, `/api/stats` | Статистика |
|
||
|
||
Защищённые админ-маршруты требуют заголовок `X-Admin-Token` с `ADMIN_PASSWORD`.
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
├── docker-compose.yml # сервисы: app + db + tailscale
|
||
├── .env.example # шаблон переменных окружения
|
||
├── Dockerfile # сборка образа (Node 20, генерация TLS-сертификата)
|
||
├── server.js # Express-приложение
|
||
├── certs/ # cert.pem приложения (монтируется в tailscale, в git не хранится)
|
||
├── db/
|
||
│ ├── init.sql # схема при первом запуске
|
||
│ └── migration.sql # миграции существующей БД
|
||
├── public/ # статика (HTML/CSS/JS админки и витрин)
|
||
├── scripts/ # вспомогательные скрипты
|
||
├── uploads/ # загруженные файлы (bind-монт, вне git)
|
||
└── backups/ # локальные бэкапы
|
||
``` |