1
0
Fork 0

add tests for arr services and some more options (#205)

This commit is contained in:
Pierre Penninckx 2024-03-12 22:40:32 -07:00 committed by GitHub
parent e6414dc911
commit 589e2c936f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 451 additions and 170 deletions

View file

@ -23,4 +23,5 @@ jobs:
run: |
nix run github:Mic92/nix-fast-build -- \
--skip-cached --no-nom \
--max-jobs 2 \
--flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)"

View file

@ -101,6 +101,7 @@
tests = pkgs.callPackage ./test/modules/lib.nix {};
};
}
// (vm_test "arr" ./test/vm/arr.nix)
// (vm_test "audiobookshelf" ./test/vm/audiobookshelf.nix)
// (vm_test "authelia" ./test/vm/authelia.nix)
// (vm_test "grocy" ./test/vm/grocy.nix)

View file

@ -21,7 +21,7 @@ rec {
in
''
set -euo pipefail
set -x
mkdir -p $(dirname ${templatePath})
ln -fs ${file} ${templatePath}
rm -f ${resultPath}

View file

@ -27,19 +27,19 @@ let
default = null;
};
authEndpoint = lib.mkOption {
type = lib.types.str;
description = "Auth endpoint for SSO.";
default = null;
example = "https://authelia.example.com";
};
upstream = lib.mkOption {
type = lib.types.str;
description = "Upstream url to be protected.";
example = "http://127.0.0.1:1234";
};
authEndpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Optional auth endpoint for SSO.";
default = null;
example = "https://authelia.example.com";
};
autheliaRules = lib.mkOption {
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
description = "Authelia rule configuration";
@ -133,6 +133,9 @@ in
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
proxy_pass ${c.upstream};
''
+ lib.optionalString (!(isNull c.authEndpoint)) ''
auth_request /authelia;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
@ -153,12 +156,10 @@ in
auth_request_set $redirect $scheme://$http_host$request_uri;
error_page 401 =302 ${c.authEndpoint}?rd=$redirect;
error_page 403 = ${c.authEndpoint}/error/403;
proxy_pass ${c.upstream};
'';
# Virtual endpoint created by nginx to forward auth requests.
locations."/authelia".extraConfig = ''
locations."/authelia".extraConfig = lib.mkIf (!(isNull c.authEndpoint)) ''
internal;
proxy_pass ${c.authEndpoint}/api/verify;

View file

@ -8,7 +8,6 @@ let
apps = {
radarr = {
defaultPort = 7001;
settingsFormat = formatXML {};
moreOptions = {
settings = lib.mkOption {
@ -17,8 +16,8 @@ let
type = lib.types.submodule {
freeformType = apps.radarr.settingsFormat.type;
options = {
APIKeyFile = lib.mkOption {
type = lib.types.path;
APIKey = lib.mkOption {
type = shblib.secretFileType;
description = "Path to api key secret file.";
};
LogLevel = lib.mkOption {
@ -26,25 +25,173 @@ let
description = "Log level.";
default = "info";
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which radarr listens to incoming requests.";
default = 7878;
};
AnalyticsEnabled = lib.mkOption {
type = lib.types.bool;
description = "Wether to send anonymous data or not.";
default = false;
};
BindAddress = lib.mkOption {
type = lib.types.str;
internal = true;
default = "127.0.0.1";
};
UrlBase = lib.mkOption {
type = lib.types.str;
internal = true;
default = "";
};
EnableSsl = lib.mkOption {
type = lib.types.bool;
internal = true;
default = false;
};
AuthenticationMethod = lib.mkOption {
type = lib.types.str;
internal = true;
default = "External";
};
AuthenticationRequired = lib.mkOption {
type = lib.types.str;
internal = true;
default = "Enabled";
};
};
};
};
};
};
sonarr = {
defaultPort = 8989;
settingsFormat = formatXML {};
moreOptions = {
settings = lib.mkOption {
description = "Specific options for sonarr.";
default = {};
type = lib.types.submodule {
freeformType = apps.sonarr.settingsFormat.type;
options = {
APIKey = lib.mkOption {
type = shblib.secretFileType;
description = "Path to api key secret file.";
};
LogLevel = lib.mkOption {
type = lib.types.enum ["debug" "info"];
description = "Log level.";
default = "info";
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which sonarr listens to incoming requests.";
default = 8989;
};
BindAddress = lib.mkOption {
type = lib.types.str;
internal = true;
default = "127.0.0.1";
};
UrlBase = lib.mkOption {
type = lib.types.str;
internal = true;
default = "";
};
EnableSsl = lib.mkOption {
type = lib.types.bool;
internal = true;
default = false;
};
AuthenticationMethod = lib.mkOption {
type = lib.types.str;
internal = true;
default = "External";
};
AuthenticationRequired = lib.mkOption {
type = lib.types.str;
internal = true;
default = "Enabled";
};
};
};
};
};
};
bazarr = {
defaultPort = 6767;
settingsFormat = formatXML {};
moreOptions = {
settings = lib.mkOption {
description = "Specific options for bazarr.";
default = {};
type = lib.types.submodule {
freeformType = apps.bazarr.settingsFormat.type;
options = {
LogLevel = lib.mkOption {
type = lib.types.enum ["debug" "info"];
description = "Log level.";
default = "info";
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which bazarr listens to incoming requests.";
default = 6767;
readOnly = true;
};
};
};
};
};
};
readarr = {
defaultPort = 8787;
settingsFormat = formatXML {};
moreOptions = {
settings = lib.mkOption {
description = "Specific options for readarr.";
default = {};
type = lib.types.submodule {
freeformType = apps.readarr.settingsFormat.type;
options = {
LogLevel = lib.mkOption {
type = lib.types.enum ["debug" "info"];
description = "Log level.";
default = "info";
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which readarr listens to incoming requests.";
default = 8787;
};
};
};
};
};
};
lidarr = {
defaultPort = 8686;
settingsFormat = formatXML {};
moreOptions = {
settings = lib.mkOption {
description = "Specific options for lidarr.";
default = {};
type = lib.types.submodule {
freeformType = apps.lidarr.settingsFormat.type;
options = {
LogLevel = lib.mkOption {
type = lib.types.enum ["debug" "info"];
description = "Log level.";
default = "info";
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which lidarr listens to incoming requests.";
default = 8686;
};
};
};
};
};
};
jackett = {
defaultPort = 9117;
settingsFormat = pkgs.formats.json {};
moreOptions = {
settings = lib.mkOption {
@ -53,8 +200,8 @@ let
type = lib.types.submodule {
freeformType = apps.jackett.settingsFormat.type;
options = {
APIKeyFile = lib.mkOption {
type = lib.types.path;
APIKey = lib.mkOption {
type = shblib.secretFileType;
description = "Path to api key secret file.";
};
FlareSolverrUrl = lib.mkOption {
@ -62,8 +209,8 @@ let
description = "FlareSolverr endpoint.";
default = null;
};
OmdbApiKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
OmdbApiKey = lib.mkOption {
type = lib.types.nullOr shblib.secretFileType;
description = "File containing the Open Movie Database API key.";
default = null;
};
@ -87,6 +234,22 @@ let
description = "Port of the proxy. Ignored if ProxyType is set to -1";
default = null;
};
Port = lib.mkOption {
type = lib.types.port;
description = "Port on which jackett listens to incoming requests.";
default = 9117;
readOnly = true;
};
AllowExternal = lib.mkOption {
type = lib.types.bool;
internal = true;
default = false;
};
UpdateDisabled = lib.mkOption {
type = lib.types.bool;
internal = true;
default = true;
};
};
};
};
@ -94,6 +257,40 @@ let
};
};
autheliaProtect = { extraBypassResources ? [] }: c: {
inherit (c) subdomain domain authEndpoint ssl;
upstream = "http://127.0.0.1:${toString c.settings.Port}";
autheliaRules = lib.optionals (!(isNull c.authEndpoint)) [
{
domain = "${c.subdomain}.${c.domain}";
policy = "bypass";
resources = extraBypassResources ++ [
"^/api.*"
];
}
{
domain = "${c.subdomain}.${c.domain}";
policy = "two_factor";
subject = ["group:arr_user"];
}
];
};
backup = name: {
systemd.tmpfiles.rules = [
"d '${config.shb.arr.${name}.dataDir}' 0750 ${config.services.${name}.user} ${config.services.${name}.group} - -"
];
users.groups.${name} = {
members = [ "backup" ];
};
systemd.services.${name}.serviceConfig = {
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
UMask = lib.mkForce "0027";
StateDirectoryMode = lib.mkForce "0750";
};
};
formatXML = {}: {
type = with lib.types; let
valueType = nullOr (oneOf [
@ -109,7 +306,7 @@ let
};
in valueType;
generate = name: value: pkgs.callPackage ({ runCommand, python3 }: runCommand name {
generate = value: builtins.readFile (pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
value = builtins.toJSON {Config = value;};
passAsFile = [ "value" ];
} (pkgs.writers.writePython3 "dict2xml" {
@ -126,7 +323,7 @@ let
os.exit(2)
with open(os.environ["out"], "w") as out:
out.write(dict2xml(content))
'')) {};
'')) {});
};
@ -149,24 +346,18 @@ let
example = "example.com";
};
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr contracts.ssl.certs;
default = null;
};
port = lib.mkOption {
type = lib.types.port;
description = "Port on which ${name} listens to incoming requests.";
default = c.defaultPort;
};
dataDir = lib.mkOption {
type = lib.types.str;
description = "Directory where ${name} stores data.";
default = "/var/lib/${name}";
};
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr contracts.ssl.certs;
default = null;
};
authEndpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
@ -191,164 +382,156 @@ in
options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps);
config = lib.mkMerge ([
{
services.radarr = lib.mkIf cfg.radarr.enable {
(lib.mkIf cfg.radarr.enable ({
services.nginx.enable = true;
services.radarr = {
enable = true;
dataDir = "/var/lib/radarr";
};
users.users.radarr = lib.mkIf cfg.radarr.enable {
users.users.radarr = {
extraGroups = [ "media" ];
};
shb.arr.radarr.settings = lib.mkIf cfg.radarr.enable {
Port = config.shb.arr.radarr.port;
BindAddress = "127.0.0.1";
UrlBase = "";
EnableSsl = "false";
AuthenticationMethod = "External";
AuthenticationRequired = "Enabled";
systemd.services.radarr.preStart = shblib.replaceSecrets {
userConfig = cfg.radarr.settings;
resultPath = "${config.services.radarr.dataDir}/config.xml";
generator = apps.radarr.settingsFormat.generate;
};
systemd.services.radarr.preStart =
let
s = cfg.radarr.settings;
templatedfileSettings =
lib.optionalAttrs (!(isNull s.APIKeyFile)) {
ApiKey = "%APIKEY%";
};
templatedSettings = (removeAttrs s [ "APIKeyFile" ]) // templatedfileSettings;
t = shblib.template (apps.radarr.settingsFormat.generate "
config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" (
lib.optionalAttrs (!(isNull s.APIKeyFile)) {
"%APIKEY%" = "$(cat ${s.APIKeyFile})";
}
);
in
lib.mkIf cfg.radarr.enable t;
shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.radarr) ];
# Listens on port 8989
services.sonarr = lib.mkIf cfg.sonarr.enable {
shb.backup.instances.radarr = cfg.radarr.backupCfg // {
sourceDirectories = [
config.shb.arr.radarr.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "radarr"))
(lib.mkIf cfg.sonarr.enable ({
services.nginx.enable = true;
services.sonarr = {
enable = true;
dataDir = "/var/lib/sonarr";
};
users.users.sonarr = lib.mkIf cfg.sonarr.enable {
users.users.sonarr = {
extraGroups = [ "media" ];
};
systemd.services.sonarr.preStart = shblib.replaceSecrets {
userConfig = cfg.sonarr.settings;
resultPath = "${config.services.sonarr.dataDir}/config.xml";
generator = apps.sonarr.settingsFormat.generate;
};
services.bazarr = lib.mkIf cfg.bazarr.enable {
shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.sonarr) ];
shb.backup.instances.sonarr = cfg.sonarr.backupCfg // {
sourceDirectories = [
config.shb.arr.sonarr.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "sonarr"))
(lib.mkIf cfg.bazarr.enable ({
services.bazarr = {
enable = true;
listenPort = cfg.bazarr.port;
listenPort = cfg.bazarr.settings.Port;
};
users.users.bazarr = lib.mkIf cfg.bazarr.enable {
users.users.bazarr = {
extraGroups = [ "media" ];
};
systemd.services.bazarr.preStart = shblib.replaceSecrets {
userConfig = cfg.bazarr.settings;
resultPath = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}/config.xml";
generator = apps.bazarr.settingsFormat.generate;
};
# Listens on port 8787
services.readarr = lib.mkIf cfg.readarr.enable {
shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.bazarr) ];
shb.backup.instances.bazarr = cfg.bazarr.backupCfg // {
sourceDirectories = [
config.shb.arr.bazarr.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "bazarr"))
(lib.mkIf cfg.readarr.enable ({
services.readarr = {
enable = true;
dataDir = "/var/lib/readarr";
};
users.users.readarr = lib.mkIf cfg.readarr.enable {
users.users.readarr = {
extraGroups = [ "media" ];
};
systemd.services.readarr.preStart = shblib.replaceSecrets {
userConfig = cfg.readarr.settings;
resultPath = "${config.services.readarr.dataDir}/config.xml";
generator = apps.readarr.settingsFormat.generate;
};
# Listens on port 8686
services.lidarr = lib.mkIf cfg.lidarr.enable {
shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.readarr) ];
shb.backup.instances.readarr = cfg.readarr.backupCfg // {
sourceDirectories = [
config.shb.arr.readarr.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "readarr"))
(lib.mkIf cfg.lidarr.enable ({
services.lidarr = {
enable = true;
dataDir = "/var/lib/lidarr";
};
users.users.lidarr = lib.mkIf cfg.lidarr.enable {
users.users.lidarr = {
extraGroups = [ "media" ];
};
systemd.services.lidarr.preStart = shblib.replaceSecrets {
userConfig = cfg.lidarr.settings;
resultPath = "${config.services.lidarr.dataDir}/config.xml";
generator = apps.lidarr.settingsFormat.generate;
};
# Listens on port 9117
services.jackett = lib.mkIf cfg.jackett.enable {
shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.lidarr) ];
shb.backup.instances.lidarr = cfg.lidarr.backupCfg // {
sourceDirectories = [
config.shb.arr.lidarr.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "lidarr"))
(lib.mkIf cfg.jackett.enable ({
services.jackett = {
enable = true;
dataDir = "/var/lib/jackett";
};
shb.arr.jackett.settings = lib.mkIf cfg.jackett.enable {
Port = config.shb.arr.jackett.port;
AllowExternal = "false";
UpdateDisabled = "true";
};
users.users.jackett = lib.mkIf cfg.jackett.enable {
users.users.jackett = {
extraGroups = [ "media" ];
};
systemd.services.jackett.preStart =
let
s = cfg.jackett.settings;
templatedfileSettings =
lib.optionalAttrs (!(isNull s.APIKeyFile)) {
APIKey = "%APIKEY%";
} // lib.optionalAttrs (!(isNull s.OmdbApiKeyFile)) {
OmdbApiKey = "%OMDBAPIKEY%";
};
templatedSettings = (removeAttrs s [ "APIKeyFile" "OmdbApiKeyFile" ]) // templatedfileSettings;
systemd.services.jackett.preStart = shblib.replaceSecrets {
userConfig = cfg.jackett.settings;
resultPath = "${config.services.jackett.dataDir}/config.xml";
generator = apps.jackett.settingsFormat.generate;
};
t = shblib.template (apps.jackett.settingsFormat.generate "jackett.json" templatedSettings) "${config.services.jackett.dataDir}/ServerConfig.json" (
lib.optionalAttrs (!(isNull s.APIKeyFile)) {
"%APIKEY%" = "$(cat ${s.APIKeyFile})";
} // lib.optionalAttrs (!(isNull s.OmdbApiKeyFile)) {
"%OMDBAPIKEY%" = "$(cat ${s.OmdbApiKeyFile})";
}
);
in
lib.mkIf cfg.jackett.enable t;
shb.nginx.autheliaProtect = [ (autheliaProtect {
extraBypassResources = [ "^/dl.*" ];
} config.shb.arr.jackett) ];
shb.nginx.autheliaProtect =
let
appProtectConfig = name: _defaults:
let
c = cfg.${name};
in
lib.mkIf (c.authEndpoint != null) {
inherit (c) subdomain domain authEndpoint ssl;
upstream = "http://127.0.0.1:${toString c.port}";
autheliaRules = [
{
domain = "${c.subdomain}.${c.domain}";
policy = "bypass";
resources = [
"^/api.*"
] ++ lib.optionals (name == "jackett") [
"^/dl.*"
];
}
{
domain = "${c.subdomain}.${c.domain}";
policy = "two_factor";
subject = ["group:arr_user"];
}
];
};
in
lib.mapAttrsToList appProtectConfig apps;
shb.backup.instances =
let
backupConfig = name: _defaults: lib.mkIf (cfg.${name}.backupCfg.enable or false) ({
${name} = (
cfg.${name}.backupCfg
// {
sourceDirectories = [
config.shb.arr.${name}.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
});
});
in
lib.mkMerge (lib.mapAttrsToList backupConfig apps);
}
] ++ map (name: lib.mkIf cfg.${name}.enable {
systemd.tmpfiles.rules = lib.mkIf (lib.hasAttr "dataDir" config.services.${name}) [
"d '${config.services.${name}.dataDir}' 0750 ${config.services.${name}.user} ${config.services.${name}.group} - -"
];
users.groups.${name} = {
members = [ "backup" ];
};
systemd.services.${name}.serviceConfig = {
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
UMask = lib.mkForce "0027";
StateDirectoryMode = lib.mkForce "0750";
};
}) (lib.attrNames apps));
shb.backup.instances.jackett = cfg.jackett.backupCfg // {
sourceDirectories = [
config.shb.arr.jackett.dataDir
];
excludePatterns = [".db-shm" ".db-wal" ".mono"];
};
} // backup "jackett"))
]);
}

View file

@ -16,6 +16,7 @@ let
shb.backup = anyOpt {};
shb.nginx = anyOpt {};
users = anyOpt {};
services.nginx = anyOpt {};
services.bazarr = anyOpt {};
services.jackett = anyOpt {};
services.lidarr = anyOpt {};
@ -39,11 +40,11 @@ in
{
testArrNoOptions = {
expected = {
systemd.services.radarr = {};
systemd.services.jackett = {};
systemd = {};
shb.backup = {};
shb.nginx.autheliaProtect = [];
users.users = {};
shb.nginx = {};
users = {};
services.nginx = {};
services.bazarr = {};
services.jackett = {};
services.lidarr = {};
@ -51,6 +52,7 @@ in
services.readarr = {};
services.sonarr = {};
};
expr = testConfig {};
};
@ -62,11 +64,19 @@ in
UMask = "0027";
};
};
systemd.services.jackett = {};
systemd.tmpfiles.rules = [
"d '/var/lib/radarr' 0750 radarr radarr - -"
];
shb.backup = {};
shb.backup.instances.radarr = {
excludePatterns = [
".db-shm"
".db-wal"
".mono"
];
sourceDirectories = [
"/var/lib/radarr"
];
};
shb.nginx.autheliaProtect = [
{
autheliaRules = [
@ -88,12 +98,12 @@ in
domain = "example.com";
authEndpoint = "https://oidc.example.com";
subdomain = "radarr";
upstream = "http://127.0.0.1:7001";
upstream = "http://127.0.0.1:7878";
ssl = null;
}
];
users.users.radarr.extraGroups = [ "media" ];
users.groups.radarr.members = [ "backup" ];
services.nginx.enable = true;
services.bazarr = {};
services.jackett = {};
services.lidarr = {};
@ -130,7 +140,6 @@ in
UMask = "0027";
};
};
systemd.services.jackett = {};
systemd.tmpfiles.rules = [
"d '/var/lib/radarr' 0750 radarr radarr - -"
];
@ -162,12 +171,12 @@ in
domain = "example.com";
authEndpoint = "https://oidc.example.com";
subdomain = "radarr";
upstream = "http://127.0.0.1:7001";
upstream = "http://127.0.0.1:7878";
ssl = null;
}
];
users.users.radarr.extraGroups = [ "media" ];
users.groups.radarr.members = [ "backup" ];
services.nginx.enable = true;
services.bazarr = {};
services.jackett = {};
services.lidarr = {};

86
test/vm/arr.nix Normal file
View file

@ -0,0 +1,86 @@
{ pkgs, lib, ... }:
let
# TODO: Test login
commonTestScript = appname: { nodes, ... }:
let
shbapp = nodes.server.shb.arr.${appname};
hasSSL = !(isNull shbapp.ssl);
fqdn = if hasSSL then "https://${appname}.example.com" else "http://${appname}.example.com";
in
''
import json
import os
import pathlib
start_all()
server.wait_for_unit("${appname}.service")
server.wait_for_unit("nginx.service")
server.wait_for_open_port(${builtins.toString shbapp.settings.Port})
if ${if hasSSL then "True" else "False"}:
server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt")
client.succeed("rm -r /etc/ssl/certs")
client.copy_from_host(str(pathlib.Path(os.environ.get("out", os.getcwd())) / "ca-certificates.crt"), "/etc/ssl/certs/ca-certificates.crt")
def curl(target, format, endpoint, succeed=True):
return json.loads(target.succeed(
"curl -X GET --fail-with-body --silent --show-error --output /dev/null --location"
+ " --connect-to ${appname}.example.com:443:server:443"
+ " --connect-to ${appname}.example.com:80:server:80"
+ f" --write-out '{format}'"
+ " " + endpoint
))
with subtest("health"):
response = curl(client, """{"code":%{response_code}}""", "${fqdn}/health")
if response['code'] != 200:
raise Exception(f"Code is {response['code']}")
with subtest("login"):
response = curl(client, """{"code":%{response_code}}""", "${fqdn}/UI/Login")
if response['code'] != 200:
raise Exception(f"Code is {response['code']}")
'';
basic = appname: pkgs.nixosTest {
name = "arr-${appname}-basic";
nodes.server = { config, pkgs, ... }: {
imports = [
{
options = {
shb.backup = lib.mkOption { type = lib.types.anything; };
};
}
../../modules/blocks/authelia.nix
../../modules/blocks/postgresql.nix
../../modules/blocks/nginx.nix
../../modules/services/arr.nix
];
shb.arr.${appname} = {
enable = true;
domain = "example.com";
subdomain = appname;
settings.APIKey.source = pkgs.writeText "APIKey" "01234567890123456789"; # Needs to be >=20 characters.
};
# Nginx port.
networking.firewall.allowedTCPPorts = [ 80 ];
};
nodes.client = {};
testScript = commonTestScript appname;
};
in
{
radarr_basic = basic "radarr";
sonarr_basic = basic "sonarr";
bazarr_basic = basic "bazarr";
readarr_basic = basic "readarr";
lidarr_basic = basic "lidarr";
jackett_basic = basic "jackett";
}