-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Guide
This guide is the entry point for anyone who wants to build, modify, or contribute to Stillwater. It covers the tech stack, project layout, and key design decisions. For environment setup and build instructions, see docs/dev-setup.md in the main repository.
| Layer | Technology |
|---|---|
| Language | Go 1.26+ |
| HTTP |
net/http stdlib (no third-party router) |
| Database | SQLite via modernc.org/sqlite (pure Go, no CGO) |
| Templates | Templ (type-safe HTML code generation) |
| CSS | Tailwind CSS v4 (standalone CLI, no Node.js) |
| Frontend | HTMX for dynamic updates, vendored JS (Cropper.js, Chart.js, Sortable.js) |
| Logging |
log/slog (structured, JSON or text) |
| Migrations | Goose (SQL-based) |
| Encryption | AES-256-GCM for secrets at rest |
git clone https://github.com/sydlexius/stillwater.git
cd stillwater
go mod download
make build # templ generate + tailwind + go build
make run # build + run with debug logging (http://localhost:1973)Full tool installation, platform-specific instructions, and Docker setup are in docs/dev-setup.md.
cmd/stillwater/ Main entry point (main.go, subcommands)
internal/
api/ HTTP handlers, router, middleware
artist/ Artist domain model, service, and repository interfaces
auth/ Session-based + API token authentication
backup/ Database backup service
config/ Configuration loading (env + YAML)
connection/ External platform connections (Emby, Jellyfin, Lidarr)
database/ SQLite database, migrations, health checks
migrations/ Goose SQL migration files
dbutil/ Shared database helpers (type conversions, nullable handling)
encryption/ AES-256-GCM encryption for secrets
event/ Channel-based event bus (pub/sub)
filesystem/ Atomic file writes (tmp/bak/rename pattern)
image/ Image fetch, crop, compare, naming, cleanup
library/ Music library management (multi-library support)
logging/ Log manager (level, format, scrubbing)
maintenance/ Scheduled maintenance tasks
nfo/ NFO file parser, writer, snapshots, conflict detection
notification/ Notification service
platform/ Platform profiles (Emby, Jellyfin, Kodi naming conventions)
provider/ Metadata source adapters and orchestrator
rule/ Rule engine (checkers, fixers, pipeline, scheduler)
scanner/ Filesystem scanner and library enumeration
scraper/ Configurable scraping definitions
settings/ Application settings service
version/ Build-time version injection
webhook/ Webhook dispatcher and inbound handlers
web/
components/ Reusable Templ components
templates/ Page-level Templ templates
static/ CSS, vendored JS, favicons
api/bruno/ Bruno API test collections
build/
docker/ Dockerfile, entrypoint script
swag/ LSIO SWAG reverse proxy configs
unraid/ Unraid Community Applications template
docs/ Developer documentation
API-first. Every feature is exposed as a REST endpoint under /api/v1/. The web UI is a consumer of the same API via HTMX. This means any operation can be scripted or automated via the API.
No CGO. The SQLite driver (modernc.org/sqlite) is a pure-Go translation of the C library. This simplifies cross-compilation and container builds -- no C toolchain required.
No third-party router. Go 1.22+ net/http provides method-based routing (GET /path, POST /path/{id}). All routes are registered in a single router.go file.
Atomic filesystem writes. All file mutations (NFO files, images) use a tmp/bak/rename pattern via internal/filesystem/. This prevents partial writes from corrupting data, even on crashes or power loss.
Singleton rate limiters. One rate limiter per metadata provider, created at application startup, shared across all handlers and background jobs. Prevents accidentally exceeding API rate limits regardless of request source.
Event-driven side effects. The event bus decouples core operations from side effects like webhook dispatch, notifications, and cascading updates. Publishers never block on subscriber processing.
Repository pattern. Data access is abstracted behind interfaces (Repository, ProviderIDRepository, ImageRepository, MemberRepository, AliasRepository, PlatformIDRepository) defined in internal/artist/repository.go. SQLite implementations live in the same package. The artist.Service aggregates all repositories and exposes two constructors: NewService(db) for production and NewServiceWithRepos(...) for injecting test mocks.
Shared database helpers. Common SQLite type conversions (bool-to-int, nullable handling, timestamp parsing) live in internal/dbutil/ and are shared across all repository implementations. This avoids duplicating conversion logic in each package.
- Architecture -- Subsystem deep dives with diagrams
- Contributing -- Code style, commit conventions, PR process
- Configuration -- Environment variables, YAML config
- Installation -- Docker setup, volumes, first-time configuration
- docs/dev-setup.md -- Full build and environment setup