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:latestgitea.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=forklessGITEATOKEN= Gitea personal access token (needswrite:repositoryscope)
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
verifyWsClientto 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
~/.profileasGITEA_TOKEN(notGITEATOKEN) - Git credentials: stored via
git credential-storeforgitea.forkless.com— usernameforkless, password is the token - To use the token:
source ~/.profilethen$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