ci: Gitea Actions instead of Drone (workflow, README, secrets)
Some checks are pending
backend / test (push) Waiting to run
backend / lint (push) Waiting to run
backend / build-api (push) Waiting to run
backend / deploy (push) Blocked by required conditions

This commit is contained in:
edadmin 2026-10-18 23:22:02 +03:00
parent 519903b930
commit 4f254d9faa
6 changed files with 139 additions and 177 deletions

View File

@ -1,67 +0,0 @@
# Drone CI/CD бэкенда (сервер подключён к Gitea; см. README, «CI/CD»).
#
# Push/PR — только проверки и сборка образа. Деплой — ТОЛЬКО ручным
# запуском пайплайна из UI Drone (событие custom, кнопка Run Pipeline):
# шаг deploy выполняется исключительно при нём.
kind: pipeline
type: docker
name: ci-cd
clone:
depth: 1
# Сервисная база для интеграционных тестов: схему накатывает сам шаг
# test (cmd/migrate), тесты выполняют сценарии в транзакции и откатывают
# их — данные в базе не остаются.
services:
- name: postgres
image: postgres:16-alpine
environment:
POSTGRES_USER: building
POSTGRES_PASSWORD: building
POSTGRES_DB: building_mgmt_test
steps:
- name: test
# Полный golang-образ, не alpine: -race требует CGO и C-компилятора.
image: golang:1.27
environment:
DATABASE_URL: postgres://building:building@postgres:5432/building_mgmt_test?sslmode=disable
commands:
- go vet ./...
# Postgres в сервисе поднимается параллельно со сборкой шага:
# повторяем накат, пока база не готова (30 попыток по 2 секунды).
- ok=0; for i in $(seq 1 30); do if go run ./cmd/migrate; then ok=1; break; fi; sleep 2; done; test "$ok" = 1
- go test -race -count=1 ./...
- go build ./...
- name: lint
image: ghcr.io/golangci/golangci-lint:v2.14.0
commands:
- golangci-lint run ./...
- name: build-api
# Образ и два тега: latest (его поднимает compose.prod.yaml) и SHA
# (для отката: собрать нужный коммит и перетегнуть latest вручную).
image: plugins/docker:27
settings:
repo: building-mgmt-api
tags:
- latest
- ${DRONE_COMMIT_SHA}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: deploy
image: docker:27-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
# -p закрепляет имя проекта: у Drone каталог клонирования свой,
# без -p имя сети стека разъехалось бы между запусками, и web
# перестал бы находить api. env-файл — постоянный файл на сервере.
- docker compose version
- docker compose -p building-mgmt --env-file /opt/building-mgmt/.env -f compose.prod.yaml up -d --wait
when:
event:
- custom

View File

@ -3,7 +3,7 @@
# mkdir -p /opt/building-mgmt && cp .env.prod.example /opt/building-mgmt/.env # mkdir -p /opt/building-mgmt && cp .env.prod.example /opt/building-mgmt/.env
# chmod 600 /opt/building-mgmt/.env # секреты, только root # chmod 600 /opt/building-mgmt/.env # секреты, только root
# #
# Заполняется один раз и правится осознанно: стек и Drone читают его # Заполняется один раз и правится осознанно: стек и CI читают его
# по пути /opt/building-mgmt/.env. Пароли и секреты в git не входят. # по пути /opt/building-mgmt/.env. Пароли и секреты в git не входят.
# --- Обязательные переменные (compose упадёт без них: :? фейл-фаст) --- # --- Обязательные переменные (compose упадёт без них: :? фейл-фаст) ---

90
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,90 @@
# CI/CD бэкенда через Gitea Actions (встроенный в Gitea, раннер на сервере).
#
# push/PR: go vet → накат миграций в сервисную postgres → go test -race →
# go build; параллельно golangci-lint; затем сборка прод-образа api
# (локальные теги latest и короткий SHA, реестр не используется).
# Деплой — ТОЛЬКО ручной запуск workflow (кнопка Run workflow): compose-файлы
# берутся из дерева этого же коммита (actions/checkout), секреты не нужны.
# web перезапускает свой workflow в репо веба — репозитории независимы.
#
# Требования к раннеру (см. README, раздел «CI/CD (Gitea Actions)»):
# runner.labels: [docker] — совпадает с runs-on ниже;
# container.valid_volumes: /var/run/docker.sock, /opt/building-mgmt —
# сокет docker для сборки/деплоя и каталог стека с .env.
name: backend
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: docker
container: golang:1.27
# Сервисная база для интеграционных тестов: схему накатывает шаг
# test (cmd/migrate), тесты выполняют сценарии в транзакции и
# откатывают их — данные в базе не остаются.
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: building
POSTGRES_PASSWORD: building
POSTGRES_DB: building_mgmt_test
env:
DATABASE_URL: postgres://building:building@postgres:5432/building_mgmt_test?sslmode=disable
steps:
- uses: actions/checkout@v4
- name: go vet
run: go vet ./...
- name: Накат миграций (ждём сервисную postgres)
# Postgres поднимается параллельно с контейнером джобы:
# повторяем накат, пока база не готова (30 попыток по 2 секунды).
run: |
ok=0; for i in $(seq 1 30); do if go run ./cmd/migrate; then ok=1; break; fi; sleep 2; done; test "$ok" = 1
- name: Тесты
# -race требует CGO — полный golang-образ, не alpine.
run: go test -race -count=1 ./...
- name: Сборка
run: go build ./...
lint:
runs-on: docker
container: ghcr.io/golangci/golangci-lint:v2.14.0
steps:
- uses: actions/checkout@v4
- name: golangci-lint
run: golangci-lint run ./...
build-api:
runs-on: docker
container: docker:27-cli
steps:
- uses: actions/checkout@v4
- name: Сборка прод-образа api
# Образ и два тега: latest (его поднимает compose.prod.yaml) и
# короткий SHA (для отката: перетегнуть latest вручную).
run: |
short="$(echo "$GITHUB_SHA" | cut -c1-7)"
docker build -t building-mgmt-api:latest -t "building-mgmt-api:${short}" .
deploy:
if: github.event_name == 'workflow_dispatch'
needs: build-api
runs-on: docker
container: docker:27-cli
steps:
- uses: actions/checkout@v4
- name: Подъём db/minio/api стека
# compose-CLI ставим в джоб-контейнер; docker — через смонтированный
# сокет хоста (valid_volumes у раннера). -p закрепляет имя проекта:
# оно задаёт имя сети, и web из своего workflow находит api.
# web не трогаем — его перезапускает workflow репо веба.
run: |
apk add --no-cache docker-cli-compose
docker compose -p building-mgmt \
--env-file /opt/building-mgmt/.env \
-f compose.prod.yaml \
up -d --wait db minio api

View File

@ -892,13 +892,13 @@ Handlers не отдают тексты ошибок БД наружу — он
golangci-lint run ./... # или: make lint golangci-lint run ./... # или: make lint
``` ```
- **CI** — Drone (`.drone.yml`, сервер подключён к Gitea): `vet` → накат - **CI** — Gitea Actions (`.gitea/workflows/ci.yml`, встроенный в Gitea):
миграций на сервисный PostgreSQL → `go test -race ./...` → `go build` → `vet` → накат миграций на сервисный PostgreSQL → `go test -race ./...` →
`golangci-lint` → сборка docker-образа. Тесты в CI идут **на живой `go build` → `golangci-lint` → сборка docker-образа. Тесты в CI идут
базе**, а не скипаются: без `DATABASE_URL` интеграционные тесты **на живой базе**, а не скипаются: без `DATABASE_URL` интеграционные
пропускаются молча. `-race` на живой базе дороже, зато ловит гонки тесты пропускаются молча. `-race` на живой базе дороже, зато ловит
между пулом соединений, лимитером и горутинами хендлеров. Деплой в гонки между пулом соединений, лимитером и горутинами хендлеров. Деплой
прод — ручным запуском того же пайплайна (см. «CI/CD (Drone + Gitea)»). в прод — ручным запуском того же workflow (см. «CI/CD (Gitea Actions)»).
- **Команды** — `Makefile`: `run`, `build`, `test`, `race`, `vet`, `lint`, - **Команды** — `Makefile`: `run`, `build`, `test`, `race`, `vet`, `lint`,
`fmt`, `swag`, `migrate`, `seed`, `up`, `down`, `tidy`. На Windows без `fmt`, `swag`, `migrate`, `seed`, `up`, `down`, `tidy`. На Windows без
@ -1028,7 +1028,7 @@ cp /opt/building-mgmt-repo/.env.prod.example /opt/building-mgmt/.env
chmod 600 /opt/building-mgmt/.env chmod 600 /opt/building-mgmt/.env
$EDITOR /opt/building-mgmt/.env # пароли, секреты, домен — см. комментарии $EDITOR /opt/building-mgmt/.env # пароли, секреты, домен — см. комментарии
# 2. Образы (Drone собирает их сам; вручную — если CI ещё не подключён) # 2. Образы (CI собирает их сам; вручную — если CI ещё не подключён)
docker build -t building-mgmt-api:latest /opt/building-mgmt-repo docker build -t building-mgmt-api:latest /opt/building-mgmt-repo
docker build -t building-mgmt-web:latest <клон репо web> docker build -t building-mgmt-web:latest <клон репо web>
@ -1038,7 +1038,7 @@ docker compose -p building-mgmt \
-f /opt/building-mgmt-repo/compose.prod.yaml up -d --wait -f /opt/building-mgmt-repo/compose.prod.yaml up -d --wait
``` ```
`-p building-mgmt` обязателен и в ручных запусках, и в Drone: имя `-p building-mgmt` обязателен и в ручных запусках, и в CI: имя
проекта задаёт имя сети, разные имена разнесут web и api по разным проекта задаёт имя сети, разные имена разнесут web и api по разным
сетям, и прокси перестанет находить api. Проверка: `http://<домен>/ping` сетям, и прокси перестанет находить api. Проверка: `http://<домен>/ping`
→ `{"message":"pong"}`. `APP_ENV=production` (задан в шаблоне) блокирует → `{"message":"pong"}`. `APP_ENV=production` (задан в шаблоне) блокирует
@ -1076,50 +1076,59 @@ docker run --rm -v building-mgmt_miniodata:/data -v "$PWD":/backup alpine \
--- ---
## CI/CD (Drone + Gitea) ## CI/CD (Gitea Actions)
CI живёт в Drone: сервер подключён к Gitea, пайплайны — `.drone.yml` в CI/CD живёт во встроенном Gitea Actions: пайплайны —
обоих репозиториях (бэкенд и веб). GitHub Actions больше нет. `.gitea/workflows/ci.yml` в обоих репозиториях (бэкенд и веб), сервер —
тот же `axe.engir.by`. GitHub Actions и Drone не используются.
### Установка (однократно, на прод-сервере) ### Установка (однократно, на прод-сервере)
```bash ```ini
# В Gitea: Настройки → Applications → OAuth2-приложение, # app.ini Gitea (Gitea ≥ 1.21), затем перезапуск Gitea:
# Redirect URI: http://<хост-drone>:8081/authorize [actions]
mkdir -p /opt/drone && cp deploy/drone/compose.yaml /opt/drone/ ENABLED = true
# /opt/drone/.env: GITEA_SERVER, DRONE_GITEA_CLIENT_ID,
# DRONE_GITEA_CLIENT_SECRET, DRONE_GITEA_ADMIN_USERNAME,
# DRONE_RPC_SECRET= # openssl rand -hex 32
docker compose -p drone --env-file /opt/drone/.env \
-f /opt/drone/compose.yaml up -d
``` ```
Drone UI — `http://<хост>:8081` (за TLS-терминалом); оттуда же Gitea Раннер исполняет задачи в docker на этом же сервере. Регистрация —
шлёт webhook. Runner исполняет шаги пайплайнов в docker на этом же токеном из UI Gitea (Администрирование → Раннеры → Создать
сервере, одна задача за раз (`capacity=1` — деплои не пересекаются). регистрационный токен, метка `docker`), каталог `/opt/act-runner`; в
`config.yaml` раннера обязательны две вещи:
### Секреты пайплайна (UI Drone, репозиторий веба) ```yaml
runner:
labels: [docker] # совпадает с runs-on: docker в workflow
container:
valid_volumes: # сокет docker для сборки/деплоя образов и
- /var/run/docker.sock # каталог стека с постоянным .env
- /opt/building-mgmt
```
- `gitea_host`, `gitea_user`, `gitea_token` — доступ к приватному репо ### Секреты (Настройки репо → Секреты → Actions)
бэкенда: web-пайплайн клонирует его за токен, чтобы перезапустить
`web` тем же `compose.prod.yaml` (один стек, один проект).
Секреты стека (`/opt/building-mgmt/.env`) в Drone не хранятся: пайплайн - бэкенд (`axe`) — секреты не нужны: деплой берёт `compose.prod.yaml`
из дерева того же коммита (`actions/checkout`);
- веб (`axe-web`): `CI_HOST` (`axe.engir.by`), `CI_USER` (`edadmin`),
`CI_TOKEN` (PAT с правом `write:repository`) — web-workflow скачивает
тарбол репо бэкенда, чтобы перезапустить `web` тем же
`compose.prod.yaml` (один стек, один проект). Имена с префиксом
`GITEA_` Gitea запрещает — префикс зарезервирован.
Секреты стека (`/opt/building-mgmt/.env`) в Actions не хранятся: деплой
только читает файл на сервере флагом `--env-file`. только читает файл на сервере флагом `--env-file`.
### Что делает каждый пайплайн ### Что делает каждый workflow
- бэкенд (push/PR и ручной запуск): `vet` → `migrate` → `test -race` → - бэкенд (push/PR и ручной запуск): `vet` → `migrate` → `test -race` →
`go build` → `golangci-lint` → сборка образа `go build` → `golangci-lint` → сборка образа
`building-mgmt-api:latest` + тег по SHA; `building-mgmt-api:latest` + тег по SHA;
- веб (push/PR и ручной запуск): `node --check` js → сборка образа - веб (push/PR и ручной запуск): `node --check` js → сборка образа
`building-mgmt-web:latest` + тег по SHA; `building-mgmt-web:latest` + тег по SHA;
- **деплой — только ручным запуском** (кнопка **Run Pipeline** в UI, - **деплой — только ручным запуском** (кнопка **Run workflow**): бэкенд —
событие `custom`): бэкенд — `docker compose -p building-mgmt `docker compose -p building-mgmt --env-file /opt/building-mgmt/.env
--env-file /opt/building-mgmt/.env -f compose.prod.yaml up -d --wait` -f compose.prod.yaml up -d --wait db minio api` (пересоздаёт api новым
(пересоздаёт api новым образом, данные не трогает); веб — то же образом, данные не трогает); веб — то же с `--no-deps web`. Push в
с `--no-deps web`. Push в `main` образы собирает, но на сервер ничего `main` образы собирает, но на сервер ничего не ставит.
не ставит.
--- ---

View File

@ -2,7 +2,7 @@
# web (порт 80). Postgres и MinIO портов наружу не пробрасывают вовсе — # web (порт 80). Postgres и MinIO портов наружу не пробрасывают вовсе —
# они видны только контейнерам приложения. # они видны только контейнерам приложения.
# #
# Образы api и web заранее собраны Drone (или вручную): # Образы api и web заранее собраны CI (или вручную):
# docker build -t building-mgmt-api:latest . # в этом репо # docker build -t building-mgmt-api:latest . # в этом репо
# docker build -t building-mgmt-web:latest . # в репо web # docker build -t building-mgmt-web:latest . # в репо web
# #
@ -11,7 +11,7 @@
# --env-file /opt/building-mgmt/.env \ # --env-file /opt/building-mgmt/.env \
# -f compose.prod.yaml up -d --wait # -f compose.prod.yaml up -d --wait
# #
# Имя проекта (-p) закреплено: по нему считается имя сети. Drone и # Имя проекта (-p) закреплено: по нему считается имя сети. CI и
# ручной запуск обязаны давать одно и то же имя, иначе web не найдёт # ручной запуск обязаны давать одно и то же имя, иначе web не найдёт
# api и стек разъедется на два. # api и стек разъедется на два.
# #
@ -71,7 +71,7 @@ services:
api: api:
image: building-mgmt-api:latest image: building-mgmt-api:latest
# build: — на случай ручной сборки без Drone: если образа нет, # build: — на случай ручной сборки без CI: если образа нет,
# compose соберёт его из этого же репо; готовый образ не трогает. # compose соберёт его из этого же репо; готовый образ не трогает.
build: build:
context: . context: .

View File

@ -1,70 +0,0 @@
# Drone CI: server (интерфейс, webhook от Gitea) и docker-runner
# (исполняет шаги .drone.yml в контейнерах этого хоста).
#
# Перед запуском — в Gitea: Настройки → Applications → OAuth2-приложение,
# Redirect URI: http://<хост-drone>:8081/authorize
#
# .env рядом с этим файлом (в git не входит):
# GITEA_SERVER=https://gitea.example.com
# DRONE_GITEA_CLIENT_ID=...
# DRONE_GITEA_CLIENT_SECRET=...
# DRONE_GITEA_ADMIN_USERNAME=admin # аккаунт Gitea, станет админом Drone
# DRONE_SERVER_HOST=drone.example.com
# DRONE_SERVER_PROTO=https
# DRONE_RPC_SECRET= # openssl rand -hex 32
#
# Запуск: docker compose -p drone --env-file .env -f compose.yaml up -d
services:
drone:
image: drone/drone:2
restart: unless-stopped
ports:
# Веб-интерфейс и цель webhook от Gitea; за TLS-терминалом.
- "8081:80"
environment:
DRONE_GITEA_SERVER: ${GITEA_SERVER:?GITEA_SERVER is required}
DRONE_GITEA_CLIENT_ID: ${DRONE_GITEA_CLIENT_ID:?DRONE_GITEA_CLIENT_ID is required}
DRONE_GITEA_CLIENT_SECRET: ${DRONE_GITEA_CLIENT_SECRET:?DRONE_GITEA_CLIENT_SECRET is required}
# Общий секрет server ↔ runner: openssl rand -hex 32.
DRONE_RPC_SECRET: ${DRONE_RPC_SECRET:?DRONE_RPC_SECRET is required}
# Первый залогинившийся через Gitea становится админом Drone.
DRONE_USER_CREATE_OAUTH: username=${DRONE_GITEA_ADMIN_USERNAME:?DRONE_GITEA_ADMIN_USERNAME is required},admin=true
# Внешний адрес интерфейса: его видят Gitea (webhook) и браузер.
DRONE_SERVER_HOST: ${DRONE_SERVER_HOST:?DRONE_SERVER_HOST is required}
DRONE_SERVER_PROTO: ${DRONE_SERVER_PROTO:-https}
volumes:
- drone:/data
networks:
- drone
runner:
image: drone/drone-runner-docker:1
restart: unless-stopped
depends_on:
- drone
environment:
DRONE_RPC_PROTO: http
DRONE_RPC_HOST: drone
DRONE_RPC_SECRET: ${DRONE_RPC_SECRET:?DRONE_RPC_SECRET is required}
# Один конвейер за раз: деплои не должны пересекаться.
DRONE_RUNNER_CAPACITY: "1"
DRONE_RUNNER_NAME: prod-server
# Шаги пайплайнов (и их сервисы — postgres для тестов) поднимаются
# именно в этой сети: имя сети указано явно (name: drone), чтобы
# совпадало со значением DRONE_RUNNER_NETWORKS.
DRONE_RUNNER_NETWORKS: drone
volumes:
# Доступ к docker-демону: сборка образов, compose, деплой.
- /var/run/docker.sock:/var/run/docker.sock
networks:
- drone
networks:
# Имя фиксируем: DRONE_RUNNER_NETWORKS ссылается на него буквально,
# а префикс проекта (drone_drone) runner бы не узнал.
drone:
name: drone
volumes:
drone: