Files
excalidraw-mcp-sentinel/docs/HANDOFF-agent-setup.md
T

5.6 KiB

Agent Handoff: ARM64 Docker Build & Collaboration Setup

Overview

Gitea server at gitea.forkless.com (RPi 5, native ARM64) hosts the repo forkless/excalidraw-mcp-sentinel. An ARM64 Gitea Actions runner is registered on the Gitea server itself (labels: arm64, ubuntu-latest).

Local workspace: /home/pe1085/development/excalidraw-mcp-sentinel Remote: https://gitea.forkless.com/forkless/excalidraw-mcp-sentinel


What Was Done

1. Gitea Actions Workflow

.gitea/workflows/docker.yml — builds both Docker images on ARM64 runner:

  • gitea.forkless.com/forkless/excalidraw-mcp-sentinel:latest
  • gitea.forkless.com/forkless/excalidraw-mcp-sentinel-canvas:latest

Triggers on push to main (paths: Dockerfile, src/, frontend/, .gitea/**), PRs, and workflow_dispatch.

2. Alpine Base Images

Both Dockerfile and Dockerfile.canvas changed from node:20-slim to node:20-alpine. Build tools via apk add --no-cache python3 make g++.

3. AMD64 Runner Removed

There was a local x86_64 Gitea Actions runner (wsl-runner) on this machine that competed with the ARM64 runner. It was stopped and its .runner file deleted from /home/pe1085/development/gitea-test/. It had label ubuntu-latest:docker://catthehacker/ubuntu:full-latest — would build amd64 images that don't run on ARM64.

4. Frontend WebSocket URL

frontend/src/App.tsx line 438:

const wsUrl = 'wss://excalidraw.forkless.com'

Previously used window.location.host — hardcoded to the forkless domain now.

5. Reverse Proxy Config (NPM / OpenResty)

The server at excalidraw.forkless.com runs behind Nginx Proxy Manager v2.15.1. Two settings fixed WebSocket connectivity:

  • WebSockets Support toggle enabled in NPM (or advanced config: proxy_set_header Upgrade $http_upgrade;)
  • ALLOWED_ORIGINS env var must include https://excalidraw.forkless.com

6. trust proxy

src/server.ts line 43:

app.set('trust proxy', 1);

Required because NPM sets X-Forwarded-For headers and express-rate-limit throws an error without it.

7. Gitea Actions Secrets

Create these in the Gitea repo settings → Actions → Secrets:

  • GITEAUSER = forkless
  • GITEATOKEN = Gitea personal access token (needs write:repository scope)

What Still Needs Work

1. Collab URL Hardcoded in Excalidraw Bundle

The Excalidraw npm library has wss://oss-collab.excalidraw.com hardcoded in its built JS. This is NOT configurable via component props — the collabServerUrl prop is not a documented/working prop in v0.18.x.

Two options:

A) Build-time patch (currently reverted but script exists): scripts/patch-collab-url.mjs replaces the URL in dist/frontend/assets/ after Vite builds. Enable it by restoring:

"build:frontend": "vite build && node scripts/patch-collab-url.mjs"

B) Keep default (current): Let the Excalidraw library connect to the official collab server. Requires NPM/OpenResty to allow egress to oss-collab.excalidraw.com. Currently works after cache flush + trust proxy fix.

2. ALLOWED_ORIGINS Env Var

The verifyWsClient in src/security.ts checks Origin header against ALLOWED_ORIGINS. Defaults to http://localhost:3000 and http://127.0.0.1:3000. For production, set:

ALLOWED_ORIGINS=https://excalidraw.forkless.com

Otherwise WebSocket connections from the real domain are rejected.

3. Unused Props in App.tsx

Line 1864-1867 of frontend/src/App.tsx:

collabServerUrl="wss://excalidraw.forkless.com/socket.io"
onCollabDialogOpen={() => {}}

These are likely ignored by the Excalidraw library (not documented props). Can be removed — the real collab URL is controlled by the build-time patch or the default in the npm bundle.

4. WebSocket Auth

When EXCALIDRAW_API_KEY is set, the WS server sends auth_required on connect, waits for hello with the API key, and closes if not received. The frontend handles this in connectWebSocket()sendHello(). This works but could be strengthened:

  • Add IP-based rate limiting for WS connections
  • Add verifyWsClient to reject missing origins (currently allows no-origin)

5. Docker Image Tags

Currently only :latest and :$sha tags are pushed. For versioned releases, trigger on v* tags is in the workflow but untested.


Credentials & Access

  • Gitea API token: stored in ~/.profile as GITEA_TOKEN (not GITEATOKEN)
  • Git credentials: stored via git credential-store for gitea.forkless.com — username forkless, password is the token
  • To use the token: source ~/.profile then $GITEA_TOKEN
  • Or via git: TOKEN=$(git credential fill <<< $'protocol=https\nhost=gitea.forkless.com\npath=/forkless/excalidraw-mcp-sentinel.git\n' 2>/dev/null | grep "^password=" | cut -d= -f2)

Quick Commands

# Check latest build
TOKEN=$(git credential fill <<< $'protocol=https\nhost=gitea.forkless.com\npath=/forkless/excalidraw-mcp-sentinel.git\n' 2>/dev/null | grep "^password=" | cut -d= -f2) && curl -s -H "Authorization: token $TOKEN" "https://gitea.forkless.com/api/v1/repos/forkless/excalidraw-mcp-sentinel/actions/runs?limit=3" | python3 -c "import sys,json;d=json.load(sys.stdin);[print(f'Run {r[\"id\"]}: {r[\"status\"]:>10} {r.get(\"conclusion\",\"-\"):>10} {r[\"head_sha\"][:12]}') for r in d.get('workflow_runs',[])]"

# Check Docker image manifest
docker manifest inspect gitea.forkless.com/forkless/excalidraw-mcp-sentinel:latest

# Git credential helper (broken 'tea' → fixed 'tea-cli')
git config --global credential.https://gitea.forkless.com.helper '!tea-cli login helper'

# Push without credential helper issues
git -c credential.https://gitea.forkless.com.helper='!tea-cli login helper' push origin main