From a06c3d2e364b33dd25df3847f2938d7ae152e059 Mon Sep 17 00:00:00 2001 From: Fork Less Date: Wed, 24 Jun 2026 20:25:09 +0200 Subject: [PATCH] cleanup: remove broken patch script ref + unused collab props --- docs/HANDOFF-agent-setup.md | 161 +++++++++++++++++++++--------------- frontend/src/App.tsx | 2 - package.json | 2 +- 3 files changed, 95 insertions(+), 70 deletions(-) diff --git a/docs/HANDOFF-agent-setup.md b/docs/HANDOFF-agent-setup.md index 22d9aa3..d024f81 100644 --- a/docs/HANDOFF-agent-setup.md +++ b/docs/HANDOFF-agent-setup.md @@ -4,7 +4,7 @@ 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`). +on the Gitea server itself (labels: `arm64`, `ubuntu-latest`). Runner #1. Local workspace: `/home/pe1085/development/excalidraw-mcp-sentinel` Remote: `https://gitea.forkless.com/forkless/excalidraw-mcp-sentinel` @@ -19,108 +19,135 @@ Remote: `https://gitea.forkless.com/forkless/excalidraw-mcp-sentinel` - `gitea.forkless.com/forkless/excalidraw-mcp-sentinel-canvas:latest` Triggers on push to `main` (paths: Dockerfile, src/**, frontend/**, .gitea/**), -PRs, and `workflow_dispatch`. +PRs, and `workflow_dispatch`. **Avoid empty commits** — they don't match the +`paths` filter and won't trigger a build. ### 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 +There was a local x86_64 Gitea Actions runner (`wsl-runner`, id=1) 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 `ubuntu-latest:docker://catthehacker/ubuntu:full-latest` — would build amd64 -images that don't run on ARM64. +images that can't run on ARM64. **Do not re-register it.** Only the RPi 5 +ARM64 runner should exist. ### 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. +Previously used `window.location.host` — hardcoded to the forkless domain. +This is for the app's custom sync protocol (JSON messages via `ws` library), +not the Excalidraw collaboration Socket.IO. -### 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` +### 5. Reverse Proxy Config (NPM v2.15.1 / OpenResty) +The server at `excalidraw.forkless.com` runs behind Nginx Proxy Manager. +**Critical settings for WebSocket to work:** +- **WebSockets Support** toggle enabled in NPM +- The WebSocket upgrade headers (`Upgrade`, `Connection`) are forwarded +- **Do NOT** add custom nginx proxy config in the Advanced tab unless the + toggle alone doesn't work — the toggle handles it. Custom config can + conflict and break NPM entirely (requires container restart to recover). -### 6. trust proxy +### 6. `trust proxy` in Express `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. +Required because NPM sets `X-Forwarded-For` and express-rate-limit rejects +requests without this setting. -### 7. Gitea Actions Secrets -Create these in the Gitea repo settings → Actions → Secrets: +### 7. `ALLOWED_ORIGINS` Env Var +The `verifyWsClient()` in `src/security.ts` checks `Origin` header against +`ALLOWED_ORIGINS`. Default: `http://localhost:3000`, `http://127.0.0.1:3000`. +**Must include `https://excalidraw.forkless.com` for production.** + +### 8. Gitea Actions Secrets +Set in Gitea repo → Settings → Actions → Secrets: - `GITEAUSER` = `forkless` -- `GITEATOKEN` = Gitea personal access token (needs `write:repository` scope) +- `GITEATOKEN` = Gitea PAT with `write:repository` scope + +### 9. `collabServerUrl` Prop (Unused / Placeholder) +`frontend/src/App.tsx` line 1864-1865 still has: +```tsx +collabServerUrl="wss://excalidraw.forkless.com/socket.io" +onCollabDialogOpen={() => {}} +``` +These are **not documented/working props** in `@excalidraw/excalidraw` v0.18.x +and are likely ignored by the library. They can be safely removed. The real +collaboration URL is hardcoded in the Excalidraw npm bundle and connects to +`wss://oss-collab.excalidraw.com` by default. --- ## 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. +### 1. Build-Time URL Patch (Broken / Remove) +Commit `6ed842d` added `scripts/patch-collab-url.mjs` and modified `package.json` +to run it after `vite build`. The script replaces `wss://oss-collab.excalidraw.com` +with a custom URL in the built bundle. -Two options: +Current state: +- `package.json` still has `"build:frontend": "vite build && node scripts/patch-collab-url.mjs"` +- The script file was deleted from disk but exists in git history +- **Next build will fail** because the script file is missing -**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" +**Remove it:** +```bash +cd /home/pe1085/development/excalidraw-mcp-sentinel +# Option A: Remove the script reference from package.json +sed -i 's/ && node scripts\/patch-collab-url.mjs//' package.json +# Option B: Restore the script file from git +git restore 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. +The official collab server at `oss-collab.excalidraw.com` works fine with the +trust proxy + CORS + cache-flush fix. Only self-host this if the official +server is blocked. -### 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. +### 2. Docker Build Fails (Missing Patch Script) +Build 783 failed because of the missing script file. Fix above first, then +trigger a clean build (must modify a file matching the paths filter — +`Dockerfile`, `src/**`, `frontend/**`, `.gitea/**`). -### 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. +### 3. LocalStorage Cache +Browser caches the Excalidraw bundle aggressively. When switching between +collab URLs, users must clear localStorage + hard refresh (Ctrl+F5) or +the old URL persists in their browser. -### 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) +### 4. WebSocket Auth Hardening +When `EXCALIDRAW_API_KEY` is set, the WS requires auth. The frontend sends +the key in a `hello` message after receiving `auth_required`. Without the +key, the connection drops. Consider: +- Rate-limit WS connections by IP +- Reject no-origin connections in `verifyWsClient` (currently allows) ### 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. +Only `:latest` and `:$sha` are pushed. Version tags (`v*`) trigger is in +the workflow but untested. --- ## Credentials & Access -- **Gitea API token**: stored in `~/.profile` as `GITEA_TOKEN` (not `GITEATOKEN`) +- **Gitea API token**: stored in `~/.profile` as `GITEA_TOKEN` - **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)` + `gitea.forkless.com` — username `forkless`, password = Gitea PAT +- To use the token in exec_shell: + `source ~/.profile && echo "$GITEA_TOKEN"` (DO NOT source `~/.profile` + if the user objects — but the token is stored there) +- Alternative: read via git credential helper: + `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)` + +### Git Credential Helper Fix +The system had a broken `credential.helper` for `gitea.forkless.com` pointing +to `!tea login helper` — but the binary is `tea-cli`, not `tea`. Fixed with: +```bash +git config --global --replace-all credential.https://gitea.forkless.com.helper '!tea-cli login helper' +``` --- @@ -128,14 +155,14 @@ trigger on `v*` tags is in the workflow but untested. ```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',[])]" +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=5" | 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 +git push origin main + +# Trigger dispatch (if paths filter blocks push trigger) +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 -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" "https://gitea.forkless.com/api/v1/repos/forkless/excalidraw-mcp-sentinel/actions/workflows/docker.yml/dispatches" -d '{"ref":"main"}' ``` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cd49c07..b7e64f0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1861,8 +1861,6 @@ function App(): JSX.Element { } }} onChange={handleCanvasChange} - collabServerUrl="wss://excalidraw.forkless.com/socket.io" - onCollabDialogOpen={() => {}} /> diff --git a/package.json b/package.json index e604f58..2df9610 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "start": "npm run build:server && node dist/index.js", "canvas": "npm run build:server && node dist/server.js", "build": "npm run build:frontend && npm run build:server", - "build:frontend": "vite build && node scripts/patch-collab-url.mjs", + "build:frontend": "vite build", "build:server": "npx tsc", "build:types": "npx tsc --emitDeclarationOnly", "dev": "concurrently \"npm run dev:server\" \"vite\"",