From 2d19eef9c216e35ac86e531e1a011b9172e8d486 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 11 Sep 2026 13:16:43 +0300 Subject: [PATCH 1/2] ci: add protected production deploy workflow --- .gitea/workflows/deploy.yml | 63 +++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 4 +++ README.md | 10 +++--- app/version.py | 6 ++-- pyproject.toml | 2 +- tests/test_api.py | 2 +- 6 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 .gitea/workflows/deploy.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..bfc1e93 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,63 @@ +name: Deploy production + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + env: + PROD_PATH: /srv/miem_workers + steps: + - uses: actions/checkout@v4 + - name: Prepare release + shell: bash + run: | + set -euo pipefail + tar --exclude=.git --exclude=.env -cf "$RUNNER_TEMP/miem-workers-release.tar" . + printf '%s\n' "$PROD_SSH_KEY" > "$RUNNER_TEMP/deploy_key" + printf '%s\n' "$PROD_KNOWN_HOSTS" > "$RUNNER_TEMP/known_hosts" + chmod 600 "$RUNNER_TEMP/deploy_key" "$RUNNER_TEMP/known_hosts" + env: + PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }} + PROD_KNOWN_HOSTS: ${{ secrets.PROD_KNOWN_HOSTS }} + - name: Upload release + shell: bash + run: | + set -euo pipefail + scp -i "$RUNNER_TEMP/deploy_key" \ + -o BatchMode=yes \ + -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ + "$RUNNER_TEMP/miem-workers-release.tar" \ + "$PROD_USER@$PROD_HOST:/tmp/miem-workers-release.tar" + env: + PROD_HOST: ${{ secrets.PROD_HOST }} + PROD_USER: ${{ secrets.PROD_USER }} + - name: Deploy and verify + shell: bash + run: | + set -euo pipefail + ssh -i "$RUNNER_TEMP/deploy_key" \ + -o BatchMode=yes \ + -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ + "$PROD_USER@$PROD_HOST" \ + "PROD_PATH='$PROD_PATH' bash -s" <<'REMOTE' + set -euo pipefail + cd "$PROD_PATH" + mkdir -p "$HOME/backups/miem_workers" + backup="$HOME/backups/miem_workers/miem_workers-$(date +%Y%m%d-%H%M%S).sql" + docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > "$backup" + docker run --rm \ + -v /tmp/miem-workers-release.tar:/src/release.tar:ro \ + -v "$PROD_PATH":/dst \ + alpine:3.20 sh -c 'tar -xf /src/release.tar -C /dst' + docker compose up -d --build + curl --fail --silent --show-error http://127.0.0.1:8000/api/health + docker compose ps + rm -f /tmp/miem-workers-release.tar + REMOTE + env: + PROD_HOST: ${{ secrets.PROD_HOST }} + PROD_USER: ${{ secrets.PROD_USER }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd01e5..9d4b5c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.8.2 + +- Добавлен production CD workflow с backup PostgreSQL и проверкой healthcheck после обновления. + ## 0.8.1 - Улучшены доступность, фильтры, состояния и адаптивность административного каталога. diff --git a/README.md b/README.md index a2c4eba..a9eea95 100644 --- a/README.md +++ b/README.md @@ -119,9 +119,11 @@ curl -X POST http://localhost:8000/api/crawl-runs --cookie "miem_admin_session=. docker compose logs -f api docker compose logs -f worker docker compose exec postgres pg_dump -U miem miem_workers > backup.sql -docker compose down -``` - +docker compose down +``` + +Production deploy выполняется workflow `Deploy production` после push в `main` или вручную. Для него нужны Actions secrets `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY` и `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. Перед обновлением workflow сохраняет PostgreSQL backup в домашний каталог deploy-пользователя, пересобирает `api` и `worker` и проверяет `/api/health`. + ## Проверки После установки зависимостей из `requirements.txt` выполните: @@ -133,4 +135,4 @@ node --check app/static/admin.js Тесты с данными используют временную SQLite; отдельные API smoke-тесты запускают приложение с его текущей конфигурацией. Браузерная проверка: `pip install playwright`, `python -m playwright install chromium`, затем `python tests/browser_admin.py`. Она проверяет реальный рендеринг страниц, клавиатуру, диалог, фильтры и восстановление прогресса после ошибки. Скриншоты сохраняются во временную папку; путь выводится в конце. -Версия сервиса: `0.8.1`. Админка всегда показывает версии backend и frontend в footer. +Версия сервиса: `0.8.2`. Админка всегда показывает версии backend и frontend в footer. diff --git a/app/version.py b/app/version.py index b797d9d..9024ee5 100644 --- a/app/version.py +++ b/app/version.py @@ -1,3 +1,3 @@ -APP_VERSION = "0.8.1" -FRONTEND_VERSION = "0.8.1" -BACKEND_VERSION = "0.8.1" +APP_VERSION = "0.8.2" +FRONTEND_VERSION = "0.8.2" +BACKEND_VERSION = "0.8.2" diff --git a/pyproject.toml b/pyproject.toml index 4bcfbab..82d6487 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "miem-workers" -version = "0.8.1" +version = "0.8.2" description = "MIEM employees parser, admin API, and web admin" requires-python = ">=3.11" dependencies = [ diff --git a/tests/test_api.py b/tests/test_api.py index 212f52b..bc24263 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -18,7 +18,7 @@ def test_health_returns_versions(): response = TestClient(app).get("/api/health") assert response.status_code == 200 - assert response.json()["backend_version"] == "0.8.1" + assert response.json()["backend_version"] == "0.8.2" def test_mcp_endpoint_is_removed(): -- 2.49.1 From 880ac6c53e034f982bd8c832c6d73fedf3cb92a2 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 11 Sep 2026 13:26:59 +0300 Subject: [PATCH 2/2] ci: gate production deploy on tests --- .gitea/workflows/ci.yml | 63 +++++++++++++++++++++++++++++++++++++ .gitea/workflows/deploy.yml | 63 ------------------------------------- README.md | 2 +- 3 files changed, 64 insertions(+), 64 deletions(-) delete mode 100644 .gitea/workflows/deploy.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6ba2cbb..e8fd648 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: jobs: test: @@ -22,3 +23,65 @@ jobs: - run: python -m pip install -r requirements.txt - run: python -m pytest -q - run: node --check app/static/admin.js + + deploy: + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' + needs: test + runs-on: ubuntu-latest + concurrency: + group: production-deploy + cancel-in-progress: false + env: + PROD_PATH: /srv/miem_workers + RELEASE_NAME: miem-workers-release-${{ github.run_id }}.tar + steps: + - uses: actions/checkout@v4 + - name: Prepare release + shell: bash + run: | + set -euo pipefail + tar --exclude=.git --exclude=.env -cf "$RUNNER_TEMP/$RELEASE_NAME" . + printf '%s\n' "$PROD_SSH_KEY" > "$RUNNER_TEMP/deploy_key" + printf '%s\n' "$PROD_KNOWN_HOSTS" > "$RUNNER_TEMP/known_hosts" + chmod 600 "$RUNNER_TEMP/deploy_key" "$RUNNER_TEMP/known_hosts" + env: + PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }} + PROD_KNOWN_HOSTS: ${{ secrets.PROD_KNOWN_HOSTS }} + - name: Upload release + shell: bash + run: | + set -euo pipefail + scp -i "$RUNNER_TEMP/deploy_key" \ + -o BatchMode=yes \ + -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ + "$RUNNER_TEMP/$RELEASE_NAME" \ + "$PROD_USER@$PROD_HOST:/tmp/$RELEASE_NAME" + env: + PROD_HOST: ${{ secrets.PROD_HOST }} + PROD_USER: ${{ secrets.PROD_USER }} + - name: Deploy and verify + shell: bash + run: | + set -euo pipefail + ssh -i "$RUNNER_TEMP/deploy_key" \ + -o BatchMode=yes \ + -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ + "$PROD_USER@$PROD_HOST" \ + "PROD_PATH='$PROD_PATH' RELEASE_NAME='$RELEASE_NAME' bash -s" <<'REMOTE' + set -euo pipefail + cd "$PROD_PATH" + mkdir -p "$HOME/backups/miem_workers" + backup="$HOME/backups/miem_workers/miem_workers-$(date +%Y%m%d-%H%M%S).sql" + docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > "$backup" + docker run --rm \ + -v "/tmp/$RELEASE_NAME:/src/release.tar:ro" \ + -v "$PROD_PATH":/dst \ + alpine:3.20 sh -c 'tar -xf /src/release.tar -C /dst' + docker compose up -d --build + curl --fail --silent --show-error http://127.0.0.1:8000/api/health + docker compose ps + rm -f "/tmp/$RELEASE_NAME" + REMOTE + env: + PROD_HOST: ${{ secrets.PROD_HOST }} + PROD_USER: ${{ secrets.PROD_USER }} diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index bfc1e93..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Deploy production - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - deploy: - runs-on: ubuntu-latest - env: - PROD_PATH: /srv/miem_workers - steps: - - uses: actions/checkout@v4 - - name: Prepare release - shell: bash - run: | - set -euo pipefail - tar --exclude=.git --exclude=.env -cf "$RUNNER_TEMP/miem-workers-release.tar" . - printf '%s\n' "$PROD_SSH_KEY" > "$RUNNER_TEMP/deploy_key" - printf '%s\n' "$PROD_KNOWN_HOSTS" > "$RUNNER_TEMP/known_hosts" - chmod 600 "$RUNNER_TEMP/deploy_key" "$RUNNER_TEMP/known_hosts" - env: - PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }} - PROD_KNOWN_HOSTS: ${{ secrets.PROD_KNOWN_HOSTS }} - - name: Upload release - shell: bash - run: | - set -euo pipefail - scp -i "$RUNNER_TEMP/deploy_key" \ - -o BatchMode=yes \ - -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ - "$RUNNER_TEMP/miem-workers-release.tar" \ - "$PROD_USER@$PROD_HOST:/tmp/miem-workers-release.tar" - env: - PROD_HOST: ${{ secrets.PROD_HOST }} - PROD_USER: ${{ secrets.PROD_USER }} - - name: Deploy and verify - shell: bash - run: | - set -euo pipefail - ssh -i "$RUNNER_TEMP/deploy_key" \ - -o BatchMode=yes \ - -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ - "$PROD_USER@$PROD_HOST" \ - "PROD_PATH='$PROD_PATH' bash -s" <<'REMOTE' - set -euo pipefail - cd "$PROD_PATH" - mkdir -p "$HOME/backups/miem_workers" - backup="$HOME/backups/miem_workers/miem_workers-$(date +%Y%m%d-%H%M%S).sql" - docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > "$backup" - docker run --rm \ - -v /tmp/miem-workers-release.tar:/src/release.tar:ro \ - -v "$PROD_PATH":/dst \ - alpine:3.20 sh -c 'tar -xf /src/release.tar -C /dst' - docker compose up -d --build - curl --fail --silent --show-error http://127.0.0.1:8000/api/health - docker compose ps - rm -f /tmp/miem-workers-release.tar - REMOTE - env: - PROD_HOST: ${{ secrets.PROD_HOST }} - PROD_USER: ${{ secrets.PROD_USER }} diff --git a/README.md b/README.md index a9eea95..2dfee27 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ docker compose exec postgres pg_dump -U miem miem_workers > backup.sql docker compose down ``` -Production deploy выполняется workflow `Deploy production` после push в `main` или вручную. Для него нужны Actions secrets `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY` и `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. Перед обновлением workflow сохраняет PostgreSQL backup в домашний каталог deploy-пользователя, пересобирает `api` и `worker` и проверяет `/api/health`. +Production deploy выполняется job `deploy` workflow `CI` после успешного `test` на push в `main` или вручную. Для него нужны Actions secrets `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY` и `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. Перед обновлением workflow сохраняет PostgreSQL backup в домашний каталог deploy-пользователя, пересобирает `api` и `worker` и проверяет `/api/health`. Одновременно выполняется только один production deploy. ## Проверки -- 2.49.1