Files
pipu-music/docs/deployment.md
T
mrleo1nidandClaude Opus 5.5 4fa1add80d
ci / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 48s
ci / api (push) Successful in 51s
ci / recommender (push) Canceled after 2s
ci / ts (packages + web) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (apps/web/Dockerfile, web, ) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, api, api) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/recommender/Dockerfile, recommender, ) (push) Canceled after 0s
ci / mobile (Metro-бандл Android) (push) Canceled after 0s
fix: address project review findings
Security and robustness:
- storage-agent rejects an empty INTERNAL_TOKEN (config notEmpty + middleware)
- api takes the client IP only from X-Real-IP (chi RealIP trusted True-Client-IP)
- /readyz reports ok/fail and logs the cause; checks run in parallel with a timeout
- storage-agent shuts down gracefully; shared httpx.NewServer/Serve with IdleTimeout
- goose migrations take a Postgres advisory lock
- recommender readyz: per-check timeout, redis socket timeouts

Clients:
- Android release allows cleartext HTTP for LAN servers (expo-build-properties)
- checkServer times out after 10s; connect button is disabled while checking

Deploy and CI:
- install.sh hands cache/uploads/updates/models to the non-root service users
- update.sh refuses downgrades unless PIPU_ALLOW_DOWNGRADE=1
- recommender/analyzer get only the env they need; required secrets fail fast
- nginx security headers and CSP; strip True-Client-IP
- dev compose binds ports to 127.0.0.1; postgres/redis images pinned
- version.mjs fails on a malformed explicit tag instead of releasing 0.0.0-dev
- contract check catches untracked generated files
- final releases fail without a release keystore; changelog skips rc tags

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

107 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Развёртывание
> Пошаговая инструкция «с нуля до работающего сервера на домене» — [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
├── cache/
│ ├── transcode/
│ └── covers/
├── postgres/
├── meili/
├── redis/
├── models/ # модели Essentia (скачиваются при первом старте analyzer)
├── updates/ # кэш APK релизов
├── uploads/ # staging очереди на добавление (размер ≈ лимит очереди, по умолчанию 10 ГБ)
├── backups/
└── caddy/ # сертификаты Let's Encrypt (если используется compose.tls.yaml)
```
Перед первым запуском: `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_FILE` | `/run/secrets/jwt_ed25519.pem` | Генерируется `install.sh` |
| `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).
## Бэкапы
- `pg_dump` ежедневно (cron-контейнер `backup`), хранить 14 дней + еженедельные 8 недель → `/srv/pipu/backups`.
- 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` (миграции пишутся обратно-совместимыми в пределах минорной версии: сначала добавить,
потом — в следующем релизе — удалить).