favoritter/README.md
Ole-Morten Duesund 845b152f15 docs: add README with features, config, deployment, and API docs
Covers quick start (binary and container), all environment variables,
Caddy deployment examples (subdomain, subpath, remote proxy),
API usage with curl examples, complete route table, tech stack,
security features, and development instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:20:48 +02:00

8.1 KiB

Favoritter

A self-hosted web application for collecting and sharing favorites. Movies, songs, cars, lego sets, flowers, pictures — anything you love.

Built as a single Go binary with SQLite, server-rendered HTML, and HTMX for interactivity. Designed to be easy to deploy for technical users who can share access with friends and family.

Features

  • Favorites — Add favorites with a description, optional URL, optional image upload, and tags
  • Tags — Autocomplete from existing tags, browse by tag
  • Privacy — Each favorite can be public or private, with a configurable default
  • User profiles — Public or limited visibility, with avatar, bio, and display name
  • Admin panel — User management, tag management, signup request approval, site settings
  • Signup modes — Open registration, approval-required, or closed
  • Atom feeds — Global, per-user, and per-tag feeds
  • Import/Export — JSON and CSV, for data portability
  • JSON API — Full REST API under /api/v1/ for third-party clients
  • OpenGraph — Rich link previews when sharing favorites
  • Accessible — Semantic HTML, keyboard navigation, screen reader support, lang="nb", WCAG 2.2 AA target
  • Proxy-aware — Supports deployment behind Caddy/nginx on a different machine (WireGuard, Tailscale)

Quick Start

Run directly

# Build
go build -o favoritter ./cmd/favoritter

# Run (creates admin user on first start)
FAVORITTER_ADMIN_USERNAME=admin \
FAVORITTER_ADMIN_PASSWORD=changeme \
./favoritter

Open http://localhost:8080 in your browser.

Run with Podman/Docker

BUILDAH_FORMAT=docker podman build \
  --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --build-arg GIT_REVISION="$(git describe --always --dirty)" \
  -t favoritter .

podman run -d \
  -p 8080:8080 \
  -v favoritter-data:/data \
  -e FAVORITTER_ADMIN_USERNAME=admin \
  -e FAVORITTER_ADMIN_PASSWORD=changeme \
  favoritter

Configuration

All configuration is via environment variables. Sensible defaults are provided for everything except the initial admin credentials.

Variable Default Description
FAVORITTER_ADMIN_USERNAME (none) Initial admin username (created on first run)
FAVORITTER_ADMIN_PASSWORD (none) Initial admin password
FAVORITTER_DB_PATH ./data/favoritter.db SQLite database file path
FAVORITTER_LISTEN :8080 Listen address (e.g. :8080, 10.0.0.5:8080)
FAVORITTER_BASE_PATH / Base path for subpath deployment (e.g. /faves)
FAVORITTER_EXTERNAL_URL (auto) Public URL (e.g. https://faves.example.com). Used for feeds, cookies, OpenGraph. If unset, inferred from Host header.
FAVORITTER_TRUSTED_PROXIES 127.0.0.1 Comma-separated IPs/CIDRs to trust for X-Forwarded-For (e.g. 100.64.0.0/10 for Tailscale)
FAVORITTER_UPLOAD_DIR ./data/uploads Directory for uploaded images
FAVORITTER_MAX_UPLOAD_SIZE 10485760 Maximum upload size in bytes (10 MB)
FAVORITTER_SESSION_LIFETIME 720h Session cookie lifetime (30 days)
FAVORITTER_ARGON2_MEMORY 65536 Argon2id memory cost in KiB (64 MB)
FAVORITTER_ARGON2_TIME 3 Argon2id iterations
FAVORITTER_ARGON2_PARALLELISM 2 Argon2id parallelism
FAVORITTER_RATE_LIMIT 60 Auth endpoint rate limit (requests/minute/IP)
FAVORITTER_SITE_NAME Favoritter Site name shown in UI and feeds
FAVORITTER_DEV_MODE false Enable live template reload and debug logging

Deployment

Behind Caddy (subdomain)

faves.example.com {
    reverse_proxy 10.0.0.5:8080
}

Set FAVORITTER_EXTERNAL_URL=https://faves.example.com and FAVORITTER_TRUSTED_PROXIES=<caddy-ip>.

Behind Caddy (subpath)

example.com {
    handle_path /faves/* {
        reverse_proxy 10.0.0.5:8080
    }
}

Set FAVORITTER_BASE_PATH=/faves and FAVORITTER_EXTERNAL_URL=https://example.com/faves.

Remote proxy (WireGuard/Tailscale)

Caddy and Favoritter can run on different machines. Set FAVORITTER_TRUSTED_PROXIES to the proxy's IP or CIDR (e.g. 100.64.0.0/10 for Tailscale, 10.0.0.0/24 for WireGuard).

API

The JSON API lives under /api/v1/. Authenticate by posting to /api/v1/auth/login to get a session cookie, then use it for subsequent requests.

# Login
curl -c cookies.txt -X POST http://localhost:8080/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"changeme"}'

# List your faves
curl -b cookies.txt http://localhost:8080/api/v1/faves

# Create a fave
curl -b cookies.txt -X POST http://localhost:8080/api/v1/faves \
  -H 'Content-Type: application/json' \
  -d '{"description":"Blade Runner 2049","url":"https://example.com","privacy":"public","tags":["film","sci-fi"]}'

# Export
curl -b cookies.txt http://localhost:8080/api/v1/export/json

See the route list for all available endpoints.

Routes

Web

Method Path Description
GET / Home page / public feed
GET/POST /login Login
GET/POST /signup Signup (mode-dependent)
POST /logout Logout
GET/POST /reset-password Forced password reset
GET /faves Own faves list
GET /faves/new New fave form
POST /faves Create fave
GET /faves/{id} View fave
GET /faves/{id}/edit Edit fave form
POST /faves/{id} Update fave
DELETE /faves/{id} Delete fave
GET /tags/search?q= Tag autocomplete (HTMX)
GET /tags/{name} Browse by tag
GET /u/{username} Public profile
GET/POST /settings User settings
POST /settings/avatar Upload avatar
POST /settings/password Change password
GET /export Export page
GET /export/json Download JSON
GET /export/csv Download CSV
GET/POST /import Import page
GET /feed.xml Global Atom feed
GET /u/{username}/feed.xml User Atom feed
GET /tags/{name}/feed.xml Tag Atom feed
GET /admin Admin dashboard
GET/POST /admin/users User management
GET /admin/tags Tag management
GET/POST /admin/signup-requests Signup request management
GET/POST /admin/settings Site settings
GET /health Health check

API (/api/v1/)

Method Path Description
POST /api/v1/auth/login Login, returns session
POST /api/v1/auth/logout Logout
GET /api/v1/faves List own faves
POST /api/v1/faves Create fave
GET /api/v1/faves/{id} Get fave
PUT /api/v1/faves/{id} Update fave
DELETE /api/v1/faves/{id} Delete fave
GET /api/v1/tags?q= Search tags
GET /api/v1/users/{username} Public profile
GET /api/v1/users/{username}/faves User's public faves
GET /api/v1/export/json Export own faves
POST /api/v1/import Import faves

Tech Stack

  • Go (1.22+ stdlib router, html/template, embed, log/slog)
  • SQLite via modernc.org/sqlite (pure Go, no CGO)
  • HTMX for interactive UI without a JS framework
  • Pico CSS for semantic HTML styling
  • Argon2id for password hashing
  • gorilla/feeds for Atom feed generation

Only 3 external Go dependencies.

Security

  • Argon2id password hashing with timing-attack prevention
  • CSRF double-submit cookie pattern (auto-included by HTMX)
  • Security headers: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  • Rate limiting on authentication endpoints
  • Image uploads: EXIF metadata stripped, re-encoded, UUID filenames, path traversal protection
  • Proxy-aware: trusts X-Forwarded-For only from configured proxy CIDRs
  • Session cookies: HttpOnly, Secure (from EXTERNAL_URL or X-Forwarded-Proto), SameSite=Lax

Development

# Run in dev mode (live template reload, debug logging)
FAVORITTER_DEV_MODE=true \
FAVORITTER_ADMIN_USERNAME=admin \
FAVORITTER_ADMIN_PASSWORD=dev \
go run ./cmd/favoritter

# Run tests
go test ./...

License

GNU Affero General Public License v3.0 (AGPL-3.0-or-later)