From fcb51e066ef7b6c14107aa305e0219632e9513ad Mon Sep 17 00:00:00 2001 From: BBQ Date: Fri, 20 Feb 2026 03:37:41 +0800 Subject: [PATCH 1/5] ci(docker): add docker-publish workflow and clean up release.yml Add dedicated docker-publish.yml with full CI/CD pipeline: - Build & push server/agent/web/mcp images on tag, main push, and PR - Publish to both Docker Hub and GHCR - Semver tag strategy (latest, version, major.minor, major, sha) - GHA build cache, SLSA provenance, and SBOM - PR builds validate without pushing Remove superseded dockerhub job from release.yml. --- .github/workflows/docker-publish.yml | 93 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 48 -------------- 2 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..4be76c9b --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,93 @@ +name: Docker Publish + +on: + push: + branches: [main] + tags: ["v*.*.*"] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + packages: write + id-token: write + +jobs: + docker: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - image: server + dockerfile: docker/Dockerfile.server + - image: agent + dockerfile: docker/Dockerfile.agent + - image: web + dockerfile: docker/Dockerfile.web + - image: mcp + dockerfile: docker/Dockerfile.mcp + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + memohai/${{ matrix.image }} + ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + labels: | + org.opencontainers.image.title=memoh-${{ matrix.image }} + org.opencontainers.image.description=Memoh ${{ matrix.image }} - Multi-member AI agent platform + org.opencontainers.image.vendor=memohai + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + build-args: | + VERSION=${{ steps.meta.outputs.version }} + COMMIT_HASH=${{ github.sha }} + BUILD_TIME=${{ fromJson(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} + VITE_API_URL=/api + VITE_AGENT_URL=/agent + provenance: true + sbom: true + cache-from: type=gha,scope=${{ matrix.image }} + cache-to: type=gha,scope=${{ matrix.image }},mode=max diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e1ef2cab..ccce14cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,13 +8,9 @@ on: push: tags: - 'v*' - release: - types: - - published jobs: release: - if: github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -39,47 +35,3 @@ jobs: # env: # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} # NPM_CONFIG_PROVENANCE: true - - dockerhub: - if: github.event_name == 'release' - runs-on: ubuntu-latest - strategy: - matrix: - include: - - image: server - dockerfile: docker/Dockerfile.server - - image: agent - dockerfile: docker/Dockerfile.agent - - image: web - dockerfile: docker/Dockerfile.web - steps: - - uses: actions/checkout@v4 - - - name: Set build time - id: vars - run: echo "build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push multi-arch image - uses: docker/build-push-action@v6 - with: - context: . - file: ${{ matrix.dockerfile }} - platforms: linux/amd64,linux/arm64 - push: true - tags: memohai/${{ matrix.image }}:${{ github.event.release.tag_name }} - build-args: | - VERSION=${{ github.event.release.tag_name }} - COMMIT_HASH=${{ github.sha }} - BUILD_TIME=${{ steps.vars.outputs.build_time }} \ No newline at end of file From 6304869c3c4f782421442e85f299ac1b8eae760a Mon Sep 17 00:00:00 2001 From: BBQ Date: Fri, 20 Feb 2026 03:45:53 +0800 Subject: [PATCH 2/5] ci: add migration validation workflow Run migrate up -> down -> up against a temporary PostgreSQL service container on every PR and push to main, verifying all migrations apply, rollback, and re-apply correctly. --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ff0dab74 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + migrate: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:18-alpine + env: + POSTGRES_DB: memoh_test + POSTGRES_USER: memoh + POSTGRES_PASSWORD: memoh123 + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U memoh" + --health-interval 5s + --health-timeout 3s + --health-retries 5 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Generate config + run: | + cat > config.toml <<'TOML' + [postgres] + host = "127.0.0.1" + port = 5432 + user = "memoh" + password = "memoh123" + database = "memoh_test" + sslmode = "disable" + TOML + + - name: Migrate up + run: go run ./cmd/agent migrate up + + - name: Migrate down + run: go run ./cmd/agent migrate down + + - name: Migrate up (idempotent) + run: go run ./cmd/agent migrate up From 50b05be1837e5fcee9797977645025f48bef3047 Mon Sep 17 00:00:00 2001 From: BBQ Date: Fri, 20 Feb 2026 03:51:04 +0800 Subject: [PATCH 3/5] ci(docker): only push on tag, skip latest for prereleases - Remove push-to-main trigger; only tag push publishes images - Prerelease tags (e.g. v0.1.0-beta.2) publish their version tag only, without updating latest or short semver tags --- .github/workflows/docker-publish.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 4be76c9b..b43a7776 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,7 +2,6 @@ name: Docker Publish on: push: - branches: [main] tags: ["v*.*.*"] pull_request: branches: [main] @@ -39,8 +38,7 @@ jobs: memohai/${{ matrix.image }} ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=ref,event=branch + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-') }} type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -58,14 +56,14 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - if: github.event_name != 'pull_request' + if: startsWith(github.ref, 'refs/tags/') uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - if: github.event_name != 'pull_request' + if: startsWith(github.ref, 'refs/tags/') uses: docker/login-action@v3 with: registry: ghcr.io @@ -77,7 +75,7 @@ jobs: with: context: . file: ${{ matrix.dockerfile }} - push: ${{ github.event_name != 'pull_request' }} + push: ${{ startsWith(github.ref, 'refs/tags/') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64 From f472182b824946c604ed1dcada240fcba6334808 Mon Sep 17 00:00:00 2001 From: BBQ Date: Fri, 20 Feb 2026 03:54:43 +0800 Subject: [PATCH 4/5] ci: optimize workflow performance - PR docker builds use single arch (amd64 only), tag push uses dual - Add paths-ignore to skip CI on docs-only changes - Add concurrency groups to cancel stale runs on re-push - Build Go binary once instead of 3x go run in migrate job --- .github/workflows/ci.yml | 21 ++++++++++++++++++--- .github/workflows/docker-publish.yml | 10 +++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0dab74..676772a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,20 @@ name: CI on: push: branches: [main] + paths-ignore: + - "docs/**" + - "**.md" + - "devenv/**" pull_request: branches: [main] + paths-ignore: + - "docs/**" + - "**.md" + - "devenv/**" + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true permissions: contents: read @@ -33,6 +45,9 @@ jobs: with: go-version-file: go.mod + - name: Build + run: go build -o memoh-server ./cmd/agent + - name: Generate config run: | cat > config.toml <<'TOML' @@ -46,10 +61,10 @@ jobs: TOML - name: Migrate up - run: go run ./cmd/agent migrate up + run: ./memoh-server migrate up - name: Migrate down - run: go run ./cmd/agent migrate down + run: ./memoh-server migrate down - name: Migrate up (idempotent) - run: go run ./cmd/agent migrate up + run: ./memoh-server migrate up diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b43a7776..829a87e3 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -5,8 +5,16 @@ on: tags: ["v*.*.*"] pull_request: branches: [main] + paths-ignore: + - "docs/**" + - "**.md" + - "devenv/**" workflow_dispatch: +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read packages: write @@ -78,7 +86,7 @@ jobs: push: ${{ startsWith(github.ref, 'refs/tags/') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 + platforms: ${{ startsWith(github.ref, 'refs/tags/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} build-args: | VERSION=${{ steps.meta.outputs.version }} COMMIT_HASH=${{ github.sha }} From e6bd5a11af26f0500e577cf9a32e60c89dc0475b Mon Sep 17 00:00:00 2001 From: BBQ Date: Fri, 20 Feb 2026 03:55:48 +0800 Subject: [PATCH 5/5] ci: only run migrate job when db/migrations changes --- .github/workflows/ci.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 676772a9..1810e3aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,16 +3,12 @@ name: CI on: push: branches: [main] - paths-ignore: - - "docs/**" - - "**.md" - - "devenv/**" + paths: + - "db/migrations/**" pull_request: branches: [main] - paths-ignore: - - "docs/**" - - "**.md" - - "devenv/**" + paths: + - "db/migrations/**" concurrency: group: ci-${{ github.ref }}