Skip to content

feat: snare serve — self-hosted callback server#2

Merged
peg merged 1 commit into
mainfrom
feat/snare-serve
Mar 14, 2026
Merged

feat: snare serve — self-hosted callback server#2
peg merged 1 commit into
mainfrom
feat/snare-serve

Conversation

@peg

@peg peg commented Mar 14, 2026

Copy link
Copy Markdown
Owner

Summary

Implements snare serve — a self-hosted Go HTTP server that replaces the Cloudflare Worker backend at snare.sh.

New package: internal/serve/

File Description
db.go SQLite layer via modernc.org/sqlite (pure Go, no CGo). Tables: devices, tokens, events. WAL mode, foreign keys on.
server.go HTTP server, route wiring, optional Let's Encrypt TLS via golang.org/x/crypto/acme/autocert.
handlers.go All API handlers — same contract as the Cloudflare Worker.
webhook.go Outbound webhook delivery: Discord embeds, Slack attachments, generic JSON.
dashboard.go Dark monospace SSR dashboard with 10s auto-refresh.
serve_test.go 13 tests covering canary handler, auth middleware, device creation, DB round-trips.

API routes (identical to Worker)

GET/POST /c/{token}[/*]    → 1×1 GIF, fires alert async (no auth, body never read)
POST     /api/devices       → create device (server-minted ID)
POST     /api/register      → register token webhook (Bearer device_secret)
POST     /api/revoke        → remove registration (Bearer device_secret)
GET      /api/events/{token}→ retrieve events (Bearer device_secret)
GET      /health            → {"status":"ok"}

Dashboard routes (new)

GET /                        → SSR HTML dashboard (dark terminal theme)
GET /api/dashboard/alerts    → recent 50 alerts as JSON
GET /api/dashboard/devices   → registered devices as JSON

CLI

snare serve --port 8080 --db ~/.snare/serve/snare.db [--tls-domain example.com] [--webhook-url https://...]

Docker

docker compose up -d

Compose exposes port 8080, mounts /data volume, and ships a healthcheck hitting /health.

Privacy guarantee preserved

The canary callback handler writes the GIF response before any goroutine processes the alert. Request bodies are never read, stored, or forwarded — same guarantee as the Worker.

Quality

  • go build ./...
  • go vet ./...
  • 13 tests pass ✅
  • No CGo required (pure Go SQLite)

Replaces the Cloudflare Worker backend with a self-hosted Go HTTP server.

## What's included

### internal/serve/ (new package)
- db.go         — SQLite (modernc.org/sqlite, pure Go) with schema for
                  devices, tokens, events tables
- server.go     — HTTP server with optional Let's Encrypt TLS via autocert
- handlers.go   — All API routes, same contract as the Cloudflare Worker:
                    GET/POST /c/{token}[/*]   canary callback → 1×1 GIF
                    POST /api/devices         create device (server-minted ID)
                    POST /api/register        register token webhook (Bearer auth)
                    POST /api/revoke          remove registration (Bearer auth)
                    GET  /api/events/{token}  retrieve events (Bearer auth)
                    GET  /health              {"status":"ok"}
                  Plus dashboard routes:
                    GET  /                    HTML dashboard (SSR, dark terminal theme)
                    GET  /api/dashboard/alerts   recent alerts JSON
                    GET  /api/dashboard/devices  registered devices JSON
- webhook.go    — Outbound webhook delivery (Discord, Slack, generic JSON)
- dashboard.go  — html/template SSR dashboard, auto-refreshes every 10s
- serve_test.go — 13 tests covering canary handler, auth middleware,
                  device creation, hashing, and DB round-trips

### CLI
- internal/cli/cli.go — added 'snare serve' subcommand with flags:
    --port <n>             (default 8080)
    --db <path>            (default ~/.snare/serve/snare.db)
    --tls-domain <domain>  optional Let's Encrypt
    --webhook-url <url>    global fallback webhook

### Docker
- Dockerfile        — multi-stage build, Alpine runtime, pure Go (CGO_ENABLED=0)
- docker-compose.yml — single-service compose with volume mount and healthcheck

## Privacy guarantee preserved
The /c/{token} handler returns the GIF response before any body is read.
Alert processing runs in a goroutine after the response is written.
Request bodies are never read, stored, or forwarded.
@peg
peg merged commit 6f7b403 into main Mar 14, 2026
1 check passed
@peg
peg deleted the feat/snare-serve branch March 15, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant