From 01319c58d7e32acf47be49984cc35148f9bc3ebf Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Tue, 20 Oct 2026 00:16:05 +0300 Subject: [PATCH] build(deploy): build images on the server instead of publishing to a registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy/compose.yaml: every app service has `build:` (repo root context), local tags pipu-music-: - deploy/update.sh: git fetch → checkout tag (latest release by default, or vX.Y.Z / main) → docker compose build --pull → up -d → prune → /readyz; re-executes from a temp copy because checkout may replace the script - install.sh / .env.example: no REGISTRY, PIPU_VERSION set by update.sh, .env owned by the invoking user - release.yml: drop image publishing; release ships APK + sha256 only - ci.yml: new `docker` job builds all images without push - docs: install guide uses git clone, deployment/ci-cd updated, ADR-013 Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/ci.yml | 21 ++++++++ .gitea/workflows/release.yml | 49 ++----------------- README.md | 5 +- deploy/.env.example | 7 ++- deploy/compose.yaml | 38 ++++++++++++--- deploy/install.sh | 8 ++- deploy/update.sh | 42 ++++++++++++++-- docs/ci-cd.md | 22 ++++----- docs/decisions.md | 15 ++++++ docs/deployment.md | 94 +++++++++--------------------------- docs/install-guide.md | 86 +++++++++++++++++++-------------- 11 files changed, 201 insertions(+), 186 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index c76325c..ddb5a2a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -73,6 +73,27 @@ jobs: - run: pnpm test - run: pnpm --filter @pipu/web build + docker: + name: docker (образы собираются — их сервер собирает сам при обновлении) + runs-on: ubuntu-latest + strategy: + matrix: + include: + - { name: api, dockerfile: services/api/Dockerfile, target: api } + - { name: storage-agent, dockerfile: services/api/Dockerfile, target: storage-agent } + - { name: recommender, dockerfile: services/recommender/Dockerfile, target: "" } + - { name: web, dockerfile: apps/web/Dockerfile, target: "" } + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + target: ${{ matrix.target }} + push: false + build-args: VERSION=ci + mobile: name: mobile (Metro-бандл Android) runs-on: ubuntu-latest diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index c3b0439..64bff03 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,10 +1,7 @@ -# Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N] (docs/ci-cd.md): -# проверки → Docker-образы в Gitea Container Registry → подписанный APK → Gitea Release с ассетами. +# Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N] (docs/ci-cd.md): проверки → подписанный APK → Gitea Release. +# Docker-образы не публикуются: сервер собирает их сам из клонированного репозитория (deploy/update.sh). # -# Переменные репозитория (Settings → Actions → Variables): -# REGISTRY — хост реестра, например git.home.lan (по умолчанию — хост сервера Gitea) # Секреты (Settings → Actions → Secrets): -# REGISTRY_USER, REGISTRY_TOKEN — push образов (scope: write:package) # RELEASE_TOKEN — создание релиза и загрузка ассетов (scope: write:repository) # ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD — подпись APK name: release @@ -45,47 +42,9 @@ jobs: - name: TypeScript run: pnpm i18n:check && pnpm test:scripts && pnpm typecheck && pnpm test - images: - needs: verify - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - include: - - { name: api, dockerfile: services/api/Dockerfile, target: api } - - { name: storage-agent, dockerfile: services/api/Dockerfile, target: storage-agent } - - { name: recommender, dockerfile: services/recommender/Dockerfile, target: "" } - - { name: web, dockerfile: apps/web/Dockerfile, target: "" } - steps: - - uses: actions/checkout@v4 - - name: Имя образа - id: img - run: | - registry="${{ vars.REGISTRY }}" - [ -n "$registry" ] || registry="$(echo '${{ github.server_url }}' | sed -E 's#^https?://##')" - owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" - echo "registry=$registry" >> "$GITHUB_OUTPUT" - echo "image=$registry/$owner/pipu-music-${{ matrix.name }}" >> "$GITHUB_OUTPUT" - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 - with: - registry: ${{ steps.img.outputs.registry }} - username: ${{ secrets.REGISTRY_USER }} - password: ${{ secrets.REGISTRY_TOKEN }} - - uses: docker/build-push-action@v6 - with: - context: . - file: ${{ matrix.dockerfile }} - target: ${{ matrix.target }} - push: true - build-args: VERSION=${{ needs.verify.outputs.version }} - tags: | - ${{ steps.img.outputs.image }}:${{ needs.verify.outputs.version }} - ${{ needs.verify.outputs.prerelease == 'false' && format('{0}:latest', steps.img.outputs.image) || '' }} - android-release: name: android + release - needs: [verify, images] + needs: verify runs-on: ubuntu-latest env: PIPU_VERSION: ${{ needs.verify.outputs.version }} @@ -159,7 +118,7 @@ jobs: '{tag_name:$tag, name:$name, body:$notes, prerelease:$pre, draft:false}')" id="$(curl -fsS -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \ -d "$body" "$API/releases" | jq -r .id)" - for f in "$APK" "$APK.sha256" deploy/compose.yaml deploy/compose.tls.yaml deploy/Caddyfile deploy/.env.example deploy/install.sh deploy/update.sh; do + for f in "$APK" "$APK.sha256"; do curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@$f" "$API/releases/$id/assets?name=$(basename "$f")" > /dev/null echo "uploaded $(basename "$f")" diff --git a/README.md b/README.md index a88e830..d44f2c4 100644 --- a/README.md +++ b/README.md @@ -65,5 +65,6 @@ pnpm generate && (cd services/api && go generate ./...) ## Релиз -Тег `vX.Y.Z` (или `vX.Y.Z-rc.N`) запускает `.gitea/workflows/release.yml`: проверки → образы в Gitea Container Registry → -подписанный APK → Gitea Release. Нужные переменные и секреты перечислены в шапке workflow и в [docs/ci-cd.md](docs/ci-cd.md). +Тег `vX.Y.Z` (или `vX.Y.Z-rc.N`) запускает `.gitea/workflows/release.yml`: проверки → подписанный APK → Gitea Release. +Docker-образы не публикуются: сервер клонирует репозиторий и собирает их сам (`deploy/update.sh`, +[docs/install-guide.md](docs/install-guide.md)). Нужные секреты — в шапке workflow и в [docs/ci-cd.md](docs/ci-cd.md). diff --git a/deploy/.env.example b/deploy/.env.example index 8ab0df3..22546ac 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -1,9 +1,8 @@ # Скопируйте в .env (install.sh делает это сам и генерирует секреты). Описание — docs/deployment.md. -# --- Образы --- -# Реестр Gitea: <хост>/<владелец>, например gitea.hsrv.site/mrleo1nid -REGISTRY=git.example.lan/owner -PIPU_VERSION=0.1.0 +# --- Версия и данные --- +# PIPU_VERSION проставляет update.sh по git-тегу — вручную не менять +PIPU_VERSION=dev PIPU_DATA=/srv/pipu # --- Домен и HTTPS (docs/install-guide.md) --- diff --git a/deploy/compose.yaml b/deploy/compose.yaml index ab0996a..2fbdea7 100644 --- a/deploy/compose.yaml +++ b/deploy/compose.yaml @@ -1,7 +1,15 @@ -# Продакшен-развёртывание (docs/deployment.md). Перед первым запуском: sudo ./install.sh -# Обновление: ./update.sh +# Продакшен-развёртывание (docs/install-guide.md). Образы собираются на сервере из клонированного репозитория. +# Перед первым запуском: sudo ./install.sh. Установка / обновление: ./update.sh [vX.Y.Z] name: pipu +x-version: &version + VERSION: ${PIPU_VERSION:-0.0.0-dev} + +x-build-rec: &build-rec + context: .. + dockerfile: services/recommender/Dockerfile + args: *version + x-library-ro: &library-ro type: bind source: ${PIPU_DATA:-/srv/pipu}/library @@ -15,7 +23,11 @@ x-restart: &restart services: nginx: <<: *restart - image: ${REGISTRY}/pipu-music-web:${PIPU_VERSION} + image: pipu-music-web:${PIPU_VERSION:-dev} + build: + context: .. + dockerfile: apps/web/Dockerfile + args: *version ports: ["${HTTP_PORT:-80}:80"] volumes: - *library-ro @@ -25,7 +37,12 @@ services: api: <<: *restart - image: ${REGISTRY}/pipu-music-api:${PIPU_VERSION} + image: pipu-music-api:${PIPU_VERSION:-dev} + build: + context: .. + dockerfile: services/api/Dockerfile + target: api + args: *version env_file: .env environment: &db-env DATABASE_URL: postgres://pipu:${POSTGRES_PASSWORD}@postgres:5432/pipu?sslmode=disable @@ -41,7 +58,12 @@ services: storage-agent: <<: *restart - image: ${REGISTRY}/pipu-music-storage-agent:${PIPU_VERSION} + image: pipu-music-storage-agent:${PIPU_VERSION:-dev} + build: + context: .. + dockerfile: services/api/Dockerfile + target: storage-agent + args: *version cap_add: [SYS_ADMIN] security_opt: ["apparmor:unconfined"] environment: @@ -56,7 +78,8 @@ services: recommender: <<: *restart - image: ${REGISTRY}/pipu-music-recommender:${PIPU_VERSION} + image: pipu-music-recommender:${PIPU_VERSION:-dev} + build: *build-rec command: ["serve"] env_file: .env environment: *db-env @@ -65,7 +88,8 @@ services: analyzer: <<: *restart - image: ${REGISTRY}/pipu-music-recommender:${PIPU_VERSION} + image: pipu-music-recommender:${PIPU_VERSION:-dev} + build: *build-rec command: ["analyze", "--workers", "4"] env_file: .env environment: *db-env diff --git a/deploy/install.sh b/deploy/install.sh index c36f4f8..d281830 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -4,6 +4,9 @@ set -euo pipefail cd "$(dirname "$0")" +# .env и каталоги создаются от root, но сам репозиторий должен остаться у пользователя, который делает git pull. +OWNER="${SUDO_USER:-root}" + DATA="${PIPU_DATA:-/srv/pipu}" echo "→ каталоги в $DATA" @@ -44,7 +47,8 @@ if [ ! -f .env ]; then 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 chmod 600 .env - echo " Заполните в .env: REGISTRY, PIPU_VERSION, DOMAIN, ACME_EMAIL" + chown "$OWNER" .env + echo " Заполните в .env: DOMAIN и ACME_EMAIL (или настройте свой прокси — docs/install-guide.md)" fi -echo "Готово. Запуск: ./update.sh \$(grep ^PIPU_VERSION= .env | cut -d= -f2)" +echo "Готово. Сборка и запуск последнего релиза: ./update.sh" diff --git a/deploy/update.sh b/deploy/update.sh index d615482..97e0369 100644 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -1,13 +1,47 @@ #!/usr/bin/env bash -# Обновление / откат на указанную версию: ./update.sh 1.3.0 +# Установка / обновление / откат из клонированного репозитория: образы собираются локально. +# ./update.sh — последний релизный тег (без -rc) +# ./update.sh v0.2.0 — конкретная версия (можно без «v») +# ./update.sh main — текущая ветка main (для тестов, не для продакшена) set -euo pipefail -cd "$(dirname "$0")" -VERSION="${1:?usage: $0 }" +# git checkout может заменить этот файл прямо во время работы — выполняемся из временной копии. +if [ -z "${PIPU_UPDATE_REEXEC:-}" ]; then + tmp="$(mktemp)" + cp "$0" "$tmp" + PIPU_UPDATE_REEXEC=1 PIPU_DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)" exec bash "$tmp" "$@" +fi +trap 'rm -f "$0"' EXIT +cd "$PIPU_DEPLOY_DIR" + +[ -f .env ] || { echo "нет .env — сначала sudo ./install.sh" >&2; exit 1; } + +REF="${1:-}" +git fetch --tags --prune --quiet origin + +if [ -z "$REF" ]; then + REF="$(git tag -l 'v*' --sort=-v:refname | grep -v -- '-rc' | head -1)" + [ -n "$REF" ] || { echo "в репозитории нет релизных тегов; укажите версию или main" >&2; exit 1; } +elif [[ "$REF" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + REF="v$REF" +fi + +echo "→ переключаемся на $REF" +if git show-ref --verify --quiet "refs/remotes/origin/$REF"; then + git checkout --quiet "$REF" + git merge --ff-only --quiet "origin/$REF" +else + git checkout --quiet --detach "$REF" +fi + +VERSION="$(git describe --tags --always --dirty)" +VERSION="${VERSION#v}" sed -i "s|^PIPU_VERSION=.*|PIPU_VERSION=$VERSION|" .env +echo "→ версия $VERSION, сборка образов (первая сборка — 5–15 минут)" -docker compose pull +docker compose build --pull docker compose up -d --remove-orphans +docker image prune -f >/dev/null echo "→ ждём готовности api" for _ in $(seq 1 60); do diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 4b9ce2f..756866d 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -27,6 +27,7 @@ jobs: recommender: # uv sync; ruff; mypy --strict; pytest (вкл. оффлайн-оценку качества) ts: # pnpm install --frozen-lockfile; turbo lint typecheck test; i18n:check web-build: # vite build, проверка размера бандла + docker: # сборка всех Docker-образов без push (сервер собирает их сам — ADR-013) mobile-check: # tsc, jest, expo prebuild + ./gradlew assembleDebug (только если менялся mobile/) e2e: # needs: api, web-build; docker compose -f deploy/compose.test.yaml up; playwright test ``` @@ -45,35 +46,30 @@ on: jobs: verify: # все проверки ci.yml (reusable workflow) - images: # needs: verify - strategy: { matrix: { service: [api, storage-agent, recommender, web] } } - # docker buildx build --push - # ${REGISTRY}/${OWNER}/pipu-music-${service}:${VERSION} и :latest (кроме pre-release) - # web-образ = nginx + собранная SPA + конфиг - - android: # needs: verify + android-release: # needs: verify # setup-java 17, Android SDK, pnpm install # VERSION / VERSION_CODE из тега → app.config.ts # expo prebuild --platform android --clean # ./gradlew assembleRelease (signingConfig из секретов) # apksigner verify; sha256sum > pipu-music-${VERSION}.apk.sha256 - release: # needs: [images, android] - # git-cliff --latest > CHANGELOG_RELEASE.md + # changelog из Conventional Commits между тегами # создать Gitea Release через API (POST /repos/{owner}/{repo}/releases), prerelease по суффиксу - # загрузить ассеты: APK, .sha256, deploy/compose.yaml, deploy/.env.example + # загрузить ассеты: APK и .sha256 ``` +Docker-образы **не публикуются**: сервер собирает их сам из клонированного репозитория (ADR-013). +Чтобы сборка на сервере не ломалась, job `docker` в `ci.yml` собирает все образы без push на каждый PR. + ## Секреты | Секрет | Назначение | |---|---| -| `REGISTRY_USER`, `REGISTRY_TOKEN` | Push в Gitea Container Registry | | `RELEASE_TOKEN` | Создание релиза и загрузка ассетов | | `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` | Подпись APK. **Ключ один на всю жизнь приложения** — хранить резервную копию вне Gitea. | ## Деплой -Автодеплой не обязателен. Рекомендуемый вариант: на сервере `deploy/update.sh ` — -`docker compose pull && docker compose up -d` с `PIPU_VERSION=`; миграции применяются при старте `api`. +Автодеплой не обязателен. На сервере в клоне репозитория: `deploy/update.sh [vX.Y.Z]` — checkout тега, +`docker compose build` и `up -d`; миграции применяются при старте `api` ([install-guide.md](install-guide.md)). Опционально — job `deploy` в `release.yml` по SSH на сервер для не-pre-release тегов (с ручным approval через environment). diff --git a/docs/decisions.md b/docs/decisions.md index d1d39da..50273b8 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -155,3 +155,18 @@ Postgres остаётся источником истины, индексы пе **Последствия.** Публичный `api` по-прежнему не может изменить библиотеку. Нужны права на запись в NFS-экспорт для UID агента. Подробно — [uploads.md](uploads.md). + +--- + +## ADR-013. Образы собираются на сервере, реестр не используется + +**Контекст.** Публикация образов в Gitea Container Registry требует настройки реестра, токенов на push и pull, +`docker login` на сервере. Сервер один, и он сам может собрать образы. + +**Решение.** На сервере клонируется репозиторий; `deploy/update.sh [vX.Y.Z]` переключается на тег и выполняет +`docker compose build` + `up -d` (`build:` у сервисов в `deploy/compose.yaml`). Релиз в Gitea содержит только APK. +CI на каждый PR собирает все образы без push, чтобы сборка на сервере не ломалась. + +**Последствия.** Серверу нужен доступ к репозиторию и интернет на время сборки; первая сборка — 5–15 минут, +нужно ~5–10 ГБ под кэш сборки. Откат — `update.sh` на предыдущий тег (пересборка из кэша). Отпадают секреты +`REGISTRY_USER` / `REGISTRY_TOKEN`. Заменяет часть ADR-001 / ci-cd о публикации образов. diff --git a/docs/deployment.md b/docs/deployment.md index f789213..08eef97 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -32,85 +32,33 @@ Перед первым запуском: `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` -```yaml -x-library-ro: &library-ro - type: bind - source: /srv/pipu/library - target: /library - read_only: true - bind: { propagation: rslave } +Актуальная конфигурация — сам файл [`deploy/compose.yaml`](../deploy/compose.yaml); здесь — как он устроен. -services: - nginx: - image: ${REGISTRY}/pipu-music-web:${PIPU_VERSION} - ports: ["443:443", "80:80"] - volumes: - - *library-ro - - /srv/pipu/cache:/cache:ro - - /srv/pipu/uploads:/uploads:ro # прослушивание очереди админом (internal location) - - ./certs:/etc/nginx/certs:ro - depends_on: [api] +- **Образы собираются на сервере** из клонированного репозитория (`build:` у каждого сервиса, контекст — корень репо), + в реестр ничего не публикуется. Теги локальные: `pipu-music-<сервис>:<версия>`, версия — `git describe` текущего тега + (её проставляет `update.sh` в `PIPU_VERSION`). - api: - image: ${REGISTRY}/pipu-music-api:${PIPU_VERSION} - env_file: .env - volumes: - - *library-ro - - /srv/pipu/cache:/cache - - /srv/pipu/updates:/updates - - /srv/pipu/uploads:/uploads - depends_on: [postgres, redis, meilisearch, storage-agent] +| Сервис | Собирается из | Особенности | +|---|---|---| +| `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:pg17` | Healthcheck `pg_isready` | +| `redis` | `redis:7-alpine` | AOF | +| `meilisearch` | `getmeili/meilisearch:v1.11` | | +| `caddy` (из `compose.tls.yaml`) | `caddy:2.10-alpine` | HTTPS на `DOMAIN`, nginx тогда без открытых портов | - storage-agent: - image: ${REGISTRY}/pipu-music-storage-agent:${PIPU_VERSION} - cap_add: [SYS_ADMIN] - security_opt: ["apparmor:unconfined"] - volumes: - - type: bind - source: /srv/pipu/library - target: /library - bind: { propagation: rshared } - - /srv/pipu/uploads:/uploads:ro # читает staging при импорте - # /library-rw/ монтируется агентом внутри своего контейнера, наружу не пробрасывается - environment: [INTERNAL_TOKEN, DATABASE_URL, PIPU_FILE_UID, PIPU_FILE_GID] - - recommender: - image: ${REGISTRY}/pipu-music-recommender:${PIPU_VERSION} - command: ["serve"] - env_file: .env - depends_on: [postgres, redis] - - analyzer: - image: ${REGISTRY}/pipu-music-recommender:${PIPU_VERSION} - command: ["analyze", "--workers", "4"] - env_file: .env - volumes: [*library-ro, /srv/pipu/models:/models] - deploy: { resources: { limits: { cpus: "4" } } } - - postgres: - image: pgvector/pgvector:pg17 - volumes: [/srv/pipu/postgres:/var/lib/postgresql/data] - - redis: - image: redis:7-alpine - command: ["redis-server", "--appendonly", "yes"] - volumes: [/srv/pipu/redis:/data] - - meilisearch: - image: getmeili/meilisearch:v1.11 - environment: [MEILI_MASTER_KEY, MEILI_ENV=production] - volumes: [/srv/pipu/meili:/meili_data] -``` - -Наружу открыт только `nginx`. Все остальные — во внутренней сети compose. +- Библиотека монтируется в `nginx`, `api`, `analyzer` только на чтение с `propagation: rslave`. +- Наружу открыт только `nginx` (или `caddy`). Все остальные — во внутренней сети compose. ## Конфигурация (`.env`) | Переменная | Пример / по умолчанию | Описание | |---|---|---| -| `PIPU_VERSION` | `1.0.0` | Версия образов | +| `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` | | @@ -150,7 +98,9 @@ services: ## Обновление ```bash -cd /opt/pipu && ./update.sh 1.3.0 # pull образов, up -d, миграции при старте api, проверка /readyz +cd /opt/pipu-music/deploy && ./update.sh # последний релизный тег ``` -Откат: `./update.sh 1.2.4` (миграции пишутся обратно-совместимыми в пределах минорной версии: сначала добавить, +Скрипт: `git fetch` → checkout тега → `docker compose build --pull` → `up -d` → удаление старых образов → +ожидание `/readyz`; миграции применяются при старте api. +Конкретная версия или откат: `./update.sh v1.2.4` (миграции пишутся обратно-совместимыми в пределах минорной версии: сначала добавить, потом — в следующем релизе — удалить). diff --git a/docs/install-guide.md b/docs/install-guide.md index 55a1728..d297987 100644 --- a/docs/install-guide.md +++ b/docs/install-guide.md @@ -7,7 +7,10 @@ В примерах: - домен — `music.example.com`, замените на свой; - репозиторий — `gitea.hsrv.site/mrleo1nid/pipu-music`; -- реестр образов — `gitea.hsrv.site/mrleo1nid`. +- каталог установки — `/opt/pipu-music`. + +Готовые Docker-образы не публикуются: сервер клонирует репозиторий и собирает образы сам +(`docker compose build`). Релизы в Gitea содержат только APK для Android. > **Что умеет текущая версия.** В `v0.1.0` (этап M0) работают развёртывание, HTTPS и проверка связи > клиентов с сервером. Вход, библиотека и плеер появятся в следующих версиях @@ -19,12 +22,13 @@ | Что | Требование | |---|---| -| Сервер | Linux x86_64 (Debian 12 / Ubuntu 22.04+), 4+ ядра, 8+ ГБ RAM, 20+ ГБ на диске под систему и кэши | +| Сервер | 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-сервер с библиотекой (подключается позже из админки) | -| Gitea | Аккаунт с доступом к репозиторию, если пакеты и релизы приватные | +| Интернет на сервере | Нужен при сборке: скачиваются базовые образы и зависимости Go / npm / Python | +| Gitea | Доступ к репозиторию на чтение (если он приватный — токен или SSH-ключ) | --- @@ -56,6 +60,18 @@ dig +short music.example.com 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 @@ -70,28 +86,21 @@ sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw allow 443/udp --- -## 4. Файлы развёртывания - -Каталог для конфигурации: +## 4. Клонирование репозитория ```bash -sudo mkdir -p /opt/pipu && sudo chown "$USER" /opt/pipu && cd /opt/pipu -``` - -Скачайте файлы из каталога `deploy/` репозитория. Пока нужная версия не вышла (в релизе `v0.1.0` -ещё нет `compose.tls.yaml` и `Caddyfile`), берите их из ветки `main`; начиная с `v0.1.1` те же файлы -лежат в ассетах релиза (`…/releases/download/vX.Y.Z/<файл>`). - -```bash -for f in compose.yaml compose.tls.yaml Caddyfile .env.example install.sh update.sh; do curl -fsSLO "https://gitea.hsrv.site/mrleo1nid/pipu-music/raw/branch/main/deploy/$f"; done +sudo mkdir -p /opt/pipu-music && sudo chown "$USER" /opt/pipu-music ``` ```bash -chmod +x install.sh update.sh +git clone https://gitea.hsrv.site/mrleo1nid/pipu-music.git /opt/pipu-music && cd /opt/pipu-music/deploy ``` -> Если репозиторий приватный, добавьте к `curl` заголовок с токеном Gitea (право `read:repository`): -> `-H "Authorization: token <ТОКЕН>"`. +> Если репозиторий приватный: клонируйте по 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`. --- @@ -107,35 +116,33 @@ sudo ./install.sh подключит админ, были видны всем контейнерам, и ставит systemd-юнит, повторяющий это после перезагрузки; - создаёт `.env` со случайными паролями БД, ключом поиска и межсервисным токеном. -Заполните в `/opt/pipu/.env`: +Заполните в `/opt/pipu-music/deploy/.env`: ```ini -REGISTRY=gitea.hsrv.site/mrleo1nid -PIPU_VERSION=0.1.0 - COMPOSE_FILE=compose.yaml:compose.tls.yaml DOMAIN=music.example.com ACME_EMAIL=you@example.com ``` `ACME_EMAIL` — адрес, на который Let's Encrypt пишет, если с сертификатом что-то не так. -Остальные значения менять не нужно. +Остальные значения менять не нужно; `PIPU_VERSION` проставляет `update.sh`. -Если образы в Gitea приватные, войдите в реестр токеном с правом `read:package`: - -```bash -docker login gitea.hsrv.site -``` +`.env` не хранится в git (он в `.gitignore`), поэтому обновления репозитория его не трогают. --- -## 6. Запуск +## 6. Сборка и запуск ```bash -./update.sh 0.1.0 +./update.sh ``` -Скрипт скачает образы, запустит все сервисы и дождётся готовности API (до 2 минут). +Скрипт переключает репозиторий на последний релизный тег (например, `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 секунд. Проверка с любого компьютера: @@ -240,13 +247,15 @@ curl -s https://music.example.com/readyz ## 10. Обновление, бэкапы, удаление -Обновление (и откат) на любую версию: +Обновление до последнего релиза: ```bash -cd /opt/pipu && ./update.sh 0.2.0 +cd /opt/pipu-music/deploy && ./update.sh ``` -Миграции БД применяются автоматически при старте API. +Конкретная версия или откат: `./update.sh v0.2.0`. Скрипт делает `git fetch`, переключается на тег, +пересобирает образы и перезапускает сервисы; старые образы удаляются. Миграции БД применяются +автоматически при старте API. Бэкап базы (всё остальное восстанавливается само — см. [deployment.md](deployment.md#бэкапы)): @@ -257,7 +266,7 @@ docker compose exec -T postgres pg_dump -U pipu -Fc pipu > /srv/pipu/backups/pip Остановить сервис, сохранив данные: ```bash -cd /opt/pipu && docker compose down +cd /opt/pipu-music/deploy && docker compose down ``` --- @@ -269,7 +278,10 @@ cd /opt/pipu && docker compose down | Сертификат не выдаётся, браузер ругается на 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`: «pull access denied» | Неверный `REGISTRY` в `.env` или нужен `docker login gitea.hsrv.site` | +| `update.sh`: ошибка `git fetch` | Нет доступа к репозиторию: проверьте SSH-ключ или сохранённый токен (`git fetch` вручную) | +| Сборка падает на скачивании зависимостей | Нет интернета или DNS на сервере; повторите `./update.sh` — уже собранные слои берутся из кэша | +| Сборка падает: «no space left on device» | Очистите кэш сборки: `docker builder prune` | +| `permission denied … docker.sock` | Пользователь не в группе `docker` (шаг 3) или вы не перелогинились | | `storage-agent` не стартует, ошибка про propagation / shared mount | Ещё раз `sudo ./install.sh`; на хосте `findmnt -o TARGET,PROPAGATION /srv/pipu/library` должно показать `shared` | | Android: «Сервер не отвечает» | Тот же адрес открывается в браузере телефона; в адресе нет опечатки; телефон в сети с доступом к серверу | | Android: «Эта версия приложения слишком старая» | Установите APK из последнего релиза | @@ -277,5 +289,5 @@ cd /opt/pipu && docker compose down Логи всех сервисов сразу: ```bash -cd /opt/pipu && docker compose logs -f --tail=100 +cd /opt/pipu-music/deploy && docker compose logs -f --tail=100 ```