628 lines
19 KiB
Nix
628 lines
19 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
shb,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.shb.arr;
|
|
|
|
apps = {
|
|
radarr = {
|
|
settingsFormat = shb.formatXML { enclosingRoot = "Config"; };
|
|
moreOptions = {
|
|
settings = lib.mkOption {
|
|
description = "Specific options for radarr.";
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
freeformType = apps.radarr.settingsFormat.type;
|
|
options = {
|
|
ApiKey = lib.mkOption {
|
|
type = shb.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 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 = {
|
|
settingsFormat = shb.formatXML { enclosingRoot = "Config"; };
|
|
moreOptions = {
|
|
settings = lib.mkOption {
|
|
description = "Specific options for sonarr.";
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
freeformType = apps.sonarr.settingsFormat.type;
|
|
options = {
|
|
ApiKey = lib.mkOption {
|
|
type = shb.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 = {
|
|
settingsFormat = shb.formatXML { enclosingRoot = "Config"; };
|
|
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";
|
|
};
|
|
ApiKey = lib.mkOption {
|
|
type = shb.secretFileType;
|
|
description = "Path to api key secret file.";
|
|
};
|
|
Port = lib.mkOption {
|
|
type = lib.types.port;
|
|
description = "Port on which bazarr listens to incoming requests.";
|
|
default = 6767;
|
|
readOnly = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
readarr = {
|
|
settingsFormat = shb.formatXML { enclosingRoot = "Config"; };
|
|
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";
|
|
};
|
|
ApiKey = lib.mkOption {
|
|
type = shb.secretFileType;
|
|
description = "Path to api key secret file.";
|
|
};
|
|
Port = lib.mkOption {
|
|
type = lib.types.port;
|
|
description = "Port on which readarr listens to incoming requests.";
|
|
default = 8787;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
lidarr = {
|
|
settingsFormat = shb.formatXML { enclosingRoot = "Config"; };
|
|
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";
|
|
};
|
|
ApiKey = lib.mkOption {
|
|
type = shb.secretFileType;
|
|
description = "Path to api key secret file.";
|
|
};
|
|
Port = lib.mkOption {
|
|
type = lib.types.port;
|
|
description = "Port on which lidarr listens to incoming requests.";
|
|
default = 8686;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
jackett = {
|
|
settingsFormat = pkgs.formats.json { };
|
|
moreOptions = {
|
|
settings = lib.mkOption {
|
|
description = "Specific options for jackett.";
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
freeformType = apps.jackett.settingsFormat.type;
|
|
options = {
|
|
ApiKey = lib.mkOption {
|
|
type = shb.secretFileType;
|
|
description = "Path to api key secret file.";
|
|
};
|
|
FlareSolverrUrl = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "FlareSolverr endpoint.";
|
|
default = null;
|
|
};
|
|
OmdbApiKey = lib.mkOption {
|
|
type = lib.types.nullOr shb.secretFileType;
|
|
description = "File containing the Open Movie Database API key.";
|
|
default = null;
|
|
};
|
|
ProxyType = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"-1"
|
|
"0"
|
|
"1"
|
|
"2"
|
|
];
|
|
default = "-1";
|
|
description = ''
|
|
-1 = disabled
|
|
0 = HTTP
|
|
1 = SOCKS4
|
|
2 = SOCKS5
|
|
'';
|
|
};
|
|
ProxyUrl = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "URL of the proxy. Ignored if ProxyType is set to -1";
|
|
default = null;
|
|
};
|
|
ProxyPort = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.port;
|
|
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;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
vhosts =
|
|
{
|
|
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.*"
|
|
"^/feed.*"
|
|
];
|
|
}
|
|
{
|
|
domain = "${c.subdomain}.${c.domain}";
|
|
policy = "two_factor";
|
|
subject = [ "group:${c.ldapUserGroup}" ];
|
|
}
|
|
];
|
|
};
|
|
|
|
appOption =
|
|
name: c:
|
|
lib.nameValuePair name (
|
|
lib.mkOption {
|
|
description = "Configuration for ${name}";
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = {
|
|
enable = lib.mkEnableOption name;
|
|
|
|
subdomain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Subdomain under which ${name} will be served.";
|
|
example = name;
|
|
};
|
|
|
|
domain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Domain under which ${name} will be served.";
|
|
example = "example.com";
|
|
};
|
|
|
|
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 shb.contracts.ssl.certs;
|
|
default = null;
|
|
};
|
|
|
|
ldapUserGroup = lib.mkOption {
|
|
description = ''
|
|
LDAP group a user must belong to be able to login.
|
|
|
|
Note that all users are admins too.
|
|
'';
|
|
type = lib.types.str;
|
|
default = "arr_user";
|
|
};
|
|
|
|
authEndpoint = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Endpoint to the SSO provider. Leave null to not have SSO configured.";
|
|
example = "https://authelia.example.com";
|
|
};
|
|
|
|
backup = lib.mkOption {
|
|
description = ''
|
|
Backup configuration.
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.backup.mkRequester {
|
|
user = name;
|
|
sourceDirectories = [
|
|
cfg.${name}.dataDir
|
|
];
|
|
excludePatterns = [
|
|
".db-shm"
|
|
".db-wal"
|
|
".mono"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
|
|
dashboard = lib.mkOption {
|
|
description = ''
|
|
Dashboard contract consumer
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.dashboard.mkRequester {
|
|
externalUrl = "https://${cfg.${name}.subdomain}.${cfg.${name}.domain}";
|
|
externalUrlText = "https://\${config.shb.arr.${name}.subdomain}.\${config.shb.arr.${name}.domain}";
|
|
internalUrl = "http://127.0.0.1:${toString cfg.${name}.settings.Port}";
|
|
};
|
|
};
|
|
};
|
|
}
|
|
// (c.moreOptions or { });
|
|
};
|
|
}
|
|
);
|
|
in
|
|
{
|
|
imports = [
|
|
../../lib/module.nix
|
|
../blocks/nginx.nix
|
|
../blocks/lldap.nix
|
|
];
|
|
|
|
options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps);
|
|
|
|
config = lib.mkMerge [
|
|
(lib.mkIf cfg.radarr.enable (
|
|
let
|
|
cfg' = cfg.radarr;
|
|
isSSOEnabled = !(isNull cfg'.authEndpoint);
|
|
in
|
|
{
|
|
services.nginx.enable = true;
|
|
|
|
services.radarr = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
};
|
|
|
|
systemd.services.radarr.preStart = shb.replaceSecrets {
|
|
userConfig =
|
|
cfg'.settings
|
|
// (lib.optionalAttrs isSSOEnabled {
|
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
|
AuthenticationMethod = "External";
|
|
});
|
|
resultPath = "${cfg'.dataDir}/config.xml";
|
|
generator = shb.replaceSecretsFormatAdapter apps.radarr.settingsFormat;
|
|
};
|
|
|
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
|
|
(lib.mkIf cfg.sonarr.enable (
|
|
let
|
|
cfg' = cfg.sonarr;
|
|
isSSOEnabled = !(isNull cfg'.authEndpoint);
|
|
in
|
|
{
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg'.dataDir} 0700 ${config.services.sonarr.user} ${config.services.sonarr.user}"
|
|
];
|
|
|
|
services.nginx.enable = true;
|
|
|
|
services.sonarr = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
};
|
|
users.users.sonarr = {
|
|
extraGroups = [ "media" ];
|
|
};
|
|
|
|
systemd.services.sonarr.preStart = shb.replaceSecrets {
|
|
userConfig =
|
|
cfg'.settings
|
|
// (lib.optionalAttrs isSSOEnabled {
|
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
|
AuthenticationMethod = "External";
|
|
});
|
|
resultPath = "${cfg'.dataDir}/config.xml";
|
|
generator = apps.sonarr.settingsFormat.generate;
|
|
};
|
|
|
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
|
|
(lib.mkIf cfg.bazarr.enable (
|
|
let
|
|
cfg' = cfg.bazarr;
|
|
isSSOEnabled = !(isNull cfg'.authEndpoint);
|
|
in
|
|
{
|
|
services.bazarr = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
listenPort = cfg'.settings.Port;
|
|
};
|
|
users.users.bazarr = {
|
|
extraGroups = [ "media" ];
|
|
};
|
|
# This is actually not working. Bazarr uses a config file in dataDir/config/config.yaml
|
|
# which includes all configuration so we must somehow merge our declarative config with it.
|
|
# It's doable but will take some time. Help is welcomed.
|
|
#
|
|
# systemd.services.bazarr.preStart = shb.replaceSecrets {
|
|
# userConfig =
|
|
# cfg'.settings
|
|
# // (lib.optionalAttrs isSSOEnabled {
|
|
# AuthenticationRequired = "DisabledForLocalAddresses";
|
|
# AuthenticationMethod = "External";
|
|
# });
|
|
# resultPath = "${cfg'.dataDir}/config.xml";
|
|
# generator = apps.bazarr.settingsFormat.generate;
|
|
# };
|
|
|
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
|
|
(lib.mkIf cfg.readarr.enable (
|
|
let
|
|
cfg' = cfg.readarr;
|
|
isSSOEnabled = !(isNull cfg'.authEndpoint);
|
|
in
|
|
{
|
|
services.readarr = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
};
|
|
users.users.readarr = {
|
|
extraGroups = [ "media" ];
|
|
};
|
|
systemd.services.readarr.preStart = shb.replaceSecrets {
|
|
userConfig =
|
|
cfg'.settings
|
|
// (lib.optionalAttrs isSSOEnabled {
|
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
|
AuthenticationMethod = "External";
|
|
});
|
|
resultPath = "${cfg'.dataDir}/config.xml";
|
|
generator = apps.readarr.settingsFormat.generate;
|
|
};
|
|
|
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
|
|
(lib.mkIf cfg.lidarr.enable (
|
|
let
|
|
cfg' = cfg.lidarr;
|
|
isSSOEnabled = !(isNull cfg'.authEndpoint);
|
|
in
|
|
{
|
|
services.lidarr = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
};
|
|
users.users.lidarr = {
|
|
extraGroups = [ "media" ];
|
|
};
|
|
systemd.services.lidarr.preStart = shb.replaceSecrets {
|
|
userConfig =
|
|
cfg'.settings
|
|
// (lib.optionalAttrs isSSOEnabled {
|
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
|
AuthenticationMethod = "External";
|
|
});
|
|
resultPath = "${cfg'.dataDir}/config.xml";
|
|
generator = apps.lidarr.settingsFormat.generate;
|
|
};
|
|
|
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
|
|
(lib.mkIf cfg.jackett.enable (
|
|
let
|
|
cfg' = cfg.jackett;
|
|
in
|
|
{
|
|
services.jackett = {
|
|
enable = true;
|
|
dataDir = cfg'.dataDir;
|
|
};
|
|
# TODO: avoid implicitly relying on the media group
|
|
users.users.jackett = {
|
|
extraGroups = [ "media" ];
|
|
};
|
|
systemd.services.jackett.preStart = shb.replaceSecrets {
|
|
userConfig = shb.renameAttrName cfg'.settings "ApiKey" "APIKey";
|
|
resultPath = "${cfg'.dataDir}/ServerConfig.json";
|
|
generator = apps.jackett.settingsFormat.generate;
|
|
};
|
|
|
|
shb.nginx.vhosts = [
|
|
(vhosts {
|
|
extraBypassResources = [ "^/dl.*" ];
|
|
} cfg')
|
|
];
|
|
|
|
shb.lldap.ensureGroups = {
|
|
${cfg'.ldapUserGroup} = { };
|
|
};
|
|
}
|
|
))
|
|
];
|
|
}
|