ci / api / api (push) Successful in 22s
ci / db / db (push) Successful in 2m4s
release / release (версия и заметки) (push) Successful in 5s
release / android (APK в релиз) (push) Successful in 4m11s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m33s
The server kept the git.example.lan placeholder from .env.example, so the /android page and in-app updates had no release. install.sh and update.sh now fill GITEA_URL and GITEA_REPO from the clone's origin (https, ssh and scp forms, credentials stripped) when they are empty or still the placeholder, and the API treats the placeholder as not configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
366 lines
20 KiB
Markdown
366 lines
20 KiB
Markdown
# Установка сервера и подключение приложений
|
||
|
||
Пошаговая инструкция: от чистого Linux-сервера до работающего Pipu Music на своём домене
|
||
с HTTPS, плюс подключение веб- и Android-клиента. Справочник по устройству развёртывания —
|
||
[deployment.md](deployment.md).
|
||
|
||
В примерах:
|
||
- домен — `music.example.com`, замените на свой;
|
||
- репозиторий — `gitea.hsrv.site/mrleo1nid/pipu-music`, клонируется в `/opt/pipu-music`;
|
||
- диск с музыкой смонтирован в `/mnt/music`;
|
||
- данные сервиса (БД, кэши, сертификаты) — в `/srv/pipu`.
|
||
|
||
Готовые Docker-образы не публикуются: сервер клонирует репозиторий и собирает образы сам
|
||
(`docker compose build`). Релизы в Gitea содержат только APK для Android.
|
||
|
||
> **Что умеет текущая версия.** В `v0.1.0` (этап M0) работают развёртывание, HTTPS и проверка связи
|
||
> клиентов с сервером. Вход, библиотека и плеер появятся в следующих версиях
|
||
> (см. [roadmap.md](roadmap.md)). Шаги, которые зависят от версии, помечены.
|
||
|
||
---
|
||
|
||
## 1. Что нужно заранее
|
||
|
||
| Что | Требование |
|
||
|---|---|
|
||
| Сервер | 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) из интернета на сервер |
|
||
| Музыка | Отдельный диск с библиотекой, подключённый к серверу (шаг 4). NFS-хранилища можно добавить позже из админки |
|
||
| Интернет на сервере | Нужен при сборке: скачиваются базовые образы и зависимости Go / npm / Python |
|
||
| Gitea | Доступ к репозиторию на чтение (если он приватный — токен или SSH-ключ) |
|
||
|
||
---
|
||
|
||
## 2. DNS
|
||
|
||
Создайте у регистратора или в DNS-панели запись:
|
||
|
||
| Тип | Имя | Значение |
|
||
|---|---|---|
|
||
| `A` | `music` | публичный IPv4 сервера |
|
||
| `AAAA` | `music` | публичный IPv6 (если есть; если нет — запись не создавайте) |
|
||
|
||
Если сервер стоит дома за роутером, пробросьте на роутере порты 80 и 443 на внутренний IP сервера.
|
||
|
||
Проверка — команда должна вернуть IP вашего сервера:
|
||
|
||
```bash
|
||
dig +short music.example.com
|
||
```
|
||
|
||
> Сертификат Let's Encrypt выдаётся, только когда домен уже указывает на сервер и порт 80 доступен из интернета.
|
||
> Не запускайте шаг 7, пока эта проверка не проходит.
|
||
|
||
---
|
||
|
||
## 3. Docker
|
||
|
||
```bash
|
||
curl -fsSL https://get.docker.com | sudo sh
|
||
```
|
||
|
||
Добавьте своего пользователя в группу `docker`, чтобы запускать сборку без `sudo` (после этого перелогиньтесь):
|
||
|
||
```bash
|
||
sudo usermod -aG docker "$USER"
|
||
```
|
||
|
||
И `git`, если его нет:
|
||
|
||
```bash
|
||
sudo apt-get install -y git
|
||
```
|
||
|
||
Проверьте, что есть Docker Compose v2.24 или новее (нужен для `compose.tls.yaml`):
|
||
|
||
```bash
|
||
docker compose version
|
||
```
|
||
|
||
Откройте порты в файрволе, если он включён (пример для `ufw`):
|
||
|
||
```bash
|
||
sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw allow 443/udp
|
||
```
|
||
|
||
---
|
||
|
||
## 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
|
||
```
|
||
|
||
```bash
|
||
git clone https://gitea.hsrv.site/mrleo1nid/pipu-music.git /opt/pipu-music && cd /opt/pipu-music/deploy
|
||
```
|
||
|
||
> Если репозиторий приватный: клонируйте по SSH (`git@gitea.hsrv.site:mrleo1nid/pipu-music.git`) с ключом,
|
||
> добавленным в Gitea, или по HTTPS с токеном (право `read:repository`) — Git спросит логин и токен вместо пароля.
|
||
> Сохраните доступ (`git config credential.helper store` или SSH-ключ): `update.sh` делает `git fetch` при каждом обновлении.
|
||
|
||
Все дальнейшие команды выполняются из `/opt/pipu-music/deploy`.
|
||
|
||
---
|
||
|
||
## 6. Подготовка сервера
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```ini
|
||
COMPOSE_FILE=compose.yaml:compose.tls.yaml
|
||
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`.
|
||
|
||
`.env` не хранится в git (он в `.gitignore`), поэтому обновления репозитория его не трогают.
|
||
|
||
---
|
||
|
||
## 7. Сборка и запуск
|
||
|
||
```bash
|
||
./update.sh
|
||
```
|
||
|
||
Скрипт переключает репозиторий на последний релизный тег (например, `v0.1.1`), собирает образы, запускает
|
||
все сервисы и ждёт готовности API (до 2 минут). Первая сборка занимает 5–15 минут, следующие — быстрее
|
||
за счёт кэша Docker. Конкретная версия: `./update.sh v0.1.1`.
|
||
|
||
> Локальная сборка поддерживается начиная с `v0.1.1`: в теге `v0.1.0` `compose.yaml` ещё ссылается на реестр образов.
|
||
> Пока `v0.1.1` не вышел, запускайте `./update.sh main`.
|
||
При первом запуске Caddy сам получит сертификат для `DOMAIN`, обычно за 10–30 секунд.
|
||
|
||
Проверка с любого компьютера:
|
||
|
||
```bash
|
||
curl -s https://music.example.com/api/v1/server-info
|
||
```
|
||
|
||
Ожидаемый ответ: `{"name":"pipu-music","version":"0.1.0",...}`. Проверка всех зависимостей:
|
||
|
||
```bash
|
||
curl -s https://music.example.com/readyz
|
||
```
|
||
|
||
Должно вернуться `"status":"ok"` и `ok` для `postgres` и `redis`.
|
||
|
||
---
|
||
|
||
## 8. Веб-клиент
|
||
|
||
Откройте `https://music.example.com` в браузере.
|
||
|
||
- **v0.1.0:** карточка «Сервер 0.1.0 на связи» и переключатель языка RU / EN.
|
||
- **С версии 0.2 (M1):** экран входа. Первый вход — логин `admin` и пароль, который сервер один раз
|
||
выводит в лог при первом запуске:
|
||
|
||
```bash
|
||
docker compose logs api | grep -i bootstrap
|
||
```
|
||
|
||
Сразу после входа приложение попросит сменить логин и пароль администратора
|
||
([auth-and-roles.md](auth-and-roles.md#первый-запуск-bootstrap)). Чтобы задать пароль заранее, впишите
|
||
`BOOTSTRAP_ADMIN_PASSWORD` в `.env` **до** первого запуска.
|
||
- **С версии 0.3 (M2):** Админка → Хранилища → «Добавить хранилище» → тип «Локальная папка» → выберите
|
||
подпапку на диске `/mnt/music` (в списке она видна как `/library-host/...`) → «Проверить» → шаблоны путей
|
||
→ «Сохранить и пересканировать» ([library-and-storage.md](library-and-storage.md)).
|
||
Там же можно добавить NFS-хранилище: адрес сервера и путь экспорта.
|
||
|
||
---
|
||
|
||
## 9. Android-приложение
|
||
|
||
1. На телефоне откройте страницу релизов
|
||
`https://gitea.hsrv.site/mrleo1nid/pipu-music/releases` и скачайте `pipu-music-<версия>.apk`.
|
||
2. Откройте файл. Android попросит разрешить установку из этого источника (браузера или файлового менеджера) —
|
||
разрешите и вернитесь к установке.
|
||
3. Запустите **Pipu Music**, на экране подключения введите адрес сервера: `music.example.com`
|
||
(`https://` подставится сам) → «Проверить».
|
||
4. Ожидаемый результат — «Сервер 0.1.0 на связи». Если версия приложения слишком старая для сервера,
|
||
приложение попросит обновиться.
|
||
|
||
Дальше приложение обновляется само: с версии M5 оно проверяет новые релизы через ваш сервер
|
||
и предлагает обновиться ([mobile.md](mobile.md#самообновление)). Адрес Gitea и репозиторий (`GITEA_URL`, `GITEA_REPO`)
|
||
`install.sh` и `update.sh` берут из `git remote` клона; `GITEA_TOKEN` (только на чтение) нужен, если репозиторий
|
||
закрытый. Если сервер не видит Gitea по внешнему адресу — впишите внутренний в `GITEA_URL`. Раздать приложение —
|
||
ссылка `https://<ваш-домен>/android`.
|
||
|
||
> Приложению нужен настоящий сертификат. С самоподписанным сертификатом и голым IP-адресом подключиться не получится.
|
||
> Поэтому нужен домен с Let's Encrypt, как в этой инструкции.
|
||
|
||
---
|
||
|
||
## 10. Вариант: свой reverse proxy вместо Caddy
|
||
|
||
Если на сервере уже есть Traefik, Nginx Proxy Manager или свой nginx с сертификатами:
|
||
|
||
1. В `.env` закомментируйте `COMPOSE_FILE` и задайте порт, на котором Pipu будет слушать только локально:
|
||
|
||
```ini
|
||
#COMPOSE_FILE=compose.yaml:compose.tls.yaml
|
||
HTTP_PORT=127.0.0.1:8081
|
||
```
|
||
|
||
2. Направьте прокси для `music.example.com` на `http://127.0.0.1:8081` с такими настройками:
|
||
- передавать заголовки `Host`, `X-Forwarded-For`, `X-Forwarded-Proto`;
|
||
- лимит тела запроса не меньше **16 МБ** (загрузка треков идёт чанками по 8 МБ);
|
||
- **без буферизации ответов** (стриминг аудио), длинные таймауты чтения (≥ 1 ч).
|
||
|
||
Пример для nginx:
|
||
|
||
```nginx
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name music.example.com;
|
||
# ssl_certificate / ssl_certificate_key — как у остальных ваших сайтов
|
||
|
||
client_max_body_size 16m;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8081;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_buffering off;
|
||
proxy_read_timeout 1h;
|
||
}
|
||
}
|
||
```
|
||
|
||
3. Запуск и проверки — как в шагах 7–9.
|
||
|
||
Если прокси работает на другой машине, укажите в `HTTP_PORT` внутренний адрес сервера (`192.168.1.10:8081`) и
|
||
закройте этот порт от интернета файрволом.
|
||
|
||
---
|
||
|
||
## 11. Обновление, бэкапы, удаление
|
||
|
||
Обновление до последнего релиза:
|
||
|
||
```bash
|
||
cd /opt/pipu-music/deploy && ./update.sh
|
||
```
|
||
|
||
Конкретная версия или откат: `./update.sh v0.2.0`. Скрипт делает `git fetch`, переключается на тег,
|
||
пересобирает образы и перезапускает сервисы; старые образы удаляются. Миграции БД применяются
|
||
автоматически при старте API.
|
||
|
||
Бэкапы базы делаются сами: каждую ночь в `/srv/pipu/backups`, раз в неделю копия проверяется восстановлением,
|
||
перед каждым `update.sh` — внеочередная копия. Всё остальное восстанавливается само — см.
|
||
[deployment.md](deployment.md#бэкапы). Копия сейчас и восстановление:
|
||
|
||
```bash
|
||
cd /opt/pipu-music/deploy && docker compose run --rm backup once
|
||
```
|
||
|
||
```bash
|
||
cd /opt/pipu-music/deploy && ./restore.sh
|
||
```
|
||
|
||
Без аргумента `restore.sh` показывает список копий; с именем файла (`./restore.sh daily/pipu-20261020-033000.dump`)
|
||
восстанавливает базу, сохранив прежнюю рядом.
|
||
|
||
Остановить сервис, сохранив данные:
|
||
|
||
```bash
|
||
cd /opt/pipu-music/deploy && docker compose down
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Если что-то не работает
|
||
|
||
| Симптом | Что проверить |
|
||
|---|---|
|
||
| Сертификат не выдаётся, браузер ругается на HTTPS | `dig +short music.example.com` указывает на сервер; порт 80 открыт снаружи; `docker compose logs caddy` |
|
||
| `502 Bad Gateway` | API ещё запускается или упал: `docker compose ps`, `docker compose logs api` |
|
||
| `/readyz` отвечает `degraded` | Какая зависимость не `ok` — её логи: `docker compose logs postgres` / `redis` |
|
||
| `update.sh`: ошибка `git fetch` | Нет доступа к репозиторию: проверьте SSH-ключ или сохранённый токен (`git fetch` вручную) |
|
||
| Сборка падает на скачивании зависимостей | Нет интернета или DNS на сервере; повторите `./update.sh` — уже собранные слои берутся из кэша |
|
||
| Сборка падает: «no space left on device» | Очистите кэш сборки: `docker builder prune` |
|
||
| `permission denied … docker.sock` | Пользователь не в группе `docker` (шаг 3) или вы не перелогинились |
|
||
| Сервисы не стартуют: `… is not a shared or slave mount` | `sudo mount --make-rshared /` (и добавьте в автозагрузку); на systemd-системах корень и так shared |
|
||
| `update.sh`: «LIBRARY_HOST … пуст» | Диск с музыкой не смонтирован: `findmnt /mnt/music`, `sudo mount -a`; если папка и должна быть пустой — `PIPU_ALLOW_EMPTY_LIBRARY=1 ./update.sh` |
|
||
| Сервисы не стартуют: `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 из последнего релиза |
|
||
|
||
Логи всех сервисов сразу:
|
||
|
||
```bash
|
||
cd /opt/pipu-music/deploy && docker compose logs -f --tail=100
|
||
```
|