diff --git a/deploy/.env.example b/deploy/.env.example index 22546ac..417ef38 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -4,6 +4,9 @@ # PIPU_VERSION проставляет update.sh по git-тегу — вручную не менять PIPU_VERSION=dev PIPU_DATA=/srv/pipu +# Папка с музыкой на локальном диске сервера (источники type=local), например смонтированный диск. +# Пусто — $PIPU_DATA/library-host. NFS-хранилища сюда не относятся: их подключает админка. +LIBRARY_HOST= # --- Домен и HTTPS (docs/install-guide.md) --- # Вариант А: встроенный Caddy с Let's Encrypt — оставьте строку COMPOSE_FILE и заполните DOMAIN, ACME_EMAIL. diff --git a/deploy/compose.yaml b/deploy/compose.yaml index 114de9f..3e9953d 100644 --- a/deploy/compose.yaml +++ b/deploy/compose.yaml @@ -17,6 +17,14 @@ x-library-ro: &library-ro read_only: true bind: { propagation: rslave } +# Локальная музыка (источники type=local): каталог хоста, например смонтированный диск /mnt/music. +# Пишет в него только storage-agent (импорт одобренных загрузок), остальным — только чтение. +x-library-host-ro: &library-host-ro + type: bind + source: ${LIBRARY_HOST:-${PIPU_DATA:-/srv/pipu}/library-host} + target: /library-host + read_only: true + x-restart: &restart restart: unless-stopped @@ -31,6 +39,7 @@ services: ports: ["${HTTP_PORT:-80}:80"] volumes: - *library-ro + - *library-host-ro - ${PIPU_DATA:-/srv/pipu}/cache:/cache:ro - ${PIPU_DATA:-/srv/pipu}/uploads:/uploads:ro depends_on: [api] @@ -48,6 +57,7 @@ services: DATABASE_URL: postgres://pipu:${POSTGRES_PASSWORD:?заполните POSTGRES_PASSWORD в .env (sudo ./install.sh)}@postgres:5432/pipu?sslmode=disable volumes: - *library-ro + - *library-host-ro - ${PIPU_DATA:-/srv/pipu}/cache:/cache - ${PIPU_DATA:-/srv/pipu}/updates:/updates - ${PIPU_DATA:-/srv/pipu}/uploads:/uploads @@ -75,6 +85,9 @@ services: source: ${PIPU_DATA:-/srv/pipu}/library target: /library bind: { propagation: rshared } + - type: bind + source: ${LIBRARY_HOST:-${PIPU_DATA:-/srv/pipu}/library-host} + target: /library-host - ${PIPU_DATA:-/srv/pipu}/uploads:/uploads:ro recommender: @@ -102,6 +115,7 @@ services: LOG_LEVEL: ${LOG_LEVEL:-info} volumes: - *library-ro + - *library-host-ro - ${PIPU_DATA:-/srv/pipu}/models:/models deploy: { resources: { limits: { cpus: "4" } } } depends_on: diff --git a/deploy/install.sh b/deploy/install.sh index 639d2e3..824209f 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -17,6 +17,16 @@ mkdir -p "$DATA"/{library,library-host,cache/transcode,cache/covers,postgres,mei chown 65532:65532 "$DATA"/{cache,cache/transcode,cache/covers,updates,uploads} chown 10001:10001 "$DATA/models" +# Папка с локальной музыкой: sudo LIBRARY_HOST=/mnt/music ./install.sh (или LIBRARY_HOST в .env) +LIBRARY_HOST="${LIBRARY_HOST:-$( [ -f .env ] && sed -n 's/^LIBRARY_HOST=//p' .env || true )}" +if [ -n "$LIBRARY_HOST" ]; then + if [ ! -d "$LIBRARY_HOST" ]; then + echo "LIBRARY_HOST=$LIBRARY_HOST: каталога нет — смонтируйте диск с музыкой (docs/install-guide.md, шаг 4)" >&2 + exit 1 + fi + findmnt -n --target "$LIBRARY_HOST" -o TARGET,SOURCE | sed 's/^/→ музыка: /' +fi + # storage-agent монтирует NFS внутрь $DATA/library; чтобы точки монтирования видели другие контейнеры, # каталог должен быть отдельной точкой монтирования с shared propagation. if ! findmnt -n "$DATA/library" >/dev/null 2>&1; then @@ -50,10 +60,19 @@ secret() { head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 40; } if [ ! -f .env ]; then echo "→ .env из .env.example" cp .env.example .env - sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(secret)|; s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(secret)|; s|^INTERNAL_TOKEN=.*|INTERNAL_TOKEN=$(secret)|; s|^PIPU_DATA=.*|PIPU_DATA=$DATA|" .env + sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(secret)|; s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(secret)|; s|^INTERNAL_TOKEN=.*|INTERNAL_TOKEN=$(secret)|; s|^PIPU_DATA=.*|PIPU_DATA=$DATA|; s|^LIBRARY_HOST=.*|LIBRARY_HOST=$LIBRARY_HOST|" .env chmod 600 .env chown "$OWNER" .env echo " Заполните в .env: DOMAIN и ACME_EMAIL (или настройте свой прокси — docs/install-guide.md)" fi +# LIBRARY_HOST, переданный при повторном запуске, попадает и в уже существующий .env +if [ -n "$LIBRARY_HOST" ]; then + if grep -q '^LIBRARY_HOST=' .env; then + sed -i "s|^LIBRARY_HOST=.*|LIBRARY_HOST=$LIBRARY_HOST|" .env + else + echo "LIBRARY_HOST=$LIBRARY_HOST" >> .env + fi +fi + echo "Готово. Сборка и запуск последнего релиза: ./update.sh" diff --git a/deploy/nginx/default.conf.template b/deploy/nginx/default.conf.template index fe7317d..369f375 100644 --- a/deploy/nginx/default.conf.template +++ b/deploy/nginx/default.conf.template @@ -48,6 +48,7 @@ server { # Аудио и обложки отдаёт nginx по заголовку X-Accel-Redirect от api (docs/playback.md). location /_internal/library/ { internal; alias /library/; sendfile on; aio threads; } + location /_internal/library-host/ { internal; alias /library-host/; sendfile on; aio threads; } location /_internal/cache/ { internal; alias /cache/transcode/; sendfile on; } location /_internal/covers/ { internal; alias /cache/covers/; expires 30d; } location /_internal/uploads/ { internal; alias /uploads/; sendfile on; } diff --git a/docs/deployment.md b/docs/deployment.md index 3077071..182269f 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -15,7 +15,7 @@ ``` /srv/pipu/ ├── library/ # точки монтирования источников (storage-agent, rshared) -├── library-host/ # опционально: заранее смонтированные папки для type=local +├── library-host/ # папка для type=local по умолчанию; обычно вместо неё LIBRARY_HOST=/mnt/music ├── cache/ │ ├── transcode/ │ └── covers/ @@ -29,6 +29,10 @@ └── caddy/ # сертификаты Let's Encrypt (если используется compose.tls.yaml) ``` +Музыка на локальном диске сервера подключается через `LIBRARY_HOST` в `.env` (например, `/mnt/music`): +каталог хоста монтируется в `api`, `nginx` и `analyzer` только на чтение, в `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 для повторения после перезагрузки). diff --git a/docs/install-guide.md b/docs/install-guide.md index d297987..383ab9e 100644 --- a/docs/install-guide.md +++ b/docs/install-guide.md @@ -6,8 +6,9 @@ В примерах: - домен — `music.example.com`, замените на свой; -- репозиторий — `gitea.hsrv.site/mrleo1nid/pipu-music`; -- каталог установки — `/opt/pipu-music`. +- репозиторий — `gitea.hsrv.site/mrleo1nid/pipu-music`, клонируется в `/opt/pipu-music`; +- диск с музыкой смонтирован в `/mnt/music`; +- данные сервиса (БД, кэши, сертификаты) — в `/srv/pipu`. Готовые Docker-образы не публикуются: сервер клонирует репозиторий и собирает образы сам (`docker compose build`). Релизы в Gitea содержат только APK для Android. @@ -22,11 +23,11 @@ | Что | Требование | |---|---| -| Сервер | Linux x86_64 (Debian 12 / Ubuntu 22.04+), 4+ ядра, 8+ ГБ RAM, 30+ ГБ на диске (система, образы, кэш сборки, данные) | +| Сервер | Linux x86_64 (Debian 12 / Ubuntu 22.04+), 4+ ядра, 8+ ГБ RAM, 30+ ГБ на системном диске (система, образы, кэш сборки, данные) | | Доступ | root или пользователь с `sudo` | | Домен | Поддомен, которым вы управляете, например `music.example.com` | | Сеть | Входящие **80/tcp** и **443/tcp** (+ **443/udp** для HTTP/3) из интернета на сервер | -| Музыка | NFS-сервер с библиотекой (подключается позже из админки) | +| Музыка | Отдельный диск с библиотекой, подключённый к серверу (шаг 4). NFS-хранилища можно добавить позже из админки | | Интернет на сервере | Нужен при сборке: скачиваются базовые образы и зависимости Go / npm / Python | | Gitea | Доступ к репозиторию на чтение (если он приватный — токен или SSH-ключ) | @@ -50,7 +51,7 @@ dig +short music.example.com ``` > Сертификат Let's Encrypt выдаётся, только когда домен уже указывает на сервер и порт 80 доступен из интернета. -> Не запускайте шаг 6, пока эта проверка не проходит. +> Не запускайте шаг 7, пока эта проверка не проходит. --- @@ -86,7 +87,51 @@ sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw allow 443/udp --- -## 4. Клонирование репозитория +## 4. Диск с музыкой + +Музыка лежит на отдельном диске, смонтированном в `/mnt/music`. Если диск уже смонтирован — проверьте его +командой из конца шага и переходите к шагу 5. + +Найдите диск и его UUID (раздел с музыкой — например, `/dev/sdb1`): + +```bash +lsblk -f +``` + +Создайте точку монтирования: + +```bash +sudo mkdir -p /mnt/music +``` + +Добавьте в `/etc/fstab` строку (подставьте свой UUID и файловую систему из вывода `lsblk -f`): + +```ini +UUID=1234abcd-0000-0000-0000-000000000000 /mnt/music ext4 defaults,nofail,noatime 0 2 +``` + +`nofail` нужен, чтобы сервер загружался, даже если диск не подключён. Смонтируйте и проверьте: + +```bash +sudo mount -a && findmnt /mnt/music && ls /mnt/music | head +``` + +Сервисы Pipu работают в контейнерах не от root, поэтому музыка должна быть доступна на чтение всем: +файлы — `644`, каталоги — `755`. Если раньше права были закрытыми: + +```bash +sudo chmod -R a+rX /mnt/music +``` + +> Для дисков с NTFS / exFAT права задаются опциями монтирования, а не `chmod`: добавьте в fstab +> `uid=0,gid=0,umask=022` вместо `defaults`. + +Pipu только читает этот диск. Записывать в него будет лишь импорт одобренных треков из очереди на добавление, +и только если в админке разрешить это для хранилища ([uploads.md](uploads.md)). + +--- + +## 5. Клонирование репозитория ```bash sudo mkdir -p /opt/pipu-music && sudo chown "$USER" /opt/pipu-music @@ -104,16 +149,18 @@ git clone https://gitea.hsrv.site/mrleo1nid/pipu-music.git /opt/pipu-music && cd --- -## 5. Подготовка сервера +## 6. Подготовка сервера ```bash -sudo ./install.sh +sudo LIBRARY_HOST=/mnt/music ./install.sh ``` Скрипт: - создаёт каталоги данных в `/srv/pipu` (БД, кэши, загрузки, сертификаты); - делает `/srv/pipu/library` точкой монтирования с shared propagation, чтобы NFS-хранилища, которые потом подключит админ, были видны всем контейнерам, и ставит systemd-юнит, повторяющий это после перезагрузки; +- проверяет, что `/mnt/music` существует, и записывает его в `.env` как `LIBRARY_HOST` — эту папку получат + контейнеры `api`, `nginx` и `analyzer` (внутри они видят её как `/library-host`); - создаёт `.env` со случайными паролями БД, ключом поиска и межсервисным токеном. Заполните в `/opt/pipu-music/deploy/.env`: @@ -124,6 +171,14 @@ DOMAIN=music.example.com ACME_EMAIL=you@example.com ``` +Проверьте, что там же стоит путь к музыке (его вписал `install.sh`): + +```ini +LIBRARY_HOST=/mnt/music +``` + +Если диск смонтирован в другое место — поправьте путь здесь и перезапустите сервисы (`./update.sh`). + `ACME_EMAIL` — адрес, на который Let's Encrypt пишет, если с сертификатом что-то не так. Остальные значения менять не нужно; `PIPU_VERSION` проставляет `update.sh`. @@ -131,7 +186,7 @@ ACME_EMAIL=you@example.com --- -## 6. Сборка и запуск +## 7. Сборка и запуск ```bash ./update.sh @@ -161,7 +216,7 @@ curl -s https://music.example.com/readyz --- -## 7. Веб-клиент +## 8. Веб-клиент Откройте `https://music.example.com` в браузере. @@ -176,12 +231,14 @@ curl -s https://music.example.com/readyz Сразу после входа приложение попросит сменить логин и пароль администратора ([auth-and-roles.md](auth-and-roles.md#первый-запуск-bootstrap)). Чтобы задать пароль заранее, впишите `BOOTSTRAP_ADMIN_PASSWORD` в `.env` **до** первого запуска. -- **С версии 0.3 (M2):** Админка → Хранилища → «Добавить хранилище» → адрес NFS-сервера и путь экспорта - → «Проверить» → шаблоны путей → «Сохранить и пересканировать» ([library-and-storage.md](library-and-storage.md)). +- **С версии 0.3 (M2):** Админка → Хранилища → «Добавить хранилище» → тип «Локальная папка» → выберите + подпапку на диске `/mnt/music` (в списке она видна как `/library-host/...`) → «Проверить» → шаблоны путей + → «Сохранить и пересканировать» ([library-and-storage.md](library-and-storage.md)). + Там же можно добавить NFS-хранилище: адрес сервера и путь экспорта. --- -## 8. Android-приложение +## 9. Android-приложение 1. На телефоне откройте страницу релизов `https://gitea.hsrv.site/mrleo1nid/pipu-music/releases` и скачайте `pipu-music-<версия>.apk`. @@ -201,7 +258,7 @@ curl -s https://music.example.com/readyz --- -## 9. Вариант: свой reverse proxy вместо Caddy +## 10. Вариант: свой reverse proxy вместо Caddy Если на сервере уже есть Traefik, Nginx Proxy Manager или свой nginx с сертификатами: @@ -238,14 +295,14 @@ curl -s https://music.example.com/readyz } ``` -3. Запуск и проверки — как в шагах 6–8. +3. Запуск и проверки — как в шагах 7–9. Если прокси работает на другой машине, укажите в `HTTP_PORT` внутренний адрес сервера (`192.168.1.10:8081`) и закройте этот порт от интернета файрволом. --- -## 10. Обновление, бэкапы, удаление +## 11. Обновление, бэкапы, удаление Обновление до последнего релиза: @@ -271,7 +328,7 @@ cd /opt/pipu-music/deploy && docker compose down --- -## 11. Если что-то не работает +## 12. Если что-то не работает | Симптом | Что проверить | |---|---| @@ -282,6 +339,8 @@ cd /opt/pipu-music/deploy && docker compose down | Сборка падает на скачивании зависимостей | Нет интернета или DNS на сервере; повторите `./update.sh` — уже собранные слои берутся из кэша | | Сборка падает: «no space left on device» | Очистите кэш сборки: `docker builder prune` | | `permission denied … docker.sock` | Пользователь не в группе `docker` (шаг 3) или вы не перелогинились | +| Сервисы не стартуют: `bind source path does not exist: /mnt/music` | Диск не смонтирован: `findmnt /mnt/music`, `sudo mount -a`; путь в `LIBRARY_HOST` в `.env` | +| Хранилище «Локальная папка» пустое или ошибка доступа | Диск смонтирован (`ls /mnt/music`); права на чтение для всех — `sudo chmod -R a+rX /mnt/music` (шаг 4) | | `storage-agent` не стартует, ошибка про propagation / shared mount | Ещё раз `sudo ./install.sh`; на хосте `findmnt -o TARGET,PROPAGATION /srv/pipu/library` должно показать `shared` | | Android: «Сервер не отвечает» | Тот же адрес открывается в браузере телефона; в адресе нет опечатки; телефон в сети с доступом к серверу | | Android: «Эта версия приложения слишком старая» | Установите APK из последнего релиза | diff --git a/docs/library-and-storage.md b/docs/library-and-storage.md index 25f4ce2..69cd4af 100644 --- a/docs/library-and-storage.md +++ b/docs/library-and-storage.md @@ -7,7 +7,7 @@ | Тип | Как подключается | |---|---| | `nfs` | Админ указывает `server`, `export`, опции. `storage-agent` выполняет `mount -t nfs -o server:export /library/`. | -| `local` | Папка, уже смонтированная на хосте в `/srv/pipu/library-host` (например, через fstab). Админ выбирает подпапку. Нужен как запасной путь и для dev. | +| `local` | Папка на диске сервера, заранее смонтированная на хосте (например, диск `/mnt/music` через fstab) и указанная в `LIBRARY_HOST`. В контейнерах видна как `/library-host`; админ выбирает подпапку. | ### Монтирование (storage-agent)