add sonarr service and overlay sonarr/radarr unstable packages.

This commit is contained in:
Joseph Hanson 2025-02-03 16:13:03 -06:00
parent 9041d39a77
commit 1e1d27b85a
Signed by: jahanson
SSH key fingerprint: SHA256:vy6dKBECV522aPAwklFM3ReKAVB086rT3oWwiuiFG7o
7 changed files with 415 additions and 107 deletions

View file

@ -1,4 +1,5 @@
{...}: {
{ ... }:
{
secrets = {
# Minio
"minio" = {
@ -6,107 +7,113 @@
owner = "minio";
group = "minio";
mode = "400";
restartUnits = ["minio.service"];
restartUnits = [ "minio.service" ];
};
# Syncthing
"syncthing/publicCert" = {
sopsFile = ../secrets.sops.yaml;
owner = "jahanson";
mode = "400";
restartUnits = ["syncthing.service"];
restartUnits = [ "syncthing.service" ];
};
"syncthing/privateKey" = {
sopsFile = ../secrets.sops.yaml;
owner = "jahanson";
mode = "400";
restartUnits = ["syncthing.service"];
restartUnits = [ "syncthing.service" ];
};
# Prowlarr
"arr/prowlarr/apiKey" = {
sopsFile = ../secrets.sops.yaml;
owner = "prowlarr";
mode = "400";
restartUnits = ["prowlarr.service"];
restartUnits = [ "prowlarr.service" ];
};
"arr/prowlarr/postgres/dbName" = {
sopsFile = ../secrets.sops.yaml;
owner = "prowlarr";
mode = "400";
restartUnits = ["prowlarr.service"];
restartUnits = [ "prowlarr.service" ];
};
"arr/prowlarr/postgres/user" = {
sopsFile = ../secrets.sops.yaml;
owner = "prowlarr";
mode = "400";
restartUnits = ["prowlarr.service"];
restartUnits = [ "prowlarr.service" ];
};
"arr/prowlarr/postgres/password" = {
sopsFile = ../secrets.sops.yaml;
owner = "prowlarr";
mode = "400";
restartUnits = ["prowlarr.service"];
restartUnits = [ "prowlarr.service" ];
};
"arr/prowlarr/postgres/host" = {
sopsFile = ../secrets.sops.yaml;
owner = "prowlarr";
mode = "400";
restartUnits = ["prowlarr.service"];
restartUnits = [ "prowlarr.service" ];
};
# Sonarr
"arr/sonarr/apiKey" = {
sopsFile = ../secrets.sops.yaml;
owner = "sonarr";
mode = "400";
restartUnits = [ "sonarr.service" ];
};
"arr/sonarr/postgres/dbName" = {
sopsFile = ../secrets.sops.yaml;
owner = "sonarr";
mode = "400";
restartUnits = [ "sonarr.service" ];
};
"arr/sonarr/postgres/user" = {
sopsFile = ../secrets.sops.yaml;
owner = "sonarr";
mode = "400";
restartUnits = [ "sonarr.service" ];
};
"arr/sonarr/postgres/password" = {
sopsFile = ../secrets.sops.yaml;
owner = "sonarr";
mode = "400";
restartUnits = [ "sonarr.service" ];
};
"arr/sonarr/postgres/host" = {
sopsFile = ../secrets.sops.yaml;
owner = "sonarr";
mode = "400";
restartUnits = [ "sonarr.service" ];
};
# # Sonarr
# "arr/sonarr/apiKey" = {
# sopsFile = ../secrets.sops.yaml;
# owner = "sonarr";
# mode = "400";
# restartUnits = [ "sonarr.service" ];
# };
# "arr/sonarr/postgres/dbName" = {
# sopsFile = ../secrets.sops.yaml;
# owner = "sonarr";
# mode = "400";
# restartUnits = [ "sonarr.service" ];
# };
# "arr/sonarr/postgres/user" = {
# sopsFile = ../secrets.sops.yaml;
# owner = "sonarr";
# mode = "400";
# restartUnits = [ "sonarr.service" ];
# };
# "arr/sonarr/postgres/password" = {
# sopsFile = ../secrets.sops.yaml;
# owner = "sonarr";
# mode = "400";
# restartUnits = [ "sonarr.service" ];
# };
# # Radarr
"arr/radarr/apiKey" = {
sopsFile = ../secrets.sops.yaml;
owner = "radarr";
mode = "400";
restartUnits = ["radarr.service"];
restartUnits = [ "radarr.service" ];
};
"arr/radarr/postgres/dbName" = {
sopsFile = ../secrets.sops.yaml;
owner = "radarr";
mode = "400";
restartUnits = ["radarr.service"];
restartUnits = [ "radarr.service" ];
};
"arr/radarr/postgres/user" = {
sopsFile = ../secrets.sops.yaml;
owner = "radarr";
mode = "400";
restartUnits = ["radarr.service"];
restartUnits = [ "radarr.service" ];
};
"arr/radarr/postgres/password" = {
sopsFile = ../secrets.sops.yaml;
owner = "radarr";
mode = "400";
restartUnits = ["radarr.service"];
restartUnits = [ "radarr.service" ];
};
"arr/radarr/postgres/host" = {
sopsFile = ../secrets.sops.yaml;
owner = "radarr";
mode = "400";
restartUnits = ["radarr.service"];
restartUnits = [ "radarr.service" ];
};
};
}

View file

@ -252,6 +252,26 @@ in
passwordFile = config.sops.secrets."arr/radarr/postgres/password".path;
};
};
# Sonarr
sonarr = {
enable = true;
package = pkgs.unstable.sonarr;
dataDir = "/nahar/sonarr";
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;
};
};
# Sabnzbd
sabnzbd = {
enable = true;

View file

@ -16,6 +16,7 @@
./reboot-required-check.nix
./sabnzbd
./sanoid
./sonarr
./syncthing
./zfs-nightly-snap
];

View file

@ -0,0 +1,283 @@
{
config,
pkgs,
lib,
utils,
...
}:
with lib;
let
cfg = config.mySystem.services.sonarr;
dbOptions = {
options = {
enable = mkEnableOption "Database configuration for sonarr";
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/sonarr_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 = "sonarr";
description = "Direct database user (mutually exclusive with userFile)";
};
userFile = mkOption {
type = types.str;
default = "";
example = "/run/secrets/sonarr_db_user";
description = "Database user from a file (mutually exclusive with user)";
};
passwordFile = mkOption {
type = types.path;
default = "/run/secrets/sonarr_db_password";
description = "Database password from a file (always used)";
};
dbname = mkOption {
type = types.str;
default = "sonarr_main";
description = "Database name";
};
};
};
in
{
options.mySystem.services.sonarr = {
enable = mkEnableOption "Sonarr";
package = mkPackageOption pkgs "Sonarr" { };
user = mkOption {
type = types.str;
default = "sonarr";
description = "User account under which sonarr runs.";
};
group = mkOption {
type = types.str;
default = "sonarr";
description = "Group under which sonarr runs.";
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/sonarr";
description = "Storage directory for sonarr data";
};
tvDir = mkOption {
type = types.path;
default = "/mnt/media/tv";
description = "Directory where tv shows are stored";
};
port = mkOption {
type = types.port;
default = 8989;
description = "Port for sonarr web interface";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Open firewall ports for sonarr";
};
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 sonarr (mutually exclusive with apiKeyFile)";
};
apiKeyFile = mkOption {
type = types.path;
default = "/run/secrets/sonarr_api_key";
description = "API key for sonarr 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 = "sonarr"; # or userFile
passwordFile = "/run/secrets/sonarr_db_password";
dbname = "sonarr_main";
};
description = "Database settings for sonarr.";
};
};
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;
})
];
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"
];
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = [
"uts"
"ipc"
"pid"
"user"
"cgroup"
"net"
];
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
}
# 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
# 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})"
# Final permissions
chmod 600 /run/sonarr/secrets.env
chown ${cfg.user}:${cfg.group} /run/sonarr/secrets.env
''}";
EnvironmentFile = [ "-/run/sonarr/secrets.env" ];
})
];
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
users.groups.${cfg.group} = { };
users.users = mkIf (cfg.user == "sonarr") {
sonarr = {
inherit (cfg) group;
isSystemUser = true;
home = cfg.dataDir;
};
};
};
}

View file

@ -12,11 +12,9 @@
openssl,
nixosTests,
zlib,
}: let
os =
if stdenv.hostPlatform.isDarwin
then "osx"
else "linux";
}:
let
os = if stdenv.hostPlatform.isDarwin then "osx" else "linux";
arch =
{
x86_64-linux = "x64";
@ -24,8 +22,7 @@
x86_64-darwin = "x64";
aarch64-darwin = "arm64";
}
."${stdenv.hostPlatform.system}"
or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
."${stdenv.hostPlatform.system}" or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
hash =
{
@ -36,7 +33,7 @@
}
."${arch}-${os}_hash";
in
stdenv.mkDerivation rec {
stdenv.mkDerivation rec {
pname = "radarr";
version = "5.17.2.9580";
@ -45,7 +42,7 @@ in
sha256 = hash;
};
nativeBuildInputs = [makeWrapper];
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
@ -75,5 +72,5 @@ in
tests.smoke-test = nixosTests.radarr;
};
mainProgram = "Radarr";
}
meta.mainProgram = "Radarr";
}

View file

@ -12,11 +12,9 @@
openssl,
nixosTests,
zlib,
}: let
os =
if stdenv.hostPlatform.isDarwin
then "osx"
else "linux";
}:
let
os = if stdenv.hostPlatform.isDarwin then "osx" else "linux";
arch =
{
x86_64-linux = "x64";
@ -24,8 +22,7 @@
x86_64-darwin = "x64";
aarch64-darwin = "arm64";
}
."${stdenv.hostPlatform.system}"
or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
."${stdenv.hostPlatform.system}" or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
hash =
{
@ -36,7 +33,7 @@
}
."${arch}-${os}_hash";
in
stdenv.mkDerivation rec {
stdenv.mkDerivation rec {
pname = "sonarr";
version = "4.0.12.2823";
@ -45,7 +42,7 @@ in
sha256 = hash;
};
nativeBuildInputs = [makeWrapper];
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
@ -73,5 +70,5 @@ in
tests.smoke-test = nixosTests.radarr;
};
mainProgram = "Sonarr";
}
meta.mainProgram = "Sonarr";
}

View file

@ -1,10 +1,12 @@
{inputs, ...}: let
{ inputs, ... }:
let
# smartmontoolsOverlay = import ./smartmontools { };
# vivaldiOverlay = self: super: { vivaldi = super.callPackage ./vivaldi { }; };
coderOverlay = self: super: {coder = super.callPackage ./coder {};};
modsOverlay = self: super: {mods = super.callPackage ./charm-mods {};};
termiusOverlay = self: super: {termius = super.callPackage ./termius {};};
in {
coderOverlay = self: super: { coder = super.callPackage ./coder { }; };
modsOverlay = self: super: { mods = super.callPackage ./charm-mods { }; };
termiusOverlay = self: super: { termius = super.callPackage ./termius { }; };
in
{
# smartmontools = smartmontoolsOverlay;
# vivaldi = vivaldiOverlay;
coder = coderOverlay;
@ -25,16 +27,17 @@ in {
// {
# Add talosctl to the unstable set
talosctl = final.unstable.callPackage ./talosctl {
inherit
(final.unstable)
inherit (final.unstable)
lib
buildGoModule
fetchFromGitHub
installShellFiles
;
};
xpipe = final.unstable.callPackage ./xpipe/ptb.nix {};
prowlarr = final.unstable.callPackage ./arr/prowlarr.nix {};
xpipe = final.unstable.callPackage ./xpipe/ptb.nix { };
prowlarr = final.unstable.callPackage ./arr/prowlarr.nix { };
radarr = final.unstable.callPackage ./arr/radarr.nix { };
sonarr = final.unstable.callPackage ./arr/sonarr.nix { };
};
};
}