2026-06-09 08:45:33 +00:00
# Excalidraw Rooms
A self-hosted Excalidraw library with per-user drawing storage and management.
## Features
2026-07-02 14:29:10 +00:00
- Dashboard to manage all your drawings (create, open, rename, delete)
2026-06-09 08:45:33 +00:00
- Per-user storage (via Authentik SSO headers)
2026-07-02 14:29:10 +00:00
- Rename drawings from the dashboard or by clicking the drawing name in the editor
- Native Excalidraw export via the editor's hamburger menu: "Save to..."
(.excalidraw file) and "Export image..." (PNG / SVG / clipboard)
- Autosave (2s debounce) + manual save (Ctrl+S or menu "Save now")
2026-06-09 08:45:33 +00:00
- Persistent storage via NFS
## Docker Image
```
2026-07-02 14:29:10 +00:00
ghcr.io/viktorbarzin/excalidraw-library:latest
2026-06-09 08:45:33 +00:00
```
2026-07-02 14:29:10 +00:00
Built by GitHub Actions (`.github/workflows/build-excalidraw.yml` in the infra
repo, ADR-0002) on every master push touching `stacks/excalidraw/project/**` ;
tags `:latest` + `:<git-sha>` . The package is PRIVATE — cluster pulls use the
Kyverno-synced `ghcr-credentials` secret. Keel polls `:latest` and rolls the
deployment on digest change.
The legacy manually-built DockerHub image `viktorbarzin/excalidraw-library:v4`
is frozen as the rollback target; nothing pushes to it anymore.
2026-06-09 08:45:33 +00:00
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DATA_DIR` | `/data` | Directory where drawings are stored |
| `PORT` | `8080` | HTTP server port |
### Storage
Mount a persistent volume to the `DATA_DIR` path. Drawings are stored as `.excalidraw` files, organized by username:
```
/data/
├── user1/
│ ├── drawing1.excalidraw
│ └── drawing2.excalidraw
└── user2/
└── my-diagram.excalidraw
```
2026-07-02 14:29:10 +00:00
The filename (without extension) is both the drawing ID and its display name;
renaming a drawing renames the file (`os.Rename` , mtime preserved).
2026-06-09 08:45:33 +00:00
2026-07-02 14:29:10 +00:00
## Deployment
2026-06-09 08:45:33 +00:00
2026-07-02 14:29:10 +00:00
Deployed by the `stacks/excalidraw` Terraform stack (namespace `excalidraw` ,
service `draw` , ingress `draw.viktorbarzin.me` with `auth = "required"` ).
2026-06-09 08:45:33 +00:00
### With Authentik SSO
The application reads user identity from Authentik headers:
- `X-Authentik-Username` - Used to create per-user storage directories
- `X-Authentik-Email` - Displayed in UI
- `X-Authentik-Name` - Displayed in UI
2026-07-02 14:29:10 +00:00
Requests without `X-Authentik-Username` fall back to the `anonymous` user.
2026-06-09 08:45:33 +00:00
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/` | Dashboard UI |
| GET | `/api/drawings` | List all drawings for current user |
| GET | `/api/drawings/:id` | Get drawing data |
| PUT | `/api/drawings/:id` | Save drawing |
2026-07-02 14:29:10 +00:00
| PATCH | `/api/drawings/:id` | Rename drawing — body `{"name": "<new-name>"}` ; returns `{"status":"renamed","id":"<new-id>"}` ; 409 if the target name exists |
2026-06-09 08:45:33 +00:00
| DELETE | `/api/drawings/:id` | Delete drawing |
| GET | `/api/user` | Get current user info |
| GET | `/draw/:id` | Open drawing in editor |
2026-07-02 14:29:10 +00:00
Rename names are sanitized server-side to `[a-zA-Z0-9-_]` (other characters
become `-` ; a trailing `.excalidraw` is stripped). Existing IDs are accepted
as-is for backward compatibility with API clients.
## Development
```bash
# Run tests
go test ./...
# Run locally
DATA_DIR=/tmp/excalidraw-data go run .
```
2026-06-09 08:45:33 +00:00
## License
MIT