Files
pipu-music/docs/deployment.md
T
mrleo1nidandClaude Opus 5.5 aacf55679a
ci / db / db (push) Failing after 52s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m3s
feat(deploy): nightly database backups with verified restore (M7)
A backup container dumps the database every night, keeps 14 daily and 8 weekly
copies and once a week restores a copy into a scratch database to check tables
and row counts. update.sh takes a copy before updating, restore.sh brings one
back and keeps the old database aside. CI runs the dump, verify and restore
cycle on the database the end-to-end journey filled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:59:27 +03:00

11 KiB
Raw Permalink Blame History

Развёртывание

Пошаговая инструкция «с нуля до работающего сервера на домене» — 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; здесь — как он устроен.

  • Образы собираются на сервере из клонированного репозитория (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.

Бэкапы

  • Контейнер 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, вне зоны ответственности приложения.

Обновление

cd /opt/pipu-music/deploy && ./update.sh          # последний релизный тег

Скрипт: git fetch → checkout тега → docker compose build --pull → up -d → удаление старых образов → ожидание /readyz; миграции применяются при старте api. Конкретная версия или откат: ./update.sh v1.2.4 (миграции пишутся обратно-совместимыми в пределах минорной версии: сначала добавить, потом — в следующем релизе — удалить).