Redesign the Command dashboard and application chrome #198
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*.*.*"] | |
| pull_request: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| env: | |
| CI: "true" | |
| TURBO_TELEMETRY_DISABLED: "1" | |
| DATABASE_URL: postgresql://muster:muster@127.0.0.1:5432/muster | |
| REDIS_URL: redis://127.0.0.1:6379 | |
| BETTER_AUTH_SECRET: muster-ci-only-secret-at-least-32-characters | |
| BETTER_AUTH_URL: http://127.0.0.1:3000 | |
| # Unit tests stub object-storage env; no MinIO service required for quality. | |
| OBJECT_STORAGE_ENDPOINT: http://127.0.0.1:9000 | |
| OBJECT_STORAGE_REGION: us-east-1 | |
| OBJECT_STORAGE_BUCKET: muster-evidence | |
| OBJECT_STORAGE_ACCESS_KEY: muster | |
| OBJECT_STORAGE_SECRET_KEY: local-minio-secret | |
| jobs: | |
| # Fast path: unit, lint, typecheck, build, migration drift, shell installers. | |
| # Postgres only — no MinIO/Redis containers (unit tests mock or skip). | |
| quality: | |
| runs-on: ubuntu-24.04 | |
| services: | |
| postgres: | |
| image: postgres:17.6-bookworm | |
| env: | |
| POSTGRES_USER: muster | |
| POSTGRES_PASSWORD: muster | |
| POSTGRES_DB: muster | |
| ports: ["5432:5432"] | |
| options: >- | |
| --health-cmd "pg_isready -U muster -d muster" | |
| --health-interval 5s --health-timeout 3s --health-retries 20 | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4 | |
| with: | |
| version: 11.17.0 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: "26" | |
| cache: pnpm | |
| - name: Turbo cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: .turbo | |
| key: turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- | |
| turbo-${{ runner.os }}-node26- | |
| - run: pnpm install --frozen-lockfile | |
| # Pure shell tests: no install side-effects beyond repo files. | |
| - run: pnpm test:release-homelab | |
| - run: pnpm test:release-image | |
| - run: pnpm test:homelab-installer | |
| - run: pnpm skills:validate | |
| - run: pnpm kelpie:certify-mock | |
| - name: Database migrate + bootstrap + clean verify | |
| run: | | |
| pnpm exec turbo build --filter=@muster/database | |
| pnpm db:migrate | |
| pnpm db:bootstrap | |
| pnpm db:verify-clean | |
| - name: Lint, typecheck, unit tests, build | |
| run: | | |
| # Single turbo invocation reuses package graph and local turbo cache. | |
| pnpm exec turbo run lint typecheck test build --concurrency=100% | |
| - name: Verify contracts + migrations stay committed | |
| run: | | |
| pnpm contracts:generate | |
| pnpm db:generate | |
| git diff --exit-code -- packages/contracts packages/database/migrations | |
| # Secrets/audit/CodeQL only — full image rebuild lives in `container` (once). | |
| release-security: | |
| permissions: | |
| contents: read | |
| security-events: write | |
| uses: ./.github/workflows/security.yml | |
| # Build once per workflow; do not wait for quality on PRs (wall-clock parallel). | |
| # On main/tag, promote still waits for quality + security via the promote job. | |
| container: | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| image_ref: ${{ steps.image.outputs.ref }} | |
| image_digest: ${{ steps.build.outputs.digest }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| attestations: write | |
| id-token: write | |
| env: | |
| REGISTRY: ghcr.io | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Normalize image reference | |
| id: image | |
| shell: bash | |
| run: | | |
| image_ref="${REGISTRY}/${GITHUB_REPOSITORY,,}" | |
| if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then | |
| build_ref="${image_ref}:staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| else | |
| build_ref="${image_ref}:verify-${GITHUB_SHA}" | |
| fi | |
| { | |
| printf 'ref=%s\n' "$image_ref" | |
| printf 'build_ref=%s\n' "$build_ref" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Log in to GitHub Container Registry | |
| if: github.event_name == 'push' | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # load:true (PR path) cannot export manifest lists. Provenance/SBOM | |
| # attestations produce multi-artifact images, so only enable them on | |
| # push where we publish to the registry instead of loading locally. | |
| - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 | |
| id: build | |
| with: | |
| context: . | |
| push: ${{ github.event_name == 'push' }} | |
| load: ${{ github.event_name == 'pull_request' }} | |
| platforms: linux/amd64 | |
| tags: ${{ steps.image.outputs.build_ref }} | |
| labels: | | |
| org.opencontainers.image.title=Muster | |
| org.opencontainers.image.description=Shared workspace for human and agent-driven security operations | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| provenance: ${{ github.event_name == 'push' && 'mode=max' || false }} | |
| sbom: ${{ github.event_name == 'push' }} | |
| cache-from: type=gha,scope=muster-image | |
| cache-to: type=gha,mode=max,scope=muster-image | |
| - name: Scan built image with Trivy | |
| run: | | |
| docker run --rm \ | |
| --volume /var/run/docker.sock:/var/run/docker.sock \ | |
| aquasec/trivy:0.67.2 image \ | |
| --exit-code 1 --ignore-unfixed --severity HIGH,CRITICAL \ | |
| "${{ steps.image.outputs.build_ref }}" | |
| - name: Generate release SBOM | |
| if: github.event_name == 'push' | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 | |
| with: | |
| image: ${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }} | |
| format: cyclonedx-json | |
| output-file: muster-sbom.cdx.json | |
| upload-artifact: false | |
| - name: Generate pull request SBOM | |
| if: github.event_name == 'pull_request' | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 | |
| with: | |
| image: ${{ steps.image.outputs.build_ref }} | |
| format: cyclonedx-json | |
| output-file: muster-sbom.cdx.json | |
| upload-artifact: false | |
| - name: Verify staged OCI application platform | |
| if: github.event_name == 'push' | |
| run: | | |
| docker buildx imagetools inspect \ | |
| "${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }}" --raw | | |
| ./scripts/verify-image-platform.sh | |
| - name: Record immutable image evidence and checksums | |
| if: github.event_name == 'push' | |
| run: | | |
| printf '%s\n' \ | |
| "${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }}" > muster-image.txt | |
| sha256sum muster-sbom.cdx.json muster-image.txt > SHA256SUMS | |
| - name: Upload release evidence | |
| if: github.event_name == 'push' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: muster-release-evidence | |
| path: | | |
| muster-sbom.cdx.json | |
| muster-image.txt | |
| SHA256SUMS | |
| - name: Verify container image exists | |
| if: github.event_name == 'pull_request' | |
| run: docker image inspect "${{ steps.image.outputs.build_ref }}" | |
| - name: Attest published image provenance | |
| if: github.event_name == 'push' | |
| uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4 | |
| with: | |
| subject-name: ${{ steps.image.outputs.ref }} | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| push-to-registry: true | |
| - name: Attest published image SBOM | |
| if: github.event_name == 'push' | |
| uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4 | |
| with: | |
| subject-name: ${{ steps.image.outputs.ref }} | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| sbom-path: muster-sbom.cdx.json | |
| push-to-registry: true | |
| - name: Verify anonymous staging pull | |
| if: github.event_name == 'push' | |
| shell: bash | |
| run: | | |
| public_image="${{ steps.image.outputs.build_ref }}" | |
| docker logout "$REGISTRY" || true | |
| docker pull "$public_image" | |
| docker image inspect "$public_image" \ | |
| --format '{{.Os}}/{{.Architecture}}' | grep -x 'linux/amd64' | |
| docker buildx imagetools inspect "$public_image" --raw | | |
| ./scripts/verify-image-platform.sh | |
| # Merge gate: quality + container + security must all pass before promote. | |
| promote: | |
| if: github.event_name == 'push' | |
| needs: [quality, container, release-security] | |
| runs-on: ubuntu-24.04 | |
| concurrency: | |
| group: muster-release-tags-${{ github.repository }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_REF: ${{ needs.container.outputs.image_ref }} | |
| IMAGE_DIGEST: ${{ needs.container.outputs.image_digest }} | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Generate release tags | |
| id: meta | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: ${{ env.IMAGE_REF }} | |
| tags: | | |
| type=ref,event=tag | |
| type=sha,format=long | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Promote verified digest to release tags | |
| env: | |
| RELEASE_TAGS: ${{ steps.meta.outputs.tags }} | |
| run: | | |
| promotion_policy() { | |
| case "${1##*:}" in | |
| "sha-${GITHUB_SHA}" | v*) printf 'immutable\n' ;; | |
| *) | |
| printf 'Unexpected release tag: %s\n' "$1" >&2 | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| while IFS= read -r tag; do | |
| [[ -n "$tag" ]] || continue | |
| policy="$(promotion_policy "$tag")" | |
| ./scripts/promote-image-tag.sh \ | |
| "$tag" "${IMAGE_REF}@${IMAGE_DIGEST}" "$policy" check | |
| done <<< "$RELEASE_TAGS" | |
| while IFS= read -r tag; do | |
| [[ -n "$tag" ]] || continue | |
| policy="$(promotion_policy "$tag")" | |
| ./scripts/promote-image-tag.sh \ | |
| "$tag" "${IMAGE_REF}@${IMAGE_DIGEST}" "$policy" apply | |
| done <<< "$RELEASE_TAGS" | |
| - name: Publication summary | |
| run: | | |
| { | |
| echo "### Published container" | |
| echo "\`${IMAGE_REF}@${IMAGE_DIGEST}\`" | |
| echo "The workflow verified an anonymous pull after publication. GHCR visibility is configured once at the package level and retained by subsequent releases." | |
| } >> "$GITHUB_STEP_SUMMARY" |