diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..42d6165 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +.github +.gitea +.venv +__pycache__ +*.pyc +.pytest_cache +.env +.env.* +web/node_modules +web/dist +*.md +!README.md diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..711d2e3 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,24 @@ +# Forgejo / Gitea Actions — проверка перед деплоем +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Pytest + run: | + pip install -e ".[dev]" + pytest -q diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..0e6c481 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,48 @@ +# Деплой на сервер по SSH после пуша тега v* или вручную (в т.ч. откат на старый тег). +name: Deploy + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + ref: + description: "Git ref (тег для релиза или отката, напр. v1.5.0 или v1.4.1)" + required: true + default: "main" + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Определить ревизию + id: pick + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "ref=${{ inputs.ref }}" >> "$GITHUB_OUTPUT" + else + echo "ref=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" + fi + + - name: SSH — fetch, checkout, docker compose + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + script_stop: true + command_timeout: 20m + script: | + set -euo pipefail + cd "${{ secrets.DEPLOY_PATH }}" + git fetch origin --tags --prune + git checkout "${{ steps.pick.outputs.ref }}" + if git show-ref --verify --quiet "refs/remotes/origin/${{ steps.pick.outputs.ref }}"; then + git reset --hard "origin/${{ steps.pick.outputs.ref }}" + else + git reset --hard "${{ steps.pick.outputs.ref }}" + fi + docker compose build + docker compose up -d + docker compose ps diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d5581..9566823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ Формат: семантическое версионирование `MAJOR.MINOR.PATCH`. Git-теги `v1.0.0`, `v1.1.0` и т.д. — см. [docs/VERSIONING.md](docs/VERSIONING.md). +## [1.6.0] — 2026-04-03 + +Docker-образ, `docker-compose.yml`, CI/CD Forgejo/Gitea Actions. + +### Добавлено + +- **`Dockerfile`**, **`docker-compose.yml`**, **`deploy/entrypoint.sh`** — `alembic upgrade` + `uvicorn` (отключение: `SKIP_ALEMBIC=1`). +- **`.gitea/workflows/ci.yml`** — pytest на push в `main` и PR. +- **`.gitea/workflows/deploy.yml`** — деплой по пушу тега `v*` или вручную; **откат** = тот же workflow с `ref` = старый тег. +- **[docs/CICD.md](docs/CICD.md)** — секреты, подготовка `root@pvestandt9`, порядок релиза и отката. + ## [1.5.0] — 2026-04-03 IRM-ядро: инциденты, задачи, эскалации, миграция БД, документация. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5b88b16 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim-bookworm + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends libpq5 \ + && rm -rf /var/lib/apt/lists/* + +COPY pyproject.toml README.md alembic.ini ./ +COPY alembic ./alembic +COPY onguard24 ./onguard24 +COPY deploy/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh \ + && pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir . + +ENV PYTHONUNBUFFERED=1 +EXPOSE 8080 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index a00d2ea..2d44ba9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # onGuard24 -**Версия: 1.5.0** · Модульный монолит на **Python (FastAPI)**: ядро, приём алертов из Grafana, заготовки модулей (дежурства, контакты, светофор), PostgreSQL, проверки Vault / Grafana / Forgejo. +**Версия: 1.6.0** · Модульный монолит на **Python (FastAPI)**: ядро, приём алертов из Grafana, заготовки модулей (дежурства, контакты, светофор), PostgreSQL, проверки Vault / Grafana / Forgejo. | Документ | Назначение | |----------|------------| @@ -11,6 +11,7 @@ | [docs/DOMAIN.md](docs/DOMAIN.md) | Сущности (инцидент, алерт, эскалация), шина событий | | [docs/MODULES.md](docs/MODULES.md) | Как добавлять модули и подписки на события | | [docs/IRM.md](docs/IRM.md) | Функционал IRM: что делаем, что в Grafana | +| [docs/CICD.md](docs/CICD.md) | Forgejo Actions: деплой на сервер, откат по тегу | **Репозиторий:** [forgejo.pvenode.ru/admin/onGuard24](https://forgejo.pvenode.ru/admin/onGuard24) @@ -97,3 +98,11 @@ pytest ``` Покрытие: `/health`, `/api/v1/status`, webhook Grafana; внешние вызовы (Vault, Grafana, Forgejo) в тестах статуса подменяются моками. + +## Docker и CI/CD + +```bash +docker compose build && docker compose up -d +``` + +Деплой через **Forgejo Actions** (тег `v*`, SSH на сервер): см. [docs/CICD.md](docs/CICD.md). **Откат:** вручную запустить workflow **Deploy** с полем `ref` = нужный старый тег. diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh new file mode 100644 index 0000000..1c08462 --- /dev/null +++ b/deploy/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e +cd /app +if [ -n "${DATABASE_URL:-}" ] && [ "${SKIP_ALEMBIC:-0}" != "1" ]; then + alembic upgrade head +fi +exec uvicorn onguard24.main:app --host 0.0.0.0 --port "${PORT:-8080}" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..18657e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +# Прод: на сервере рядом с репозиторием лежит .env (не в git). +# Сборка: docker compose build && docker compose up -d +services: + onguard24: + build: . + image: onguard24:latest + container_name: onguard24 + restart: unless-stopped + env_file: + - .env + ports: + - "${ONGUARD_HTTP_PORT:-8080}:8080" + environment: + SKIP_ALEMBIC: ${SKIP_ALEMBIC:-0} + PORT: "8080" + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health')"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 07bef1c..92fae12 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -22,9 +22,13 @@ onGuard24/ │ │ └── forgejo_api.py # Forgejo/Gitea API (token + probe/fallback) │ └── modules/ # IRM: incidents, tasks, escalations, … + registry + ui_support ├── web/ # Vite + React (опционально) +├── Dockerfile +├── docker-compose.yml +├── deploy/entrypoint.sh ├── pyproject.toml ├── pytest.ini ├── tests/ # pytest: health, status, ingress +├── .gitea/workflows/ # CI + SSH deploy (Forgejo Actions) ├── CHANGELOG.md └── docs/ ``` diff --git a/docs/CICD.md b/docs/CICD.md new file mode 100644 index 0000000..2b9f33c --- /dev/null +++ b/docs/CICD.md @@ -0,0 +1,85 @@ +# CI/CD (Forgejo / Gitea) и деплой на `pvestandt9` + +Цель: **пуш тега `v*`** → автоматический деплой на сервер; **откат** — повторный запуск workflow с другим тегом. + +## Что в репозитории + +| Файл | Назначение | +|------|------------| +| `.gitea/workflows/ci.yml` | На `push` в `main` и PR: `pytest`. | +| `.gitea/workflows/deploy.yml` | На `push` тега `v*`: SSH на сервер → `git checkout` → `docker compose build/up`. | +| `Dockerfile` | Образ Python 3.12, `pip install .`, entrypoint: `alembic upgrade` + `uvicorn`. | +| `docker-compose.yml` | Сервис `onguard24`, порт `8080`, `env_file: .env`. | +| `deploy/entrypoint.sh` | Перед стартом: `alembic upgrade head` (отключить: `SKIP_ALEMBIC=1` в `.env`). | + +## Включить Actions в Forgejo + +1. Админка инстанса или репозитория: включить **Actions**. +2. Зарегистрировать **runner** с меткой `ubuntu-latest` (или изменить `runs-on` в YAML на вашу метку, например `self-hosted`). +3. Если образы `actions/checkout` недоступны, в настройках Actions задайте зеркало GitHub или используйте встроенные экшены Forgejo (см. документацию вашей версии). + +## Секреты репозитория + +**Настройки репозитория → Actions → Secrets:** + +| Секрет | Пример | Описание | +|--------|--------|----------| +| `DEPLOY_HOST` | `pvestandt9` или IP | Хост SSH. | +| `DEPLOY_USER` | `root` | Пользователь SSH. | +| `DEPLOY_SSH_KEY` | содержимое `id_rsa` | Приватный ключ (весь PEM, многострочный). | +| `DEPLOY_PATH` | `/opt/onGuard24` | Каталог **клона git** на сервере (там же `docker-compose.yml`). | + +Ключ лучше отдельный **deploy key** только на чтение репозитория или полный доступ, если runner делает только `git fetch`. + +## Однократная подготовка сервера (`root@pvestandt9`) + +```bash +ssh root@pvestandt9 +apt-get update && apt-get install -y git docker.io docker-compose-v2 +# или Docker CE по инструкции вашей ОС + +mkdir -p /opt/onGuard24 +cd /opt +git clone https://forgejo.pvenode.ru/admin/onGuard24.git onGuard24 +cd onGuard24 +``` + +Создайте **`/opt/onGuard24/.env`** (как в `.env.example`): `DATABASE_URL`, `HTTP_ADDR` (в контейнере порт задаёт compose; для приложения можно `0.0.0.0:8080`), секреты Grafana и т.д. + +Настройте **`git remote`** и доступ (`~/.ssh` deploy key или `git credential`), чтобы **`git fetch` без пароля** работал от пользователя, под которым заходит CI (часто `root`). + +Проверка вручную: + +```bash +cd /opt/onGuard24 +docker compose build && docker compose up -d +curl -s http://127.0.0.1:8080/health +``` + +При необходимости пробросьте порт наружу (nginx, firewall). + +## Релиз (деплой новой версии) + +1. Закоммитить код, запушить тег: `git tag v1.6.0 && git push origin v1.6.0`. +2. Workflow **Deploy** запустится сам, на сервере обновится код и пересоберётся контейнер. + +## Откат версии (простой процесс) + +1. В Forgejo: **Actions → Deploy → Run workflow**. +2. В поле **ref** указать **старый тег**, например `v1.4.1`. +3. Запустить. На сервере выполнится `checkout` и `reset` на этот ref, затем `docker compose build && up -d`. + +Убедитесь, что старый тег есть в `origin` (`git push --tags` не удалялся). + +## Миграции БД + +По умолчанию при каждом старте контейнера выполняется **`alembic upgrade head`**. Если нужно отключить (и гонять миграции вручную), в `.env` на сервере: `SKIP_ALEMBIC=1`. + +## Устранение неполадок + +- **Runner не берёт job** — проверьте `runs-on` и метки runner. +- **SSH fails** — `ssh -i key root@DEPLOY_HOST` с машины runner; `known_hosts` при необходимости добавьте в экшен (расширение `appleboy/ssh-action` / `ssh-keyscan`). +- **`git checkout` fails** — выполните на сервере `git fetch --tags` вручную, проверьте remote URL и ключ. +- **База недоступна из контейнера** — в `DATABASE_URL` укажите хост, доступный **из Docker** (не `127.0.0.1` хоста, если БД на хосте — используйте IP хоста или `host.docker.internal` где поддерживается). + +См. также [VERSIONING.md](VERSIONING.md) и [IRM.md](IRM.md). diff --git a/onguard24/__init__.py b/onguard24/__init__.py index e077da5..7f42710 100644 --- a/onguard24/__init__.py +++ b/onguard24/__init__.py @@ -1,3 +1,3 @@ """onGuard24 — модульный монолит (ядро + модули).""" -__version__ = "1.5.0" +__version__ = "1.6.0" diff --git a/pyproject.toml b/pyproject.toml index 46012bf..2585536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "onguard24" -version = "1.5.0" +version = "1.6.0" description = "onGuard24 — модульный сервис (аналог IRM)" readme = "README.md" requires-python = ">=3.11"