Maraudarr architecture πΊοΈ¶
Maraudarr separates selection, representation, rendering, validation, and writing so each boundary can be tested without launching the generated stack.
Follow the generation flow¶
This is the path of one successful Maraudarr generation run, from the user's service choices to safely published Plundarr files and preserved application configuration. It describes project generation, not container startup.
%%{init: {"flowchart": {"nodeSpacing": 40, "rankSpacing": 52}, "themeVariables": {"fontSize": "18px"}}}%%
flowchart TB
CLI["π§ Capture intent<br/><code>cli.py</code>"]
Catalog["π Resolve the catalog<br/><code>catalog.py</code>"]
Plan["π Build an immutable StackPlan<br/><code>models.py</code>"]
Render["π§© Render selected fragments<br/><code>render.py + text.py</code>"]
Stage["π¦ Stage candidate files<br/>temporary output directory"]
Validate["π Validate the staged project<br/><code>docker-compose config</code>"]
Publish["β
Atomically publish files<br/>Compose + .env + example.env"]
Config["π Seed config safely<br/>preserve existing application state"]
CLI -->|"preset + service choices"| Catalog
Catalog -->|"ordered services + dependencies"| Plan
Plan -->|"generation contract"| Render
Render -->|"candidate project files"| Stage
Stage -->|"staged Compose + environment"| Validate
Validate -->|"validation passes"| Publish
Publish -->|"then apply safe seeds"| Config
1. Parse intent¶
maraudarr.cli accepts an interactive configure voyage or a deterministic build command. The CLI normalizes service additions and removals, then writes normal user-facing output to dist/<preset>/; --output remains available for an exact automation directory. It does not decide dependency order or edit source templates.
2. Load and resolve the catalog¶
Catalog reads the TOML catalog and validates every referenced Compose, environment, dependency, recommendation, preset service, project identity, network default, media root, media library profile, and host-port offset. Catalog.resolve then:
- Starts with a preset or explicit custom selection.
- Applies removals and additions.
- Restores services declared as preset core requirements.
- Rejects unknown or empty selections.
- Recursively adds required dependencies.
- Sorts services by catalog order and stable service ID.
Core services are restored in step 3 and cannot be removed. Default services are only the preset's initial checkbox state, so users can replace qBittorrent with a Usenet client, remove Watchtower where it defaults, or select both download modes without a separate add-on mechanism.
A logical service may declare compose_services to include several containers from one source chart. Tracearr owns its application, database, and Redis this way, so selection and removal always apply to the complete group.
The result is an immutable StackPlan. Renderers consume that plan rather than repeating selection logic.
3. Render without flattening intent¶
The source templates are intentionally readable, commented Compose and environment fragments. Maraudarr performs narrow text transformations so the generated deployment retains those comments and unresolved ${VARIABLES}.
text.py locates service declarations, framed environment sections, shared anchors, and footer blocks. render.py combines selected fragments and applies conditional additions such as Gluetun ports, Homepage cards and calendar integrations, preset-aware media libraries, the Lidarr-selected Plex music mount, collision-free project port defaults, and fresh environment values. Jellyfin remains deliberately invariant: every preset mounts one writable media root at /data.
Important
Replacing this layer with a generic YAML load-and-dump cycle would discard comments and weaken the generated file as an operator-facing artifact.
4. Preserve environment state¶
Existing assignments are indexed by variable name. Selected variables keep their user-managed lines, temporarily inactive service values move to a marked footer, and first-run secrets replace placeholders only when no existing value is available. COMPOSE_PROJECT_NAME remains Maraudarr-owned so preset identity cannot drift accidentally.
The default Plundarr preset therefore owns the plundarr Compose project. Maraudarr's local build/run chart is always invoked with the separate explicit project name maraudarr, preventing either lifecycle from removing the other.
5. Stage, validate, and publish output¶
Compose and environment files are written to a temporary directory inside the requested preset directory. Maraudarr asks Docker Compose to validate that staged pair, then atomically replaces the public files only after validation succeeds. A missing Docker executable is tolerated for dependency-free source testing; an installed Docker Compose that rejects the chart is a hard failure.
Config seeding follows a different safety rule: missing seeds are copied, project-owned README files may be refreshed, and existing application files are never replaced. The VPN recovery migration additionally refreshes the previous bundled Gluetun wrapper only when its SHA-256 matches the known unmodified seed. Customized scripts and symlinks remain untouched. Destructive cleanup belongs exclusively to the explicit make delete-config target.
make nuke removes project Docker resources and transient runtime residue but does not call delete-config; generated .env, host backups, and bind-mounted application state remain intact. All application volumes, including Tracearr databases and internal backups, remain intact. Only isolated tests may delete their recorded volumes after verifying ownership labels.
Understand failure boundaries¶
| Error family | Meaning |
|---|---|
CatalogError |
Invalid catalog data or impossible service selection |
TemplateError |
A source fragment no longer matches its documented structure |
RenderError |
Required rendered content is missing or Compose validation failed |
UserCancelled |
An intentional interactive exit, reported with status 130 |
OSError |
Filesystem or process failure reported to the user with corrective guidance |