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
36 changes: 35 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
# VCS / IDE
.git
.next
.github
.claude
.hermes
.turbo
.next
.playwright
.vscode
.idea

# Dependencies & build outputs (rebuilt inside the image)
node_modules
**/node_modules
**/dist
**/.next
**/coverage
**/tsconfig.tsbuildinfo

# Tests & local tooling (not needed for production image)
tests
**/*.test.ts
**/*.spec.ts
test-results
playwright-report
screenshots
vitest.config.ts
playwright*.config.ts

# Docs / non-runtime content (keep skills + deploy runtime)
docs
*.md
!skills/**/SKILL.md

# Env & secrets
.env
.env.*
!.env.example

# Logs / temp
*.log
tmp
.DS_Store
101 changes: 41 additions & 60 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@ on:
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:
Expand All @@ -35,84 +43,56 @@ jobs:
options: >-
--health-cmd "pg_isready -U muster -d muster"
--health-interval 5s --health-timeout 3s --health-retries 20
redis:
image: redis:8.2.1-bookworm
ports: ["6379:6379"]
options: >-
--health-cmd "redis-cli ping"
--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: "24"
node-version: "26"
cache: pnpm
- name: Start object storage
run: |
docker run --detach --name muster-ci-minio \
--publish 9000:9000 \
--env MINIO_ROOT_USER=muster \
--env MINIO_ROOT_PASSWORD=local-minio-secret \
minio/minio:RELEASE.2025-09-07T16-13-09Z \
server /data
for attempt in {1..20}; do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then
break
fi
if [ "$attempt" -eq 20 ]; then
docker logs muster-ci-minio
exit 1
fi
sleep 2
done
docker run --rm --network host \
--entrypoint /bin/sh \
minio/mc:RELEASE.2025-08-13T08-35-41Z \
-c 'mc alias set ci http://127.0.0.1:9000 muster local-minio-secret &&
mc mb --ignore-existing ci/muster-evidence &&
mc version enable ci/muster-evidence &&
mc anonymous set none ci/muster-evidence'
- 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
- name: Build database dependencies
run: pnpm exec turbo build --filter=@muster/database
- run: pnpm db:migrate
- run: pnpm db:bootstrap
- run: pnpm db:verify-clean
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test:homelab-installer
- run: pnpm test:unit
- run: pnpm contracts:generate
- name: Verify committed migrations
- 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/database/migrations
- run: pnpm build
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test --config=playwright.clean.config.ts
- run: MUSTER_DEMO_MODE=true pnpm db:seed
- run: pnpm exec playwright test tests/muster.spec.ts tests/jessie-hunt.spec.ts --project=chromium
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: playwright-report
path: playwright-report
if-no-files-found: ignore
retention-days: 14
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:
needs: quality
runs-on: ubuntu-24.04
outputs:
image_ref: ${{ steps.image.outputs.ref }}
Expand Down Expand Up @@ -165,8 +145,8 @@ jobs:
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
cache-to: type=gha,mode=max
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 \
Expand Down Expand Up @@ -211,7 +191,7 @@ jobs:
muster-sbom.cdx.json
muster-image.txt
SHA256SUMS
- name: Verify container starts
- 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
Expand Down Expand Up @@ -241,9 +221,10 @@ jobs:
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: [container, release-security]
needs: [quality, container, release-security]
runs-on: ubuntu-24.04
concurrency:
group: muster-release-tags-${{ github.repository }}
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
# Full history only for gitleaks; shallow clone is enough for audit/licences.
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
Expand All @@ -33,7 +34,7 @@ jobs:
version: 11.17.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
node-version: "26"
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level high
Expand All @@ -46,7 +47,12 @@ jobs:
name: dependency-licences
path: licenses.json

# Full image rebuild + Trivy used to run on every PR *and* duplicate the
# main CI container job. Keep the deep image security pass on schedule
# (and when this workflow is run on main via schedule only). PR/push image
# scan lives in ci.yml `container` (single build).
image:
if: github.event_name == 'schedule'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
Expand All @@ -57,6 +63,8 @@ jobs:
load: true
pull: true
tags: muster:security
cache-from: type=gha,scope=muster-image
cache-to: type=gha,mode=max,scope=muster-image
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: muster:security
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Thank you for improving the shared workspace for human and agent-driven security

1. Open an issue describing the operator problem and security impact.
2. Add or update an architecture decision record for material boundary changes.
3. Run `pnpm check` and the relevant Playwright project.
3. Run `pnpm check` .
4. Explain migrations, capability changes, connector compatibility, and rollback in the pull request.

Commit generated migrations and public JSON Schemas. Do not hand-edit generated Drizzle snapshots.
Expand Down
62 changes: 54 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,68 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS build
ENV PNPM_HOME=/pnpm
ENV PATH=$PNPM_HOME:$PATH
RUN corepack enable
# -----------------------------------------------------------------------------
# Build stage — layer order optimised for BuildKit + GHA cache hits.
# Copy lockfiles and package manifests before sources so dependency installs
# reuse cache when only app code changes.
# -----------------------------------------------------------------------------
FROM node:26-bookworm-slim AS build
ENV PNPM_HOME=/pnpm \
PATH=/pnpm:$PATH \
CI=true \
TURBO_TELEMETRY_DISABLED=1 \
NEXT_TELEMETRY_DISABLED=1
# Node 26 official slim images no longer ship corepack on PATH by default.
RUN npm install -g corepack@latest && corepack enable
WORKDIR /workspace

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json ./
COPY apps/agent-gateway/package.json apps/agent-gateway/
COPY apps/mcp-server/package.json apps/mcp-server/
COPY apps/web/package.json apps/web/
COPY apps/worker/package.json apps/worker/
COPY packages/agent-harness/package.json packages/agent-harness/
COPY packages/agents/package.json packages/agents/
COPY packages/alerts/package.json packages/alerts/
COPY packages/api-client/package.json packages/api-client/
COPY packages/audit/package.json packages/audit/
COPY packages/auth/package.json packages/auth/
COPY packages/authz/package.json packages/authz/
COPY packages/config/package.json packages/config/
COPY packages/contracts/package.json packages/contracts/
COPY packages/database/package.json packages/database/
COPY packages/event-protocol/package.json packages/event-protocol/
COPY packages/evidence/package.json packages/evidence/
COPY packages/integrations/package.json packages/integrations/
COPY packages/investigations/package.json packages/investigations/
COPY packages/mcp/package.json packages/mcp/
COPY packages/notifications/package.json packages/notifications/
COPY packages/rooms/package.json packages/rooms/
COPY packages/search/package.json packages/search/
COPY packages/test-utils/package.json packages/test-utils/
COPY packages/ui/package.json packages/ui/
COPY packages/workflows/package.json packages/workflows/

RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile

COPY . .
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm contracts:generate && pnpm build

RUN --mount=type=cache,id=turbo,target=/workspace/.turbo \
pnpm contracts:generate && pnpm build

RUN pnpm deploy --filter=@muster/worker --prod /prod/worker \
&& pnpm deploy --filter=@muster/agent-gateway --prod /prod/agent-gateway \
&& pnpm deploy --filter=@muster/database --prod /prod/database \
&& mkdir -p /workspace/apps/web/.next/standalone/apps/web/.next \
&& cp -R /workspace/apps/web/.next/static /workspace/apps/web/.next/standalone/apps/web/.next/static \
&& cp -R /workspace/apps/web/public /workspace/apps/web/.next/standalone/apps/web/public

FROM gcr.io/distroless/nodejs24-debian13:nonroot AS runtime
ENV NODE_ENV=production
# -----------------------------------------------------------------------------
# Runtime — distroless Node 26
# -----------------------------------------------------------------------------
FROM gcr.io/distroless/nodejs26-debian13:nonroot AS runtime
ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1
WORKDIR /app
COPY --from=build --chown=nonroot:nonroot /workspace/apps/web/.next/standalone ./web
COPY --from=build --chown=nonroot:nonroot /prod/worker ./worker
Expand Down
Loading
Loading