diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c76835a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,50 @@ +# Build context = repository root (deploy/docker/docker-bake.hcl). +.git +.gitea +.github +.cursor +.claude +.codegraph +.agents +memory-bank +.vscode +.idea + +**/.DS_Store +**/Thumbs.db +**/.env +**/.env.* +!**/.env.example +!**/.env.*.example + +node_modules +**/node_modules +**/dist +apps/web/src/routeTree.gen.ts +apps/web/playwright-report +apps/web/test-results + +*.md +AGENTS.md +CONTRIBUTING.md +LICENSE +docs + +.pre-commit-config.yaml +.releaserc.json +.commitlintrc.* +commitlint.config.cjs +redocly.yaml +package-lock.json + +data +*.exe +**/*.test.ts +**/*.spec.ts +coverage +.coverage +*.out +.release-version +CHANGELOG.md +deploy/docker/docker-bake.override.hcl +deploy/compose/runtime-logs diff --git a/.gitea/README.md b/.gitea/README.md new file mode 100644 index 0000000..3a61f3a --- /dev/null +++ b/.gitea/README.md @@ -0,0 +1,80 @@ +# Gitea Actions + +| Workflow | Когда | Что | +|----------|--------|-----| +| [workflows/ci.yaml](workflows/ci.yaml) | pull request в main/master | quality gates + commitlint | +| [workflows/cd.yaml](workflows/cd.yaml) | push в main/master | quality gates + semantic-release + docker push | +| [workflows/quality.yaml](workflows/quality.yaml) | reusable (`workflow_call`) | changes, openapi, web, api, commitlint, docker-check | + +Подробнее: [docs/releasing.md](../docs/releasing.md). + +## CI (quality gates) + +Job **changes** вычисляет флаги по путям в diff. Полный прогон: `.gitea/workflows/*`, `scripts/*`, корневой `package.json` / `pnpm-lock.yaml` / `.releaserc.json`. Правки `.cursor/`, `.claude/`, `*.md` (кроме `docs/openapi.yaml`) quality jobs не запускают. + +На **pull request** — **commitlint**. При изменении `deploy/docker/**` / `.dockerignore` — job **docker-check** (`bake --print`, bake без `--push` если есть доступ к registry). + +Кэш зависимостей — нативный `actions/cache` (cache server act_runner), ключ `sha256sum` lockfile (не `hashFiles`). Пути **абсолютные** (`$HOME/.pnpm-store`): тильда `~` на Gitea часто не раскрывается и даёт вечный miss. + +Кэшируется целиком: pnpm store + `node_modules` + corepack. При hit: `pnpm install --offline`. + +Если restore пишет `connect ECONNREFUSED` / `cache server not configured` — на runner включите cache server (см. ниже). Иначе каждый job снова качает пакеты (~минуты). + +Runner: `ubuntu-latest`, Docker для **docker-check** (PR) и **publish** (CD). + +## CD (job publish) + +После успешных quality gates на **push в main** job **publish**: + +1. `pnpm exec semantic-release` — тег `vX.Y.Z` на **текущий commit** (без дополнительного commit в main). +2. Gitea Release + `CHANGELOG.md` как attachment (не в git). +3. Зеркало base-образов в `evofw-buildcache:base-*` (`deploy/docker/mirror-base-images.sh`; skip существующих тегов, `linux/amd64`, retry при 429). +4. `docker buildx bake default --push` с `VERSION=X.Y.Z`, `pull=false`, named builder `evofw` (`cleanup: false`). + +Если releasable-коммитов нет — semantic-release no-op, образы не публикуются. + +Повтор упавшего **publish** (тег уже есть, bake нет): detect берёт `v*` на `HEAD` и всё равно пушит образы. Подробнее: [docs/releasing.md](../docs/releasing.md#перезапуск-упавшего-job-publish). + +### Секреты + +**`ACTIONS_PAT`**: push tags, releases, Container Registry. Для git tag fallback: `github.token`. Push OCI — **только PAT** (у `GITEA_TOKEN` нет права packages). + +### Теги образов + +```text +git.shx.one//evofw:latest +git.shx.one//evofw:v1.2.3 +git.shx.one//evofw:1.2.3 +git.shx.one//evofw: +git.shx.one//evofw:sha- +``` + +Тот же манифест публикуется как `evofirewall` (drop-in для старого compose). + +Кэш сборки: `evofw-buildcache:node-buildcache` и `evofw-buildcache:base-*`. + +Пример: + +```bash +docker pull git.shx.one/denozord/evofw:1.2.3 +``` + +См. [deploy/docker/README.md](../deploy/docker/README.md). + +## act_runner: cache server + +`actions/cache` ходит в **встроенный cache server** runner (не GitHub `type=gha`). Кэш локален для этого runner. + +В `config.yaml` runner: + +```yaml +cache: + enabled: true + dir: "" # по умолчанию $HOME/.cache/actcache + host: "" # IP, доступный из job-контейнера (не 0.0.0.0) + port: 8088 +``` + +Если runner в Docker, а jobs — отдельные контейнеры: пробросьте порт и задайте `host` (LAN IP хоста) или `external_server: "http://:8088/"`. Иначе restore — timeout/ECONNREFUSED и пакеты качаются снова. + +Не делайте `docker system prune -a` по cron: сотрётся и Docker-кэш FROM, и пользы от `cleanup: false` у buildx не будет. diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml new file mode 100644 index 0000000..b46f6f0 --- /dev/null +++ b/.gitea/workflows/cd.yaml @@ -0,0 +1,149 @@ +name: CD + +on: + push: + branches: [main, master] + +permissions: + contents: read + +jobs: + quality: + uses: ./.gitea/workflows/quality.yaml + with: + is_pull_request: false + before_sha: ${{ github.event.before }} + head_sha: ${{ github.sha }} + allow_registry_login: false + secrets: + ACTIONS_PAT: ${{ secrets.ACTIONS_PAT }} + + publish: + needs: [quality] + if: >- + always() && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && + needs.quality.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + releases: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + fetch-tags: true + token: ${{ secrets.ACTIONS_PAT || gitea.token }} + persist-credentials: true + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "22" + - name: Export cache paths + run: sh scripts/ci/export-cache-env.sh + - id: pnpm-hash + run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - id: pnpm-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ${{ env.PNPM_STORE_DIR }} + ${{ env.COREPACK_HOME }} + node_modules + apps/web/node_modules + apps/api/node_modules + packages/ui/node_modules + packages/shared/node_modules + packages/db/node_modules + key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }} + restore-keys: | + pnpm-${{ runner.os }}- + - name: Install release tooling + env: + PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }} + run: sh scripts/ci/pnpm-ci.sh + - name: Verify releasable commit messages + run: pnpm exec node scripts/commit/verify-release-commits.mjs + - name: Semantic release + run: pnpm exec semantic-release + env: + GITEA_URL: https://git.shx.one + GITEA_TOKEN: ${{ secrets.ACTIONS_PAT || gitea.token }} + - name: Detect new release + id: rel + run: | + set -euo pipefail + version="" + if [ -f .release-version ]; then + version="$(tr -d '[:space:]' < .release-version)" + echo "New release from semantic-release: $version" + else + git fetch --tags --force origin || true + tag="$(git tag --points-at HEAD --list 'v*.*.*' | sort -V | tail -n1 || true)" + if [ -n "${tag:-}" ]; then + version="${tag#v}" + echo "Reuse existing tag $tag on HEAD (release retry)" + fi + fi + if [ -n "${version:-}" ]; then + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "released=true" >> "$GITHUB_OUTPUT" + else + echo "released=false" >> "$GITHUB_OUTPUT" + echo "No releasable commits — skipping image publish" + fi + - name: Set up Docker Buildx + if: steps.rel.outputs.released == 'true' + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + with: + name: evofw + driver: docker-container + cleanup: false + - name: Prepare image metadata + if: steps.rel.outputs.released == 'true' + id: meta + run: | + set -euo pipefail + echo "version=${{ steps.rel.outputs.version }}" >> "$GITHUB_OUTPUT" + owner_lc="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" + echo "owner_lc=$owner_lc" >> "$GITHUB_OUTPUT" + short_sha="$(echo '${{ github.sha }}' | cut -c1-7)" + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + echo "build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + - name: Log in to Gitea Registry + if: steps.rel.outputs.released == 'true' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: git.shx.one + username: ${{ gitea.actor }} + password: ${{ secrets.ACTIONS_PAT }} + - name: Mirror base images into buildcache + if: steps.rel.outputs.released == 'true' + env: + REGISTRY: git.shx.one/${{ steps.meta.outputs.owner_lc }} + MIRROR_ENV_FILE: ${{ runner.temp }}/mirror-base.env + run: sh deploy/docker/mirror-base-images.sh + - name: Build and push images (bake) + if: steps.rel.outputs.released == 'true' + env: + REGISTRY: git.shx.one/${{ steps.meta.outputs.owner_lc }} + IMAGE_TAG: latest + VERSION: ${{ steps.meta.outputs.version }} + SHORT_SHA: ${{ steps.meta.outputs.short_sha }} + SHA_FULL: ${{ github.sha }} + BUILD_TIME: ${{ steps.meta.outputs.build_time }} + CACHE_REF_NODE: git.shx.one/${{ steps.meta.outputs.owner_lc }}/evofw-buildcache:node-buildcache + BUILDX_BAKE_ENTITLEMENTS_FS: "0" + BUILDX_BAKE_FILE_RELATIVE_PATHS: "1" + MIRROR_ENV_FILE: ${{ runner.temp }}/mirror-base.env + working-directory: deploy/docker + run: | + set -euxo pipefail + if [ -f "${MIRROR_ENV_FILE}" ]; then + set -a + # shellcheck disable=SC1090 + . "${MIRROR_ENV_FILE}" + set +a + fi + docker buildx bake --allow=fs.read="${{ github.workspace }}" \ + -f docker-bake.hcl default --push diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..ba99831 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + branches: [main, master] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + quality: + uses: ./.gitea/workflows/quality.yaml + with: + is_pull_request: true + base_sha: ${{ github.event.pull_request.base.sha }} + head_sha: ${{ github.event.pull_request.head.sha }} + allow_registry_login: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + secrets: + ACTIONS_PAT: ${{ secrets.ACTIONS_PAT }} diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml deleted file mode 100644 index 7f0056c..0000000 --- a/.gitea/workflows/docker.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Build and Push EvoFirewall Docker Image - -on: - push: - branches: [main, develop, 'feature/**', 'release/**', 'hotfix/**'] - tags: ['v*'] - paths: ['**'] - pull_request: - branches: [main, develop] - paths: ['**'] - -jobs: - build-and-push: - if: startsWith(gitea.ref, 'refs/tags/v') || (gitea.ref_name == 'main' && gitea.event_name == 'push') - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Gitea Registry - uses: docker/login-action@v3 - with: - registry: git.shx.one - username: ${{ gitea.actor }} - password: ${{ secrets.ACTIONS_PAT }} - - - name: Create version file - run: | - VERSION=$(cat VERSION) - BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - BRANCH="${{ gitea.ref_name }}" - echo "APP_VERSION=${VERSION}" > ./version.txt - echo "BUILD_DATE=${BUILD_DATE}" >> ./version.txt - echo "GIT_BRANCH=${BRANCH}" >> ./version.txt - echo "GIT_COMMIT=${{ gitea.sha }}" >> ./version.txt - echo "GIT_COMMIT_SHORT=$(echo ${{ gitea.sha }} | cut -c1-7)" >> ./version.txt - echo "BUILD_TIMESTAMP=$(date -u +%s)" >> ./version.txt - - # Docker registry refs must be lowercase (repo name is EvoFirewall). - - name: Lowercase image repository - id: image - run: echo "repo=$(echo '${{ gitea.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: git.shx.one/${{ steps.image.outputs.repo }} - tags: | - type=semver,pattern={{version}} - type=raw,value=latest,enable=${{ gitea.ref_name == 'main' }} - type=sha,prefix={{date 'YYYYMMDD'}}-,enable=${{ gitea.ref_name == 'main' }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - NODE_VERSION=22.23.0-bookworm-slim - cache-from: type=registry,ref=git.shx.one/${{ steps.image.outputs.repo }}:buildcache - cache-to: type=registry,ref=git.shx.one/${{ steps.image.outputs.repo }}:buildcache,mode=max - provenance: true - - create-release: - needs: build-and-push - if: startsWith(gitea.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate changelog - id: changelog - run: | - LAST_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - if [[ -n "$LAST_TAG" ]]; then - CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${LAST_TAG}..HEAD) - else - CHANGELOG=$(git log --pretty=format:"- %s (%h)" -n 20) - fi - echo "CHANGELOG<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Create Gitea Release - run: | - curl -X POST \ - -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ - -H "Content-Type: application/json" \ - "https://git.shx.one/api/v1/repos/${{ gitea.repository }}/releases" \ - -d "{\"tag_name\":\"${{ gitea.ref_name }}\",\"name\":\"${{ gitea.ref_name }}\",\"body\":$(echo '${{ steps.changelog.outputs.CHANGELOG }}' | jq -Rs .)}" diff --git a/.gitea/workflows/quality.yaml b/.gitea/workflows/quality.yaml new file mode 100644 index 0000000..8b6a08f --- /dev/null +++ b/.gitea/workflows/quality.yaml @@ -0,0 +1,336 @@ +# Quality gates (reusable). Callers: ci.yaml (PR), cd.yaml (push main). +name: quality + +on: + workflow_call: + inputs: + is_pull_request: + type: boolean + required: true + base_sha: + type: string + required: false + default: "" + head_sha: + type: string + required: false + default: "" + before_sha: + type: string + required: false + default: "" + allow_registry_login: + type: boolean + required: false + default: false + secrets: + ACTIONS_PAT: + required: false + +permissions: + contents: read + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + openapi: ${{ steps.detect.outputs.openapi }} + web: ${{ steps.detect.outputs.web }} + api: ${{ steps.detect.outputs.api }} + docker: ${{ steps.detect.outputs.docker }} + steps: + - if: ${{ inputs.is_pull_request }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - if: ${{ inputs.is_pull_request == false }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 2 + - id: detect + name: Detect changed paths per module + env: + IS_PR: ${{ inputs.is_pull_request }} + BASE_SHA: ${{ inputs.base_sha }} + HEAD_SHA: ${{ inputs.head_sha }} + BEFORE_SHA: ${{ inputs.before_sha }} + run: | + set -euo pipefail + + openapi=false + web=false + api=false + docker=false + + set_all_flags_true() { + openapi=true + web=true + api=true + docker=true + } + + write_outputs() { + for v in openapi web api docker; do + eval "echo \"\$v=\$$v\"" >> "$GITHUB_OUTPUT" + done + } + + if [ "$IS_PR" = "true" ]; then + FILES="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")" + else + after="${HEAD_SHA:-$(git rev-parse HEAD)}" + before="$BEFORE_SHA" + if [ -n "$before" ] && [ "$before" != "0000000000000000000000000000000000000000" ]; then + FILES="$(git diff --name-only "$before" "$after")" + elif git rev-parse --verify HEAD~1 >/dev/null 2>&1; then + FILES="$(git diff --name-only HEAD~1 HEAD)" + else + set_all_flags_true + write_outputs + echo "No parent commit — full pipeline (all modules)" + exit 0 + fi + fi + + if [ -z "$(printf '%s' "$FILES" | tr -d '[:space:]')" ]; then + set_all_flags_true + write_outputs + echo "Empty diff — full pipeline fallback" + exit 0 + fi + + full_pipeline=false + + while IFS= read -r f || [ -n "${f:-}" ]; do + [ -z "${f:-}" ] && continue + case "$f" in + .gitea/workflows/*|scripts/*) + full_pipeline=true + ;; + docs/openapi.yaml|redocly.yaml) + openapi=true + ;; + .cursor/*|.claude/*|.codegraph/*|.agents/*) + ;; + *.md|AGENTS.md) + ;; + apps/web/README.md|apps/web/components.json|packages/ui/components.json) + ;; + apps/web/*|packages/ui/*) + web=true + ;; + apps/api/*|packages/db/*) + api=true + ;; + packages/shared/*) + web=true + api=true + ;; + deploy/compose/*|deploy/docker/*|.dockerignore) + docker=true + ;; + docs/*) + ;; + package.json|pnpm-lock.yaml|pnpm-workspace.yaml|turbo.json|.releaserc.json|commitlint.config.cjs) + full_pipeline=true + ;; + *) + ;; + esac + done <<< "$FILES" + + if $full_pipeline; then + set_all_flags_true + fi + + write_outputs + + echo "Changed files (first 30):" + printf '%s\n' "$FILES" | head -n 30 + echo "--- flags ---" + echo "openapi=$openapi web=$web api=$api docker=$docker full_pipeline=$full_pipeline" + + openapi: + needs: [changes] + if: needs.changes.outputs.openapi == 'true' || needs.changes.outputs.web == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "22" + - name: Export cache paths + run: sh scripts/ci/export-cache-env.sh + - id: pnpm-hash + run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - id: pnpm-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ${{ env.PNPM_STORE_DIR }} + ${{ env.COREPACK_HOME }} + node_modules + apps/web/node_modules + apps/api/node_modules + packages/ui/node_modules + packages/shared/node_modules + packages/db/node_modules + key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }} + restore-keys: | + pnpm-${{ runner.os }}- + - name: pnpm install, Redocly + env: + PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }} + run: | + set -euxo pipefail + sh scripts/ci/pnpm-ci.sh + pnpm exec redocly lint docs/openapi.yaml + + web: + needs: [changes] + if: needs.changes.outputs.web == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "22" + - name: Export cache paths + run: sh scripts/ci/export-cache-env.sh + - id: pnpm-hash + run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - id: pnpm-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ${{ env.PNPM_STORE_DIR }} + ${{ env.COREPACK_HOME }} + node_modules + apps/web/node_modules + apps/api/node_modules + packages/ui/node_modules + packages/shared/node_modules + packages/db/node_modules + key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }} + restore-keys: | + pnpm-${{ runner.os }}- + - name: pnpm install, typecheck, build + env: + PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }} + run: | + set -euxo pipefail + sh scripts/ci/pnpm-ci.sh + pnpm --filter @evofw/web run typecheck + pnpm --filter @evofw/web run build + + api: + needs: [changes] + if: needs.changes.outputs.api == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "22" + - name: Export cache paths + run: sh scripts/ci/export-cache-env.sh + - id: pnpm-hash + run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - id: pnpm-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ${{ env.PNPM_STORE_DIR }} + ${{ env.COREPACK_HOME }} + node_modules + apps/web/node_modules + apps/api/node_modules + packages/ui/node_modules + packages/shared/node_modules + packages/db/node_modules + key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }} + restore-keys: | + pnpm-${{ runner.os }}- + - name: pnpm install, test, build + env: + PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }} + run: | + set -euxo pipefail + sh scripts/ci/pnpm-ci.sh + pnpm --filter @evofw/api test + pnpm --filter @evofw/api build + + commitlint: + if: inputs.is_pull_request + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "22" + - name: Export cache paths + run: sh scripts/ci/export-cache-env.sh + - id: pnpm-hash + run: echo "key=$(sha256sum pnpm-lock.yaml | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - id: pnpm-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ${{ env.PNPM_STORE_DIR }} + ${{ env.COREPACK_HOME }} + node_modules + apps/web/node_modules + apps/api/node_modules + packages/ui/node_modules + packages/shared/node_modules + packages/db/node_modules + key: pnpm-${{ runner.os }}-${{ steps.pnpm-hash.outputs.key }} + restore-keys: | + pnpm-${{ runner.os }}- + - name: Lint commit messages + env: + BASE_SHA: ${{ inputs.base_sha }} + HEAD_SHA: ${{ inputs.head_sha }} + PNPM_CACHE_HIT: ${{ steps.pnpm-cache.outputs.cache-hit }} + run: | + set -euxo pipefail + sh scripts/ci/pnpm-ci.sh + pnpm exec commitlint --from "$BASE_SHA" --to "$HEAD_SHA" + + docker-check: + needs: [changes] + if: inputs.is_pull_request && needs.changes.outputs.docker == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + with: + name: evofw + driver: docker-container + cleanup: false + - name: Log in to Gitea Registry + if: inputs.allow_registry_login + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: git.shx.one + username: ${{ gitea.actor }} + password: ${{ secrets.ACTIONS_PAT }} + - name: bake --print + working-directory: deploy/docker + env: + BUILDX_BAKE_ENTITLEMENTS_FS: "0" + BUILDX_BAKE_FILE_RELATIVE_PATHS: "1" + run: docker buildx bake --allow=fs.read="${{ github.workspace }}" -f docker-bake.hcl --print default + - name: bake (no push) + if: inputs.allow_registry_login + working-directory: deploy/docker + env: + BUILDX_BAKE_ENTITLEMENTS_FS: "0" + BUILDX_BAKE_FILE_RELATIVE_PATHS: "1" + run: | + set -euxo pipefail + owner_lc="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" + export CACHE_REF_NODE="git.shx.one/${owner_lc}/evofw-buildcache:node-buildcache" + docker buildx bake --allow=fs.read="${{ github.workspace }}" -f docker-bake.hcl default diff --git a/.gitignore b/.gitignore index a534cf1..23a4e08 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ Thumbs.db # Build version.txt +.release-version +CHANGELOG.md +deploy/docker/docker-bake.override.hcl *.tsbuildinfo # Local MCP configs (may contain REUI license Bearer) .cursor/mcp.json diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..47cd6ea --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,50 @@ +{ + "branches": ["main", "master"], + "tagFormat": "v${version}", + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { "type": "feat", "release": "minor" }, + { "type": "fix", "release": "patch" }, + { "type": "perf", "release": "patch" }, + { "type": "ci", "release": "patch" }, + { "type": "refactor", "release": "patch" }, + { "breaking": true, "release": "major" } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + [ + "@semantic-release/exec", + { + "successCmd": "echo ${nextRelease.version} > .release-version" + } + ], + [ + "@markwylde/semantic-release-gitea", + { + "giteaUrl": "https://git.shx.one", + "assets": [ + { + "path": "CHANGELOG.md", + "label": "Changelog" + } + ] + } + ] + ] +} diff --git a/AGENTS.md b/AGENTS.md index 43838d4..511b23d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,11 @@ Skill: `.claude/skills/reui` (`668fb463eb`). ## Docs -`docs/architecture.md`, `docs/agents.md`, `docs/integrate-auth-portal.md`, `docs/integrate-evobgp.md`. +`docs/architecture.md`, `docs/agents.md`, `docs/integrate-auth-portal.md`, `docs/integrate-evobgp.md`, `docs/releasing.md`. + +## CI / Docker + +Gitea: `.gitea/workflows/{ci,cd,quality}.yaml`. Образы: `deploy/docker` (`node:22-alpine`, bake + registry cache). Не корневой `Dockerfile`. ## Git diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 57461b7..0000000 --- a/Dockerfile +++ /dev/null @@ -1,53 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG NODE_VERSION=22.23.0-bookworm-slim - -FROM node:${NODE_VERSION} AS build -WORKDIR /app -RUN apt-get update \ - && apt-get install -y --no-install-recommends python3 make g++ \ - && rm -rf /var/lib/apt/lists/* \ - && corepack enable - -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./ -COPY apps/web/package.json apps/web/ -COPY apps/api/package.json apps/api/ -COPY packages/ui/package.json packages/ui/ -COPY packages/shared/package.json packages/shared/ -COPY packages/db/package.json packages/db/ -RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ - pnpm install --frozen-lockfile - -COPY apps/web apps/web -COPY apps/api apps/api -COPY packages/ui packages/ui -COPY packages/shared packages/shared -COPY packages/db packages/db - -RUN pnpm turbo build --filter=@evofw/web --filter=@evofw/api \ - && pnpm --filter @evofw/api deploy --prod /out \ - && cp -r apps/web/dist /out/static \ - && mkdir -p /out/dist/agent-scripts \ - && cp -r apps/api/src/agent-scripts/* /out/dist/agent-scripts/ \ - && rm -rf /out/src /out/test /out/.turbo \ - && rm -rf /out/node_modules/@evofw/db/src /out/node_modules/@evofw/db/.turbo \ - && rm -rf /out/node_modules/@evofw/shared/src /out/node_modules/@evofw/shared/.turbo - -FROM node:${NODE_VERSION} -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app -ENV NODE_ENV=production \ - STATIC_DIR=/app/static \ - DATABASE_URL=sqlite:/data/app.db \ - SERVER_PORT=8080 - -COPY --from=build /out ./ - -EXPOSE 8080 -VOLUME ["/data"] -HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD ["node", "-e", "fetch('http://127.0.0.1:8080/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] -CMD ["node", "dist/server.js"] diff --git a/README.md b/README.md index 949e6f6..d8365ac 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,15 @@ curl -fsSL http://localhost:8080/v1/agent/install.sh | \ Затем Approve в UI `/agents`. +## Docker + +```bash +cd deploy/docker +docker buildx bake --allow=fs.read=../.. -f docker-bake.hcl --print default +``` + +Compose: [`deploy/compose/docker-compose.example.yaml`](deploy/compose/docker-compose.example.yaml). Образ: `git.shx.one//evofw` (алиас `evofirewall`). CI/релизы: [`docs/releasing.md`](docs/releasing.md). + ## Docs См. [`docs/README.md`](docs/README.md). diff --git a/VERSION b/VERSION deleted file mode 100644 index 6c6aa7c..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1.0 \ No newline at end of file diff --git a/apps/web/src/components/agents/add-agent-sheet.tsx b/apps/web/src/components/agents/add-agent-sheet.tsx index da3aa44..7bb9b44 100644 --- a/apps/web/src/components/agents/add-agent-sheet.tsx +++ b/apps/web/src/components/agents/add-agent-sheet.tsx @@ -169,7 +169,9 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) { title="Добавить агента" description="Создайте агента и короткую install-ссылку." form={form} - onSubmit={(values) => create.mutateAsync(values)} + onSubmit={async (values) => { + await create.mutateAsync(values) + }} footer={ <>