diff --git a/.github/workflows/deploy.yml b/.forgejo/workflows/deploy.yml similarity index 58% rename from .github/workflows/deploy.yml rename to .forgejo/workflows/deploy.yml index 09b9254a..e9189f88 100644 --- a/.github/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -1,7 +1,9 @@ name: Deploy -# Деплоится только при изменениях основного стека. -# Obsidian-стек (CouchDB) — отдельный workflow `deploy-obsidian.yml`. +# Forgejo Actions equivalent of .github/workflows/deploy.yml +# Migration 2026-05-16: GitHub → Forgejo (git.gendsgn.ru) +# Builds images on Forgejo runner, pushes to ghcr.io (GitHub Container Registry), +# SSH-deploys to Beget VPS (46.173.16.127). on: push: branches: [main] @@ -10,10 +12,8 @@ on: - "frontend/**" - "docker-compose.prod.yml" - "Caddyfile" - - ".github/workflows/deploy.yml" + - ".forgejo/workflows/deploy.yml" - "data/sql/*.sql" - # NB: shared compose-fragments (network) — тоже триггерят main - # потому что Caddy шарит сеть с obsidian-stack workflow_dispatch: concurrency: @@ -28,9 +28,6 @@ env: jobs: changes: runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read outputs: backend: ${{ steps.filter.outputs.backend }} frontend: ${{ steps.filter.outputs.frontend }} @@ -43,19 +40,17 @@ jobs: filters: | backend: - 'backend/**' + - 'data/sql/**' frontend: - 'frontend/**' infra: - 'docker-compose.prod.yml' - 'Caddyfile' - - '.github/workflows/deploy.yml' + - '.forgejo/workflows/deploy.yml' build-backend: runs-on: ubuntu-latest needs: changes - permissions: - contents: read - packages: write if: | needs.changes.outputs.backend == 'true' || needs.changes.outputs.infra == 'true' || @@ -63,12 +58,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GHCR (shell-based — docker/login-action@v3 unreliable под Forgejo Actions) + env: + GHCR_PAT: ${{ secrets.GHCR_PAT }} + run: | + echo "$GHCR_PAT" | docker login ghcr.io -u lekss361 --password-stdin - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -79,8 +73,8 @@ jobs: context: ./backend target: runner push: true - cache-from: type=gha,scope=backend-lean - cache-to: type=gha,mode=max,scope=backend-lean + cache-from: type=registry,ref=${{ env.IMAGE_BACKEND }}:buildcache + cache-to: type=registry,ref=${{ env.IMAGE_BACKEND }}:buildcache,mode=max tags: | ${{ env.IMAGE_BACKEND }}:latest ${{ env.IMAGE_BACKEND }}:${{ github.sha }} @@ -88,9 +82,6 @@ jobs: build-worker: runs-on: ubuntu-latest needs: changes - permissions: - contents: read - packages: write if: | needs.changes.outputs.backend == 'true' || needs.changes.outputs.infra == 'true' || @@ -98,12 +89,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GHCR (shell-based — docker/login-action@v3 unreliable под Forgejo Actions) + env: + GHCR_PAT: ${{ secrets.GHCR_PAT }} + run: | + echo "$GHCR_PAT" | docker login ghcr.io -u lekss361 --password-stdin - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -114,8 +104,8 @@ jobs: context: ./backend target: runner-with-chromium push: true - cache-from: type=gha,scope=worker-chromium - cache-to: type=gha,mode=max,scope=worker-chromium + cache-from: type=registry,ref=${{ env.IMAGE_WORKER }}:buildcache + cache-to: type=registry,ref=${{ env.IMAGE_WORKER }}:buildcache,mode=max tags: | ${{ env.IMAGE_WORKER }}:latest ${{ env.IMAGE_WORKER }}:${{ github.sha }} @@ -123,9 +113,6 @@ jobs: build-frontend: runs-on: ubuntu-latest needs: changes - permissions: - contents: read - packages: write if: | needs.changes.outputs.frontend == 'true' || needs.changes.outputs.infra == 'true' || @@ -133,12 +120,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GHCR (shell-based — docker/login-action@v3 unreliable под Forgejo Actions) + env: + GHCR_PAT: ${{ secrets.GHCR_PAT }} + run: | + echo "$GHCR_PAT" | docker login ghcr.io -u lekss361 --password-stdin - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -148,8 +134,8 @@ jobs: with: context: ./frontend push: true - cache-from: type=gha,scope=frontend - cache-to: type=gha,mode=max,scope=frontend + cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache + cache-to: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache,mode=max tags: | ${{ env.IMAGE_FRONTEND }}:latest ${{ env.IMAGE_FRONTEND }}:${{ github.sha }} @@ -157,10 +143,6 @@ jobs: deploy: runs-on: ubuntu-latest needs: [changes, build-backend, build-worker, build-frontend] - permissions: - contents: read - # Запускается если ни один build не завершился failure. - # `always()` позволяет выполнить job даже когда зависимые jobs были skipped. if: | always() && !cancelled() && @@ -173,26 +155,26 @@ jobs: env: IMAGE_TAG: latest SENTRY_RELEASE_VAL: ${{ github.sha }} + GHCR_PAT: ${{ secrets.GHCR_PAT }} with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - port: ${{ secrets.DEPLOY_PORT || 22 }} - envs: IMAGE_TAG,SENTRY_RELEASE_VAL + port: ${{ secrets.DEPLOY_PORT }} + envs: IMAGE_TAG,SENTRY_RELEASE_VAL,GHCR_PAT script: | set -euo pipefail cd /opt/gendesign # Sync compose / Caddyfile / init scripts from the repo. - # --ff-only refuses if there are local commits on the VM (we don't expect any). + # Origin теперь Forgejo (после migration 2026-05-16) — HTTPS basic auth + # через PAT в URL не нужен т.к. repo читается через deploy key (SSH) + # ИЛИ public read-only mode. Forgejo gendesign — private, нужен auth: + # `git remote get-url origin` должен указывать на Forgejo с auth. git fetch origin main git reset --hard origin/main - # Sentry release tracking — записываем git-sha в .env.runtime. - # ВАЖНО: НЕ перезаписываем файл целиком (там могут быть - # COUCHDB_PASSWORD и др. user-managed secrets). Заменяем только - # строку SENTRY_RELEASE или добавляем если её нет. - # IMAGE_TAG=latest для docker pull; SENTRY_RELEASE_VAL = git sha для трекинга. + # Sentry release tracking mkdir -p backend touch backend/.env.runtime if grep -q '^SENTRY_RELEASE=' backend/.env.runtime; then @@ -202,27 +184,19 @@ jobs: fi chmod 600 backend/.env.runtime - # Создать external network если её нет (нужна Caddy для маршрута - # obsidian.gendsgn.ru → couchdb из отдельного obsidian-stack). + # External network для Caddy + obsidian-stack share docker network inspect gendesign_shared >/dev/null 2>&1 \ || docker network create gendesign_shared + # Re-login to GHCR (PAT может быть rotated после initial setup) + echo "$GHCR_PAT" | docker login ghcr.io -u lekss361 --password-stdin + export IMAGE_TAG="$IMAGE_TAG" - # Project name явно — `gendesign` (по имени папки auto), но - # фиксируем для consistency с obsidian-stack (-p gendesign-obsidian). docker compose -p gendesign -f docker-compose.prod.yml pull - # ── Apply pending SQL migrations ────────────────────────────────── - # Tracking table: _schema_migrations (filename TEXT PRIMARY KEY). - # Each NN_*.sql file is applied exactly once, in sort order. - # ON_ERROR_STOP=on ensures a failing migration aborts the deploy. - # POSTGRES_USER/PASSWORD/DB живут в /opt/gendesign/.env (root — - # их читает compose через env_file для postgres service). - # backend/.env содержит ТОЛЬКО backend-app vars (SCRAPE_ADMIN_TOKEN, - # OPENROUTESERVICE_API_KEY, etc), без POSTGRES_*. + # Apply pending SQL migrations set -a; source .env; set +a - # Ensure tracking table exists (idempotent). docker compose -p gendesign -f docker-compose.prod.yml exec -T postgres \ psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=on -c " CREATE TABLE IF NOT EXISTS _schema_migrations ( @@ -231,7 +205,6 @@ jobs: ); " - # Apply each pending .sql file in NN order. for sql_file in $(ls -1 data/sql/*.sql 2>/dev/null | sort); do fname=$(basename "$sql_file") applied=$(docker compose -p gendesign -f docker-compose.prod.yml exec -T postgres \ @@ -251,18 +224,13 @@ jobs: fi done echo "All migrations applied." - # ───────────────────────────────────────────────────────────────── docker compose -p gendesign -f docker-compose.prod.yml up -d - # Caddyfile is bind-mounted; up -d won't re-read it. Reload explicitly. - # `|| true` so a temporary Caddy hiccup doesn't fail the whole deploy. docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy \ caddy reload --config /etc/caddy/Caddyfile || true - # Clean up disk — aggressive чтобы избежать накопления. - # 1. Для каждого gendesign-* repo оставляем 2 самых свежих SHA-тега - # + :latest. Docker images сортирует по Created DESC. + # Cleanup old images for repo in ghcr.io/lekss361/gendesign-backend \ ghcr.io/lekss361/gendesign-worker \ ghcr.io/lekss361/gendesign-frontend; do @@ -271,14 +239,10 @@ jobs: | tail -n +3 \ | xargs -r docker rmi 2>/dev/null || true done - # 2. ВСЕ unused images (даже свежие). Running контейнеры безопасны - # — их images не unused. Раньше был filter until=72h, но при - # частых деплоях оставались "fresh dangling" layers, накапливая - # до 50GB за неделю. docker image prune -af || true docker builder prune -af || true - # Wait for backend to be healthy (up to 30 s). + # Health check for i in $(seq 1 30); do curl -fsS http://localhost:8000/health && break sleep 1