Skip to content

feat: add JWT secret configuration to Docker Compose files #289

feat: add JWT secret configuration to Docker Compose files

feat: add JWT secret configuration to Docker Compose files #289

Workflow file for this run

name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
# P2.1: bundlesize check. Фейлим pipeline, если main-bundle вырос > 1 MB gzipped,
# или любой chunk > 1.5 MB gzipped. Пороги консервативные; уточнить после baseline.
# UR bug_022: fail-fast если build не произвёл dist/assets (иначе safety-net
# беззвучно проходит при сломанном build/rename outDir).
- name: Frontend bundle size check
run: |
if [ ! -d ../internal/web/dist/assets ]; then
echo "::error::Frontend build output not found at ../internal/web/dist/assets"
exit 1
fi
found=$(find ../internal/web/dist/assets -type f \( -name '*.js' -o -name '*.css' \) | wc -l)
if [ "$found" -eq 0 ]; then
echo "::error::No bundle assets found (expected .js/.css in dist/assets)"
exit 1
fi
total_gz=0
oversized=0
while IFS= read -r f; do
size=$(gzip -c "$f" | wc -c)
total_gz=$((total_gz + size))
kb=$((size / 1024))
echo " ${f#../internal/web/dist/} ${kb}KB (gzipped)"
if [ "$size" -gt 1572864 ]; then
echo "::error file=${f}::Chunk $(basename "$f") = ${kb}KB gzipped > 1.5MB budget"
oversized=$((oversized + 1))
fi
done < <(find ../internal/web/dist/assets -type f \( -name '*.js' -o -name '*.css' \))
total_mb=$((total_gz / 1024 / 1024))
echo "Total bundle (gzipped): ${total_mb}MB across ${found} files"
if [ "$oversized" -gt 0 ]; then
exit 1
fi
if [ "$total_gz" -gt 5242880 ]; then
echo "::error::Total bundle ${total_mb}MB > 5MB hard limit"
exit 1
fi
working-directory: web
# golangci-lint через go install, чтобы бинарник собрался под
# установленный на runner'е Go (1.25.9). Action @v6 использует
# prebuilt-бинарник собранный под старый Go 1.24 → конфликт с
# go.mod 1.25.
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- name: Run golangci-lint
run: golangci-lint run --timeout=5m ./...
test:
name: Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: tjudge
POSTGRES_PASSWORD: secret
POSTGRES_DB: tjudge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download dependencies
run: go mod download
- name: Run migrations
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
run: |
go run cmd/migrations/main.go up
- name: Run tests
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
run: go test -v -coverprofile=coverage.out -covermode=atomic ./...
# P2.21 roadmap: текущий минимальный threshold — 45%. Целевой — 70% к Q3.
# Поднимать threshold нужно одновременно с добавлением тестов на новые
# packages (audit, idempotency, cache_control).
# Чтобы не ломать CI на старых PR, делаем step-by-step ratchet:
# 45 → 50 → 55 → 60 → 65 → 70.
- name: Check coverage threshold
run: |
grep -v '/tests/contract/mocks/' coverage.out > coverage-filtered.out || cp coverage.out coverage-filtered.out
TOTAL=$(go tool cover -func=coverage-filtered.out | grep '^total:' | awk '{print $3}' | tr -d '%')
echo "Total coverage: ${TOTAL}%"
if [ $(echo "$TOTAL < 45.0" | bc -l) -eq 1 ]; then
echo "::error::Coverage ${TOTAL}% is below minimum threshold of 45%"
exit 1
fi
echo "Coverage ${TOTAL}% meets the minimum threshold of 45%"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage-filtered.out
flags: unittests
name: codecov-umbrella
race:
name: Race Detection
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: tjudge
POSTGRES_PASSWORD: secret
POSTGRES_DB: tjudge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download dependencies
run: go mod download
- name: Run migrations
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
run: |
go run cmd/migrations/main.go up
- name: Run tests with race detector
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
run: go test -race -count=1 -timeout=10m ./...
- name: Build with race detector
run: go build -race ./cmd/api ./cmd/worker
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download Go dependencies
run: go mod download
- name: Build API
run: go build -v -o bin/api ./cmd/api
- name: Build Worker
run: go build -v -o bin/worker ./cmd/worker
- name: Build Migrations
run: go build -v -o bin/migrate ./cmd/migrations
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries
path: bin/
retention-days: 7
security:
name: Security Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
# P1.9: gosec блокирует pipeline на MEDIUM-severity findings (и выше).
# Все legacy-issues разобраны:
# G301 (dir perms) → 0o750.
# G302/G306 (exec bit)→ 0o750 + #nosec с обоснованием (bot/wrapper
# требуют execute).
# G304 (os.Open var) → #nosec (paths UUID-controlled или
# absUploadDir-validated).
# G204 (exec var) → #nosec (hardcoded binary names, args без
# user-input).
#
# Используем go-install latest вместо securego/gosec-action, т.к. старый
# docker-образ (2.21.4) ставит exit-code по ВСЕМ findings независимо от
# -severity фильтра → false-positive fail на legacy-коде.
- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
- name: Run gosec (text output для логов)
run: gosec -severity medium -confidence medium ./...
- name: Run gosec (SARIF для Code Scanning)
if: always()
run: gosec -severity medium -confidence medium -fmt sarif -out results.sarif ./... || true
- name: Upload SARIF file
# Requires Code Scanning to be enabled: Settings → Code security → Code scanning
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
category: gosec
continue-on-error: true
# P1.9: govulncheck блокирующий для обычных CVE.
#
# Два accepted-risk advisories в docker/docker (Moby daemon AuthZ):
# GO-2026-4887, GO-2026-4883 — daemon-side (Moby plugin authorization),
# fix N/A upstream. Наш код использует docker-client-library только для
# Container{Create,Start,Logs,Kill}; AuthZ-plugins не задействованы.
# Если govulncheck возвращает только эти две — step проходит; любая
# новая vulnerability ломает build.
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: |
set +e
output=$(govulncheck -format=text ./... 2>&1)
code=$?
echo "$output"
if [ "$code" -eq 0 ]; then
exit 0
fi
# Извлекаем ID каждой найденной vulnerability (строки "Vulnerability #N: GO-YYYY-NNNN").
# Затем вычитаем accepted-list; остаток должен быть пустым.
ids=$(echo "$output" | grep -Eo 'GO-[0-9]{4}-[0-9]+' | sort -u)
accepted="GO-2026-4887 GO-2026-4883"
unknown=""
for id in $ids; do
case " $accepted " in
*" $id "*) ;;
*) unknown="$unknown $id" ;;
esac
done
if [ -n "$unknown" ]; then
echo "::error::Found new CVE(s) not in accepted list:${unknown}"
exit 1
fi
echo "Only accepted Moby daemon-side advisories present; treating as PASS."
# P1.9: npm audit для frontend-зависимостей (блокирует high+).
- name: Run npm audit
run: npm audit --audit-level=high
working-directory: web
# P1.9: Trivy filesystem scan на high/critical CVE.
# Используем master-tag: aquasecurity публикует версии как v-tags
# (v0.24.0, v0.25.0...), @master гарантированно резолвится.
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
ignore-unfixed: true
format: 'table'
# Accepted CVEs (нет доступного исправления в Go module registry):
# CVE-2026-34040 — Moby authz bypass, fix v29.3.1 но в registry только v28.5.2.
trivyignores: .trivyignore
contract:
name: Contract Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download dependencies
run: go mod download
- name: Run contract tests
run: go test -v -tags=contract -count=1 -timeout=5m ./tests/contract/...
integration:
name: Integration Tests
runs-on: ubuntu-latest
needs: [test, build]
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: tjudge
POSTGRES_PASSWORD: secret
POSTGRES_DB: tjudge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Download dependencies
run: go mod download
- name: Run migrations
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
run: |
go run cmd/migrations/main.go up
- name: Run integration tests
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
JWT_SECRET: test-jwt-secret-for-ci
RUN_INTEGRATION: true
run: go test -v -tags=integration -timeout=10m ./tests/integration/...
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: [integration]
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: tjudge
POSTGRES_PASSWORD: secret
POSTGRES_DB: tjudge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download dependencies
run: go mod download
- name: Run migrations
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
run: |
go run cmd/migrations/main.go up
- name: Build and start API server
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
JWT_SECRET: test-jwt-secret-for-ci
API_PORT: 8080
run: |
go build -o bin/api ./cmd/api
./bin/api &
# Wait for API to be ready (up to 30s)
for i in $(seq 1 30); do
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
echo "API server is ready"
break
fi
sleep 1
done
curl -f http://localhost:8080/health || exit 1
- name: Run E2E tests
env:
E2E_API_URL: http://localhost:8080
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
run: go test -v -tags=e2e -timeout=15m ./tests/e2e/...
chaos:
name: Chaos Tests
runs-on: ubuntu-latest
needs: [e2e]
if: github.ref == 'refs/heads/main'
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: tjudge
POSTGRES_PASSWORD: secret
POSTGRES_DB: tjudge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
run: npm ci
working-directory: web
- name: Build frontend
run: npm run build
working-directory: web
- name: Download dependencies
run: go mod download
- name: Run migrations
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
run: |
go run cmd/migrations/main.go up
- name: Build and start API server
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
JWT_SECRET: test-jwt-secret-for-ci
API_PORT: 8080
run: |
go build -o bin/api ./cmd/api
./bin/api &
# Wait for API to be ready (up to 30s)
for i in $(seq 1 30); do
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
echo "API server is ready"
break
fi
sleep 1
done
curl -f http://localhost:8080/health || exit 1
- name: Run Chaos tests
env:
CHAOS_API_URL: http://localhost:8080
DB_HOST: localhost
DB_PORT: 5432
DB_USER: tjudge
DB_PASSWORD: secret
DB_NAME: tjudge_test
REDIS_HOST: localhost
REDIS_PORT: 6379
run: go test -v -tags=chaos -timeout=20m ./tests/chaos/...