server: initial media server config

This commit is contained in:
ooks-io 2024-11-23 14:58:05 +11:00
parent 05274f898b
commit 7590d9af07
13 changed files with 546 additions and 2 deletions

View file

@ -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";
};

View file

@ -2,5 +2,6 @@
imports = [
./website
./forgejo
./media-server
];
}

View file

@ -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;
};
};
}

View file

@ -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;
};
};
};
}

View file

@ -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;
};
}

View file

@ -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;
};
};
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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 <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md>
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;
};
}

View file

@ -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";
};
};
};
};
}