Skip to content

Redesign the Command dashboard and application chrome #198

Redesign the Command dashboard and application chrome

Redesign the Command dashboard and application chrome #198

Workflow file for this run

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"