379 lines
12 KiB
Nix
379 lines
12 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
utils,
|
|
...
|
|
}:
|
|
with lib;
|
|
let
|
|
cfg = config.mySystem.services.radarr;
|
|
dbOptions = {
|
|
options = {
|
|
enable = mkEnableOption "Database configuration for Radarr";
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = "127.0.0.1";
|
|
description = "Direct database host (mutually exclusive with hostFile)";
|
|
};
|
|
hostFile = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = "/run/secrets/radarr_db_host";
|
|
description = "Database host from a file (mutually exclusive with host)";
|
|
};
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = "5432";
|
|
description = "Database port";
|
|
};
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "radarr";
|
|
description = "Direct database user (mutually exclusive with userFile)";
|
|
};
|
|
userFile = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = "/run/secrets/radarr_db_user";
|
|
description = "Database user from a file (mutually exclusive with user)";
|
|
};
|
|
passwordFile = mkOption {
|
|
type = types.path;
|
|
default = "/run/secrets/radarr_db_password";
|
|
description = "Database password from a file (always used)";
|
|
};
|
|
dbname = mkOption {
|
|
type = types.str;
|
|
default = "radarr_main";
|
|
description = "Database name";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Function to create instance-specific configuration
|
|
mkRadarrInstance = name: instanceCfg: {
|
|
|
|
systemd.services."radarr-${name}" = {
|
|
description = "Radarr (${name})";
|
|
|
|
};
|
|
|
|
networking.firewall = mkIf instanceCfg.openFirewall {
|
|
allowedTCPPorts = [ instanceCfg.port ];
|
|
};
|
|
|
|
users.groups.${instanceCfg.group} = { };
|
|
users.users = mkIf (instanceCfg.user == "radarr") {
|
|
radarr = {
|
|
inherit (instanceCfg) group;
|
|
isSystemUser = true;
|
|
home = instanceCfg.dataDir;
|
|
};
|
|
};
|
|
};
|
|
in
|
|
{
|
|
options.mySystem.services.radarr = {
|
|
enable = mkEnableOption "Radarr (global)";
|
|
|
|
instances = mkOption {
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
{ name, ... }:
|
|
{
|
|
options = {
|
|
enable = mkEnableOption "Radarr (instance)";
|
|
|
|
package = mkPackageOption pkgs "Radarr" { };
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "radarr";
|
|
description = "User account under which radarr runs.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "radarr";
|
|
description = "Group under which radarr runs.";
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/radarr/${name}";
|
|
description = "Storage directory for radarr data";
|
|
};
|
|
|
|
moviesDir = mkOption {
|
|
type = types.path;
|
|
default = "/mnt/media/movies";
|
|
description = "Directory where movies are stored";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 7878;
|
|
description = "Port for radarr web interface";
|
|
};
|
|
|
|
openFirewall = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Open firewall ports for radarr";
|
|
};
|
|
|
|
hardening = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable security hardening features";
|
|
};
|
|
|
|
apiKey = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = "abc123";
|
|
description = "Direct API key for radarr (mutually exclusive with apiKeyFile)";
|
|
};
|
|
|
|
apiKeyFile = mkOption {
|
|
type = types.path;
|
|
default = "/run/secrets/radarr_api_key";
|
|
description = "API key for radarr from a file (mutually exclusive with apiKey)";
|
|
};
|
|
|
|
db = mkOption {
|
|
type = types.submodule dbOptions;
|
|
example = {
|
|
enable = true;
|
|
host = "10.5.0.5"; # or use hostFile
|
|
port = "5432";
|
|
user = "radarr"; # or userFile
|
|
passwordFile = "/run/secrets/radarr_db_password";
|
|
dbname = "radarr_main";
|
|
};
|
|
description = "Database settings for radarr.";
|
|
};
|
|
|
|
extraEnvVars = mkOption {
|
|
type = types.attrs;
|
|
default = { };
|
|
example = {
|
|
MY_VAR = "my value";
|
|
};
|
|
description = "Extra environment variables for radarr.";
|
|
};
|
|
|
|
extraEnvVarFile = mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
example = "/run/secrets/radarr_extra_env";
|
|
description = "Extra environment file for Radarr.";
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
default = { };
|
|
description = "Radarr instance configurations.";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# Add assertions for all instances
|
|
assertions = flatten (
|
|
mapAttrsToList (
|
|
name: instanceCfg:
|
|
if instanceCfg.enable then
|
|
[
|
|
{
|
|
assertion = !(instanceCfg.db.host != "" && instanceCfg.db.hostFile != "");
|
|
message = "Specify either a direct database host via db.host or a file via db.hostFile (leave direct host empty).";
|
|
}
|
|
{
|
|
assertion = !(instanceCfg.db.user != "radarr" && instanceCfg.db.userFile != "");
|
|
message = "Specify either a direct database user via db.user or a file via db.userFile.";
|
|
}
|
|
{
|
|
assertion = !(instanceCfg.apiKey != "" && instanceCfg.apiKeyFile != "");
|
|
message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty).";
|
|
}
|
|
]
|
|
else
|
|
[ ]
|
|
) cfg.instances
|
|
);
|
|
|
|
# Create systemd tmpfiles rules for each enabled instance
|
|
systemd.tmpfiles.rules = flatten (
|
|
mapAttrsToList (
|
|
name: instanceCfg:
|
|
if instanceCfg.enable then
|
|
[
|
|
"d ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}"
|
|
]
|
|
else
|
|
[ ]
|
|
) cfg.instances
|
|
);
|
|
|
|
# Create services for each enabled instance
|
|
systemd.services = mapAttrs' (
|
|
name: instanceCfg:
|
|
nameValuePair "radarr-${name}" (
|
|
mkIf instanceCfg.enable {
|
|
description = "Radarr (${name})";
|
|
after = [
|
|
"network.target"
|
|
"nss-lookup.target"
|
|
];
|
|
wantedBy = [ "multi-user.target" ];
|
|
environment = lib.mkMerge [
|
|
{
|
|
RADARR__APP__INSTANCENAME = name;
|
|
RADARR__APP__THEME = "dark";
|
|
RADARR__AUTH__METHOD = "External";
|
|
RADARR__AUTH__REQUIRED = "DisabledForLocalAddresses";
|
|
RADARR__LOG__DBENABLED = "False";
|
|
RADARR__LOG__LEVEL = "info";
|
|
RADARR__SERVER__PORT = toString instanceCfg.port;
|
|
RADARR__UPDATE__BRANCH = "develop";
|
|
}
|
|
(lib.mkIf instanceCfg.db.enable {
|
|
RADARR__POSTGRES__PORT = toString instanceCfg.db.port;
|
|
RADARR__POSTGRES__MAINDB = instanceCfg.db.dbname;
|
|
})
|
|
instanceCfg.extraEnvVars
|
|
];
|
|
|
|
serviceConfig = lib.mkMerge [
|
|
{
|
|
Type = "simple";
|
|
User = instanceCfg.user;
|
|
Group = instanceCfg.group;
|
|
ExecStart = utils.escapeSystemdExecArgs [
|
|
(lib.getExe instanceCfg.package)
|
|
"-nobrowser"
|
|
"-data=${instanceCfg.dataDir}"
|
|
"-port=${toString instanceCfg.port}"
|
|
];
|
|
WorkingDirectory = instanceCfg.dataDir;
|
|
RuntimeDirectory = "radarr-${name}";
|
|
LogsDirectory = "radarr-${name}";
|
|
RuntimeDirectoryMode = "0750";
|
|
Restart = "on-failure";
|
|
RestartSec = 5;
|
|
}
|
|
(lib.mkIf instanceCfg.hardening {
|
|
CapabilityBoundingSet = [ "" ];
|
|
DeviceAllow = [ "" ];
|
|
DevicePolicy = "closed";
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = false;
|
|
NoNewPrivileges = true;
|
|
PrivateDevices = true;
|
|
PrivateTmp = true;
|
|
ProtectControlGroups = true;
|
|
ProtectHome = "read-only";
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectSystem = "strict";
|
|
ReadWritePaths = [
|
|
instanceCfg.dataDir
|
|
instanceCfg.moviesDir
|
|
"/var/log/radarr-${name}"
|
|
"/eru/media"
|
|
];
|
|
RestrictAddressFamilies = [
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
"AF_NETLINK"
|
|
];
|
|
RestrictNamespaces = [
|
|
"uts"
|
|
"ipc"
|
|
"pid"
|
|
"user"
|
|
"cgroup"
|
|
"net"
|
|
];
|
|
RestrictSUIDSGID = true;
|
|
SystemCallArchitectures = "native";
|
|
SystemCallFilter = [
|
|
"@system-service"
|
|
];
|
|
})
|
|
(lib.mkIf instanceCfg.db.enable {
|
|
ExecStartPre = "+${pkgs.writeShellScript "radarr-${name}-pre-script" ''
|
|
mkdir -p /run/radarr-${name}
|
|
rm -f /run/radarr-${name}/secrets.env
|
|
|
|
# Helper function to safely write variables
|
|
write_var() {
|
|
local var_name="$1"
|
|
local value="$2"
|
|
if [ -n "$value" ]; then
|
|
printf "%s=%s\n" "$var_name" "$value" >> /run/radarr-${name}/secrets.env
|
|
fi
|
|
}
|
|
|
|
# API Key (direct value or file)
|
|
if [ -n "${instanceCfg.apiKey}" ]; then
|
|
write_var "RADARR__AUTH__APIKEY" "${instanceCfg.apiKey}"
|
|
else
|
|
write_var "RADARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})"
|
|
fi
|
|
|
|
# Database Configuration
|
|
write_var "RADARR__POSTGRES__HOST" "$([ -n "${instanceCfg.db.host}" ] && echo "${instanceCfg.db.host}" || cat "${instanceCfg.db.hostFile}")"
|
|
write_var "RADARR__POSTGRES__USER" "$([ -n "${instanceCfg.db.user}" ] && echo "${instanceCfg.db.user}" || cat "${instanceCfg.db.userFile}")"
|
|
write_var "RADARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})"
|
|
|
|
# Final permissions
|
|
chmod 600 /run/radarr-${name}/secrets.env
|
|
chown ${instanceCfg.user}:${instanceCfg.group} /run/radarr-${name}/secrets.env
|
|
''}";
|
|
|
|
EnvironmentFile = (
|
|
[ "-/run/radarr-${name}/secrets.env" ]
|
|
++ lib.optional (
|
|
instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != ""
|
|
) instanceCfg.extraEnvVarFile
|
|
);
|
|
})
|
|
];
|
|
}
|
|
)
|
|
) cfg.instances;
|
|
|
|
# Firewall configurations
|
|
networking.firewall = mkMerge (
|
|
mapAttrsToList (
|
|
name: instanceCfg:
|
|
mkIf (instanceCfg.enable && instanceCfg.openFirewall) {
|
|
allowedTCPPorts = [ instanceCfg.port ];
|
|
}
|
|
) cfg.instances
|
|
);
|
|
|
|
# Users and groups
|
|
users = mkMerge (
|
|
mapAttrsToList (
|
|
name: instanceCfg:
|
|
mkIf instanceCfg.enable {
|
|
groups.${instanceCfg.group} = { };
|
|
users = mkIf (instanceCfg.user == "radarr") {
|
|
radarr = {
|
|
inherit (instanceCfg) group;
|
|
isSystemUser = true;
|
|
# home = instanceCfg.dataDir;
|
|
home = "/nahar/radarr";
|
|
};
|
|
};
|
|
}
|
|
) cfg.instances
|
|
);
|
|
};
|
|
|
|
}
|