add tests for arr services and some more options (#205)
This commit is contained in:
parent
e6414dc911
commit
589e2c936f
7 changed files with 451 additions and 170 deletions
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
@ -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)"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -21,7 +21,7 @@ rec {
|
|||
in
|
||||
''
|
||||
set -euo pipefail
|
||||
set -x
|
||||
|
||||
mkdir -p $(dirname ${templatePath})
|
||||
ln -fs ${file} ${templatePath}
|
||||
rm -f ${resultPath}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"))
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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
86
test/vm/arr.nix
Normal 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";
|
||||
}
|
Loading…
Reference in a new issue