Architecture
Expert
Find where code lives and which rules apply before you change something big.
The short version
Link to The short versionHolyArrs is one Go program with the web app built in. It serves the web app and a JSON API on port 6262, and keeps its data in 2 SQLite files in the config folder. It calls out to Downloaders, Sources (through Jackett or Prowlarr), media servers, alert channels and the HolyArrs metadata server.
Follow one request. Someone asks for a movie on Explore.
requestsrecords the ask.- After approval,
moviesaskssourcesfor Releases. - The pick rules in
mediafilesscore them. downloadssends the best one to the Downloader.- When it finishes,
mediafilesimports the file. mediaserversandalertsare told.
Code map
Link to Code mapapp/backend/cmd/holyarrs/ main.go starts the program; wiring.go builds the modules and joins themapp/backend/internal/<module>/ one flat folder per moduleapp/backend/internal/core/ plumbing every module may useapp/backend/tools/ dev-only programsapp/frontend/ the SvelteKit web app; embed.go bundles its buildapp/frontend/src/routes/<page>/ thin pages that combine module partsapp/frontend/src/lib/<module>/ each module's web codeapp/frontend/src/lib/ui/ parts used by 2 or more modules, and nothing elseapp/frontend/src/lib/core/ server client, app state, helpersapp/tests/ end-to-end tests (Playwright)deploy/ the Dockerfile and release scriptsdocs/ guides and these pagesgo.mod sits at the repo root because go:embed cannot reach a parent folder.
Where to find things
Link to Where to find things| You want to change | Look in |
|---|---|
| How a Release is picked | internal/mediafiles |
| A Downloader client | internal/downloads, contract tests in internal/downloads/conformance_test.go |
| An alert channel | internal/alerts |
| Sign-in, users, API keys | internal/accounts |
| The first-run wizard | internal/setup, app/frontend/src/routes/welcome |
| Importing a finished file (unpack, tags, matching) | internal/importer |
| Library folders, adopting and renaming | internal/library |
| How rows and shelves are picked and ordered | internal/suggest |
| Which parts of the app are on | internal/core/parts |
| A page address | app/frontend/src/routes/ |
| A migration | internal/core/database/migrations/ |
| The Docker image | deploy/Dockerfile |
The rules
Link to The rulesChecks in app/backend/tools/structurecheck fail the build when one breaks. Run them with the line in Testing.
- Layer order. A module imports only modules on a lower layer, and only their root package. Modules on the same layer never import each other.
- Calling up. When a lower module needs something from a higher one, the higher one passes plain data down, or the lower one defines a small interface that
wiring.gofills in. - SQL stays home. Only a module’s own
store*.gofiles hold SQL, and only over its own tables. To read another module’s rows, call its service. - Shared tables belong to core. Reach
settings,schedules,secrets,jobsandeventsthrough core helpers. - One way out. Every outside call goes through
core/outbound. - Held-back features register a switch with
core/startup. While it is off, the route is not there and the page hides the feature. - No grab-bag packages. No
util,common,sharedorhelpers.
The layer order is in Modules.
For experts
Link to For expertsInside a module
Every backend module uses the same file names.
| File | Holds |
|---|---|
routes.go |
API routes, each with the permission it needs |
service.go and topic files |
Logic other modules call |
store.go, store_<topic>.go |
All SQL for the module’s own tables |
types.go |
The module’s records |
On the web side, lib/<module>/ holds index.ts (what other modules may use), its Svelte parts and their tests, plus api.ts and state.svelte.ts only when it has its own calls or state.
Core packages
Core is plumbing. It holds no product rules and imports no module.
| Package | Holds |
|---|---|
core/database |
Opening and migrating both SQLite files, query helpers, settings and events helpers, shared row fields, history trimming |
core/files |
File and folder helpers, free disk space |
core/releasename |
Quality, group and episode numbers from a release name |
core/outbound |
The one way to call outside servers, with address checks |
core/secrets |
The key store and key rotation |
core/logs |
The app log and crash notes |
core/jobs |
The job queue, schedules and the loops that restart after a crash |
core/webserver |
Router plumbing, URL base, compression, live push |
core/startup |
Run-as user, low-memory mode, held-back switches, memory limit, parts |
core/parts |
The part switches every module asks before it serves or runs |
core/model |
Shared types such as actors and subjects |
When core must touch module data, the module registers a hook with core in wiring.go. Key rotation works this way.
Data
| File | Holds |
|---|---|
holyarrs.db |
The Library, requests, settings and everything else that matters |
holyarrs-events.db |
History and other event rows. It grows and is trimmed without touching the main file |
Migrations are numbered SQL files in core/database/migrations/main and core/database/migrations/events. They run at start.
Related
Link to RelatedHolyArrs is free. If it saved you time, star it on GitHub, sponsor it or buy me a coffee.