# Развёртывание > Пошаговая инструкция «с нуля до работающего сервера на домене» — [install-guide.md](install-guide.md). > Здесь — справочник: требования, каталоги, сервисы, переменные. ## Требования к серверу - Linux x86_64 (нужен mount propagation для storage-agent), Docker 24+, Docker Compose v2. - 4+ ядра, 8+ ГБ RAM (аудиоанализ при первом запуске ест CPU; TensorFlow-модели ~1–2 ГБ RAM). - Диск: ~5 ГБ под систему и БД + кэш транскодов (по умолчанию 50 ГБ) + обложки (~1 ГБ на 10k альбомов). - Сетевой доступ к NFS-серверам. ## Каталоги на хосте ``` /srv/pipu/ ├── library/ # точки монтирования источников (storage-agent, rshared) ├── library-host/ # папка для type=local по умолчанию; обычно вместо неё LIBRARY_HOST=/mnt/music ├── cache/ │ ├── transcode/ │ └── covers/ ├── postgres/ ├── meili/ ├── redis/ ├── models/ # модели Essentia (скачиваются при первом старте analyzer) ├── updates/ # кэш APK релизов ├── uploads/ # staging очереди на добавление (размер ≈ лимит очереди, по умолчанию 10 ГБ) ├── backups/ └── caddy/ # сертификаты Let's Encrypt (если используется compose.tls.yaml) ``` Музыка на локальном диске сервера подключается через `LIBRARY_HOST` в `.env` (например, `/mnt/music`): каталог хоста монтируется во все сервисы, включая `storage-agent`, только на чтение (запись появится вместе с импортом одобренных загрузок), внутри контейнеров он виден как `/library-host`. Перед первым запуском: `mkdir -p /srv/pipu/library && mount --bind /srv/pipu/library /srv/pipu/library && mount --make-rshared /srv/pipu/library` (делает `deploy/install.sh`, плюс systemd-unit для повторения после перезагрузки). ## `deploy/compose.yaml` Актуальная конфигурация — сам файл [`deploy/compose.yaml`](../deploy/compose.yaml); здесь — как он устроен. - **Образы собираются на сервере** из клонированного репозитория (`build:` у каждого сервиса, контекст — корень репо), в реестр ничего не публикуется. Теги локальные: `pipu-music-<сервис>:<версия>`, версия — `git describe` текущего тега (её проставляет `update.sh` в `PIPU_VERSION`). | Сервис | Собирается из | Особенности | |---|---|---| | `nginx` | `apps/web/Dockerfile` | SPA + конфиг nginx; единственный открытый порт (`HTTP_PORT`) или за Caddy | | `api` | `services/api/Dockerfile`, target `api` | Миграции при старте | | `storage-agent` | `services/api/Dockerfile`, target `storage-agent` | `CAP_SYS_ADMIN`, `apparmor:unconfined`, библиотека с `propagation: rshared` | | `recommender`, `analyzer` | `services/recommender/Dockerfile` (один образ) | Команды `serve` и `analyze --workers 4` | | `postgres` | `pgvector/pgvector:0.8.7-pg17` | Healthcheck `pg_isready` | | `redis` | `redis:7.4-alpine` | AOF | | `meilisearch` | `getmeili/meilisearch:v1.11` | | | `caddy` (из `compose.tls.yaml`) | `caddy:2.10-alpine` | HTTPS на `DOMAIN`, nginx тогда без открытых портов | - Библиотека монтируется в `nginx`, `api`, `analyzer` только на чтение с `propagation: rslave`. - Наружу открыт только `nginx` (или `caddy`). Все остальные — во внутренней сети compose. ## Конфигурация (`.env`) | Переменная | Пример / по умолчанию | Описание | |---|---|---| | `PIPU_VERSION` | проставляет `update.sh` | Версия собранных образов (`git describe`) | | `PUBLIC_URL` | `https://music.home.lan` | Внешний адрес | | `DATABASE_URL` | `postgres://pipu:…@postgres:5432/pipu` | | | `REDIS_URL` | `redis://redis:6379/0` | | | `MEILI_URL`, `MEILI_MASTER_KEY` | | | | `JWT_PRIVATE_KEY` | base64 от 32 байт (seed Ed25519) | Подпись access-токенов. Создают `install.sh` / `update.sh`, если его нет; пусто — случайный ключ до перезапуска | | `JWT_PREVIOUS_KEYS` | пусто | Прежние ключи через запятую — токены, подписанные ими, ещё принимаются (ротация) | | `ACCESS_TOKEN_TTL` | `15m` | Срок access-токена | | `COOKIE_SECURE` | `true` | Флаг `Secure` у refresh-cookie; `false` — только для разработки по http не на localhost | | `STREAM_SIGNING_KEY` | 32 байта base64 | HMAC подписанных ссылок | | `INTERNAL_TOKEN` | случайный | Межсервисная аутентификация | | `BOOTSTRAP_ADMIN_USERNAME` | `admin` | | | `BOOTSTRAP_ADMIN_PASSWORD` | пусто → сгенерировать и вывести в лог | | | `TRANSCODE_CACHE_LIMIT_GB` | `50` | | | `TRANSCODE_CONCURRENCY` | `2` | | | `GITEA_URL`, `GITEA_REPO`, `GITEA_TOKEN` | `https://git.home.lan`, `owner/pipu-music`, read-only токен | Для самообновления Android | | `PIPU_FILE_UID`, `PIPU_FILE_GID` | `1000` | Владелец файлов, которые агент записывает в принимающие хранилища | | `UPLOADS_DIR` | `/uploads` | Staging очереди на добавление (лимит очереди задаётся в админке) | | `LOG_LEVEL` | `info` | | ## TLS - **Свой домен (рекомендуется)** — `deploy/compose.tls.yaml` добавляет Caddy, который получает и продлевает сертификат Let's Encrypt для `DOMAIN` и проксирует на nginx; nginx тогда наружу не открыт. Включается строкой `COMPOSE_FILE=compose.yaml:compose.tls.yaml` в `.env`. - **Свой reverse proxy** (Traefik, NPM, nginx) — без `compose.tls.yaml`, прокси направляется на `HTTP_PORT` (например `127.0.0.1:8081`); нужны `X-Forwarded-*`, тело до 16 МБ, без буферизации ответов. - nginx внутри образа `web` берёт схему и IP клиента из `X-Forwarded-Proto` / `X-Forwarded-For`, если запрос пришёл из частной сети (docker, LAN). - Локальная сеть без домена — свой CA; сертификат CA нужно установить на устройства или включить пиннинг в мобильном (см. mobile.md). Пошаговая установка на домен — [install-guide.md](install-guide.md). ## Бэкапы - Контейнер `backup` (`deploy/backup.sh loop`): каждый день в `BACKUP_HOUR`:30 UTC (по умолчанию 3:30) — `pg_dump -Fc` → `/srv/pipu/backups/daily/`, хранится `BACKUP_KEEP_DAILY` = 14 последних; первая копия за неделю копируется в `weekly/` (`BACKUP_KEEP_WEEKLY` = 8). Рядом с копией — `*.counts`: число строк в каждой таблице. - Раз в неделю копия **проверяется восстановлением**: разворачивается во временную базу `pipu_verify`, сверяются таблицы и число строк, база удаляется; время последней успешной проверки — `backups/.last-verify`, ошибки — в `docker compose logs backup` («ПРОВЕРКА НЕ ПРОЙДЕНА»). Тот же цикл копия → проверка → восстановление выполняется в CI (`ci / db`) на базе после сквозного сценария. - `update.sh` перед обновлением делает внеочередную копию. - Вручную: `docker compose run --rm backup once` — копия сейчас; `docker compose run --rm backup verify /backups/daily/<файл>`. - Восстановление: `./restore.sh` — список копий, `./restore.sh daily/pipu-….dump` — останавливает api, рекомендации и анализ, проверяет копию, переименовывает текущую базу в `pipu_before_restore_<время>` (страховка; удалить — `docker compose exec postgres dropdb -U pipu <имя>`), восстанавливает и запускает сервисы; поиск переиндексируется при старте api. Redis (сессии волны, очереди) не восстанавливается — пользователи просто начнут новую волну. - Копии лежат на том же сервере: для защиты от потери диска синхронизируйте `/srv/pipu/backups` на другую машину (например, `rclone sync` или `rsync` по cron). - Meilisearch, кэши и модели **не бэкапятся** — восстанавливаются (`reindex`, повторный транскод / анализ). - Признаки анализа хранятся в Postgres — после восстановления из бэкапа повторный многочасовой анализ не нужен. - Сама музыка — на NFS, вне зоны ответственности приложения. ## Обновление ```bash cd /opt/pipu-music/deploy && ./update.sh # последний релизный тег ``` Скрипт: `git fetch` → checkout тега → `docker compose build --pull` → `up -d` → удаление старых образов → ожидание `/readyz`; миграции применяются при старте api. Конкретная версия или откат: `./update.sh v1.2.4` (миграции пишутся обратно-совместимыми в пределах минорной версии: сначала добавить, потом — в следующем релизе — удалить).