# excalidraw-go Excalidraw auto-hébergé avec un **gestionnaire de fichiers intégré** : arborescence de dossiers, création, renommage, déplacement (glisser-déposer ou menu), duplication, suppression (corbeille), recherche, import/export de fichiers `.excalidraw`, sauvegarde automatique côté serveur. Interface web unique, responsive (l'explorateur devient un tiroir sur mobile). - **Backend** : Go 1.24, bibliothèque standard uniquement (aucune dépendance externe), API REST/JSON, stockage en fichiers `.excalidraw` sur un volume. - **Frontend** : React 19 + composant officiel [`@excalidraw/excalidraw`](https://www.npmjs.com/package/@excalidraw/excalidraw) 0.18.1 (licence MIT), build Vite **embarqué dans le binaire Go** (`embed`). - **Image** : multi-stage, finale en `distroless/static:nonroot` (~53 Mo), un seul conteneur, aucun accès Internet requis à l'exécution (polices Excalidraw servies localement). ## Architecture ``` Navigateur ──HTTP──▶ conteneur excalidraw-go (:8080, uid 65532, rootfs read-only) ├─ / SPA React + Excalidraw (embarquée dans le binaire) ├─ /api/... API fichiers (Go net/http) └─ /healthz sonde │ ▼ volume /data ├─ projets/réseau/sdn.excalidraw ← fichiers JSON Excalidraw standards ├─ .library.excalidrawlib ← bibliothèque partagée └─ .trash//... ← éléments supprimés ``` Les schémas restent des fichiers `.excalidraw` standards : ils s'ouvrent tels quels dans excalidraw.com, VS Code (extension Excalidraw) ou Obsidian, et se sauvegardent/versionnent comme n'importe quel répertoire (rsync, restic, git...). ## Démarrage ```bash cp .env.example .env # optionnel : port, authentification Basic docker compose up -d --build # http://localhost:8080 ``` | Variable | Défaut | Rôle | |-----------------|----------|-------------------------------------------------------------------| | `LISTEN_ADDR` | `:8080` | Adresse d'écoute | | `DATA_DIR` | `/data` | Racine des schémas | | `MAX_BODY_MB` | `50` | Taille max d'un schéma (images embarquées en base64 comprises) | | `AUTH_USER` / `AUTH_PASSWORD` | vide | Active une authentification HTTP Basic si les deux sont définis | Bind mount au lieu du volume nommé : `- ./data:/data` puis `chown -R 65532:65532 ./data`. ## Utilisation - **+ Schéma / + Dossier** : création dans le dossier courant. - **⋯** sur une ligne : renommer, déplacer, dupliquer, télécharger, supprimer. - **Glisser-déposer** (souris) d'un élément sur un dossier, sur `..` ou sur le fil d'Ariane pour le déplacer. Sur écran tactile : menu ⋯ → *Déplacer…* - **Importer** : envoie un ou plusieurs `.excalidraw` locaux dans le dossier courant. - **Sauvegarde** : automatique 1,5 s après la dernière modification, immédiate avec `Ctrl/Cmd+S`. Le statut (Modifié / Enregistrement… / Enregistré) est affiché dans la barre du haut. - **Lien direct** : l'URL `http://hôte:8080/#/dossier/schema.excalidraw` ouvre le schéma. - **Bibliothèque** Excalidraw : persistée côté serveur, partagée entre tous les schémas. ### Concurrence Chaque lecture renvoie un `ETag` (SHA-256 tronqué du contenu) ; chaque sauvegarde l'envoie en `If-Match`. Si le fichier a été modifié ailleurs (autre onglet, autre poste), le serveur répond `412` et l'interface propose d'écraser ou de recharger. Il n'y a pas d'édition collaborative temps réel (voir *Évolutions*). ## API Tous les chemins sont relatifs à `DATA_DIR`, en notation `/`. Les composants commençant par `.` sont refusés, et les liens symboliques sortant de la racine sont bloqués. | Méthode | Route | Corps / paramètres | Réponse | |---|---|---|---| | GET | `/api/list?path=dir` | | `[Entry]` (dossiers puis schémas) | | GET | `/api/search?q=texte` | | `[Entry]` (200 max) | | GET | `/api/file?path=x.excalidraw` | | JSON du schéma + `ETag` | | PUT | `/api/file?path=x.excalidraw` | JSON ; `If-Match: ` ou `If-None-Match: *` (création) | `Entry` + `ETag` ; `409` existe, `412` conflit | | POST | `/api/folder` | `{"path":"a/b"}` | `Entry` | | POST | `/api/move` | `{"from":"a.excalidraw","to":"d/b.excalidraw"}` | `Entry` (renommage / déplacement, fichier ou dossier) | | POST | `/api/copy` | `{"from":"…","to":"…"}` | `Entry` | | DELETE | `/api/entry?path=x[&recursive=true]` | | `204` (déplacé dans `.trash/`) | | GET/PUT | `/api/library` | bibliothèque `excalidrawlib` | | | GET | `/healthz` | | `ok` (hors authentification) | `Entry` = `{"name","path","isDir","size","modTime"}`. Exemple d'automatisation (génération de schémas par script) : ```bash curl -X PUT 'http://localhost:8080/api/file?path=auto/inventaire.excalidraw' \ -H 'If-None-Match: *' --data-binary @inventaire.excalidraw ``` ## Développement ```bash # Terminal 1 : API go run ./cmd/server # DATA_DIR=./data par défaut # Terminal 2 : frontend avec rechargement à chaud (proxy /api -> :8080) cd frontend && npm install && npm run dev # Tests et build complet hors Docker go test ./... cd frontend && npm run build && cd .. && go build -o server ./cmd/server ``` Arborescence : ``` cmd/server/main.go serveur HTTP, SPA embarquée, auth Basic, healthcheck internal/storage/ accès disque : sécurité des chemins, écriture atomique, ETag, corbeille internal/api/ routes REST web/embed.go embarque web/dist (produit par le build Vite) frontend/src/App.tsx éditeur Excalidraw, sauvegarde auto, conflits, bibliothèque frontend/src/Explorer.tsx gestionnaire de fichiers ``` ## Sécurité - Conteneur non-root, rootfs en lecture seule, `cap_drop: ALL`, `no-new-privileges`. - L'authentification Basic intégrée suffit pour un usage local ; en exposition réseau, placer le service derrière un reverse proxy TLS (Traefik, Caddy, nginx) avec SSO (Authelia, oauth2-proxy, Authentik) et laisser `AUTH_*` vides. - Écritures atomiques (fichier temporaire + `fsync` + `rename`) : pas de schéma tronqué en cas d'arrêt brutal. - Aucune suppression physique : la purge de `.trash/` est à la charge de l'exploitant (ex. `find /data/.trash -mindepth 1 -maxdepth 1 -mtime +30 -exec rm -rf {} +`). ## Limites et évolutions possibles - **Collaboration temps réel** : nécessiterait le serveur [excalidraw-room](https://github.com/excalidraw/excalidraw-room) (WebSocket) et la logique de réconciliation (`reconcileElements`) côté client. - **Miniatures** dans l'explorateur : générables côté client via `exportToSvg` et stockées à côté du fichier. - **Historique de versions** : commit git automatique dans `DATA_DIR` à chaque sauvegarde, ou snapshots du volume (ZFS/Btrfs). - **Multi-utilisateurs avec espaces privés** : identité transmise par le reverse proxy (`X-Forwarded-User`) et racine par utilisateur. - Les schémas avec beaucoup d'images embarquées sont volumineux (base64 dans le JSON) ; ajuster `MAX_BODY_MB`.