Hermes 5a4cfcbf64 feat: AYON single-sign-on (ticket exchange, task boards, browser entry)
- POST /api/auth/ayon/exchange: redeem single-use ticket (issued by the
  AYON addon) via AYON_EXCHANGE_URL, mint session JWT, get-or-create
  internal user row and the <project>/<task> board in the AYON collection
- db: getOrCreateAyonUser / getOrCreateAyonBoard / grantAyonCollectionAccess
- frontend: /b route redeems ticket from URL and forwards to the board
- password login/register paths untouched (legacy instance support)
2026-09-04 13:40:33 +00:00
2026-04-28 19:58:53 +05:30

RefBoard

A self-hosted, real-time collaborative reference board — like PureRef, but on the web, multiplayer, and with markdown notes, threaded review comments, and PDF support baked in.

Drop images, videos, and PDFs onto an infinite GPU canvas. Pan, zoom, group, align, annotate. Share a board with your team and watch each others' cursors in real time. Pin a comment to a thumbnail and resolve it like a code review.

Built because we needed PureRef's painlessness, Miro's collaboration, and a code-review's threading — without paying three different SaaS subscriptions for them.

License: MIT


Features

Canvas

  • Infinite, GPU-accelerated canvas (Pixi.js v8) — handles thousands of items without dropping frames
  • Drag & drop images, videos, and PDFs from your filesystem or clipboard
  • Drop image URLs directly from the browser
  • Pan / zoom / fit-all, selection, lasso, group, ungroup
  • Undo / redo, locked layers, hidden layers
  • Pen / draw tool, sticky notes, text labels
  • Markdown cards (BlockNote-powered editor) right on the canvas

PureRef-parity power tools

  • 40+ keyboard shortcuts mapped to PureRef defaults
  • Align, distribute, normalize size / scale / width / height
  • Auto-arrange in grid / row / column / by name / by z-order / random / stack
  • Optimal pack, overlay compare (Ctrl+Y), flip H/V, reset transform, grayscale, lock
  • Right-click context menu with everything

Real-time collaboration

  • Live cursors with name labels
  • Full-scene sync over Socket.IO with interaction-aware deferral
  • Presence (online avatars), follow-user mode, share dialog with role-based access (owner / editor / viewer)
  • Public, read-only sharing links via collection share token

Review & threads

  • Pin a comment to any object on the canvas → starts a thread
  • Threaded comments with status (open / resolved)
  • Review mode toggles a clean overlay for walking through feedback

Activity log

  • Per-board audit trail visible from the toolbar — uploads, board renames, threads, replies
  • Live updates over Socket.IO (no refresh needed when collaborators are working)
  • Time-grouped feed (Today / Yesterday / older), pagination, deactivation-safe author labels

Media pipeline

  • Image variants (thumbnail / hires / LOD) generated on upload via Sharp
  • Video poster + duration + dimensions extracted via ffmpeg
  • PDF → page thumbnails + hires renders via poppler
  • Background media worker so the upload feels instant

Auth & admin

  • JWT-based email/password auth
  • First user is auto-admin
  • SEED_ADMIN_* env vars to bootstrap an admin on first boot
  • ALLOW_SELF_REGISTRATION flag — when off, only admins can create accounts
  • Admin dashboard at /admin — list users, create accounts, reset passwords, promote/demote between admin and member, deactivate / reactivate, and toggle self-registration on or off at runtime (admin-only, JWT-gated)
  • Admin REST endpoints work with either a JWT belonging to an admin user or an X-API-Key header for bots

Deployment

  • One docker compose up starts RefBoard + a bundled MinIO for object storage
  • Dockerfile is multi-stage and self-contained
  • SQLite (WAL mode) for metadata — zero external DB dependency

Requires Docker + Docker Compose v2.

git clone https://github.com/metalfinger/refboard
cd refboard
bash scripts/setup.sh           # macOS / Linux
# or, on Windows PowerShell:
# .\scripts\setup.ps1

The script copies .env.example.env, pulls the pre-built multi-arch image from GHCR, brings the stack up, waits for the backend to report healthy, and opens the URL in your browser. Re-running it is safe.

Prefer the raw commands? They're equivalent to:

cp .env.example .env
docker compose pull
docker compose up -d

Open http://localhost:8000, create the first admin account on the screen RefBoard shows you, and you're in. No env-var editing required to get started — JWT_SECRET is generated and persisted on first boot, and the first user you register is auto-promoted to admin.

For production / non-localhost installs, see the going-public checklist below — you'll want to set JWT_SECRET explicitly, lock down CORS_ORIGIN, and turn off self-registration.

To build from source instead of pulling (slower; needed only if you've modified the code):

docker compose up --build

The default image is ghcr.io/metalfinger/refboard:latest. Pin a specific version by setting REFBOARD_IMAGE=ghcr.io/metalfinger/refboard:v0.5.0 in .env.

MinIO console (S3 dashboard) is at http://localhost:9001 — login is whatever you set as MINIO_ACCESS_KEY / MINIO_SECRET_KEY in .env (defaults to minioadmin / minioadmin).

Persistent state lives under ./.docker-data/ (SQLite + MinIO objects). Back this up — that's also where the auto-generated JWT_SECRET lives, so losing it logs everyone out.


Manual install (without Docker)

Requires Node.js 20+, ffmpeg, and poppler-utils on your PATH. The Docker image installs these automatically; for a manual install you need to bring them yourself.

# macOS
brew install ffmpeg poppler

# Debian / Ubuntu
sudo apt install ffmpeg poppler-utils

If poppler-utils is missing, image and video uploads still work, but PDF uploads will fail with a clear 501 POPPLER_MISSING error rather than crashing.

You also need an S3-compatible object store reachable from the backend — easiest is to run MinIO standalone.

# 1. Object storage
docker run -d --name minio -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \
  -v $(pwd)/.docker-data/minio:/data \
  minio/minio server /data --console-address ":9001"

# 2. Backend
cd backend
npm install
DB_PATH=./data/refboard.db \
MINIO_ENDPOINT=localhost \
node server.js
# JWT_SECRET is auto-generated and persisted on first boot — set it explicitly
# only if you want to manage it via an external secrets manager. The first user
# you register in the browser becomes admin automatically.

# 3. Frontend (dev mode, separate terminal)
cd frontend
npm install
npm run dev

In dev mode the Vite server proxies /api and /socket.io to the backend on port 8000 — open the URL Vite prints.

For production, run npm run build in frontend/ — the backend serves the built frontend/dist automatically.


Configuration

All knobs live in .env. See .env.example for the full annotated list. Highlights:

Variable Required? What it does
JWT_SECRET yes (in prod) Signs auth tokens. Make it long and random.
DB_PATH no SQLite file path. Defaults to /app/data/refboard.db (Docker).
MINIO_ENDPOINT / _PORT / _ACCESS_KEY / _SECRET_KEY / _BUCKET yes S3-compatible storage.
SEED_ADMIN_EMAIL + SEED_ADMIN_PASSWORD no Idempotent first-boot admin bootstrap.
ALLOW_SELF_REGISTRATION no Initial seed only — sets the runtime toggle on first boot. After that, control it from the admin dashboard. Default false.
MAX_FILE_SIZE_MB no Per-file upload cap. Default 200.
REFBOARD_API_KEY no Enables programmatic upload via X-API-Key header.

Putting it on a public domain

RefBoard is just an HTTP server on port 8000 — every reverse-proxy / tunneling option works. The only non-obvious bit is that it uses Socket.IO over WebSockets, so whatever fronts it must allow WS upgrades.

Pre-baked deployment configs for the common patterns (Cloudflare Tunnel sidecar, Caddy auto-TLS, Fly.io, Render, single-container FS-storage) live in examples/. The hand-rolled instructions below are still valid; the examples just spare you the YAML.

This is what I run my own instance behind. No router config, no public IP, no Let's Encrypt — Cloudflare proxies the connection through an outbound tunnel from the box.

# 1. Install cloudflared (macOS / Linux examples)
brew install cloudflared          # macOS
# OR
sudo apt install cloudflared      # Debian/Ubuntu (see Cloudflare docs for repo setup)

# 2. Authenticate (opens browser to pick a Cloudflare account / zone)
cloudflared tunnel login

# 3. Create a named tunnel
cloudflared tunnel create refboard

# 4. Route a hostname to it (replace example.com with your zone)
cloudflared tunnel route dns refboard refboard.example.com

# 5. Run the tunnel pointed at the local RefBoard
cloudflared tunnel run --url http://localhost:8000 refboard

For a permanent install, generate a config at ~/.cloudflared/config.yml:

tunnel: refboard
credentials-file: /Users/you/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: refboard.example.com
    service: http://localhost:8000
  - service: http_status:404

Then cloudflared service install to make it boot at startup.

Heads-up: Cloudflare's free plan caps proxied request bodies at 100 MB. If you regularly upload videos / large PDFs above that, set MAX_FILE_SIZE_MB accordingly, or pair Cloudflare Tunnel with a direct path for uploads (e.g. tunnel only the SPA, expose the upload API via something else), or upgrade your Cloudflare plan.

Caddy reverse proxy (one-line TLS via Let's Encrypt)

If the box is publicly reachable (cloud VPS, port 443 open):

refboard.example.com {
    reverse_proxy localhost:8000
}

That's the whole Caddyfile. Caddy auto-provisions and renews the certificate, and reverse_proxy upgrades WebSockets transparently.

nginx reverse proxy

server {
    server_name refboard.example.com;
    listen 443 ssl;
    # ssl_certificate / ssl_certificate_key from certbot or your CA

    client_max_body_size 250M;

    location / {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
    }
}

Tailscale (private-to-your-team access without a domain)

If you don't want it on the public internet at all:

tailscale serve --bg http://localhost:8000
# now reachable at https://<machine>.<tailnet>.ts.net

Anyone in your tailnet can hit it; no one else can.

Checklist when going public

  • Set JWT_SECRET explicitly (openssl rand -base64 64). For production installs we recommend pinning the secret in .env rather than relying on the auto-generated one in the SQLite settings table — easier to rotate, easier to back up to a secrets manager.
  • Set NODE_ENV=production.
  • Set CORS_ORIGIN=https://your.domain (drop the wildcard).
  • Confirm self-registration is off in the admin dashboard (defaults off; only flips on if you set ALLOW_SELF_REGISTRATION=true on first boot).
  • Restrict the MinIO console (port 9001) to localhost — only the S3 API on 9000 needs to be reachable from the backend, and the backend already proxies media bytes through /api/images/*, so MinIO does not need to be exposed publicly.
  • Keep ./.docker-data/ (or DB_PATH + MinIO data dir) backed up — that's all your state.

Architecture

┌────────────────────────────────────────────────────────────┐
│ Browser                                                    │
│  React + TypeScript + Vite                                 │
│  Pixi.js v8 canvas · BlockNote markdown · Socket.IO client │
└──────────────────┬─────────────────────────────┬───────────┘
                   │ HTTPS / WS                  │
                   ▼                             ▼
       ┌───────────────────────┐    ┌──────────────────────┐
       │ Express + Socket.IO   │    │  Static frontend     │
       │  /api/* REST          │    │  (served by backend) │
       │  /socket.io WS rooms  │    └──────────────────────┘
       │  Media worker (queue) │
       └────┬──────────┬───────┘
            │          │
            ▼          ▼
    ┌──────────┐   ┌──────────────────┐
    │ SQLite   │   │ MinIO (S3)       │
    │ (WAL)    │   │ images / videos  │
    │ metadata │   │ pdf renders      │
    └──────────┘   └──────────────────┘
  • Frontend — React + Vite + TypeScript. Pixi.js v8 powers the canvas (LOD-aware, viewport-culled, GPU-batched). BlockNote provides the markdown editor used for sticky cards. Socket.IO syncs scene state.
  • Backend — Express for REST, Socket.IO for real-time, better-sqlite3 for metadata (WAL mode), Sharp + ffmpeg + poppler for media processing. A background worker handles thumbnails/hires/PDF rasterization out of the request path.
  • Storage — MinIO (or any S3 API). The backend proxies media bytes through /api/images/* so URLs stay stable across deployment moves.

See CHANGELOG.md for the version history (v0.1.0 → v0.5.0).


Roadmap

  • Admin dashboard frontend (live at /admin — user create / reset-password / role / deactivate)
  • Per-board activity log (uploads, board events, threads, comments — live via Socket.IO)
  • Pre-built multi-arch image at ghcr.io/metalfinger/refboard (linux/amd64 + linux/arm64) — published on every push to main
  • Zero-edit first bootJWT_SECRET auto-generated and persisted, first registered user is auto-admin
  • FS storage adapterSTORAGE_BACKEND=fs drops the MinIO dependency for single-container installs
  • One-click installersscripts/setup.sh (macOS / Linux) and scripts/setup.ps1 (Windows)
  • PaaS deploy templates — Fly.io, Render, Cloudflare Tunnel sidecar, Caddy auto-TLS in examples/
  • Mobile-friendly read-only board view
  • Export board → PDF / image grid
  • Optional remote storage adapters (S3 direct, R2)

Contributing

Issues and PRs welcome. The codebase is single-language on the frontend (TypeScript + React) and small on the backend (a few hundred lines per route file). The hardest parts are in frontend/src/canvas/ (the Pixi-powered scene graph and sync engine).


License

MIT — see LICENSE. Built and maintained by Hiren Kangad.

S
Description
RefBoard fork for AYON-only operation: AYON as identity provider, task-scoped boards. (upstream: metalfinger/refboard)
Readme MIT
918 KiB
2026-09-05 00:17:31 +00:00
Languages
TypeScript 83.5%
JavaScript 15.5%
Shell 0.5%
PowerShell 0.3%