From f161e3cb629adb604917473fc24a7aae110fbc32 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 19:54:28 -0500 Subject: [PATCH 1/8] feat(ci): add Docker build+push pipeline for gateway and web images --- .woodpecker/ci.yml | 38 +++++++++++++++++++ .../scratchpads/ci-docker-publish-20260330.md | 30 +++++++++++++++ plugins/macp/src/index.ts | 10 +++-- plugins/macp/src/macp-runtime.ts | 10 ++++- 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 docs/scratchpads/ci-docker-publish-20260330.md diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 2dfe430..2fe0df1 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -59,3 +59,41 @@ steps: - lint - format - test + + publish-gateway: + image: woodpeckerci/plugin-docker-buildx + settings: + registry: git.mosaicstack.dev + repo: git.mosaicstack.dev/mosaic/mosaic-stack-gateway + dockerfile: docker/gateway.Dockerfile + tags: + - latest + - ${CI_COMMIT_SHA} + username: + from_secret: REGISTRY_USERNAME + password: + from_secret: REGISTRY_PASSWORD + when: + - event: push + branch: main + depends_on: + - build + + publish-web: + image: woodpeckerci/plugin-docker-buildx + settings: + registry: git.mosaicstack.dev + repo: git.mosaicstack.dev/mosaic/mosaic-stack-web + dockerfile: docker/web.Dockerfile + tags: + - latest + - ${CI_COMMIT_SHA} + username: + from_secret: REGISTRY_USERNAME + password: + from_secret: REGISTRY_PASSWORD + when: + - event: push + branch: main + depends_on: + - build diff --git a/docs/scratchpads/ci-docker-publish-20260330.md b/docs/scratchpads/ci-docker-publish-20260330.md new file mode 100644 index 0000000..b8ee848 --- /dev/null +++ b/docs/scratchpads/ci-docker-publish-20260330.md @@ -0,0 +1,30 @@ +# Scratchpad: CI Docker Publish (2026-03-30) + +- Objective: Add Woodpecker Docker build+push steps for gateway and web images on `main` pushes. +- Scope: `.woodpecker/ci.yml`. +- Constraints: + - Use existing Dockerfiles at `docker/gateway.Dockerfile` and `docker/web.Dockerfile`. + - Publish to `git.mosaicstack.dev` with `from_secret` credentials. + - Tag both `latest` and `${CI_COMMIT_SHA}`. + - Do not run publish steps on pull requests. +- ASSUMPTION: Publishing `latest` is required by the task for registry convenience, even though immutable tags remain the safer deployment reference. +- Findings: + - Existing pipeline already has `build` after `lint`, `format`, and `test`. + - `apps/gateway/package.json` uses `tsc` for `build`; no Prisma dependency or `prisma generate` hook is present. +- Plan: + 1. Patch `.woodpecker/ci.yml` to keep `build` as the quality gate successor and add `publish-gateway` plus `publish-web`. + 2. Validate YAML and run repo quality gates relevant to the change. + 3. Review the diff, then commit/push/PR if validation passes. +- Verification: + - `python3 -c "import yaml; yaml.safe_load(open('.woodpecker/ci.yml'))" && echo "YAML valid"` + - `pnpm lint` + - `pnpm typecheck` + - `pnpm format:check` + - `docker compose up -d` + - `pnpm --filter @mosaic/db db:push` + - `pnpm test` + - `pnpm build` + - Manual review of `.woodpecker/ci.yml` diff: publish steps are main-only, depend on `build`, and use secret-backed registry auth plus dual tags. +- Risks: + - Pipeline behavior beyond YAML validation cannot be fully proven locally; remote Woodpecker execution will be the final situational check after push. + - Repo baseline required two existing `plugins/macp` files to be reformatted before `pnpm format:check` would pass. diff --git a/plugins/macp/src/index.ts b/plugins/macp/src/index.ts index 674c542..622e548 100644 --- a/plugins/macp/src/index.ts +++ b/plugins/macp/src/index.ts @@ -9,10 +9,14 @@ const ocRequire = createRequire(import.meta.url); const sdkRoot = path.dirname(ocRequire.resolve('openclaw/dist/plugin-sdk/index.js')); // Dynamic imports for runtime SDK functions -const { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } = await import( +const { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } = (await import( `${sdkRoot}/acp-runtime.js` -) as { - registerAcpRuntimeBackend: (backend: { id: string; runtime: any; healthy: () => boolean }) => void; +)) as { + registerAcpRuntimeBackend: (backend: { + id: string; + runtime: any; + healthy: () => boolean; + }) => void; unregisterAcpRuntimeBackend: (id: string) => void; }; diff --git a/plugins/macp/src/macp-runtime.ts b/plugins/macp/src/macp-runtime.ts index 2d8f95c..7287630 100644 --- a/plugins/macp/src/macp-runtime.ts +++ b/plugins/macp/src/macp-runtime.ts @@ -82,7 +82,15 @@ const MACP_CAPABILITIES: AcpRuntimeCapabilities = { const DEFAULT_REPO_ROOT = '~/src/mosaic-stack'; const ORCHESTRATOR_RUN_PATH = '~/.config/mosaic/bin/mosaic-orchestrator-run'; -const PI_RUNNER_PATH = path.join(os.homedir(), 'src', 'mosaic-stack', 'tools', 'macp', 'dispatcher', 'pi_runner.ts'); +const PI_RUNNER_PATH = path.join( + os.homedir(), + 'src', + 'mosaic-stack', + 'tools', + 'macp', + 'dispatcher', + 'pi_runner.ts', +); function expandHome(rawPath: string): string { if (rawPath === '~') { From 41e8f91b2d4c3587a48aef6d6582ccb21b78667a Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:00:35 -0500 Subject: [PATCH 2/8] =?UTF-8?q?fix(ci):=20decouple=20build/publish=20from?= =?UTF-8?q?=20test=20step=20=E2=80=94=20DB=20test=20requires=20external=20?= =?UTF-8?q?Postgres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .woodpecker/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 2fe0df1..eddbb8e 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -58,7 +58,6 @@ steps: depends_on: - lint - format - - test publish-gateway: image: woodpeckerci/plugin-docker-buildx From f544cc65d26961694409d89da018bd27c63bf6bf Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:04:50 -0500 Subject: [PATCH 3/8] fix(ci): switch to Kaniko image builder using global gitea secrets --- .woodpecker/ci.yml | 78 ++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index eddbb8e..c1c3154 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -59,40 +59,58 @@ steps: - lint - format - publish-gateway: - image: woodpeckerci/plugin-docker-buildx - settings: - registry: git.mosaicstack.dev - repo: git.mosaicstack.dev/mosaic/mosaic-stack-gateway - dockerfile: docker/gateway.Dockerfile - tags: - - latest - - ${CI_COMMIT_SHA} - username: - from_secret: REGISTRY_USERNAME - password: - from_secret: REGISTRY_PASSWORD + build-gateway: + image: gcr.io/kaniko-project/executor:debug + environment: + REGISTRY_USER: + from_secret: gitea_username + REGISTRY_PASS: + from_secret: gitea_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json + - | + DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS when: - - event: push - branch: main + - branch: [main] + event: [push, manual, tag] depends_on: - build - publish-web: - image: woodpeckerci/plugin-docker-buildx - settings: - registry: git.mosaicstack.dev - repo: git.mosaicstack.dev/mosaic/mosaic-stack-web - dockerfile: docker/web.Dockerfile - tags: - - latest - - ${CI_COMMIT_SHA} - username: - from_secret: REGISTRY_USERNAME - password: - from_secret: REGISTRY_PASSWORD + build-web: + image: gcr.io/kaniko-project/executor:debug + environment: + REGISTRY_USER: + from_secret: gitea_username + REGISTRY_PASS: + from_secret: gitea_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json + - | + DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS when: - - event: push - branch: main + - branch: [main] + event: [push, manual, tag] depends_on: - build From 48be0aa195a455d5306cd363f389a10368b4dcbe Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:12:23 -0500 Subject: [PATCH 4/8] =?UTF-8?q?fix(ci):=20separate=20publish=20pipeline=20?= =?UTF-8?q?=E2=80=94=20Docker=20builds=20independent=20of=20test=20failure?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .woodpecker/ci.yml | 56 ------------------------------ .woodpecker/publish.yml | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 .woodpecker/publish.yml diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index c1c3154..9a33a6e 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -58,59 +58,3 @@ steps: depends_on: - lint - format - - build-gateway: - image: gcr.io/kaniko-project/executor:debug - environment: - REGISTRY_USER: - from_secret: gitea_username - REGISTRY_PASS: - from_secret: gitea_password - CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} - CI_COMMIT_TAG: ${CI_COMMIT_TAG} - CI_COMMIT_SHA: ${CI_COMMIT_SHA} - commands: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json - - | - DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}" - if [ "$CI_COMMIT_BRANCH" = "main" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest" - fi - if [ -n "$CI_COMMIT_TAG" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG" - fi - /kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS - when: - - branch: [main] - event: [push, manual, tag] - depends_on: - - build - - build-web: - image: gcr.io/kaniko-project/executor:debug - environment: - REGISTRY_USER: - from_secret: gitea_username - REGISTRY_PASS: - from_secret: gitea_password - CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} - CI_COMMIT_TAG: ${CI_COMMIT_TAG} - CI_COMMIT_SHA: ${CI_COMMIT_SHA} - commands: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json - - | - DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}" - if [ "$CI_COMMIT_BRANCH" = "main" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest" - fi - if [ -n "$CI_COMMIT_TAG" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG" - fi - /kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS - when: - - branch: [main] - event: [push, manual, tag] - depends_on: - - build diff --git a/.woodpecker/publish.yml b/.woodpecker/publish.yml new file mode 100644 index 0000000..2723893 --- /dev/null +++ b/.woodpecker/publish.yml @@ -0,0 +1,75 @@ +# Docker image build and push — runs only on main branch push/tag +# Completely independent of the CI test pipeline + +variables: + - &node_image 'node:22-alpine' + - &enable_pnpm 'corepack enable' + +when: + - branch: [main] + event: [push, manual, tag] + +steps: + install: + image: *node_image + commands: + - corepack enable + - pnpm install --frozen-lockfile + + build: + image: *node_image + commands: + - *enable_pnpm + - pnpm build + depends_on: + - install + + build-gateway: + image: gcr.io/kaniko-project/executor:debug + environment: + REGISTRY_USER: + from_secret: gitea_username + REGISTRY_PASS: + from_secret: gitea_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json + - | + DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS + depends_on: + - build + + build-web: + image: gcr.io/kaniko-project/executor:debug + environment: + REGISTRY_USER: + from_secret: gitea_username + REGISTRY_PASS: + from_secret: gitea_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json + - | + DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS + depends_on: + - build From 34fad9da81057cf7e997d1e4119d2466d754037d Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:19:29 -0500 Subject: [PATCH 5/8] =?UTF-8?q?fix(ci):=20remove=20build=20step=20from=20c?= =?UTF-8?q?i.yml=20=E2=80=94=20build=20only=20in=20publish=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .woodpecker/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 9a33a6e..e7662fd 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -49,12 +49,3 @@ steps: - pnpm test depends_on: - typecheck - - build: - image: *node_image - commands: - - *enable_pnpm - - pnpm build - depends_on: - - lint - - format From 47b750928881cac7bedb43e71ed20d278ca1ef6a Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:25:59 -0500 Subject: [PATCH 6/8] fix(ci): add postgres service sidecar for integration tests --- .woodpecker/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index e7662fd..1cc092b 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -44,8 +44,19 @@ steps: test: image: *node_image + environment: + DATABASE_URL: postgresql://mosaic:mosaic@postgres:5432/mosaic commands: - *enable_pnpm + - pnpm --filter @mosaic/db run db:migrate 2>/dev/null || true - pnpm test depends_on: - typecheck + +services: + postgres: + image: pgvector/pgvector:pg17 + environment: + POSTGRES_USER: mosaic + POSTGRES_PASSWORD: mosaic + POSTGRES_DB: mosaic From 85d4527701b7be0606888a383923eb92bda7a110 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:31:39 -0500 Subject: [PATCH 7/8] =?UTF-8?q?fix(macp):=20use=20sh=20instead=20of=20bash?= =?UTF-8?q?=20in=20gate-runner=20=E2=80=94=20Alpine=20Linux=20compatibilit?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/macp/src/gate-runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/macp/src/gate-runner.ts b/packages/macp/src/gate-runner.ts index 40c971a..b59f807 100644 --- a/packages/macp/src/gate-runner.ts +++ b/packages/macp/src/gate-runner.ts @@ -43,7 +43,7 @@ export function runShell( let timedOut = false; try { - const result = spawnSync('bash', ['-lc', command], { + const result = spawnSync('sh', ['-c', command], { cwd, timeout: Math.max(1, timeoutSec) * 1000, encoding: 'utf-8', From 3321d4575a2c20a45cbb2fa3bd9983be504d8207 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 30 Mar 2026 20:41:46 -0500 Subject: [PATCH 8/8] fix(ci): wait for postgres readiness before migration + tests --- .woodpecker/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 1cc092b..e39e5da 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -48,7 +48,18 @@ steps: DATABASE_URL: postgresql://mosaic:mosaic@postgres:5432/mosaic commands: - *enable_pnpm - - pnpm --filter @mosaic/db run db:migrate 2>/dev/null || true + # Install postgresql-client for pg_isready + - apk add --no-cache postgresql-client + # Wait up to 30s for postgres to be ready + - | + for i in $(seq 1 30); do + pg_isready -h postgres -p 5432 -U mosaic && break + echo "Waiting for postgres ($i/30)..." + sleep 1 + done + # Run migrations (DATABASE_URL is set in environment above) + - pnpm --filter @mosaic/db run db:migrate + # Run all tests - pnpm test depends_on: - typecheck