diff --git a/.codecov.yml b/.codecov.yml index 48213d795..66638a534 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,8 +11,22 @@ coverage: # Fail CI if Codecov upload/report indicates a problem require_ci_to_pass: yes -# Exclude folders from Codecov (adjust as needed) +# Exclude folders from Codecov ignore: - - tests/* - - docs/* - - .github/* + - "**/tests/*" + - "**/test/*" + - "**/__tests__/*" + - "**/test_*.go" + - "**/*_test.go" + - "**/*.test.ts" + - "**/*.test.tsx" + - "docs/*" + - ".github/*" + - "scripts/*" + - "tools/*" + - "frontend/node_modules/*" + - "frontend/dist/*" + - "frontend/coverage/*" + - "backend/cmd/seed/*" + - "backend/data/*" + - "*.md" diff --git a/.dockerignore b/.dockerignore index 617bf82a3..65fcdf524 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,27 +1,75 @@ +# Version control .git .gitignore -node_modules -venv -__pycache__ -*.pyc -*.pyo -*.pyd +.github/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so .Python +.venv/ +venv/ env/ -build/ -dist/ -*.egg-info -.DS_Store -.idea/ -.vscode/ -.env -.env.* -coverage/ -.coverage +ENV/ .pytest_cache/ -*.log +.coverage +*.cover +.hypothesis/ +htmlcov/ +*.egg-info/ + +# Node/Frontend build artifacts +frontend/node_modules/ +frontend/coverage/ +frontend/.vite/ +frontend/*.tsbuildinfo +# Keep frontend/dist - needed in final image + +# Go/Backend +backend/*.out +backend/coverage.*.out +# Keep backend/api binary - needed in final image + +# Databases (runtime) +backend/data/*.db +backend/cmd/api/data/*.db +*.sqlite *.sqlite3 +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Logs +*.log +logs/ + +# Environment +.env.local +.env.*.local + +# OS +.DS_Store +Thumbs.db + +# Documentation +docs/ +*.md +!README.md + # Docker -docker-compose.override.yml +docker-compose*.yml **/Dockerfile.* + +# CI/CD +.github/ +.pre-commit-config.yaml + +# Scripts +scripts/ +tools/ diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 000000000..8302d397e --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommits", + ":separateMultipleMajorReleases", + "helpers:pinGitHubActionDigests" + ], + "baseBranches": ["development"], + "timezone": "UTC", + "dependencyDashboard": true, + "prConcurrentLimit": 10, + "prHourlyLimit": 5, + "labels": ["dependencies"], + "rebaseWhen": "conflicted", + "vulnerabilityAlerts": { "enabled": true }, + "schedule": ["every weekday"], + "rangeStrategy": "bump", + "packageRules": [ + { + "description": "Automerge safe patch updates", + "matchUpdateTypes": ["patch"], + "automerge": true + }, + { + "description": "Frontend npm: automerge minor for devDependencies", + "matchManagers": ["npm"], + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"], + "automerge": true, + "labels": ["dependencies", "npm"] + }, + { + "description": "Backend Go modules", + "matchManagers": ["gomod"], + "labels": ["dependencies", "go"], + "matchUpdateTypes": ["minor", "patch"], + "automerge": false + }, + { + "description": "GitHub Actions updates", + "matchManagers": ["github-actions"], + "labels": ["dependencies", "github-actions"], + "matchUpdateTypes": ["minor", "patch"], + "automerge": true + }, + { + "description": "Docker: keep Caddy within v2 (no automatic jump to v3)", + "matchManagers": ["dockerfile"], + "matchPackageNames": ["caddy"], + "allowedVersions": "<3.0.0", + "labels": ["dependencies", "docker"], + "automerge": true + }, + { + "description": "Group non-breaking npm minor/patch", + "matchManagers": ["npm"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "npm minor/patch", + "prPriority": -1 + }, + { + "description": "Group docker base minor/patch", + "matchManagers": ["dockerfile"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "docker base updates", + "prPriority": -1 + } + ] +} diff --git a/.github/workflows/auto-add-to-project.yml b/.github/workflows/auto-add-to-project.yml index 12a37042c..5f4128bba 100644 --- a/.github/workflows/auto-add-to-project.yml +++ b/.github/workflows/auto-add-to-project.yml @@ -10,15 +10,23 @@ jobs: add-to-project: runs-on: ubuntu-latest steps: - - name: Skip if PROJECT_URL not provided + - name: Determine project URL presence + id: project_check run: | - if [ -z "${{ secrets.PROJECT_URL }}" ]; then - echo "PROJECT_URL secret not set; skipping add-to-project job." - exit 0 + if [ -n "${{ secrets.PROJECT_URL }}" ]; then + echo "has_project=true" >> $GITHUB_OUTPUT + else + echo "has_project=false" >> $GITHUB_OUTPUT fi - name: Add issue or PR to project - uses: actions/add-to-project@v0.5.0 + if: steps.project_check.outputs.has_project == 'true' + continue-on-error: true + uses: actions/add-to-project@31b3f3ccdc584546fc445612dec3f38ff5edb41c # v0.5.0 with: project-url: ${{ secrets.PROJECT_URL }} - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} + + - name: Skip summary + if: steps.project_check.outputs.has_project == 'false' + run: echo "PROJECT_URL secret missing; skipping project assignment." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml index 92c8974ee..77bdc9a93 100644 --- a/.github/workflows/auto-label-issues.yml +++ b/.github/workflows/auto-label-issues.yml @@ -11,7 +11,7 @@ jobs: issues: write steps: - name: Auto-label based on title and body - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const issue = context.payload.issue; diff --git a/.github/workflows/caddy-major-monitor.yml b/.github/workflows/caddy-major-monitor.yml new file mode 100644 index 000000000..95635ee3c --- /dev/null +++ b/.github/workflows/caddy-major-monitor.yml @@ -0,0 +1,62 @@ +name: Monitor Caddy Major Release + +on: + schedule: + - cron: '17 7 * * 1' # Mondays at 07:17 UTC + workflow_dispatch: {} + +permissions: + contents: read + issues: write + +jobs: + check-caddy-major: + runs-on: ubuntu-latest + steps: + - name: Check for Caddy v3 and open issue + uses: actions/github-script@v7 + with: + script: | + const upstream = { owner: 'caddyserver', repo: 'caddy' }; + const { data: releases } = await github.rest.repos.listReleases({ + ...upstream, + per_page: 50, + }); + const latestV3 = releases.find(r => /^v3\./.test(r.tag_name)); + if (!latestV3) { + core.info('No Caddy v3 release detected.'); + return; + } + + const issueTitle = `Track upgrade to Caddy v3 (${latestV3.tag_name})`; + + const { data: existing } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100, + }); + + if (existing.some(i => i.title === issueTitle)) { + core.info('Issue already exists โ nothing to do.'); + return; + } + + const body = [ + 'Caddy v3 has been released upstream and detected by the scheduled monitor.', + '', + `Detected release: ${latestV3.tag_name} (${latestV3.html_url})`, + '', + '- Create a feature branch to evaluate the v3 migration.', + '- Review breaking changes and update Docker base images/workflows.', + '- Validate Trivy scans and update any policies as needed.', + '', + 'Current policy: remain on latest 2.x until v3 is validated.' + ].join('\n'); + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body, + }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 866cf3ab3..22f16432d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,67 +7,35 @@ on: branches: [ main, development ] jobs: - lint: - name: Lint (ruff & flake8) + test-backend: + name: Backend Tests (Go) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 + - name: Set up Go + uses: actions/setup-go@v5 with: - python-version: '3.12' - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dev dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.dev.txt - - name: Run pre-commit - run: | - pre-commit run --all-files - - name: Run ruff - run: | - ruff check . - - name: Run flake8 + go-version: '1.22' + cache-dependency-path: backend/go.sum + - name: Run Go tests + working-directory: backend run: | - flake8 . || true + go test -v ./... - test-and-coverage: - name: Tests & Coverage + test-frontend: + name: Frontend Tests (React) runs-on: ubuntu-latest - needs: [lint] steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 with: - python-version: '3.12' - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt || true - pip install -r requirements.dev.txt - - name: Run tests with coverage - run: | - # run pytest under coverage and fail if tests fail - coverage run -m pytest -q - coverage report -m --fail-under=75 - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - fail_ci_if_error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Optional: set CODECOV_TOKEN in repo secrets if needed for private repos + working-directory: frontend + run: npm ci + - name: Run frontend tests + working-directory: frontend + run: npm test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..f378e56a8 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,44 @@ +name: CodeQL - Analyze + +on: + push: + branches: [ main, development, 'feature/**' ] + pull_request: + branches: [ main, development ] + schedule: + - cron: '0 3 * * 1' + +permissions: + contents: read + security-events: write + +jobs: + analyze: + name: CodeQL analysis (${{ matrix.language }}) + runs-on: ubuntu-latest + # Skip forked PRs where GITHUB_TOKEN lacks security-events permissions + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + permissions: + contents: read + security-events: write + actions: read + strategy: + fail-fast: false + matrix: + language: [ 'go', 'javascript-typescript' ] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/create-labels.yml b/.github/workflows/create-labels.yml index 35523829c..bf6b69dfa 100644 --- a/.github/workflows/create-labels.yml +++ b/.github/workflows/create-labels.yml @@ -11,7 +11,7 @@ jobs: issues: write steps: - name: Create all project labels - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const labels = [ diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e5e044451..1e5cbb5d2 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -11,41 +11,84 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: ${{ github.repository_owner }}/${{ github.event.repository.name }} jobs: build-and-push: name: Build and Push Docker Image runs-on: ubuntu-latest + concurrency: + group: docker-build-${{ github.ref }} + cancel-in-progress: true + outputs: + skip_build: ${{ steps.skip.outputs.skip_build }} permissions: contents: read packages: write + security-events: write steps: # Step 1: Download the code - name: ๐ฅ Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + + # Normalize IMAGE_NAME to lowercase to satisfy container registry format + - name: ๐ค Normalize image name + run: | + raw="${{ github.repository_owner }}/${{ github.event.repository.name }}" + echo "IMAGE_NAME=$(echo "$raw" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: ๐งช Determine skip condition + id: skip + run: | + should_skip=false + actor='${{ github.actor }}' + event='${{ github.event_name }}' + head_msg='${{ github.event.head_commit.message }}' + pr_title="" + if [ "$event" = "pull_request" ]; then + pr_title=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH" 2>/dev/null || echo '') + fi + if [ "$actor" = "renovate[bot]" ]; then should_skip=true; fi + if echo "$head_msg" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi + if echo "$head_msg" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi + if echo "$pr_title" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi + if echo "$pr_title" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi + echo "skip_build=$should_skip" >> $GITHUB_OUTPUT + if [ "$should_skip" = true ]; then + echo "Skipping heavy docker build for actor=$actor event=$event (message/title matched)" + else + echo "Proceeding with full docker build" + fi # Step 2: Set up QEMU for multi-platform builds (ARM, AMD64, etc.) - name: ๐ง Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 # Step 3: Set up Docker Buildx (advanced Docker builder) - name: ๐ง Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 + + # Resolve immutable digest for Caddy base + - name: ๐ฆ Resolve Caddy base digest + id: caddy + run: | + docker pull caddy:2-alpine + DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' caddy:2-alpine) + echo "image=$DIGEST" >> $GITHUB_OUTPUT # Step 4: Log in to GitHub Container Registry - name: ๐ Log in to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.CPMP_GHCR_TOKEN }} + password: ${{ secrets.PROJECT_TOKEN }} # Step 5: Figure out what tags to use - name: ๐ท๏ธ Extract metadata (tags, labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -59,11 +102,14 @@ jobs: type=semver,pattern={{major}}.{{minor}} # Tag major from git tags (v1.2.3 โ 1) type=semver,pattern={{major}} - # Tag with git SHA for tracking (first 7 characters) - type=sha,prefix=sha-,format=short + # Ephemeral tag for pull requests (derive number from GITHUB_REF if available) + type=raw,value=pr-${{ github.ref_name }},enable=${{ github.event_name == 'pull_request' }} + # Short SHA tag as fallback (for non-default non-dev push events) + type=sha,format=short,enable=${{ github.event_name != 'pull_request' }} # Step 6: Build the frontend first - name: ๐จ Build frontend + if: steps.skip.outputs.skip_build != 'true' run: | cd frontend npm ci @@ -71,19 +117,63 @@ jobs: # Step 7: Build and push Docker image - name: ๐ณ Build and push Docker image + if: steps.skip.outputs.skip_build != 'true' uses: docker/build-push-action@v5 + id: build with: context: . file: ./Dockerfile - platforms: linux/amd64,linux/arm64 - push: true + platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + CADDY_IMAGE=${{ steps.caddy.outputs.image }} - # Step 8: Create a summary + # Step 8: Run Trivy scan (table output first for visibility) + - name: ๐ Run Trivy scan (table output) + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' + id: trivy_table + continue-on-error: true + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }} + format: 'table' + severity: 'CRITICAL,HIGH' + exit-code: '0' + + # Step 9: Run Trivy security scan (SARIF) + - name: ๐ Run Trivy vulnerability scanner + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' + id: trivy + continue-on-error: true + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }} + format: 'sarif' + output: 'trivy-results.sarif' + exit-code: '0' + severity: 'CRITICAL,HIGH' + + # Step 10: Upload Trivy results to GitHub Security tab + - name: ๐ค Upload Trivy results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && (steps.trivy.outcome == 'success' || steps.trivy.outcome == 'failure') + with: + sarif_file: 'trivy-results.sarif' + + # Step 11: Fail if vulnerabilities found + - name: โ Check for vulnerabilities + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy_table.outcome == 'failure' + run: | + echo "::error::CRITICAL or HIGH vulnerabilities found in image" + exit 1 + + # Step 11: Create a summary - name: ๐ Create summary + if: steps.skip.outputs.skip_build != 'true' run: | echo "## ๐ Docker Image Built Successfully!" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -101,12 +191,26 @@ jobs: echo "docker run -d -p 8080:8080 -v caddy_data:/app/data ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY + - name: ๐ Create skip summary + if: steps.skip.outputs.skip_build == 'true' + run: | + echo "## ๐ซ Docker Build Skipped" >> $GITHUB_STEP_SUMMARY + echo "Reason: renovate bot or chore(deps)/chore commit/PR title." >> $GITHUB_STEP_SUMMARY + echo "Actor: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "Event: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY + test-image: name: Test Docker Image needs: build-and-push runs-on: ubuntu-latest + if: needs.build-and-push.outputs.skip_build != 'true' steps: + # Ensure normalized lowercase IMAGE_NAME for this job as well + - name: ๐ค Normalize image name + run: | + raw="${{ github.repository_owner }}/${{ github.event.repository.name }}" + echo "IMAGE_NAME=$(echo "$raw" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV # Step 1: Figure out which tag to test - name: ๐ท๏ธ Determine image tag id: tag @@ -133,21 +237,22 @@ jobs: -p 8080:8080 \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} - # Step 4: Wait for it to start - - name: โณ Wait for container to be ready - run: sleep 10 - - # Step 5: Check if the health endpoint works - - name: ๐ฅ Test health endpoint + # Step 4/5: Wait and check health with retries + - name: ๐ฅ Test health endpoint (retries) run: | - response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/v1/health) - if [ $response -eq 200 ]; then - echo "โ Health check passed!" - else - echo "โ Health check failed with status $response" - docker logs test-container - exit 1 - fi + set +e + for i in $(seq 1 30); do + code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/v1/health || echo "000") + if [ "$code" = "200" ]; then + echo "โ Health check passed on attempt $i" + exit 0 + fi + echo "Attempt $i/30: health not ready (code=$code); waiting..." + sleep 2 + done + echo "โ Health check failed after retries" + docker logs test-container || true + exit 1 # Step 6: Check the logs for errors - name: ๐ Check container logs diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 262a8d8a7..a25a11dcd 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -15,7 +15,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: ${{ github.repository_owner }}/${{ github.event.repository.name }} jobs: build-and-push: @@ -23,42 +23,78 @@ jobs: permissions: contents: read packages: write - + security-events: write + steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + + - name: Normalize image name + run: | + raw="${{ github.repository_owner }}/${{ github.event.repository.name }}" + echo "IMAGE_NAME=$(echo "$raw" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Determine skip condition + id: skip + run: | + should_skip=false + actor='${{ github.actor }}' + event='${{ github.event_name }}' + head_msg='${{ github.event.head_commit.message }}' + pr_title="" + if [ "$event" = "pull_request" ]; then + pr_title=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH" 2>/dev/null || echo '') + fi + if [ "$actor" = "renovate[bot]" ]; then should_skip=true; fi + if echo "$head_msg" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi + if echo "$head_msg" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi + if echo "$pr_title" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi + if echo "$pr_title" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi + echo "skip_build=$should_skip" >> $GITHUB_OUTPUT + if [ "$should_skip" = true ]; then + echo "Skipping heavy docker publish for actor=$actor event=$event (message/title matched)" + else + echo "Proceeding with docker publish" + fi - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 + + - name: Resolve Caddy base digest + id: caddy + run: | + docker pull caddy:2-alpine + DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' caddy:2-alpine) + echo "image=$DIGEST" >> $GITHUB_OUTPUT - name: Log in to Container Registry if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ secrets.PROJECT_TOKEN }} - name: Extract metadata (tags, labels) + if: steps.skip.outputs.skip_build != 'true' id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | # Tag 'latest' for main branch type=raw,value=latest,enable={{is_default_branch}} - # Tag 'development' for development branch - type=raw,value=development,enable=${{ github.ref == 'refs/heads/development' }} + # Tag 'dev' for development branch + type=raw,value=dev,enable=${{ github.ref == 'refs/heads/development' }} # Semver tags for version releases (v1.0.0 -> 1.0.0, 1.0, 1) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - # SHA for all builds - type=sha,prefix={{branch}}- - name: Build and push Docker image + if: steps.skip.outputs.skip_build != 'true' id: build-and-push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 with: context: . platforms: linux/amd64,linux/arm64 @@ -71,6 +107,53 @@ jobs: VERSION=${{ steps.meta.outputs.version }} BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} VCS_REF=${{ github.sha }} + CADDY_IMAGE=${{ steps.caddy.outputs.image }} - name: Image digest + if: steps.skip.outputs.skip_build != 'true' run: echo ${{ steps.build-and-push.outputs.digest }} + + - name: Run Trivy scan (table output first for visibility) + if: github.event_name != 'pull_request' + && steps.skip.outputs.skip_build != 'true' + id: trivy_table + continue-on-error: true + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} + format: 'table' + severity: 'CRITICAL,HIGH' + exit-code: '0' + + - name: Run Trivy vulnerability scanner + if: github.event_name != 'pull_request' + && steps.skip.outputs.skip_build != 'true' + id: trivy + continue-on-error: true + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} + format: 'sarif' + output: 'trivy-results.sarif' + exit-code: '0' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy results to GitHub Security + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && (steps.trivy.outcome == 'success' || steps.trivy.outcome == 'failure') + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results.sarif' + + - name: Check for vulnerabilities + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy_table.outcome == 'failure' + run: | + echo "::error::CRITICAL or HIGH vulnerabilities found in image" + exit 1 + + - name: Skip summary + if: steps.skip.outputs.skip_build == 'true' + run: | + echo "## ๐ซ Docker Publish Skipped" >> $GITHUB_STEP_SUMMARY + echo "Reason: renovate bot or chore(deps)/chore commit/PR title." >> $GITHUB_STEP_SUMMARY + echo "Actor: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "Event: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6db95c81a..4723453b3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,15 +25,15 @@ jobs: build: name: Build Documentation runs-on: ubuntu-latest - + steps: # Step 1: Get the code - name: ๐ฅ Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 # Step 2: Set up Node.js (for building any JS-based doc tools) - name: ๐ง Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '20' @@ -42,11 +42,11 @@ jobs: run: | # Create output directory mkdir -p _site - + # Copy all markdown files cp README.md _site/ cp -r docs _site/ - + # Create a simple HTML index that looks nice cat > _site/index.html << 'EOF' @@ -154,12 +154,12 @@ jobs:
Make your websites easy to reach - No coding required!
- +- This documentation will help you get started with Caddy Proxy Manager Plus. + This documentation will help you get started with Caddy Proxy Manager Plus. Whether you're a complete beginner or an experienced developer, we've got you covered!