5.5 KiB
Guerrilla Queue — Roadmap
Self-hosted, zero-admin digital queue. Single URL, QR code, no login.
Spec: queue-spec.md | Plan: ~/.claude/plans/peaceful-sprouting-squid.md
Stack
- Backend: Go 1.22 · gorilla/websocket · skip2/go-qrcode
- Frontend: Vanilla JS + CSS, single
static/index.html(~15 KB uncompressed) - Deploy: Docker multi-stage (→ scratch) · docker-compose · Traefik
File map
| File | Purpose |
|---|---|
main.go |
HTTP server, embed wiring, gzip middleware, routes |
hub.go |
WebSocket hub, client registry, message dispatch |
queue.go |
QueueState, Entry, all business logic + 60s timers |
static/index.html |
Entire frontend: HTML + CSS + JS inline |
Dockerfile |
Multi-stage build → scratch image |
docker-compose.yml |
Traefik labels, APP_URL / QUEUE_HOSTNAME vars |
Phases
- Phase 1 — Go skeleton (go.mod, main.go, hub.go, queue.go, static/index.html stub)
- Phase 2 — Queue core (join, selfRemove, findBy*, stateJSON, avgDuration)
- Phase 3 — Removal mechanics (markDone, voteRemove, restore, timerRemove, cleanupVotes)
- Phase 4 — Frontend (full render loop, join form, confirm flows, countdown, wait time)
- Phase 5 — QR code (/qr.png via skip2/go-qrcode), gzip middleware, ROADMAP.md
- Phase 6 — Docker & deployment files (Dockerfile, docker-compose.yml)
Deployment
Environment variables
| Variable | Default | Notes |
|---|---|---|
APP_URL |
derived from request | Full public URL encoded in the QR code. Always set this in production. |
QUEUE_HOSTNAME |
queue.example.com |
Traefik Host() routing rule |
docker-compose (quick start)
# 1. Create .env next to docker-compose.yml:
APP_URL=https://queue.yourdomain.com
QUEUE_HOSTNAME=queue.yourdomain.com
# 2. Make sure the Traefik network exists:
docker network create traefik
# 3. Build and start:
docker compose up -d --build
Without Traefik (VPS, direct port)
services:
queue:
build: .
restart: unless-stopped
ports:
- "8080:8080"
environment:
- APP_URL=https://queue.yourdomain.com
For Nginx in front:
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
WebSocket message protocol
Client → Server
type |
extra fields | when |
|---|---|---|
init |
token (empty string if none) |
on every connect |
join |
name (optional) |
user taps "Get my number" |
done |
— | user confirms "I'm done" |
mark_done |
entryId |
mark #1 as done (any queue member) |
vote_remove |
entryId |
vote to remove non-#1 entry |
restore |
— | entry owner cancels pending_removal |
Server → Client
type |
extra fields | when |
|---|---|---|
hello |
yourEntryId (null if not in queue) |
response to init |
joined |
yourToken, yourEntryId |
response to join |
state |
entries[], counter, avgDuration |
broadcast on every queue change |
error |
reason, onNumber |
e.g. already_voting |
Key design decisions
Timer safety: The 60 s removal timer fires by sending an entry ID to hub.timerC
(a buffered channel). The hub's single goroutine reads from timerC and calls
q.timerRemove() — this serialises all mutations through one goroutine with no
queue-level mutex needed.
cleanupVotes logic: When an entry is removed, its queue number is purged from all
PendingBy lists. Entries in pending_removal whose vote count drops below threshold
are restored to active (Case B: quorum lost). Entries with empty PendingBy after
cleanup are NOT restored (Case A: the single marker left, let the timer run).
Token flow: Token is generated by server on join, returned once, stored in
localStorage. On reconnect the client sends it in init; server re-associates the
*Client with the existing entry. Token is never broadcast to other clients.
Frontend XSS: entry.name is the only user-supplied string that other browsers
render. It is HTML-escaped via esc() on every insertion into innerHTML. All other
dynamic values are server-generated hex IDs or integers.
Out of scope (per spec)
Admin panel · push notifications · persistence across server restarts · multiple queues · auth/accounts · entry editing · vote retraction