From 7ce544a05159eb043904bf01d181e0b191c173f4 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 20 Apr 2026 20:24:20 +0200 Subject: [PATCH 1/5] feat: add multi-stage Dockerfile with claude CLI runtime --- Dockerfile | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..59746ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# syntax=docker/dockerfile:1 + +# ── Build stage ─────────────────────────────────────────────────────────────── +FROM golang:1.26-bookworm AS builder + +ARG VERSION=dev +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" \ + -o /out/supervisor ./cmd/supervisor + +# ── Runtime stage ───────────────────────────────────────────────────────────── +# Node.js 22 slim — needed for claude CLI subprocess +FROM node:22-slim + +# Install claude CLI (provides the `claude` binary the supervisor shells out to) +RUN npm install -g @anthropic-ai/claude-code \ + && claude --version \ + && echo "claude CLI installed" + +# Copy supervisor binary +COPY --from=builder /out/supervisor /usr/local/bin/supervisor + +# Bake in config (models.yaml + skill discipline files) +COPY config/ /app/config/ + +WORKDIR /app + +# brain/ is writable state — mount a PersistentVolume here +VOLUME /app/brain + +ENV SUPERVISOR_CONFIG_DIR=/app/config/supervisor +ENV SUPERVISOR_MODELS_FILE=/app/config/models.yaml +ENV SUPERVISOR_BRAIN_DIR=/app/brain +ENV SUPERVISOR_SESSIONS_DIR=/app/brain/sessions +ENV SUPERVISOR_PORT=3200 + +EXPOSE 3200 + +ENTRYPOINT ["/usr/local/bin/supervisor"] From 3796cfca872c841e6fd6e7091de415c747f44fd1 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 20 Apr 2026 20:27:42 +0200 Subject: [PATCH 2/5] fix: add .dockerignore and non-root USER to Dockerfile --- .dockerignore | 10 ++++++++++ Dockerfile | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6461b26 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitea +.worktrees +.DS_Store +*.log +.env* +.vscode +.idea +bin/ +brain/ diff --git a/Dockerfile b/Dockerfile index 59746ce..ebbab6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,9 @@ COPY --from=builder /out/supervisor /usr/local/bin/supervisor # Bake in config (models.yaml + skill discipline files) COPY config/ /app/config/ +# Run as non-root +RUN groupadd -r supervisor && useradd -r -g supervisor -d /app supervisor + WORKDIR /app # brain/ is writable state — mount a PersistentVolume here @@ -40,6 +43,8 @@ ENV SUPERVISOR_BRAIN_DIR=/app/brain ENV SUPERVISOR_SESSIONS_DIR=/app/brain/sessions ENV SUPERVISOR_PORT=3200 +USER supervisor + EXPOSE 3200 ENTRYPOINT ["/usr/local/bin/supervisor"] From 4ef6a22e28d04ff42499f95f04a3a93e03364eea Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 20 Apr 2026 21:36:22 +0200 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20add=20CD=20workflow=20(buildctl=20?= =?UTF-8?q?=E2=86=92=20Gitea=20registry=20=E2=86=92=20infra=20repo=20updat?= =?UTF-8?q?e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/cd.yml | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .gitea/workflows/cd.yml diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml new file mode 100644 index 0000000..c6f160e --- /dev/null +++ b/.gitea/workflows/cd.yml @@ -0,0 +1,57 @@ +name: cd + +on: + push: + branches: [main] + +jobs: + deploy: + name: Build and deploy + needs: [check] + runs-on: self-hosted + env: + SERVICE: supervisor + IMAGE: gitea.d-ma.be/mathias/supervisor + INFRA_REPO: git@gitea.d-ma.be:mathias/infra.git + BUILDKIT_HOST: unix:///run/buildkit/buildkitd.sock + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and push image + run: | + IMAGE_TAG="${{ github.sha }}" + echo "Building ${IMAGE}:${IMAGE_TAG}" + buildctl --addr "${BUILDKIT_HOST}" build \ + --frontend dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt build-arg:VERSION="${IMAGE_TAG}" \ + --output "type=image,name=${IMAGE}:${IMAGE_TAG},push=true" + echo "Built and pushed ${IMAGE}:${IMAGE_TAG}" + + - name: Update infra repo + run: | + IMAGE_TAG="${{ github.sha }}" + mkdir -p ~/.ssh + echo "${{ secrets.INFRA_DEPLOY_KEY }}" > ~/.ssh/infra_deploy_key + chmod 600 ~/.ssh/infra_deploy_key + ssh-keyscan gitea.d-ma.be >> ~/.ssh/known_hosts 2>/dev/null + + GIT_SSH_COMMAND="ssh -i ~/.ssh/infra_deploy_key -o IdentitiesOnly=yes" \ + git clone "${INFRA_REPO}" /tmp/infra-update + + cd /tmp/infra-update + sed -i "s|gitea.d-ma.be/mathias/supervisor:.*|gitea.d-ma.be/mathias/supervisor:${IMAGE_TAG}|" \ + "k3s/apps/${SERVICE}/deployment.yaml" + + git config user.email "cd-bot@d-ma.be" + git config user.name "CD Bot" + git add "k3s/apps/${SERVICE}/deployment.yaml" + git commit -m "chore(deploy): ${SERVICE} → ${IMAGE_TAG}" + GIT_SSH_COMMAND="ssh -i ~/.ssh/infra_deploy_key -o IdentitiesOnly=yes" \ + git push + + rm -rf /tmp/infra-update + rm ~/.ssh/infra_deploy_key + echo "Infra repo updated: ${SERVICE} → ${IMAGE_TAG}" From 19b019a8d877c8561fc787c63d4066ec10d9b72f Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 20 Apr 2026 21:38:11 +0200 Subject: [PATCH 4/5] fix: ensure SSH key cleanup on failure in CD workflow --- .gitea/workflows/cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml index c6f160e..ebdab4c 100644 --- a/.gitea/workflows/cd.yml +++ b/.gitea/workflows/cd.yml @@ -32,6 +32,8 @@ jobs: - name: Update infra repo run: | + set -e + trap 'rm -rf /tmp/infra-update; rm -f ~/.ssh/infra_deploy_key' EXIT IMAGE_TAG="${{ github.sha }}" mkdir -p ~/.ssh echo "${{ secrets.INFRA_DEPLOY_KEY }}" > ~/.ssh/infra_deploy_key @@ -52,6 +54,4 @@ jobs: GIT_SSH_COMMAND="ssh -i ~/.ssh/infra_deploy_key -o IdentitiesOnly=yes" \ git push - rm -rf /tmp/infra-update - rm ~/.ssh/infra_deploy_key echo "Infra repo updated: ${SERVICE} → ${IMAGE_TAG}" From 7bf19b6a7be28314384314393006139048f2007e Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Tue, 21 Apr 2026 07:05:44 +0200 Subject: [PATCH 5/5] fix: replace buildctl push with skopeo for simpler registry auth --- .gitea/workflows/cd.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml index ebdab4c..3a55e41 100644 --- a/.gitea/workflows/cd.yml +++ b/.gitea/workflows/cd.yml @@ -20,14 +20,25 @@ jobs: - name: Build and push image run: | + set -e + trap 'rm -f /tmp/supervisor-image.tar' EXIT IMAGE_TAG="${{ github.sha }}" echo "Building ${IMAGE}:${IMAGE_TAG}" + + # Build to local OCI tar (no registry auth needed at build time) buildctl --addr "${BUILDKIT_HOST}" build \ --frontend dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --opt build-arg:VERSION="${IMAGE_TAG}" \ - --output "type=image,name=${IMAGE}:${IMAGE_TAG},push=true" + --output type=oci,dest=/tmp/supervisor-image.tar + + # Push with skopeo using simple credential flag (avoids OAuth token flow) + skopeo copy \ + oci-archive:/tmp/supervisor-image.tar \ + docker://${IMAGE}:${IMAGE_TAG} \ + --dest-creds "${{ secrets.REGISTRY_CREDS }}" + echo "Built and pushed ${IMAGE}:${IMAGE_TAG}" - name: Update infra repo