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: | run: |
nix run github:Mic92/nix-fast-build -- \ nix run github:Mic92/nix-fast-build -- \
--skip-cached --no-nom \ --skip-cached --no-nom \
--max-jobs 2 \
--flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)" --flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)"

View file

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

View file

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

View file

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

View file

@ -8,7 +8,6 @@ let
apps = { apps = {
radarr = { radarr = {
defaultPort = 7001;
settingsFormat = formatXML {}; settingsFormat = formatXML {};
moreOptions = { moreOptions = {
settings = lib.mkOption { settings = lib.mkOption {
@ -17,8 +16,8 @@ let
type = lib.types.submodule { type = lib.types.submodule {
freeformType = apps.radarr.settingsFormat.type; freeformType = apps.radarr.settingsFormat.type;
options = { options = {
APIKeyFile = lib.mkOption { APIKey = lib.mkOption {
type = lib.types.path; type = shblib.secretFileType;
description = "Path to api key secret file."; description = "Path to api key secret file.";
}; };
LogLevel = lib.mkOption { LogLevel = lib.mkOption {
@ -26,25 +25,173 @@ let
description = "Log level."; description = "Log level.";
default = "info"; 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 = { 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 = { 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 = { 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 = { 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 = { jackett = {
defaultPort = 9117;
settingsFormat = pkgs.formats.json {}; settingsFormat = pkgs.formats.json {};
moreOptions = { moreOptions = {
settings = lib.mkOption { settings = lib.mkOption {
@ -53,8 +200,8 @@ let
type = lib.types.submodule { type = lib.types.submodule {
freeformType = apps.jackett.settingsFormat.type; freeformType = apps.jackett.settingsFormat.type;
options = { options = {
APIKeyFile = lib.mkOption { APIKey = lib.mkOption {
type = lib.types.path; type = shblib.secretFileType;
description = "Path to api key secret file."; description = "Path to api key secret file.";
}; };
FlareSolverrUrl = lib.mkOption { FlareSolverrUrl = lib.mkOption {
@ -62,8 +209,8 @@ let
description = "FlareSolverr endpoint."; description = "FlareSolverr endpoint.";
default = null; default = null;
}; };
OmdbApiKeyFile = lib.mkOption { OmdbApiKey = lib.mkOption {
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr shblib.secretFileType;
description = "File containing the Open Movie Database API key."; description = "File containing the Open Movie Database API key.";
default = null; default = null;
}; };
@ -87,6 +234,22 @@ let
description = "Port of the proxy. Ignored if ProxyType is set to -1"; description = "Port of the proxy. Ignored if ProxyType is set to -1";
default = null; 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 = {}: { formatXML = {}: {
type = with lib.types; let type = with lib.types; let
valueType = nullOr (oneOf [ valueType = nullOr (oneOf [
@ -109,7 +306,7 @@ let
}; };
in valueType; 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;}; value = builtins.toJSON {Config = value;};
passAsFile = [ "value" ]; passAsFile = [ "value" ];
} (pkgs.writers.writePython3 "dict2xml" { } (pkgs.writers.writePython3 "dict2xml" {
@ -126,7 +323,7 @@ let
os.exit(2) os.exit(2)
with open(os.environ["out"], "w") as out: with open(os.environ["out"], "w") as out:
out.write(dict2xml(content)) out.write(dict2xml(content))
'')) {}; '')) {});
}; };
@ -149,24 +346,18 @@ let
example = "example.com"; 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 { dataDir = lib.mkOption {
type = lib.types.str; type = lib.types.str;
description = "Directory where ${name} stores data."; description = "Directory where ${name} stores data.";
default = "/var/lib/${name}"; default = "/var/lib/${name}";
}; };
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr contracts.ssl.certs;
default = null;
};
authEndpoint = lib.mkOption { authEndpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
@ -191,164 +382,156 @@ in
options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps); options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps);
config = lib.mkMerge ([ config = lib.mkMerge ([
{ (lib.mkIf cfg.radarr.enable ({
services.radarr = lib.mkIf cfg.radarr.enable { services.nginx.enable = true;
services.radarr = {
enable = true; enable = true;
dataDir = "/var/lib/radarr"; dataDir = "/var/lib/radarr";
}; };
users.users.radarr = lib.mkIf cfg.radarr.enable {
users.users.radarr = {
extraGroups = [ "media" ]; extraGroups = [ "media" ];
}; };
shb.arr.radarr.settings = lib.mkIf cfg.radarr.enable {
Port = config.shb.arr.radarr.port; systemd.services.radarr.preStart = shblib.replaceSecrets {
BindAddress = "127.0.0.1"; userConfig = cfg.radarr.settings;
UrlBase = ""; resultPath = "${config.services.radarr.dataDir}/config.xml";
EnableSsl = "false"; generator = apps.radarr.settingsFormat.generate;
AuthenticationMethod = "External";
AuthenticationRequired = "Enabled";
}; };
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 " shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.radarr) ];
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;
# Listens on port 8989 shb.backup.instances.radarr = cfg.radarr.backupCfg // {
services.sonarr = lib.mkIf cfg.sonarr.enable { 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; enable = true;
dataDir = "/var/lib/sonarr"; dataDir = "/var/lib/sonarr";
}; };
users.users.sonarr = lib.mkIf cfg.sonarr.enable { users.users.sonarr = {
extraGroups = [ "media" ]; 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; enable = true;
listenPort = cfg.bazarr.port; listenPort = cfg.bazarr.settings.Port;
}; };
users.users.bazarr = lib.mkIf cfg.bazarr.enable { users.users.bazarr = {
extraGroups = [ "media" ]; 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 shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.bazarr) ];
services.readarr = lib.mkIf cfg.readarr.enable {
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; enable = true;
dataDir = "/var/lib/readarr"; dataDir = "/var/lib/readarr";
}; };
users.users.readarr = lib.mkIf cfg.readarr.enable { users.users.readarr = {
extraGroups = [ "media" ]; 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 shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.readarr) ];
services.lidarr = lib.mkIf cfg.lidarr.enable {
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; enable = true;
dataDir = "/var/lib/lidarr"; dataDir = "/var/lib/lidarr";
}; };
users.users.lidarr = lib.mkIf cfg.lidarr.enable { users.users.lidarr = {
extraGroups = [ "media" ]; 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 shb.nginx.autheliaProtect = [ (autheliaProtect {} config.shb.arr.lidarr) ];
services.jackett = lib.mkIf cfg.jackett.enable {
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; enable = true;
dataDir = "/var/lib/jackett"; dataDir = "/var/lib/jackett";
}; };
shb.arr.jackett.settings = lib.mkIf cfg.jackett.enable { users.users.jackett = {
Port = config.shb.arr.jackett.port;
AllowExternal = "false";
UpdateDisabled = "true";
};
users.users.jackett = lib.mkIf cfg.jackett.enable {
extraGroups = [ "media" ]; extraGroups = [ "media" ];
}; };
systemd.services.jackett.preStart = systemd.services.jackett.preStart = shblib.replaceSecrets {
let userConfig = cfg.jackett.settings;
s = cfg.jackett.settings; resultPath = "${config.services.jackett.dataDir}/config.xml";
templatedfileSettings = generator = apps.jackett.settingsFormat.generate;
lib.optionalAttrs (!(isNull s.APIKeyFile)) { };
APIKey = "%APIKEY%";
} // lib.optionalAttrs (!(isNull s.OmdbApiKeyFile)) {
OmdbApiKey = "%OMDBAPIKEY%";
};
templatedSettings = (removeAttrs s [ "APIKeyFile" "OmdbApiKeyFile" ]) // templatedfileSettings;
t = shblib.template (apps.jackett.settingsFormat.generate "jackett.json" templatedSettings) "${config.services.jackett.dataDir}/ServerConfig.json" ( shb.nginx.autheliaProtect = [ (autheliaProtect {
lib.optionalAttrs (!(isNull s.APIKeyFile)) { extraBypassResources = [ "^/dl.*" ];
"%APIKEY%" = "$(cat ${s.APIKeyFile})"; } config.shb.arr.jackett) ];
} // lib.optionalAttrs (!(isNull s.OmdbApiKeyFile)) {
"%OMDBAPIKEY%" = "$(cat ${s.OmdbApiKeyFile})";
}
);
in
lib.mkIf cfg.jackett.enable t;
shb.nginx.autheliaProtect = shb.backup.instances.jackett = cfg.jackett.backupCfg // {
let sourceDirectories = [
appProtectConfig = name: _defaults: config.shb.arr.jackett.dataDir
let ];
c = cfg.${name}; excludePatterns = [".db-shm" ".db-wal" ".mono"];
in };
lib.mkIf (c.authEndpoint != null) { } // backup "jackett"))
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));
} }

View file

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