# Установка на свой сервер (Docker Compose, без Coolify)
Портал поставляется как обычный проект Docker Compose: приложение (Python, FastAPI) + PostgreSQL 16.
Никаких внешних сервисов не нужно — Coolify, Kubernetes и т. п. необязательны. Ниже — полная установка с нуля,
обновление, резервные копии, откат и перенос на другой сервер.
## 1. Что нужно
- Сервер Linux (Debian 12/13, Ubuntu 22.04/24.04 и т. п.), 2 ядра, 2 ГБ ОЗУ, от 10 ГБ диска (+ место под фото).
- Docker Engine 24+ с плагином Compose v2: `docker --version`, `docker compose version`.
Установка: (на Debian и Ubuntu — `curl -fsSL https://get.docker.com | sh`).
- Git (или архив с исходниками).
- Доменное имя и обратный прокси с HTTPS (nginx, Nginx Proxy Manager, Caddy, Traefik — любой). Без HTTPS не работают
уведомления в браузере и установка мобильной версии на iPhone; приложение для Android в релизной сборке ходит только по HTTPS.
## 2. Получить проект
```bash
sudo mkdir -p /opt/portal && sudo chown $USER /opt/portal
git clone <адрес репозитория> /opt/portal
cd /opt/portal
git checkout main
```
## 3. Настроить переменные
```bash
cp .env.example .env
nano .env
```
| Переменная | Что указать |
|---|---|
| `DB_PASSWORD` | Пароль базы (случайный: `openssl rand -hex 24`). После первого запуска **не меняйте** — он записан в базу. |
| `SECRET_KEY` | Ключ подписи токенов, **не короче 32 символов**: `openssl rand -hex 32`. Смена завершит все сессии. |
| `ADMIN_PASSWORD` | Пароль пользователя `admin`, создаётся при первом запуске, **не короче 10 символов**. |
| `APP_TZ` | Часовой пояс предприятия (`Europe/Moscow`, `Asia/Yekaterinburg` …): смены, расписание нарядов, отчёты. |
| `APP_PORT` | Порт на хосте, куда смотрит прокси (по умолчанию 8080). |
| `FORWARDED_ALLOW_IPS` | Адрес обратного прокси, как его видит приложение (см. п. 6). |
В боевом режиме (`APP_ENV=production` прописан в `docker-compose.yml`) приложение **не запустится** со слабыми `SECRET_KEY`
или `ADMIN_PASSWORD` — это защита, а не ошибка: задайте значения по таблице.
## 4. Запустить
```bash
docker compose up -d --build
docker compose ps # app и db — running (healthy)
curl -s http://127.0.0.1:8080/api/v1/health
```
Первая сборка занимает несколько минут. Логи: `docker compose logs -f app`.
Данные хранятся в томах Docker: `pgdata` (база), `photos` (фото нарядов и задач), `appdata` (резервные копии, демо-база,
загруженный APK). Они переживают пересборку и перезапуск контейнеров. Удаляет их только `docker compose down -v` — **не делайте так**
на рабочем сервере.
## 5. Первый вход
Откройте `http://<сервер>:8080` (до настройки прокси) или ваш домен, войдите как `admin` с паролем из `ADMIN_PASSWORD`
и **сразу смените пароль**: «Администрирование → Пользователи» → admin → «Изменить» → новый пароль. Пустая система предложит **мастер первичной настройки**:
графики, должности, отделы, места, оборудование, сотрудники, карта, склады, чек-листы, резервные копии.
## 6. Обратный прокси и HTTPS
Приложение слушает обычный HTTP на `APP_PORT`; HTTPS даёт прокси. Пример для nginx на том же сервере
(сертификат — certbot: `sudo certbot --nginx -d portal.example.ru`):
```nginx
server {
listen 443 ssl http2;
server_name portal.example.ru;
# ssl_certificate / ssl_certificate_key — пропишет certbot
client_max_body_size 20m; # фото и картинки карт; иначе ошибка 413
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s; # PDF-отчёты и AI-помощник могут отвечать долго
}
}
server { listen 80; server_name portal.example.ru; return 301 https://$host$request_uri; }
```
- **Nginx Proxy Manager:** Proxy Host → Forward `http://:8080`, SSL (Let's Encrypt), Force SSL; во вкладке Advanced —
`client_max_body_size 20m;`.
- **Caddy:** `portal.example.ru { reverse_proxy 127.0.0.1:8080 }` и `request_body { max_size 20MB }`.
**Адрес прокси (`FORWARDED_ALLOW_IPS`).** Приложение верит заголовку `X-Forwarded-For` только от этого адреса — иначе любой мог бы
подделать свой IP и обойти ограничение попыток входа. Как настроить: зайдите на портал через прокси администратором →
«Администрирование → Настройки сервера», блок «Состояние», строка «Адрес клиента, каким его видит сервер». Пока прокси не указан,
там виден **адрес самого прокси** (если прокси на том же хосте — обычно шлюз сети Docker вида `172.18.0.1`, если на другой машине —
её IP). Впишите его в `FORWARDED_ALLOW_IPS` и примените: `docker compose up -d`. После этого в той же строке должен появиться
**ваш собственный** адрес — значит, всё настроено.
**Файрвол.** Порт `APP_PORT` должен быть доступен только прокси. Docker публикует порты в обход `ufw`, поэтому либо привяжите
порт к localhost (в `docker-compose.yml`: `"127.0.0.1:${APP_PORT:-8080}:8000"` — если прокси на этом же сервере), либо закройте
его правилом в цепочке `DOCKER-USER` (iptables) или на внешнем файрволе.
## 7. Приложение для Android
Готовый подписанный APK лежит в `release/` и входит в образ: ссылка «Скачать приложение для Android» на странице входа и адрес
`/app.apk` работают сразу. Новую версию можно выложить без пересборки — положить файл в том `appdata`:
```bash
docker compose cp checklists-1.7.0.apk app:/srv/data/apk/
```
Версия берётся из имени файла (`checklists-<версия>.apk`); приложения сами предложат обновиться. В приложении на телефоне
указывается адрес вашего сервера (`https://portal.example.ru`).
## 7а. Дополнительные сервисы: документация, просмотр Office, локальный ИИ
`docker-compose.yml` умеет поднять рядом с порталом ещё три сервиса. Они включаются **профилями** — строкой в `.env`:
```bash
COMPOSE_PROFILES=docs,office,ai
```
| Профиль | Что запускается | Зачем | Ресурсы сверх портала |
|---|---|---|---|
| `docs` | Nextcloud + своя PostgreSQL | хранилище документации оборудования: паспорта, схемы, руководства | 1 ГБ памяти, место под документы |
| `office` | ONLYOFFICE Document Server | Word, Excel, PowerPoint открываются прямо в портале (переводятся в PDF) | 2–4 ГБ памяти |
| `ai` | Ollama + скачивание модели | локальная модель для AI-помощника: вопросы и документы не уходят в интернет | 6–8 ГБ памяти для 7B-модели, лучше видеокарта |
Профили независимы: можно взять только `ai` или подключить свой Nextcloud и ONLYOFFICE, указав их адреса в `.env`
(`NC_URL`, `ONLYOFFICE_URL`, `PORTAL_INTERNAL_URL`).
**Настройки заполняются сами.** Портал ищет сервисы во внутренней сети Docker по именам `nextcloud`, `onlyoffice`, `ollama`
и подставляет адреса в «Настройки сервера»: Nextcloud — `http://nextcloud`, ONLYOFFICE — `http://onlyoffice` (документы
он забирает у портала по `http://app:8000`), модель — `http://ollama:11434/v1` с моделью из `OLLAMA_MODEL`. Учётная запись
Nextcloud и секрет ONLYOFFICE берутся из `.env`. Всё, что администратор поменяет в интерфейсе, важнее автоматических значений.
Что сделать после запуска:
1. **Nextcloud** откройте на порту `NEXTCLOUD_PORT` (по умолчанию 8081), войдите администратором из `.env`, создайте папку
документации (например, «Документация») с подпапками по машинам и укажите её в `NC_ROOT` или в настройках портала.
Для безопасности заведите отдельного пользователя с доступом только на чтение к этой папке и пароль приложения для
него — и впишите их в `NC_USER` / `NC_PASSWORD`.
2. **ONLYOFFICE** наружу не публикуется: портал общается с ним внутри сети Docker. Задайте свой `ONLYOFFICE_JWT`.
3. **ИИ**: дождитесь, пока `ollama-pull` скачает модель (`docker compose logs -f ollama-pull`), затем в «Настройках сервера →
AI-помощник» нажмите «Проверить» и включите помощника. Опция «Работа с документацией» включается там же.
## 8. Обновление
```bash
cd /opt/portal
git pull
docker compose up -d --build
```
Перед обновлением сделайте резервную копию (п. 9). Структура базы обновляется сама при запуске (только добавление новых полей),
поэтому откат на предыдущую версию кода возможен: `git checkout <прежний коммит> && docker compose up -d --build`.
## 9. Резервные копии
1. **Встроенные (рекомендуется):** «Администрирование → Резервные копии». Копия — zip с базой в переносимом формате и настройками
(по желанию — с фото); есть автокопии по расписанию (по умолчанию раз в сутки, хранится 14). Копии лежат в томе `appdata`
(`/srv/data/backups`) — **регулярно забирайте их на другой носитель**:
```bash
docker compose cp app:/srv/data/backups ./backups-$(date +%F)
```
2. **Средствами PostgreSQL:**
```bash
docker compose exec -T db pg_dump -U checklists -Fc checklists > portal-$(date +%F).dump
docker run --rm -v portal_photos:/p -v "$PWD":/b alpine tar czf /b/photos-$(date +%F).tgz -C /p .
```
Имя тома — `<имя проекта>_photos` (имя проекта по умолчанию — имя каталога, здесь `portal`): `docker volume ls`.
Восстановление дампа: `docker compose exec -T db pg_restore -U checklists -d checklists --clean --if-exists < portal-ДАТА.dump`.
## 10. Перенос на другой сервер
1. На старом сервере: «Резервные копии» → «Создать копию» с фото → скачать zip.
2. На новом: установка по пп. 1–6 (новые `DB_PASSWORD`/`SECRET_KEY` допустимы), вход `admin`.
3. «Резервные копии» → «Загрузить копию» → «Восстановить». Все данные, пользователи и пароли станут как на старом сервере;
после восстановления все входят заново.
## 11. Обслуживание
| Задача | Команда |
|---|---|
| Состояние | `docker compose ps` |
| Логи приложения | `docker compose logs -f --tail 200 app` |
| Перезапуск | `docker compose restart app` |
| Остановить / запустить | `docker compose stop` / `docker compose start` |
| Место на диске | `docker system df`, «Настройки сервера» → свободно на диске |
| Забыт пароль admin | другой администратор задаёт новый в «Пользователях»; если администратор один — восстановите из копии или обратитесь к разработчику |
Важно: приложение работает **одним процессом** (так настроено в образе) — планировщик нарядов и демо-режим живут внутри него.
Не масштабируйте сервис `app` на несколько копий.
## 12. Если что-то не так
- **Контейнер app перезапускается**, в логах `SECRET_KEY не задан или слишком короткий` / `ADMIN_PASSWORD слабый` — задайте значения по п. 3.
- **Фото не загружаются (413)** — не задан `client_max_body_size 20m` в прокси.
- **Все входы «блокируются на 5 минут»** — прокси не указан в `FORWARDED_ALLOW_IPS`, и все клиенты видны с одного адреса (п. 6).
- **Неверное время нарядов и смен** — проверьте `APP_TZ` и время сервера («Настройки сервера» → время сервера).
- **Нет уведомлений на телефоне** — нужен HTTPS; для Android-push через Firebase — ключ в «Настройках сервера».