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>
This commit is contained in:
parent
395b1b7523
commit
845b152f15
1 changed files with 220 additions and 0 deletions
220
README.md
Normal file
220
README.md
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
# 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](https://htmx.org/) 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
# 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](#routes) 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](https://pkg.go.dev/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
|
||||
|
||||
```bash
|
||||
# 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](LICENSE) (AGPL-3.0-or-later)
|
||||
Loading…
Add table
Add a link
Reference in a new issue