The Problem
The codebase exhibits high cognitive load and widespread duplication that increases the risk of regressions during modifications. Static analysis found maximum indentation depth of 8 in critical playback and library files, alongside 62 repeated 6-line logic blocks across a dozen files. For a self-hosted streaming application where the pipeline involves FFmpeg, SQLite, and HLS generation, these maintainability issues represent a tangible operational risk.
What This Does
Airstation is a self-hosted web application for streaming music over the Internet. The frontend is split between web/player (a minimalistic listener interface) and web/studio (an upload and management dashboard). Under the hood, the system stores track metadata and queue state in SQLite, processes audio via FFmpeg (defined in internal/pkg/ffmpeg/ffmpeg.go), and serves streams using HTTP-based HLS segmentation. The entire stack is packaged for Docker deployment, with the Go backend (internal/) handling all server logic, persistence, and external command orchestration.
How It Is Wired
Execution begins at cmd/main.go:18, which reaches 68 functions and serves as the process entry point. From there, Run in internal/http/server.go:59 acts as the central hub, reaching 54 functions and being called from 9 distinct places. This function registers request handlers (defined in internal/http/handlers.go, which contains 24 handlers including handleTracks, handleHLSPlaylist, and handleEvents) and manages the event loop via listenEvents.
The data path typically flows from an HTTP request through the handlers into the storage layer (internal/storage/sqlite/), which provides the New, Close, and Instance functions for database interaction. Audio processing is offloaded to internal/pkg/ffmpeg/ffmpeg.go, which runs external commands for tasks like MakeHLSPlaylist, TrimAudio, and PadAudio. A traced path from the entry point to an external effect is main -> New -> db via db.Exec, and Run -> DeleteOldPlaybackHistory [db via ps.db.Exec].
The module graph shows internal/http/handlers.go as a high-degree hub (24 functions, called from 1 other file, calls into 15), making it a focal point for any web-layer changes. Conversely, the web/studio/src/page/Playback.tsx, web/studio/src/page/Playlists.tsx, and web/studio/src/page/TracksLibrary.tsx files suffer from deep nesting (max depth 8), complicating control flow.
How To Use It
Setup:
docker-compose up --build
The docker-compose.yml orchestrates the services. Configuration is driven by environment variables read via getEnv calls throughout internal/station/service.go and internal/http/server.go. No .env file is committed to the repository; users must supply the required variables (details within the README or inferred from the Go code) at runtime.
Running it: The process starts with go run cmd/main.go or via the Docker entrypoint. The studio UI is typically accessible at the mapped port after containers initialize.
Real-World Use
A developer wishing to add a new track upload limit modifies internal/http/handlers.go where handleTracksUpload validates inbound requests. The upload flows through the handler into internal/storage/sqlite/queue.go or track.go for persistence. Subsequently, FFmpeg processes the file via internal/pkg/ffmpeg/ffmpeg.go to generate HLS segments, after which the listener player (web/player/src/page/CurrentTrack.tsx) picks up the playlist automatically.
Code Health & Issues
Measured findings from static analysis:
- [HIGH/cognitive_load] Deep nesting x12 — files:
web/studio/src/page/Playback.tsx,web/studio/src/page/Playlists.tsx,web/studio/src/page/TracksLibrary.tsx(max indentation depth 8). - [HIGH/clarity] Duplicated code blocks — 62 repeated 6-line blocks across 12 files, including
internal/storage/sqlite/queue.go,internal/storage/sqlite/track.go,web/player/src/api/index.ts, andweb/studio/src/api/index.ts. - [MEDIUM/cognitive_load] High branching density x2 — files:
web/player/src/page/StationInformation.tsx,internal/station/service.go(32 branch points over 94 lines).
SDLC observations:
- GitHub Actions pins third-party actions to mutable tags (
@v3,@v5), risking secret exposure if action owners update versions. - No Dependabot or Renovate configuration; 3 dependency manifests are unmonitored for updates.
- Dockerfile uses mutable base image tags (
node:22-alpine,golang:1.26-alpine). - Test coverage is minimal (2 test files against 102 source files, a ratio of 0.020).
- No
USERdirective in the Dockerfile; processes run as root by default.
The Bottom Line
Airstation functionalizes a compact, Dockerized radio streaming stack with React and Go, but the codebase carries significant technical debt. The duplication and nesting patterns found in the playback and API layers mean that straightforward feature additions carry a higher-than-ideal risk of introducing bugs. It is suitable for hobbyists or small teams needing a quick self-hosted solution, but engineering teams prioritizing long-term maintainability should budget time for refactoring the duplicated logic and flattening the control flow before productionizing the setup.