Add Excalidraw - #73
Merged
Merged
Conversation
Full self-hosted Excalidraw stack behind a single subdomain: an in-compose nginx reverse proxy fronts the runtime-configurable frontend (alswl/excalidraw, Excalidraw 0.18.1), the excalidraw-room collaboration server (socket.io) and the excalidraw-storage-backend (Keyv over redis), routing / to the frontend, /socket.io/ to the room and /api/ to the storage backend. The frontend is configured with same-origin URLs so everything works under the one entrypoint. All three application images are MIT and pinned by digest (they publish only mutable tags); redis and nginx are pinned to versioned tags. Telemetry is disabled via VITE_APP_DISABLE_TRACKING. Storage persists to a redis AOF volume; the images' sqlite backend does not load its native binding on their musl base. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNxkVRvaJrvmCwKn3fch8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Excalidraw — Add to Freeshard app store
Full self-hosted Excalidraw stack (collaborative whiteboard), added alongside WBO — WBO is untouched. Fit was pre-approved (RECOMMEND): a polished, fully-MIT whiteboard that keeps server-persisted, returnable, real-time-collaborative boards.
License: MIT (all three application images) · Min size:
s· Access:public(link-based, no accounts) · Preview zip (after CI): https://storageaccountportab0da.blob.core.windows.net/app-store/preview/feat/add-excalidraw/all_apps/excalidraw/excalidraw.zipServices and images
excalidraw(entrypoint)nginx:1.27-alpineexcalidraw-frontendalswl/excalidraw@sha256:f605184e5c9a0c2b1247ffa23dacf50e7b2f86004d25dfc7f64eaf1a7018e2cd(tagv0.18.1-fork-b2, Excalidraw 0.18.1)excalidraw-roomexcalidraw/excalidraw-room@sha256:2fe999f9be4379e3ee282fc45d75d84a691a6383dde33544514cc395287c7a70(tagsha-03ff435)excalidraw-storagealswl/excalidraw-storage-backend@sha256:ca35be07c0513e1f36be44fc40bffee4062ab8341e29f4133eafc48718acd88e(tagv2023.11.11)excalidraw-redisredis:7-alpineThe single-subdomain routing (the hard part) — solved and verified
Freeshard gives one browser-facing entrypoint = one subdomain, but Excalidraw's browser talks to three origins: the editor, the room websocket, and the storage HTTP API. Solution: an in-compose nginx reverse proxy is the single entrypoint (
excalidraw, port 80), routing under the one subdomain:/socket.io/→excalidraw-room:80(with websocket upgrade)/api/→excalidraw-storage:8080(backend global prefix/api/v2)/→excalidraw-frontend:80The frontend is pointed at same-origin URLs so no cross-origin config is needed:
VITE_APP_WS_SERVER_URL=https://excalidraw.{{ portal.domain }}(socket.io transport lands on/socket.io/, default namespace), and the storage URLs are relative (/api/v2/scenes/,/api/v2). The frontend image injects these intowindow._env_at container start (launcher.py), so no rebuild is needed. Only the nginx entrypoint joinsportal; the other four services sit on the internalexcalidrawnetwork (agents.md rules 1–3, 9).Verified locally (isolated compose, rendered template, throwaway bridge network): frontend
/→200 with mywindow._env_config injected;/socket.io/returns a real socket.io handshake advertisingupgrades:[websocket];POST /api/v2/scenes/→201 thenGET→200 reading the exact bytes back; redis AOF persisted to the volume.docker compose configparses the rendered template with zero unset-variable warnings (the$$-escaped nginx runtime vars are correct).Integration decisions
public. Collaboration is by shared link to people who are not paired to the shard, so pairing-gatedprivatewould defeat it. Board data on collaboration links is end-to-end encrypted; the link is the key. Same model as the existingmirotalk.sqlite3native binding fails to load (crash on boot — caught in the local run). Redis is Keyv's native backend with a pure-JS client;redis-server --appendonly yeson a volume gives durable, returnable boards. The official redis entrypoint chowns its volume, so no bind-mount EACCES.app_version=0.18.1is the frontend's Excalidraw version (from the image tag), not a literal tag match.update_check.pyraisesOptOut(a digest pin defeatsupdate.py's string-replace bump), with the manual-bump recipe in the reason string — same pattern as mirotalk.VITE_APP_DISABLE_TRACKING=true,VITE_APP_FIREBASE_CONFIG={}, and no analytics IDs set.idle_time_for_shutdown: 3600so a paused live room is not torn down mid-session.is_featured: false. The curation note recommends featuring Excalidraw / replacing WBO, but that is a product decision left to Max; this PR only adds the app alongside WBO.FLAGS for review + the shard smoke-test
linux/amd64single-arch (no arm64 manifest). Fine for amd64 shards; flag if any shard host is arm64.libraries.excalidraw.com(upstream default, fires only when a user opens the library panel). Left as-is; can be blanked viaVITE_APP_LIBRARY_URL/VITE_APP_LIBRARY_BACKENDif a fully-offline posture is wanted.hint). This is the one axis where WBO's named boards are nicer.Licenses verified (raw LICENSE files)
excalidraw/excalidraw— MIT ·excalidraw/excalidraw-room— MIT ·alswl/excalidraw-storage-backend(MIT fork of kiliandeca) — MIT.Checklist (agents.md)
docker-compose.yml.templateset (multi-service, internal network, single entrypoint)app_meta.jsonset (app_version,name,pretty_name,icon,entrypoints,paths,lifecycle,store_info,minimum_portal_size)upstream_reposetupdate_check.pypresent (OptOut, digest-pinned)minimum_portal_size: "s"docker compose configpassed; stack brought up and routing verified locallyRecommended reading order
apps/excalidraw/docker-compose.yml.template— the stack and the nginx routingapps/excalidraw/app_meta.json— entrypoint, access, store infoapps/excalidraw/update_check.py— digest-pin OptOut