Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .github/workflows/change-classification.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Change classification

on:
workflow_call:
outputs:
automation:
description: GitHub Actions workflows changed
value: ${{ jobs.classify.outputs.automation }}
backend:
description: Laravel runtime or tests changed
value: ${{ jobs.classify.outputs.backend }}
dependencies:
description: Composer or npm dependencies changed
value: ${{ jobs.classify.outputs.dependencies }}
frontend:
description: Frontend sources or build configuration changed
value: ${{ jobs.classify.outputs.frontend }}
infrastructure:
description: Container or deployment configuration changed
value: ${{ jobs.classify.outputs.infrastructure }}
source:
description: Application source code changed
value: ${{ jobs.classify.outputs.source }}

permissions:
contents: read
pull-requests: read

jobs:
classify:
name: Classify changed files
runs-on: ubuntu-latest
outputs:
automation: ${{ steps.filter.outputs.automation }}
backend: ${{ steps.filter.outputs.backend }}
dependencies: ${{ steps.filter.outputs.dependencies }}
frontend: ${{ steps.filter.outputs.frontend }}
infrastructure: ${{ steps.filter.outputs.infrastructure }}
source: ${{ steps.filter.outputs.source }}
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Classify changes
id: filter
uses: dorny/paths-filter@v4
with:
filters: |
automation:
- '.github/workflows/**'
backend:
- 'app/**'
- 'artisan'
- 'bootstrap/**'
- 'composer.json'
- 'composer.lock'
- 'config/**'
- 'database/**'
- 'phpunit.xml'
- 'resources/lang/**'
- 'resources/views/**'
- 'routes/**'
- 'tests/**'
dependencies:
- 'composer.json'
- 'composer.lock'
- 'package.json'
- 'package-lock.json'
frontend:
- 'package.json'
- 'package-lock.json'
- 'resources/css/**'
- 'resources/js/**'
- 'vite.config.*'
infrastructure:
- '.env.docker.example'
- 'Caddyfile'
- 'Dockerfile'
- 'compose*.yml'
- 'install.sh'
source:
- 'app/**'
- 'bootstrap/**'
- 'config/**'
- 'database/**'
- 'resources/css/**'
- 'resources/js/**'
- 'resources/lang/**'
- 'resources/views/**'
- 'routes/**'
63 changes: 58 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,27 @@ on:

permissions:
contents: read
pull-requests: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
release_validation:
name: Release-ready validation
uses: ./.github/workflows/release-validation.yml
changes:
name: Change classification
uses: ./.github/workflows/change-classification.yml

php:
name: PHP quality and tests
runs-on: ubuntu-latest
needs: frontend
needs:
- changes
- frontend
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.backend == 'true' ||
needs.changes.outputs.automation == 'true'
steps:
- name: Checkout
uses: actions/checkout@v7
Expand Down Expand Up @@ -63,6 +70,12 @@ jobs:
frontend:
name: Frontend build
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.backend == 'true' ||
needs.changes.outputs.frontend == 'true' ||
needs.changes.outputs.automation == 'true'
steps:
- name: Checkout
uses: actions/checkout@v7
Expand Down Expand Up @@ -97,7 +110,13 @@ jobs:
mariadb:
name: MariaDB integration tests
runs-on: ubuntu-latest
needs: frontend
needs:
- changes
- frontend
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.backend == 'true' ||
needs.changes.outputs.automation == 'true'
services:
db:
image: mariadb:11
Expand Down Expand Up @@ -147,6 +166,11 @@ jobs:
container:
name: Container integration test
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.infrastructure == 'true' ||
needs.changes.outputs.automation == 'true'
steps:
- name: Checkout
uses: actions/checkout@v7
Expand Down Expand Up @@ -244,6 +268,11 @@ jobs:
compose:
name: Compose configuration
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.infrastructure == 'true' ||
needs.changes.outputs.automation == 'true'
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -261,6 +290,11 @@ jobs:
shell:
name: Installer script
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.infrastructure == 'true' ||
needs.changes.outputs.automation == 'true'
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -272,3 +306,22 @@ jobs:
run: |
test -x install.sh
shellcheck install.sh

gate:
name: CI gate
runs-on: ubuntu-latest
if: always()
needs:
- changes
- php
- frontend
- mariadb
- container
- compose
- shell
steps:
- name: Require successful executed checks
if: >-
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')
run: exit 1
43 changes: 42 additions & 1 deletion .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,34 @@ on:
pull_request:
push:
branches:
- main
- master
schedule:
- cron: "0 5 * * 1"

permissions:
contents: read
pull-requests: read

concurrency:
group: security-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
changes:
name: Change classification
if: github.event_name != 'schedule'
uses: ./.github/workflows/change-classification.yml

dependency-audit:
name: Dependency Audit
runs-on: ubuntu-latest
needs: changes
if: >-
always() && (
github.event_name == 'schedule' ||
needs.changes.outputs.dependencies == 'true' ||
needs.changes.outputs.automation == 'true'
)
steps:
- name: Checkout
uses: actions/checkout@v7
Expand Down Expand Up @@ -59,6 +78,12 @@ jobs:
sast:
name: SAST
runs-on: ubuntu-latest
needs: changes
if: >-
always() && (
github.event_name == 'schedule' ||
(github.ref == 'refs/heads/master' && needs.changes.outputs.source == 'true')
)
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -70,3 +95,19 @@ jobs:
p/php
p/owasp-top-ten
p/secrets

gate:
name: Security gate
runs-on: ubuntu-latest
if: always()
needs:
- changes
- dependency-audit
- secret-scan
- sast
steps:
- name: Require successful executed checks
if: >-
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')
run: exit 1
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ protected function isAccessible(User $user, ?string $path = null): bool

## GitHub Actions CI

- Continuous Integration runs on pull requests and pushes to `master`; it checks PHP formatting, PHPUnit, the Vite production build, MariaDB compatibility, Compose-based container initialization, deployment configuration, and the installer shell script.
- Continuous Integration runs on pull requests and pushes to `master`. It classifies changed files and runs only the relevant checks: backend changes receive the Vite build plus PHP and MariaDB tests; frontend changes receive the Vite build; infrastructure changes receive Compose, installer, and container backup/restore checks. Workflow changes run the full CI suite.
- Security Audit runs a secret scan on every pull request and `master` push. Dependency audits run for dependency or workflow changes; SAST runs for source changes on `master` and in the scheduled weekly audit. The required merge checks are `CI gate` and `Security gate`.
- CI is validation-only: do not add deployment steps, repository write permissions, or secrets without explicit approval.
- The frontend workflows use Node.js 24; local frontend checks require Node.js 22.18 or later.
- Tags matching `v0.*.*` validate the release again, publish a GHCR container image with provenance and an SBOM, smoke-test its digest, and generate GitHub release notes; they must not deploy the application.
Expand Down
4 changes: 2 additions & 2 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing

Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` tag only after the `master` branch CI is green.
Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` tag only after the `master` branch `CI gate` and `Security gate` are green.

## Publish a release

Expand All @@ -13,7 +13,7 @@ Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` t
git push origin v0.1.0
```

4. Verify the Release workflow. It repeats application validation, builds and publishes the container with provenance and an SBOM, smoke-tests the published image by digest, and creates the GitHub Release.
4. Verify the Release workflow. It repeats the complete release validation (frontend build, application tests, and dependency audits) for the immutable tag, builds and publishes the container with provenance and an SBOM, smoke-tests the published image by digest, and creates the GitHub Release.
5. Check the generated release notes. Add a concise **Upgrade notes** section that calls out migrations, changed environment variables, deprecations, and any manual operator action.

## Published images
Expand Down
Loading