Application integration¶
Import nixosModules.default, choose the applications, and start with the typed
homelab.integrations declarations. Keep homelab.integration.services as the
schema-checked compatibility layer for applications without a focused module. The
integrated media example connects Sonarr,
Radarr, Lidarr, Prowlarr, qBittorrent, Jellyfin, Seerr and Bazarr. It also
selects a Recyclarr quality profile. Host configuration must supply the VPN,
existing mounts and nix-seal secret files before enabling it.
An enabled daemon does not imply a configured workflow. Integration jobs call application APIs after startup and repeat on a bounded timer. Their journals contain operation counts and sanitized errors, never response bodies or secrets. A failed job leaves its failure visible; a later run retries from the current API state. Writes are not automatically retried after an ambiguous network failure.
Ownership and safe updates¶
Every job chooses a mode:
bootstrapcreates missing collection objects and preserves existing objects. It does not continually overwrite changes made in the application UI.managedupdates the fields explicitly declared for matching objects. Other fields and undeclared objects remain intact.
Neither mode deletes libraries, users, indexers, profiles or manager connections when declarations disappear. Remove those objects explicitly in the application only after reviewing their data and dependent settings. Provider implementation changes and ambiguous names fail rather than replacing an existing object.
Singleton settings need special care. Seerr applies bootstrap singleton settings only during initial onboarding. Bazarr has no equivalent onboarding state; bootstrap leaves its existing settings untouched. Use managed mode for declared Bazarr settings.
The CLI accepts a generated JSON configuration and --dry-run. Run previews
with an isolated systemd credential directory and the same endpoint access as
the real job. Dry runs can authenticate and validate connections, but do not
write application configuration. An uninitialized player or portal reports that
bootstrap is required before a full preview is possible.
User-provided credentials¶
Use apiKeyFile for an API key and { _secret = "/run/..."; } for secret
values inside settings or resource fields. The module transforms these into
systemd LoadCredential references. Never use builtins.readFile on a secret
or put a password directly in a Nix value. API keys, passwords and token fields
reject literal nonempty values during evaluation.
For local Sonarr, Radarr, Lidarr and Prowlarr, installApiKey = true installs
the same key into a private runtime environment file for the native service.
Supply a 32-character alphanumeric API key. Remote services should leave this
option disabled. Keep native browser authentication enabled.
In nix-conf, order key installation and integration after the consuming nix-seal
activation unit. Configure the secret's restart units to include its
homelab-key-NAME.service, the application and
homelab-integrate-NAME.service. The runtime key installation unit must rerun
before the application restart after a rotation. A timer refreshes API client
credentials; it does not independently replace an application's own key.
qBittorrent uses its existing runtime INI for the login verifier. Client
connections also need the corresponding username and plaintext password as
separate runtime credentials. Bazarr also supports installApiKey, merging its
key into private YAML before startup while preserving other settings. Restart
Bazarr after replacing its key. It does not use the Servarr environment
mechanism. Provider registrations, subtitle accounts and Plex account claims
remain user-supplied deployment inputs.
Managers and indexers¶
homelab.integrations.downloadClients declares each connection once.
homelab.integrations.servarr.<instance> then references those clients and owns
typed roots, categories, tags, import policy, naming, and notifications. Instance
names are independent of application kinds, so two Sonarr or Radarr deployments
can coexist when their URLs, roots, and categories are distinct. qBittorrent
categories are created from the same declaration before manager reconciliation;
undeclared categories and tags are never removed.
homelab.integrations.prowlarr references those same named Servarr instances.
It supplies the pinned Prowlarr category defaults, creates application links,
and supports typed HTTP, SOCKS5, and FlareSolverr indexer proxies. Indexer
providers remain opt-in because their fields and accounts vary by provider.
Put provider credentials in secretFields; literal fields whose names look like
passwords, tokens, cookies, passkeys, or API keys fail evaluation. A dry run can
plan a proxy whose tag is created earlier in the same declaration without
inventing a server-side ID.
For selective VPN egress, enable homelab.indexerProxy with a runtime
passwordFile. The integrated example automatically exposes it to Prowlarr as
an authenticated SOCKS5 proxy tagged vpn. Apply that tag only to indexers that
need it. DNS resolution, authentication, LAN refusal, tunnel loss, and recovery
are covered by the VPN namespace VM test.
Each Arr resource has an endpoint, a stable match and values. Match by
name, path or label. Supported collection endpoints include root folders,
download clients, indexers, Prowlarr applications, quality profiles, tags,
notifications, delay profiles and remote path mappings.
Supported settings keys are checked against an adapter-specific contract
during Nix evaluation. This catches misspelled sections and wrong structural
types before a timer reaches an application. Fast-moving upstream fields can be
placed under extraSettings; those values recursively override settings,
retain runtime-secret checks, and carry no compatibility promise. Arr provider
payloads have the equivalent extraResources escape hatch. Prefer the typed
surface and move a field back from an escape hatch once the module adopts it.
For managers, settings.downloadHandling manages completed-download and retry
policy. settings.mediaManagement covers the bounded, storage-relevant Servarr
settings, and settings.naming covers the native Sonarr, Radarr and Lidarr
naming models. The integrated recipe enables hardlinks, keeps a 20 GiB import
floor, moves deleted or upgraded media into per-manager recycle directories for
30 days, rescans after manual refreshes, and records quality, codec, custom
format and release-group metadata in video filenames. It retains completed and
failed downloader jobs for attended cleanup and disables automatic failed-job
retry. The adapter validates each field against the selected manager and merges
only declared values. Bootstrap mode preserves singleton settings; managed mode
updates declared fields without replacing unrelated values.
Radarr download-handling settings,
per-client removal policy.
Provider resources declare an implementation. On creation the job obtains the
application's schema and merges declared fields into its defaults. Unknown
provider fields fail. On updates it preserves unspecified fields. For example:
{
endpoint = "downloadclient";
match.name = "qBittorrent";
values = {
implementation = "QBittorrent";
enable = true;
fields = {
host = config.homelab.apps.qbittorrent.bindAddress;
port = config.homelab.apps.qbittorrent.webuiPort;
movieCategory = "radarr";
username._secret = "/run/nix-seal/system/secrets/qbit-user";
password._secret = "/run/nix-seal/system/secrets/qbit-password";
};
};
}
Use the same filesystem paths in the downloader and manager. Native applications validate provider connectivity and paths; an invalid integration must fail its job. Recyclarr should own quality settings where enabled. Do not configure the same profiles with two competing reconcilers.
The integrated recipe always registers qBittorrent and conditionally registers
SABnzbd or NZBGet when either is enabled. A category must be unique for a given
client; distinct clients may use the same workflow category.
SABnzbd needs sabnzbd-api-key; NZBGet needs nzbget-username and
nzbget-password runtime secrets in addition to its native credential fragment.
This intentional separation lets systemd pass only the fields each integration
job needs. Import the Usenet example to create matching native categories and
provider policy. Do not enable both Usenet clients for the same provider queue
unless the host deliberately wants both choices.
Jellyfin and Seerr¶
homelab.integrations.jellyfin exposes initial administrator setup, portable
encoding limits, named libraries, and named users. User defaults allow playback
and remuxing but deny administration, deletion, downloads, remote access,
transcoding, and blanket library access. Any broader grant is visible in the
host configuration. Native services.jellyfin options still own packages,
devices, hardware acceleration, and process configuration. Version-specific
library, policy, and encoding fields have narrowly named escape hatches.
In managed mode, a library's declared paths replace its actual media path references. New paths are attached before old references are removed. This preserves the library ID and the files on disk. Bootstrap mode leaves existing paths alone, and removing a library declaration does not delete that library.
Managed user passwords update through the API when their runtime value changes. A private state file records salted scrypt password fingerprints so periodic runs do not revoke sessions unnecessarily. Include this state in operational backups. Changing the administrator credential used to authenticate requires coordination with the server's current credential or a separately supplied administrator API key.
Jellyfin bootstrap can begin with settings.login. After onboarding, create a
dedicated administrator API key, store it in a runtime secret, and set
apiKeyFile. Recurring reconciliation then survives attended administrator
password changes. Production readiness rejects password-only recurring
Jellyfin automation. When an API key rotates the administrator password, the
request intentionally omits the stale CurrentPw field.
homelab.integrations.seerr derives administrator credentials from the typed
Jellyfin declaration and derives each request destination from a named Servarr
instance and root. It resolves quality profiles by name and validates the root
against the live manager. Default permissions grant requests without automatic
approval. Auto-approval and automatic-request permissions fail evaluation until
allowAutomaticRequests is explicitly enabled. Movie and television quotas are
typed rolling windows; omitted quotas remain unmanaged. Existing manual
destinations remain intact.
Bazarr settings mirror its nested API settings, such as general, sonarr and
radarr. The adapter validates field names against the running service and
sends form-encoded updates. This differs from the JSON request body in the
inspected peer example: current Bazarr reads request.form.
Bazarr settings implementation.
Verification¶
integration-behavior exercises the public CLI with authenticated local HTTP
fixtures. It covers creation, repeated application, manual-field preservation,
credential replacement, dry runs, bootstrap behavior, provider schema errors,
player onboarding, request destination selection and Bazarr form encoding.
configuration-integration evaluates the Nix credential, timer, resource and
native key wiring contracts. Real-service VM tests provide separate evidence;
these HTTP fixtures alone do not prove an external-provider download workflow.
Music, audiobooks and podcasts¶
The audio example bootstraps Navidrome and Audiobookshelf accounts from runtime passwords. Managed mode updates declared user fields and rotates changed passwords; undeclared accounts remain intact. Audiobookshelf creates explicit audiobook and podcast libraries. Existing media is read-only to the players; podcast downloads have a separate writable path.
Navidrome uses its authenticated native API with X-ND-Authorization. Its first
administrator endpoint independently rejects initialization after any user
exists. Audiobookshelf uses its initialization endpoint only while the server
reports that no root account exists, then authenticates through its login API.
After bootstrap, create a named Audiobookshelf API key for a dedicated
automation administrator and set the integration job's apiKeyFile to its
runtime secret path. These keys are revocable, may expire and are intended for
server-to-server automation. When a key is present, the reconciler uses it as a
Bearer credential instead of retaining a human login password. Keep
password-based bootstrap only as long as initialization requires it.
Administrator login-password changes need coordination with the current server
credential. Navidrome rejects the integration administrator in settings.users;
change that account through an attended native account update, then update the
login credential. Names match without case distinctions, and duplicate
declarations are rejected before account writes. Do not use the everyday
listener account for configuration.
Navidrome authentication,
Audiobookshelf API, and
Audiobookshelf API keys.
The Navidrome defaults bound concurrent transcodes to two overall and one per
user, cancel abandoned transcodes, limit the transcoding cache and delay scans
until file activity settles. Symlink traversal and public sharing are disabled.
Override native settings deliberately if the existing library requires symlinks.
Jellyfin's encoding settings
can be managed through settings.encoding; unknown fields fail. The integrated
example bounds encoding threads and enables throttling and segment deletion
without selecting an unverified GPU. Host-selected hardware remains a native
Jellyfin configuration choice.
Named Arr foreign keys can use
{ _lookup = { endpoint = "qualityprofile"; name = "Standard"; }; }. The
adapter resolves exactly one current ID and fails if the name is absent or
ambiguous. The complete media example uses this for Lidarr's metadata and
quality profiles, so fresh databases need no guessed IDs. Its music root
monitors future releases without automatically monitoring every newly discovered
item.
Bazarr's settings.languageProfiles maps names to language profile fields. The
adapter retains undeclared profiles because Bazarr's API replaces the entire
collection. Avoid editing language profiles in the UI during a reconciliation
run; that upstream endpoint has no conditional-update mechanism. Provider
selection and credentials remain explicit user inputs under native Bazarr
settings sections. Choose providers before scheduling searches.
The audio example requires a stable ND_PASSWORDENCRYPTIONKEY in the Navidrome
runtime environment file. Preserve that key with the host's encrypted secret
catalog and recovery material. Changing it after Navidrome encrypts passwords
can prevent authentication. This key has a different lifecycle from account
passwords. See
Navidrome's security guidance.
Bazarr also accepts settings.enabledLanguages = [ "en" ] and
settings.defaultProfiles = { series = "English"; movies = "English"; }.
Defaults resolve the declared or existing profile by name and apply to newly
added media. Existing per-title assignments are preserved. Enabled languages are
added to the current selection. The integrated example runs one subtitle job at
a time, searches daily, and checks upgrades weekly without upgrading manually
downloaded subtitles. Provider selection and runtime account credentials remain
explicit operator inputs under the provider's native settings section. These
controls do not guarantee subtitle availability.
Bazarr configuration,
settings API.
Autobrr has a separate connection and filter recipe, including named client/indexer references, bounded filters and private credential rotation. It uses the same timers, previews and runtime credential transport.