Skip to content

Developer Guide

Jesse Slaton edited this page Mar 4, 2026 · 4 revisions

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.

Tech Stack

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

Quick Start

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.

Project Structure

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

Key Design Principles

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, 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.

Further Reading

Clone this wiki locally