From e44053a25de1847dfa7b472b9aa9cf6330eddba9 Mon Sep 17 00:00:00 2001 From: Fork Less Date: Wed, 24 Jun 2026 19:44:18 +0200 Subject: [PATCH] docs: agent handoff for ARM64 build & collab setup --- docs/HANDOFF-agent-setup.md | 141 ++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs/HANDOFF-agent-setup.md diff --git a/docs/HANDOFF-agent-setup.md b/docs/HANDOFF-agent-setup.md new file mode 100644 index 0000000..22d9aa3 --- /dev/null +++ b/docs/HANDOFF-agent-setup.md @@ -0,0 +1,141 @@ +# 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: +```ts +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: +```ts +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: +```json +"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`: +```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 + +```bash +# 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 +```