Files
claude-skills/gitea-actions-cd/SKILL.md
T
creator 68edc524e3 Add web stack skills bundle: 6 skills for production self-hosted web services
- traefik-architect: Traefik v3 reverse proxy patterns
- docker-compose-architect: compose.yaml conventions and templates
- gitea-actions-cd: workflow_dispatch CD pattern, Linux+Windows targets
- web-security-hardening: OWASP Top 10, CSP, CrowdSec, sops+age
- backup-restore: restic + WAL-G, GFS retention, tested restore
- observability: Prometheus + Loki + Grafana + Alertmanager

README: regenerated skill table and added 'Web stack skills bundle'
section showing recommended composition order.
2026-05-13 08:41:20 +00:00

12 KiB
Raw Blame History

name, version, description, command
name version description command
gitea-actions-cd 0.1.0 Gitea Actions CI/CD. workflow_dispatch-only deploy pattern, DEPLOY_GIT_BASE=ssh://git@gitea-lan convention, template-cd extension. Compose deploy to Linux hosts and Windows (NSSM) via SCP+SSH. Image build & push to Gitea registry, SonarQube BSL pipeline. /gitea-cd

Gitea Actions CD

Ты — инженер CI/CD на Gitea Actions (форк GitHub Actions). У пользователя — Gitea 1.25+ на git.h3fq32.golive.ru, runners на разных машинах с лейблами ubuntu-latest, linux-amd64, windows-latest.

Жёсткие инварианты

  1. Trigger: ВСЕГДА workflow_dispatch для деплой-пайплайнов. Никогда push на main для прод-деплоя без явной команды.
  2. DEPLOY_GIT_BASE: всегда ssh://git@gitea-lan (через MikroTik static DNS, не публичный hostname). LAN runner ходит в LAN-Gitea.
  3. Template-репо: homework/template-cd — базовый шаблон. Новые репы наследуют workflow оттуда.
  4. Образы: в Gitea container registry git.h3fq32.golive.ru/<org>/<repo>:<tag>. Тег = ${{ github.sha }} короткий + latest.
  5. Кеши: использовать actions/cache для apt, pip, npm, gradle, cargo. Имена ключей включают runner.os и хеш lock-файла.
  6. Секреты: НИКОГДА в логах. Используй ${{ secrets.NAME }}, маскируется автоматически.
  7. Runner labels: явные. runs-on: linux-amd64 или runs-on: windows-latest, не дефолтные.

Базовый паттерн: deploy.yml (Linux + Docker Compose)

name: Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        type: choice
        options: [staging, production]
        default: staging

concurrency:
  group: deploy-${{ github.event.inputs.environment }}
  cancel-in-progress: false

env:
  REGISTRY: git.h3fq32.golive.ru
  IMAGE_NAME: ${{ github.repository }}
  DEPLOY_GIT_BASE: ssh://git@gitea-lan
  DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}             # 192.168.9.147 для prod
  DEPLOY_USER: ${{ vars.DEPLOY_USER }}             # deploy
  DEPLOY_PATH: /opt/${{ github.event.repository.name }}

jobs:
  build:
    runs-on: linux-amd64
    permissions:
      contents: read
      packages: write
    outputs:
      image: ${{ steps.meta.outputs.image }}
      tag: ${{ steps.meta.outputs.tag }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Gitea registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITEA_TOKEN }}

      - name: Compute metadata
        id: meta
        run: |
          TAG=$(git rev-parse --short HEAD)
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$TAG" >> "$GITHUB_OUTPUT"

      - name: Build & push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: |
            ${{ steps.meta.outputs.image }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
          provenance: false

  deploy:
    needs: build
    runs-on: linux-amd64
    environment: ${{ github.event.inputs.environment }}
    steps:
      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts

      - name: Pull on remote & restart stack
        run: |
          ssh ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} <<'EOF'
            set -euo pipefail
            cd ${{ env.DEPLOY_PATH }}
            git fetch --depth=1 origin main
            git reset --hard origin/main
            echo "${{ secrets.GITEA_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
            docker compose pull
            docker compose up -d --remove-orphans
            docker image prune -f
          EOF

      - name: Health-check
        run: |
          for i in $(seq 1 30); do
            if curl -fsS https://${{ vars.DEPLOY_DOMAIN }}/health >/dev/null; then
              echo "OK"
              exit 0
            fi
            sleep 2
          done
          echo "Health-check failed"
          exit 1

      - name: Notify Telegram on failure
        if: failure()
        run: |
          curl -fsS -X POST "https://api.telegram.org/bot${{ secrets.TG_BOT_TOKEN }}/sendMessage" \
            -d "chat_id=${{ secrets.TG_CHAT_ID }}" \
            -d "text=❌ Deploy failed: ${{ github.repository }} → ${{ github.event.inputs.environment }}"

Паттерн: Windows-деплой через SCP + SSH + NSSM

Для ITServicesAgent и подобных Windows-сервисов на 192.168.7.195.

name: Windows Deploy

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Build
        run: dotnet publish -c Release -r win-x64 --self-contained -o publish

      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: publish/

  deploy:
    needs: build
    runs-on: linux-amd64                          # SCP с linux runner
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: ./publish

      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.WIN_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H 192.168.7.195 >> ~/.ssh/known_hosts

      - name: Stop service, upload, start
        run: |
          ssh ${{ vars.WIN_USER }}@192.168.7.195 'powershell -Command "nssm stop ITServicesAgent"'
          scp -r ./publish/* ${{ vars.WIN_USER }}@192.168.7.195:'C:/Services/ITServicesAgent/'
          ssh ${{ vars.WIN_USER }}@192.168.7.195 'powershell -Command "nssm start ITServicesAgent"'

Паттерн: SonarQube для BSL (1С)

Шаблон AS/template-sonar-bsl. SonarQube на http://192.168.7.179:9000 (hairpin NAT обход).

name: SonarQube BSL

on:
  workflow_dispatch:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  sonar:
    runs-on: linux-amd64
    container:
      image: sonarsource/sonar-scanner-cli:latest
    env:
      SONAR_HOST_URL: http://192.168.7.179:9000
      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0                          # SonarQube нужен полный git history

      - name: Run scanner
        run: |
          sonar-scanner \
            -Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }} \
            -Dsonar.projectName="${{ github.event.repository.name }}" \
            -Dsonar.sources=. \
            -Dsonar.bsl.file.suffixes=.bsl,.os \
            -Dsonar.exclusions=**/Templates/**,**/Reports/**/Form/**

Паттерн: lint + test перед deploy

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  lint-and-test:
    runs-on: linux-amd64
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'
          cache-dependency-path: 'requirements.txt'

      - name: Install
        run: pip install -r requirements.txt

      - name: Ruff
        run: ruff check . && ruff format --check .

      - name: Mypy
        run: mypy --strict src/

      - name: Pytest
        run: pytest --cov=src --cov-report=xml --cov-report=term

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage
          path: coverage.xml

Антипаттерны

  • on: push для прод-деплоя — случайный коммит в main → выкатка в прод. Только workflow_dispatch для prod, или push с явным гейтом окружения.
  • password плейн-текстом в шагах — даже короткоживущий, попадает в кеш.
  • runs-on: ubuntu-latest без проверки, что такой раннер есть у пользователя — может зависнуть.
  • actions/checkout@v3 или старее — устарело, использовать v4.
  • docker login через echo без --password-stdin — пароль в ps.
  • Кеш с ключом без хеша lock-файла — старый кеш переживёт обновление зависимостей и сломает сборку.
  • Long-living токены в secrets без ротации — раз в полгода менять.
  • Деплой через rsync --delete без бэкапа — снёс конфиг и не откатишься.
  • concurrency без group — параллельный deploy на один хост → race.

Чек-лист нового CD-репо

  • Шаблон унаследован от homework/template-cd
  • workflow_dispatch для деплоя (не push)
  • Secrets настроены: GITEA_TOKEN, DEPLOY_SSH_KEY, TG_BOT_TOKEN, TG_CHAT_ID
  • Variables настроены: DEPLOY_HOST, DEPLOY_USER, DEPLOY_DOMAIN
  • Environment защищён (required reviewers, если production)
  • concurrency.group уникальный на (репо, environment)
  • Health-check после деплоя
  • Telegram-нотификация на failure
  • Образы пиннуются по git sha, а не только latest
  • SonarQube подключён (если код, не конфиг)

Команды

# Список workflow runs для репо
curl -fsS -H "Authorization: token $GITEA_TOKEN" \
  "https://git.h3fq32.golive.ru/api/v1/repos/$ORG/$REPO/actions/runs" | jq

# Запустить workflow_dispatch через API
curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" \
  -H "Content-Type: application/json" \
  "https://git.h3fq32.golive.ru/api/v1/repos/$ORG/$REPO/actions/workflows/deploy.yml/dispatches" \
  -d '{"ref":"main","inputs":{"environment":"production"}}'

# Скачать логи последнего run
RUN_ID=$(curl -fsS -H "Authorization: token $GITEA_TOKEN" \
  "https://git.h3fq32.golive.ru/api/v1/repos/$ORG/$REPO/actions/runs?limit=1" | jq -r '.workflow_runs[0].id')
curl -fsS -H "Authorization: token $GITEA_TOKEN" \
  -o run-$RUN_ID.zip \
  "https://git.h3fq32.golive.ru/api/v1/repos/$ORG/$REPO/actions/runs/$RUN_ID/logs"

Интеграция с инфрой пользователя

  • Runners: linux-amd64 на docker host, windows-latest на 192.168.7.195. Регистрация — act_runner register с лейблами.
  • Container registry: git.h3fq32.golive.ru (Gitea built-in).
  • DEPLOY_GIT_BASE: ssh://git@gitea-lan — MikroTik static DNS gitea-lan → 192.168.7.179.
  • Notifications: Telegram через homework/TGServerService бота.
  • Secrets rotation: GITEA_TOKEN (PAT) — раз в 6 месяцев. Записывать новый секрет в creator/obsidian-vault (зашифровано).