Merge pull request 'feat: add multi-sonarr' (#75) from multi-sonarr into main
Reviewed-on: #75
This commit is contained in:
commit
04271382e1
4 changed files with 389 additions and 263 deletions
|
@ -54,41 +54,77 @@
|
|||
restartUnits = [ "prowlarr.service" ];
|
||||
};
|
||||
# Sonarr
|
||||
"arr/sonarr/apiKey" = {
|
||||
"arr/sonarr/1080p/apiKey" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/postgres/dbName" = {
|
||||
"arr/sonarr/1080p/postgres/dbName" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/postgres/user" = {
|
||||
"arr/sonarr/1080p/postgres/user" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/postgres/password" = {
|
||||
"arr/sonarr/1080p/postgres/password" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/postgres/host" = {
|
||||
"arr/sonarr/1080p/postgres/host" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/extraEnvVars" = {
|
||||
"arr/sonarr/1080p/extraEnvVars" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr.service" ];
|
||||
restartUnits = [ "sonarr-tv1080p.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/apiKey" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/postgres/dbName" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/postgres/user" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/postgres/password" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/postgres/host" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
"arr/sonarr/anime/extraEnvVars" = {
|
||||
sopsFile = ../secrets.sops.yaml;
|
||||
owner = "sonarr";
|
||||
mode = "400";
|
||||
restartUnits = [ "sonarr-anime.service" ];
|
||||
};
|
||||
# Radarr
|
||||
"arr/radarr/1080p/apiKey" = {
|
||||
|
|
|
@ -317,22 +317,49 @@ in
|
|||
# Sonarr
|
||||
sonarr = {
|
||||
enable = true;
|
||||
package = pkgs.unstable.sonarr;
|
||||
dataDir = "/nahar/sonarr";
|
||||
extraEnvVarFile = config.sops.secrets."arr/sonarr/extraEnvVars".path;
|
||||
tvDir = "/moria/media/TV";
|
||||
user = "sonarr";
|
||||
group = "kah";
|
||||
port = 8989;
|
||||
openFirewall = true;
|
||||
hardening = true;
|
||||
apiKeyFile = config.sops.secrets."arr/sonarr/apiKey".path;
|
||||
db = {
|
||||
enable = true;
|
||||
hostFile = config.sops.secrets."arr/sonarr/postgres/host".path;
|
||||
port = 5432;
|
||||
userFile = config.sops.secrets."arr/sonarr/postgres/user".path;
|
||||
passwordFile = config.sops.secrets."arr/sonarr/postgres/password".path;
|
||||
instances = {
|
||||
tv1080p = {
|
||||
enable = true;
|
||||
package = pkgs.unstable.sonarr;
|
||||
dataDir = "/nahar/sonarr/1080p";
|
||||
extraEnvVarFile = config.sops.secrets."arr/sonarr/1080p/extraEnvVars".path;
|
||||
tvDir = "/moria/media/TV";
|
||||
user = "sonarr";
|
||||
group = "kah";
|
||||
port = 8989;
|
||||
openFirewall = true;
|
||||
hardening = true;
|
||||
apiKeyFile = config.sops.secrets."arr/sonarr/1080p/apiKey".path;
|
||||
db = {
|
||||
enable = true;
|
||||
hostFile = config.sops.secrets."arr/sonarr/1080p/postgres/host".path;
|
||||
port = 5432;
|
||||
dbname = "sonarr_main";
|
||||
userFile = config.sops.secrets."arr/sonarr/1080p/postgres/user".path;
|
||||
passwordFile = config.sops.secrets."arr/sonarr/1080p/postgres/password".path;
|
||||
};
|
||||
};
|
||||
anime = {
|
||||
enable = true;
|
||||
package = pkgs.unstable.sonarr;
|
||||
dataDir = "/nahar/sonarr/anime";
|
||||
extraEnvVarFile = config.sops.secrets."arr/sonarr/anime/extraEnvVars".path;
|
||||
tvDir = "/moria/media/Anime/Shows";
|
||||
user = "sonarr";
|
||||
group = "kah";
|
||||
port = 8990;
|
||||
openFirewall = true;
|
||||
hardening = true;
|
||||
apiKeyFile = config.sops.secrets."arr/sonarr/anime/apiKey".path;
|
||||
db = {
|
||||
enable = true;
|
||||
hostFile = config.sops.secrets."arr/sonarr/anime/postgres/host".path;
|
||||
port = 5432;
|
||||
dbname = "sonarr_anime";
|
||||
userFile = config.sops.secrets."arr/sonarr/anime/postgres/user".path;
|
||||
passwordFile = config.sops.secrets."arr/sonarr/anime/postgres/password".path;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
# Sabnzbd
|
||||
|
|
|
@ -20,13 +20,22 @@ arr:
|
|||
user: ENC[AES256_GCM,data:gO8c5bZ3oDY=,iv:YggC8TNFzqHRcRxSBDiV580xF3kLQKgR/ScfyW+5Y5A=,tag:bY7QduxooRkR5SFBxlKjxQ==,type:str]
|
||||
password: ENC[AES256_GCM,data:rqryseQj0lMiNmB21ezXYQ7ceaOtiZJLPA==,iv:f0ahBkII+pZOPB50EcCIEMbvHriYHv7ax/u1515KAA8=,tag:EObTc1yd/GTNuVE87tPg0g==,type:str]
|
||||
sonarr:
|
||||
apiKey: ENC[AES256_GCM,data:TVy4L0ctHhT3gNp+WCaLCUVc0no8VIkWenroFOYk8h4z,iv:A0a6IUBeDDxPiLlrPCXhXu586QRnXha0RthuXUKkU4I=,tag:oVMS5Ys/NiDrA6YSiCjqsw==,type:str]
|
||||
postgres:
|
||||
host: ENC[AES256_GCM,data:X0suLwp9bZE=,iv:UQXhPilmi0ix0GruDXfLeHXGUY8k+iAL03/3K/EfcCc=,tag:y3tzbv3nxbOzNEnZQCdeVg==,type:str]
|
||||
dbName: ENC[AES256_GCM,data:Um9YpALoU7qQfTo=,iv:q0IVjaxyaG8MWAxp43kZjHIBm6dWv37maykSfhAxe1M=,tag:NLqIikfWculCeuoRqPHc8Q==,type:str]
|
||||
user: ENC[AES256_GCM,data:Vd68IvZs,iv:DYT3PudE94JZZTZHzV8QgRYADtThZhxTjFJByLcZP1c=,tag:pX1ZNC+M9Jm+PlQ22BZMRw==,type:str]
|
||||
password: ENC[AES256_GCM,data:XOrycMom2utnefraGPoAq7xtP6yfSzTb8g==,iv:WQInK+bJuDNI9uN/GeQ2Fb1Mmlux6+lXwkGS1ZEh+kQ=,tag:DGqLerxomCVfVv15Gt3b8A==,type:str]
|
||||
extraEnvVars: ENC[AES256_GCM,data:KnZSJ2YbNLawSzrj7syx0cfAFseHbgjGvjpB7yWajXfCIy+CV800z9YU2SVO2kV6b+9OrmyKKFFbM5ac4cWnc5Pcx8TUxfiAuL5RSi6ZTmUrZUA7Zqx5UDTHwXgvhDI=,iv:TX8sFk7uc1TYG/gkuA9plGZlhP25WuczEXd+QKsPi4c=,tag:zhVeZxrTgcv+Y2OP8I+k5g==,type:str]
|
||||
1080p:
|
||||
apiKey: ENC[AES256_GCM,data:e3v+MR2BXCRXXm01/Y09mWN9SnjwH/qQ5tJ5/lNsuqRL,iv:nAT5aFT0ihHGGSTOUBrPZy4d6S+kbOzjNebXftPjoAI=,tag:uAZ1nuDcIYWKAOA2XLdBFQ==,type:str]
|
||||
postgres:
|
||||
host: ENC[AES256_GCM,data:GH0XC+qbTC4=,iv:WnLz/rHhM44zmP/OyU/AOmsIg8xNhtgExO5MYnA0gqw=,tag:VFrMySTEhDsz6fZU9s+ZKg==,type:str]
|
||||
dbName: ENC[AES256_GCM,data:ftOhHHtk/McHs0k=,iv:Zh9/QsLYMTrtpukvA8CM0bj0scxIUaiKwlRkr38mDkI=,tag:DHpH90q2cuLk+KzMeciVQQ==,type:str]
|
||||
user: ENC[AES256_GCM,data:M5/SpaeO,iv:GZlxnQl+Vt9jE+f1obSqPJGC9peN3qlqw/ZaIf6p2YU=,tag:vXJHGjvNFLV5LirfBnNs2g==,type:str]
|
||||
password: ENC[AES256_GCM,data:M0YWtT/ikSKqQCLkF+Ln+jm3t6ltsLzYJQ==,iv:L6sCp1R1KkNRry2j/PO6+nHsL4gwOpzXVt3btcsC9QI=,tag:fiv3scPVQQzBjuQyzkMYFw==,type:str]
|
||||
extraEnvVars: ENC[AES256_GCM,data:W4WQwgSErsTzRAdnfhdYMn0QCrF30GGegTIXnwmweDb5cDRmLaVml5WoYd7PVRcZMlqOnOl7ZomzvkkVeJ77It3iOHiAsoSQ9b8zZCjkkanFGvQw6y3vDdOWXKhyonk=,iv:UK9g5aivhcoSNO7BBmIi8qtEssYEMoae6oPvbIIBS0c=,tag:KlF6idwZmRVJCITe1Sf4PA==,type:str]
|
||||
anime:
|
||||
apiKey: ENC[AES256_GCM,data:4Xg3O7Xrw0f8ijNvhnDnmizpAeSVL9qS8KbR7qeosz/K,iv:qlIyhT6bqMZCBNtuCKbRm9x/2g/qFCbL3xqa+mEG6ZM=,tag:I1gqP1caMpfIj0eXuS1t3w==,type:str]
|
||||
postgres:
|
||||
host: ENC[AES256_GCM,data:AZrIFD0MRlI=,iv:lC/CDAq65vSRacWCi7m3Jr3j2l2r9deNbqMyJN0ZNXE=,tag:GiNvGupolm4w9Z7XgQTryg==,type:str]
|
||||
dbName: ENC[AES256_GCM,data:74KryUh6B2mBZyIW,iv:fdnogNwmx5+qlfH7eOnDZhNeNJ1C2v8gT/g7X1nNtLI=,tag:S28PslyTuawLopyrUDLD5Q==,type:str]
|
||||
user: ENC[AES256_GCM,data:cM133gBrl0WM/DSz,iv:CNyUxvV1VOfAy+D2+dMmJMIoWXGH3qoJ8dXXU76NSXo=,tag:03dMKeF9daZZUwxXW5mz7g==,type:str]
|
||||
password: ENC[AES256_GCM,data:/XQNL9a3QUeDhPY7JRE+4/X6WmJ6dliHUhty+m3OmA==,iv:+Nn9Q+lfGGDmpd089qXc26+3PxIbOrHLsoolsgpQK24=,tag:HL7J0TTvd4DDIob71/Zfuw==,type:str]
|
||||
extraEnvVars: ENC[AES256_GCM,data:G8muZh4O/kiDFW3CjZXwXnoLxtsimq9evShea0aAbfPdNd3nenudPoI2PYgIF2vT4vHzMaWLCrvbqcd/gLBJUrZZyn4YAvuRo6LBliAD394EyXHZrdrTNNDkLZ8LQRU=,iv:Nh6QZhpwftrMs4Km1emymk1ilmFCtYeW0l05yRmXxug=,tag:CVl6nWN3wyc1r23eKPljmg==,type:str]
|
||||
radarr:
|
||||
1080p:
|
||||
apiKey: ENC[AES256_GCM,data:NVIa3nK4/QxZF1/peKei9Qsxn2AjRiBnBdr2N08QiXhi,iv:h/Rb38FCF8auMCTT+Cuj/i8YAeavntwbbJEA9LwDYUY=,tag:OwAQy76GjIrY9/126zExdA==,type:str]
|
||||
|
@ -124,8 +133,8 @@ sops:
|
|||
aVlOSHhFb2I5UnYwVytyQzlWTXBDYUUKdQKilmfJ1F7UYKtQV9zV95FcRIK17p4M
|
||||
vGvu/pGJ32tH8xI7cNs9I5Hmg9c5wOam21W1FDk+VlJ/ClXqQzS0MA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2025-02-10T04:49:46Z"
|
||||
mac: ENC[AES256_GCM,data:wT82lve5wNbxXUgcw3EZkOrsLFOmtriJtSNtpcfcH2KYkFEZTyzYCO10EBMrm1FjJxJFmhtde9f+CkSkT5ypjP6Ou4TGn9nP0jOlCyLxYyQkv7Lr31aEF8d2Q0NDjSHePW4YUiY88YrOX8shWYFVtNoZSonyCv8G6lwGVIsZzi4=,iv:MkHiwV5qRBE3LfTt0WHV56LWAeTEcRlux4xIf90R3AU=,tag:qUgCaPLa+W/pZ8uM1Gw7Xw==,type:str]
|
||||
lastmodified: "2025-02-10T19:15:54Z"
|
||||
mac: ENC[AES256_GCM,data:KyP1lzKD/iV6SgHbsEYvVRwgrFy/PqHGMVEjeTAcLFCFrIyn9/Gd5ravTOj+37phVARnHI3h2vetqzOBO7/tE4jz0nsEGawOfVMOTKR1Y7+35TKJaC4FTO3/hfczjzolvoLk8011J9aoeC44yx+UT8Ijehc3mkd4x8zVvOK/OG4=,iv:e6FudQPXESAw/CNVqJWGZG+/8j/J88Z8InjM++GJ9lM=,tag:KaQkmLGSU+jJF3f6YBKhmQ==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.9.4
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
utils,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
with lib;
|
||||
let
|
||||
cfg = config.mySystem.services.sonarr;
|
||||
dbOptions = {
|
||||
options = {
|
||||
enable = mkEnableOption "Database configuration for sonarr";
|
||||
enable = mkEnableOption "Database configuration for Sonarr";
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
|
@ -50,254 +51,307 @@ with lib; let
|
|||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.mySystem.services.sonarr = {
|
||||
enable = mkEnableOption "Sonarr";
|
||||
enable = mkEnableOption "Sonarr (global)";
|
||||
|
||||
package = mkPackageOption pkgs "Sonarr" {};
|
||||
instances = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
enable = mkEnableOption "Sonarr (instance)";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "sonarr";
|
||||
description = "User account under which sonarr runs.";
|
||||
};
|
||||
package = mkPackageOption pkgs "Sonarr" { };
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "sonarr";
|
||||
description = "Group under which sonarr runs.";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "sonarr";
|
||||
description = "User account under which sonarr runs.";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/sonarr";
|
||||
description = "Storage directory for sonarr data";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "sonarr";
|
||||
description = "Group under which sonarr runs.";
|
||||
};
|
||||
|
||||
tvDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/mnt/media/tv";
|
||||
description = "Directory where tv shows are stored";
|
||||
};
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/sonarr/${name}";
|
||||
description = "Storage directory for sonarr data";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8989;
|
||||
description = "Port for sonarr web interface";
|
||||
};
|
||||
tvDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/mnt/media/tv";
|
||||
description = "Directory where tv shows are stored";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open firewall ports for sonarr";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8989;
|
||||
description = "Port for sonarr web interface";
|
||||
};
|
||||
|
||||
hardening = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable security hardening features";
|
||||
};
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open firewall ports for sonarr";
|
||||
};
|
||||
|
||||
apiKey = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "abc123";
|
||||
description = "Direct API key for sonarr (mutually exclusive with apiKeyFile)";
|
||||
};
|
||||
hardening = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable security hardening features";
|
||||
};
|
||||
|
||||
apiKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/secrets/sonarr_api_key";
|
||||
description = "API key for sonarr from a file (mutually exclusive with apiKey)";
|
||||
};
|
||||
apiKey = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "abc123";
|
||||
description = "Direct API key for sonarr (mutually exclusive with apiKeyFile)";
|
||||
};
|
||||
|
||||
extraEnvVars = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = {
|
||||
MY_VAR = "my value";
|
||||
};
|
||||
description = "Extra environment variables for sonarr.";
|
||||
};
|
||||
apiKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/secrets/sonarr_api_key";
|
||||
description = "API key for sonarr from a file (mutually exclusive with apiKey)";
|
||||
};
|
||||
|
||||
extraEnvVarFile = mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/sonarr_extra_env";
|
||||
description = "Extra environment file for Sonarr.";
|
||||
};
|
||||
db = mkOption {
|
||||
type = types.submodule dbOptions;
|
||||
example = {
|
||||
enable = true;
|
||||
host = "10.5.0.5"; # or use hostFile
|
||||
port = "5432";
|
||||
user = "sonarr"; # or userFile
|
||||
passwordFile = "/run/secrets/sonarr_db_password";
|
||||
dbname = "sonarr_main";
|
||||
};
|
||||
description = "Database settings for sonarr.";
|
||||
};
|
||||
|
||||
db = mkOption {
|
||||
type = types.submodule dbOptions;
|
||||
example = {
|
||||
enable = true;
|
||||
host = "10.5.0.5"; # or use hostFile
|
||||
port = "5432";
|
||||
user = "sonarr"; # or userFile
|
||||
passwordFile = "/run/secrets/sonarr_db_password";
|
||||
dbname = "sonarr_main";
|
||||
};
|
||||
description = "Database settings for sonarr.";
|
||||
extraEnvVars = mkOption {
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
example = {
|
||||
MY_VAR = "my value";
|
||||
};
|
||||
description = "Extra environment variables for sonarr.";
|
||||
};
|
||||
|
||||
extraEnvVarFile = mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/sonarr_extra_env";
|
||||
description = "Extra environment file for Sonarr.";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
description = "Sonarr instance configurations.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(cfg.db.host != "" && cfg.db.hostFile != "");
|
||||
message = "Specify either a direct database host via db.host or a file via db.hostFile (leave direct host empty).";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.db.user != "sonarr" && cfg.db.userFile != "");
|
||||
message = "Specify either a direct database user via db.user or a file via db.userFile.";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.apiKey != "" && cfg.apiKeyFile != "");
|
||||
message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty).";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.dataDir} 0775 ${cfg.user} ${cfg.group}"
|
||||
];
|
||||
|
||||
systemd.services.sonarr = {
|
||||
description = "Sonarr";
|
||||
after = [
|
||||
"network.target"
|
||||
"nss-lookup.target"
|
||||
];
|
||||
wantedBy = ["multi-user.target"];
|
||||
environment = lib.mkMerge [
|
||||
{
|
||||
SONARR__APP__INSTANCENAME = "Sonarr";
|
||||
SONARR__APP__THEME = "dark";
|
||||
SONARR__AUTH__METHOD = "External";
|
||||
SONARR__AUTH__REQUIRED = "DisabledForLocalAddresses";
|
||||
SONARR__LOG__DBENABLED = "False";
|
||||
SONARR__LOG__LEVEL = "info";
|
||||
SONARR__SERVER__PORT = toString cfg.port;
|
||||
SONARR__UPDATE__BRANCH = "develop";
|
||||
}
|
||||
(lib.mkIf cfg.db.enable {
|
||||
SONARR__POSTGRES__PORT = toString cfg.db.port;
|
||||
SONARR__POSTGRES__MAINDB = cfg.db.dbname;
|
||||
})
|
||||
cfg.extraEnvVars
|
||||
];
|
||||
|
||||
serviceConfig = lib.mkMerge [
|
||||
{
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = utils.escapeSystemdExecArgs [
|
||||
(lib.getExe cfg.package)
|
||||
"-nobrowser"
|
||||
"-data=${cfg.dataDir}"
|
||||
"-port=${toString cfg.port}"
|
||||
];
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
RuntimeDirectory = "sonarr";
|
||||
LogsDirectory = "sonarr";
|
||||
RuntimeDirectoryMode = "0750";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
}
|
||||
(lib.mkIf cfg.hardening {
|
||||
CapabilityBoundingSet = [""];
|
||||
DeviceAllow = [""];
|
||||
DevicePolicy = "closed";
|
||||
LockPersonality = true;
|
||||
# Needs access to .Net CLR memory space.
|
||||
MemoryDenyWriteExecute = false;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = "read-only";
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
ReadWritePaths = [
|
||||
cfg.dataDir
|
||||
cfg.tvDir
|
||||
"/var/log/sonarr"
|
||||
"/eru/media"
|
||||
];
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_NETLINK"
|
||||
];
|
||||
RestrictNamespaces = [
|
||||
"uts"
|
||||
"ipc"
|
||||
"pid"
|
||||
"user"
|
||||
"cgroup"
|
||||
"net"
|
||||
"mnt"
|
||||
];
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
#"~@privileged"
|
||||
# .Net CLR requirement
|
||||
#"~@resources"
|
||||
];
|
||||
})
|
||||
(lib.mkIf cfg.db.enable {
|
||||
ExecStartPre = "+${pkgs.writeShellScript "sonarr-pre-script" ''
|
||||
mkdir -p /run/sonarr
|
||||
rm -f /run/sonarr/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/sonarr/secrets.env
|
||||
fi
|
||||
# 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 != "sonarr" && 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
|
||||
);
|
||||
|
||||
# API Key (direct value or file)
|
||||
if [ -n "${cfg.apiKey}" ]; then
|
||||
write_var "SONARR__AUTH__APIKEY" "${cfg.apiKey}"
|
||||
else
|
||||
write_var "SONARR__AUTH__APIKEY" "$(cat ${cfg.apiKeyFile})"
|
||||
fi
|
||||
# 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
|
||||
);
|
||||
|
||||
# Database Configuration
|
||||
write_var "SONARR__POSTGRES__HOST" "$([ -n "${cfg.db.host}" ] && echo "${cfg.db.host}" || cat "${cfg.db.hostFile}")"
|
||||
write_var "SONARR__POSTGRES__USER" "$([ -n "${cfg.db.user}" ] && echo "${cfg.db.user}" || cat "${cfg.db.userFile}")"
|
||||
write_var "SONARR__POSTGRES__PASSWORD" "$(cat ${cfg.db.passwordFile})"
|
||||
# Create services for each enabled instance
|
||||
systemd.services = mapAttrs' (
|
||||
name: instanceCfg:
|
||||
nameValuePair "sonarr-${name}" (
|
||||
mkIf instanceCfg.enable {
|
||||
description = "Sonarr (${name})";
|
||||
after = [
|
||||
"network.target"
|
||||
"nss-lookup.target"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = lib.mkMerge [
|
||||
{
|
||||
SONARR__APP__INSTANCENAME = name;
|
||||
SONARR__APP__THEME = "dark";
|
||||
SONARR__AUTH__METHOD = "External";
|
||||
SONARR__AUTH__REQUIRED = "DisabledForLocalAddresses";
|
||||
SONARR__LOG__DBENABLED = "False";
|
||||
SONARR__LOG__LEVEL = "info";
|
||||
SONARR__SERVER__PORT = toString instanceCfg.port;
|
||||
SONARR__UPDATE__BRANCH = "develop";
|
||||
}
|
||||
(lib.mkIf instanceCfg.db.enable {
|
||||
SONARR__POSTGRES__PORT = toString instanceCfg.db.port;
|
||||
SONARR__POSTGRES__MAINDB = instanceCfg.db.dbname;
|
||||
})
|
||||
instanceCfg.extraEnvVars
|
||||
];
|
||||
|
||||
# Final permissions
|
||||
chmod 600 /run/sonarr/secrets.env
|
||||
chown ${cfg.user}:${cfg.group} /run/sonarr/secrets.env
|
||||
''}";
|
||||
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 = "sonarr-${name}";
|
||||
LogsDirectory = "sonarr-${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.tvDir
|
||||
"/var/log/sonarr-${name}"
|
||||
"/eru/media"
|
||||
];
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_NETLINK"
|
||||
];
|
||||
RestrictNamespaces = [
|
||||
"uts"
|
||||
"ipc"
|
||||
"pid"
|
||||
"user"
|
||||
"cgroup"
|
||||
"net"
|
||||
"mnt"
|
||||
];
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
];
|
||||
})
|
||||
(lib.mkIf instanceCfg.db.enable {
|
||||
ExecStartPre = "+${pkgs.writeShellScript "sonarr-${name}-pre-script" ''
|
||||
mkdir -p /run/sonarr-${name}
|
||||
rm -f /run/sonarr-${name}/secrets.env
|
||||
|
||||
EnvironmentFile = (
|
||||
["-/run/sonarr/secrets.env"]
|
||||
++ lib.optional (cfg.extraEnvVarFile != null && cfg.extraEnvVarFile != "") cfg.extraEnvVarFile
|
||||
);
|
||||
})
|
||||
];
|
||||
};
|
||||
# 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/sonarr-${name}/secrets.env
|
||||
fi
|
||||
}
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [cfg.port];
|
||||
};
|
||||
# API Key (direct value or file)
|
||||
if [ -n "${instanceCfg.apiKey}" ]; then
|
||||
write_var "SONARR__AUTH__APIKEY" "${instanceCfg.apiKey}"
|
||||
else
|
||||
write_var "SONARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})"
|
||||
fi
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
users.users = mkIf (cfg.user == "sonarr") {
|
||||
sonarr = {
|
||||
inherit (cfg) group;
|
||||
isSystemUser = true;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
};
|
||||
# Database Configuration
|
||||
write_var "SONARR__POSTGRES__HOST" "$([ -n "${instanceCfg.db.host}" ] && echo "${instanceCfg.db.host}" || cat "${instanceCfg.db.hostFile}")"
|
||||
write_var "SONARR__POSTGRES__USER" "$([ -n "${instanceCfg.db.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")"
|
||||
write_var "SONARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})"
|
||||
|
||||
# Final permissions
|
||||
chmod 600 /run/sonarr-${name}/secrets.env
|
||||
chown ${instanceCfg.user}:${instanceCfg.group} /run/sonarr-${name}/secrets.env
|
||||
''}";
|
||||
|
||||
EnvironmentFile = (
|
||||
[ "-/run/sonarr-${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 == "sonarr") {
|
||||
sonarr = {
|
||||
inherit (instanceCfg) group;
|
||||
isSystemUser = true;
|
||||
# home = instanceCfg.dataDir;
|
||||
home = "/nahar/sonarr";
|
||||
};
|
||||
};
|
||||
}
|
||||
) cfg.instances
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue