From 7590d9af07e8d0effdd0b709d450731ca624fda3 Mon Sep 17 00:00:00 2001 From: ooks-io Date: Sat, 23 Nov 2024 14:58:05 +1100 Subject: [PATCH] server: initial media server config --- modules/nixos/server/options.nix | 2 +- modules/nixos/server/services/default.nix | 1 + .../server/services/media-server/default.nix | 30 +++ .../media-server/file-permissions.nix | 42 +++ .../server/services/media-server/jellyfin.nix | 20 ++ .../server/services/media-server/options.nix | 242 ++++++++++++++++++ .../server/services/media-server/plex.nix | 20 ++ .../server/services/media-server/prowlarr.nix | 48 ++++ .../server/services/media-server/radarr.nix | 20 ++ .../server/services/media-server/sonarr.nix | 20 ++ .../services/media-server/transmission.nix | 77 ++++++ .../server/services/media-server/users.nix | 23 ++ outputs/hosts/servers.nix | 3 +- 13 files changed, 546 insertions(+), 2 deletions(-) create mode 100644 modules/nixos/server/services/media-server/default.nix create mode 100644 modules/nixos/server/services/media-server/file-permissions.nix create mode 100644 modules/nixos/server/services/media-server/jellyfin.nix create mode 100644 modules/nixos/server/services/media-server/options.nix create mode 100644 modules/nixos/server/services/media-server/plex.nix create mode 100644 modules/nixos/server/services/media-server/prowlarr.nix create mode 100644 modules/nixos/server/services/media-server/radarr.nix create mode 100644 modules/nixos/server/services/media-server/sonarr.nix create mode 100644 modules/nixos/server/services/media-server/transmission.nix create mode 100644 modules/nixos/server/services/media-server/users.nix diff --git a/modules/nixos/server/options.nix b/modules/nixos/server/options.nix index 2b437fd..4de3252 100644 --- a/modules/nixos/server/options.nix +++ b/modules/nixos/server/options.nix @@ -14,7 +14,7 @@ in { description = "The server profile the host will use as a base"; }; services = mkOption { - type = listOf (enum ["website" "forgejo"]); + type = listOf (enum ["media-server" "website" "forgejo"]); default = []; description = "List of services the server will host"; }; diff --git a/modules/nixos/server/services/default.nix b/modules/nixos/server/services/default.nix index 6d19766..7685461 100644 --- a/modules/nixos/server/services/default.nix +++ b/modules/nixos/server/services/default.nix @@ -2,5 +2,6 @@ imports = [ ./website ./forgejo + ./media-server ]; } diff --git a/modules/nixos/server/services/media-server/default.nix b/modules/nixos/server/services/media-server/default.nix new file mode 100644 index 0000000..a5bbd81 --- /dev/null +++ b/modules/nixos/server/services/media-server/default.nix @@ -0,0 +1,30 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mkIf elem; + inherit (config.ooknet.server) services; +in { + imports = [ + ./plex.nix + ./users.nix + ./options.nix + ./jellyfin.nix + ./transmission.nix + ./file-permissions.nix + ]; + + # short cut for enabling all media-server modules + config = mkIf (elem "media-server" services) { + ooknet.server.media-server = { + enable = true; + jellyfin.enable = true; + plex.enable = true; + transmission.enable = true; + radarr.enable = true; + sonarr.enable = true; + prowlarr.enable = true; + }; + }; +} diff --git a/modules/nixos/server/services/media-server/file-permissions.nix b/modules/nixos/server/services/media-server/file-permissions.nix new file mode 100644 index 0000000..b791728 --- /dev/null +++ b/modules/nixos/server/services/media-server/file-permissions.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage groups users; + + contentPermissions = { + group = groups.media; + user = "root"; + mode = "0775"; + }; + + downloadPermissions = { + group = groups.media; + user = users.downloader; + mode = "0775"; + }; +in { + config = mkIf media-server.enable { + systemd.tmpfiles.settings = { + content-dirs = { + "${storage.content.root}"."d" = contentPermissions; + "${storage.content.movies}"."d" = contentPermissions; + "${storage.content.tv}"."d" = contentPermissions; + "${storage.content.music}"."d" = contentPermissions; + "${storage.content.books}"."d" = contentPermissions; + }; + download-dirs = { + "${storage.downloads.root}"."d" = downloadPermissions; + "${storage.downloads.incomplete}"."d" = downloadPermissions; + "${storage.downloads.watch}"."d" = downloadPermissions; + "${storage.downloads.manual}"."d" = downloadPermissions; + "${storage.downloads.radarr}"."d" = downloadPermissions; + "${storage.downloads.sonarr}"."d" = downloadPermissions; + "${storage.downloads.readarr}"."d" = downloadPermissions; + }; + }; + }; +} diff --git a/modules/nixos/server/services/media-server/jellyfin.nix b/modules/nixos/server/services/media-server/jellyfin.nix new file mode 100644 index 0000000..8caf355 --- /dev/null +++ b/modules/nixos/server/services/media-server/jellyfin.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage groups users domain proxy; +in { + config = mkIf media-server.jellyfin.enable { + services.jellyfin = { + enable = true; + user = users.streamer; + group = groups.media; + dataDir = storage.state.jellyfin; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.jellyfin}".extraConfig = proxy.jellyfin; + }; +} diff --git a/modules/nixos/server/services/media-server/options.nix b/modules/nixos/server/services/media-server/options.nix new file mode 100644 index 0000000..a9c89b3 --- /dev/null +++ b/modules/nixos/server/services/media-server/options.nix @@ -0,0 +1,242 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mkOption mkEnableOption; + inherit (lib.types) path port str lines; + inherit (config.ooknet) server; + cfg = server.media-server; + + mkSubdomain = name: + mkOption { + type = str; + default = "${name}.${server.domain}"; + }; + + mkProxy = port: '' + encode zstd gzip + reverse_proxy localhost:${toString port} { + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + } + ''; +in { + options.ooknet.server.media-server = { + enable = mkEnableOption "Enable media server functionality"; + + jellyfin.enable = mkEnableOption "Enable the Jellyfin module"; + plex.enable = mkEnableOption "Enable Plex module"; + transmission.enable = mkEnableOption "Enable Transmission module"; + radarr.enable = mkEnableOption "Enable Radarr module"; + sonarr.enable = mkEnableOption "Enable Sonarr module"; + prowlarr.enable = mkEnableOption "Enable Sonarr module"; + + storage = { + mediaRoot = mkOption { + type = path; + default = "/media"; + description = "Root directory for all media-related storage"; + }; + + content = { + root = mkOption { + type = path; + default = "${cfg.storage.mediaRoot}/content"; + description = "Root directory for media content"; + }; + movies = mkOption { + type = path; + default = "${cfg.storage.content.root}/movies"; + }; + tv = mkOption { + type = path; + default = "${cfg.storage.content.root}/tv"; + }; + music = mkOption { + type = path; + default = "${cfg.storage.content.root}/music"; + }; + books = mkOption { + type = path; + default = "${cfg.storage.content.root}/books"; + }; + }; + + downloads = { + root = mkOption { + type = path; + default = "${cfg.storage.mediaRoot}/downloads"; + }; + incomplete = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/.incomplete"; + }; + watch = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/.watch"; + }; + manual = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/manual"; + }; + radarr = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/radarr"; + }; + sonarr = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/sonarr"; + }; + readarr = mkOption { + type = path; + default = "${cfg.storage.downloads.root}/readarr"; + }; + }; + + state = { + root = mkOption { + type = path; + default = "/var/lib"; + description = "Root directory for service state"; + }; + jellyfin = mkOption { + type = path; + default = "${cfg.storage.state.root}/jellyfin"; + }; + plex = mkOption { + type = path; + default = "${cfg.storage.state.root}/plex"; + }; + sonarr = mkOption { + type = path; + default = "${cfg.storage.state.root}/sonarr"; + }; + radarr = mkOption { + type = path; + default = "${cfg.storage.state.root}/radarr"; + }; + transmission = mkOption { + type = path; + default = "${cfg.storage.state.root}/transmission"; + }; + }; + }; + + groups = { + media = mkOption { + type = str; + default = "media"; + }; + prowlarr = mkOption { + type = str; + default = "prowlarr"; + }; + radarr = mkOption { + type = str; + default = "radarr"; + }; + }; + + users = { + jellyfin = mkOption { + type = str; + default = "jellyfin"; + }; + plex = mkOption { + type = str; + default = "plex"; + }; + sonarr = mkOption { + type = str; + default = "sonarr"; + }; + transmission = mkOption { + type = str; + default = "transmission"; + }; + prowlarr = mkOption { + type = str; + default = "prowlarr"; + }; + downloader = mkOption { + type = str; + default = "downloader"; + }; + streamer = mkOption { + type = str; + default = "streamer"; + }; + }; + + ports = { + jellyfin = mkOption { + type = port; + default = 8096; + }; + plex = mkOption { + type = port; + default = 32400; + }; + transmission = { + web = mkOption { + type = port; + default = 9091; + }; + peer = mkOption { + type = port; + default = 50000; + }; + }; + sonarr = mkOption { + type = port; + default = 8989; + }; + radarr = mkOption { + type = port; + default = 7878; + }; + prowlarr = mkOption { + type = port; + default = 9696; + }; + }; + + domain = { + jellyfin = mkSubdomain "jellyfin"; + plex = mkSubdomain "plex"; + transmission = mkSubdomain "transmission"; + sonarr = mkSubdomain "sonarr"; + radarr = mkSubdomain "radarr"; + prowlarr = mkSubdomain "prowlarr"; + }; + + proxy = { + jellyfin = mkOption { + type = lines; + default = mkProxy cfg.ports.jellyfin; + }; + plex = mkOption { + type = lines; + default = mkProxy cfg.ports.plex; + }; + sonarr = mkOption { + type = lines; + default = mkProxy cfg.ports.sonarr; + }; + radarr = mkOption { + type = lines; + default = mkProxy cfg.ports.radarr; + }; + prowlarr = mkOption { + type = lines; + default = mkProxy cfg.ports.prowlarr; + }; + transmission = mkOption { + type = lines; + default = mkProxy cfg.ports.transmission.web; + }; + }; + }; +} diff --git a/modules/nixos/server/services/media-server/plex.nix b/modules/nixos/server/services/media-server/plex.nix new file mode 100644 index 0000000..5c4bfb6 --- /dev/null +++ b/modules/nixos/server/services/media-server/plex.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) groups users storage domain proxy; +in { + config = mkIf media-server.plex.enable { + services.plex = { + enable = true; + user = users.streamer; + group = groups.media; + dataDir = storage.state.plex; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.plex}".extraConfig = proxy.plex; + }; +} diff --git a/modules/nixos/server/services/media-server/prowlarr.nix b/modules/nixos/server/services/media-server/prowlarr.nix new file mode 100644 index 0000000..b4efd5c --- /dev/null +++ b/modules/nixos/server/services/media-server/prowlarr.nix @@ -0,0 +1,48 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib) mkIf getExe; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage users groups domain proxy; +in { + config = mkIf media-server.prowlarr.enable { + # we dont use the nixpkgs prowlarr service module because it lacks the option to + # declare dataDir, user and group. + + # setup user + users.users.prowlarr = { + group = groups.prowlarr; + home = storage.state.prowlarr; + }; + users.groups.prowlarr = {}; + + # basic systemd service + systemd = { + services.prowlarr = { + description = "Prowlarr"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "simple"; + User = users.prowlarr; + group = groups.prowlarr; + ExecStart = "${getExe pkgs.prowlarr} -nobrowser -data=${storage.state.prowlarr}"; + Restart = "on-failure"; + }; + }; + tmpfiles.settings.prowlarrDirs = { + "${storage.state.prowlarr}"."d" = { + mode = "700"; + user = users.prowlarr; + group = groups.prowlarr; + }; + }; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.prowlarr}".extraConfig = proxy.prowlarr; + }; +} diff --git a/modules/nixos/server/services/media-server/radarr.nix b/modules/nixos/server/services/media-server/radarr.nix new file mode 100644 index 0000000..beb5784 --- /dev/null +++ b/modules/nixos/server/services/media-server/radarr.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage users groups domain proxy; +in { + config = mkIf media-server.radarr.enable { + services.radarr = { + enable = true; + user = users.radarr; + group = groups.radarr; + dataDir = storage.state.radaar; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.radarr}".extraConfig = proxy.radarr; + }; +} diff --git a/modules/nixos/server/services/media-server/sonarr.nix b/modules/nixos/server/services/media-server/sonarr.nix new file mode 100644 index 0000000..be42fde --- /dev/null +++ b/modules/nixos/server/services/media-server/sonarr.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage users groups domain proxy; +in { + config = mkIf media-server.sonarr.enable { + services.sonarr = { + enable = true; + user = users.sonarr; + group = groups.sonarr; + dataDir = storage.state.sonarr; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.sonarr}".extraConfig = proxy.sonarr; + }; +} diff --git a/modules/nixos/server/services/media-server/transmission.nix b/modules/nixos/server/services/media-server/transmission.nix new file mode 100644 index 0000000..7999a19 --- /dev/null +++ b/modules/nixos/server/services/media-server/transmission.nix @@ -0,0 +1,77 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib) mkIf; + inherit (builtins) concatStringsSep; + inherit (config.ooknet.server) media-server; + inherit (config.ooknet.server.media-server) storage ports users groups domain proxy; +in { + config = mkIf media-server.transmission.enable { + services.transmission = { + enable = true; + package = pkgs.transmission_4; + + # systemd service permissions + user = users.downloader; + group = groups.media; + + # location of transmission config dir + home = storage.state.transmission; + + # web ui + webHome = pkgs.flood-for-transmission; + + # additional configurations + # see + settings = { + # enable in completed directory + # this is where files will be placed while still being downloaded + incomplete-dir-enabled = true; + + # enable the watch directory + # this will look for any new torrent files and start downloading them + watch-dir-enabled = true; + + # location of the main download directories + download-dir = storage.downloads.root; + incomplete-dir = storage.downloads.incomplete; + watch-dir = storage.downloads.watch; + + # rpc settings + # rpc is how we connect to the service remotely + rpc-port = ports.transmission.web; + + # what ip addresses are allowed to connect through rpc + rpc-whitelist-enabled = true; + rpc-whitelist = concatStringsSep "," [ + # localhost + "127.0.0.1" + # generic home networks + "192.168.*" + "10.*" + ]; + + # basic anti bruteforce protection + anti-brute-force-enabled = true; + + # how many authentication attempts can be made before the rpc server will deny any further + # authentication attempts. + anti-brute-force-threshold = 10; + + peer-port = ports.transmission.peer; + port-forwarding-enabled = false; + + # private trackers usually require disabling these + utp-enabled = false; + dht-enabled = false; + pex-enabled = false; + lpd-enabled = false; + }; + }; + ooknet.server.webserver.caddy.enable = true; + services.caddy.virtualHosts."${domain.transmission}".extraConfig = proxy.transmission; + }; +} diff --git a/modules/nixos/server/services/media-server/users.nix b/modules/nixos/server/services/media-server/users.nix new file mode 100644 index 0000000..6d1bcfd --- /dev/null +++ b/modules/nixos/server/services/media-server/users.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + ... +}: let + inherit (lib) elem mkIf; + inherit (config.ooknet.server) services; +in { + config = mkIf (elem "media-server" services) { + users = { + groups = { + downloader = {}; + media = {}; + }; + users = { + downloader = { + isSystemUser = true; + group = "downloader"; + }; + }; + }; + }; +} diff --git a/outputs/hosts/servers.nix b/outputs/hosts/servers.nix index a05b3cf..1902512 100644 --- a/outputs/hosts/servers.nix +++ b/outputs/hosts/servers.nix @@ -19,8 +19,9 @@ in { inherit withSystem; system = "x86_64-linux"; hostname = "ooksmedia"; + domain = "ooknet.org"; type = "desktop"; - services = []; + services = ["media-server"]; }; }; }