ookflix: initial ookflix commit

This commit is contained in:
ooks-io 2024-12-01 18:35:22 +11:00
parent 9459f9e1f6
commit 2d437acfbb
12 changed files with 482 additions and 6 deletions

View file

@ -14,7 +14,7 @@ in {
description = "The server profile the host will use as a base";
};
services = mkOption {
type = listOf (enum ["media-server" "website" "forgejo"]);
type = listOf (enum ["media-server" "website" "forgejo" "ookflix"]);
default = [];
description = "List of services the server will host";
};

View file

@ -3,5 +3,6 @@
./website
./forgejo
./media-server
./ookflix
];
}

View file

@ -0,0 +1,24 @@
{
lib,
config,
...
}: let
inherit (lib) elem mkIf;
inherit (config.ooknet.server) services;
in {
imports = [
./jellyfin.nix
./plex.nix
./options.nix
];
config = mkIf (elem "ookflix" services) {
ooknet.server.ookflix = {
gpuAcceleration.enable = true;
services = {
jellyfin.enable = true;
plex.enable = true;
};
};
};
}

View file

@ -0,0 +1,60 @@
{
config,
lib,
ook,
...
}: let
ookflixLib = import ./lib.nix {inherit lib config;};
inherit (ookflixLib) mkServiceStateDir mkServiceUser;
inherit (lib) mkIf optionalAttrs;
inherit (ook.lib.container) mkContainerLabel mkContainerEnvironment mkContainerPort;
inherit (config.ooknet.server.ookflix) volumes services groups gpuAcceleration;
inherit (config.ooknet.server.ookflix.services) jellyfin;
in {
config = mkIf services.jellyfin.enable {
hardware.nvidia-container-toolkit.enable = gpuAcceleration.enable && gpuAcceleration.type == "nvidia";
users = mkServiceUser jellyfin.user.name;
systemd.tmpfiles = mkServiceStateDir "jellyfin" jellyfin.stateDir;
virtualisation.oci-containers.containers = {
# media streaming server
# docs: <https://docs.linuxserver.io/images/docker-jellyfin/>
jellyfin = {
image = "lscr.io/linuxserver/jellyfin:latest";
autoStart = true;
hostname = "jellyfin";
ports = [(mkContainerPort jellyfin.port)];
volumes = [
"${volumes.media.movies}:/data/movies"
"${volumes.media.tv}:/data/tv"
"${jellyfin.stateDir}:/config"
];
labels = mkContainerLabel {
name = "jellyfin";
inherit (jellyfin) port domain;
homepage = {
group = "media";
description = "media-server streamer";
};
};
extraOptions = optionalAttrs gpuAcceleration.enable (
if gpuAcceleration.type == "nvidia"
then [
"--runtime=nvidia"
]
else if gpuAcceleration.type == "intel" || "amd"
then [
"--device=/dev/dri:/dev/dri"
]
else []
);
environment =
mkContainerEnvironment jellyfin.user.id groups.media.id
// {JELLYFIN_PublishedServerUrl = jellyfin.domain;}
// optionalAttrs (gpuAcceleration.enable && gpuAcceleration.type == "nvidia") {
NVIDIA_VISIBLE_DEVICES = "all";
};
};
};
};
}

View file

@ -0,0 +1,32 @@
{
config,
lib,
ook,
...
}: let
inherit (lib) mkIf;
inherit (ook.lib.container) mkContainerLabel mkContainerEnvironment mkContainerPort;
inherit (config.ooknet.server.ookflix) storage groups;
inherit (config.ooknet.server.ookflix.services) jellyseer;
in {
config = mkIf jellyseer.enable {
# media requesting for jellyfin
jellyseer = {
image = "fallenbagel/jellyseerr:latest";
autoStart = true;
hostname = "jellyseer";
ports = [(mkContainerPort jellyseer.port)];
volumes = ["${storage.state.jellyseer}:/config"];
extraOptions = ["--network" "host"];
labels = mkContainerLabel {
name = "jellyseer";
inherit (jellyseer) domain port;
homepage = {
group = "media";
description = "media-server requesting";
};
};
environment = mkContainerEnvironment jellyseer.user.id groups.media.id;
};
};
}

View file

@ -0,0 +1,111 @@
{
lib,
config,
...
}: let
inherit (lib) mkOption mkEnableOption elem assertMsg;
inherit (builtins) attrValues;
inherit (lib.types) int path port str;
inherit (config.ooknet) server;
cfg = server.ookflix;
mkSubdomainOption = name: description: example:
mkOption {
type = str;
default = "${name}.${server.domain}";
inherit description example;
};
# check config.ids for static uid/gid based on service name, if available use that, if not, use fallback.
# check fallback doesnt conflict with existing static id
getId = idType: name: fallback: let
allNixosIds = attrValues config.ids.${idType};
fallbackConflict = elem fallback allNixosIds;
in
mkOption {
type = int;
default =
config.ids.${idType}.${name}
or (
assert assertMsg (!fallbackConflict)
"Fallback ${idType} ${toString fallback} for ${name} conflicts with NixOS static allocation"; fallback
);
description = "${idType} of ${name} container";
example = 224;
};
mkUserOption = name: fallback: {
name = mkOption {
type = str;
default = name;
description = "Name of ${name} container user";
example = "${name}";
};
id = getId "uids" name fallback;
};
mkGroupOption = name: fallback: {
name = mkOption {
type = str;
default = name;
};
id = getId "gids" name fallback;
};
mkPortOption = value: description: example:
mkOption {
type = port;
default = value;
inherit description example;
};
mkVolumeOption = type: pathValue:
mkOption {
type = path;
default =
if type == "state"
then "${cfg.volumes.state.root}/${pathValue}"
else if type == "media"
then "${cfg.volumes.media.root}/${pathValue}"
else if type == "downloads"
then "${cfg.volumes.downloads.root}/${pathValue}"
else if type == "root"
then pathValue
else throw "Invalid VolumeOption type: ${type}. Must be one of 'state' 'media' 'downloads' 'root'";
};
mkServiceOptions = name: {
port,
gid,
uid,
...
} @ args: {
enable = mkEnableOption "Enable ${name} container";
port = mkPortOption args.port "Port for ${name} container." 80;
domain = mkSubdomainOption name "Domain for ${name} container." "${name}.mydomain.com";
stateDir = mkVolumeOption "state" name;
user = mkUserOption name args.uid;
group = mkGroupOption name args.gid;
};
mkServiceUser = service: {
users.${service} = {
isSystemUser = true;
uid = cfg.services.${service}.user.id;
name = service;
group = service;
};
groups.${service} = {
gid = cfg.services.${service}.group.id;
name = service;
};
};
mkServiceStateDir = service: dir: {
settings."${service}StateDir".${dir}."d" = {
mode = "0700";
user = cfg.services.${service}.user.name;
group = cfg.services.${service}.group.name;
};
};
in {
inherit mkServiceOptions mkServiceStateDir mkServiceUser mkUserOption mkPortOption mkGroupOption mkVolumeOption mkSubdomainOption;
}

View file

@ -0,0 +1,92 @@
{
lib,
config,
...
}: let
ookflixLib = import ./lib.nix {inherit lib config;};
inherit (ookflixLib) mkVolumeOption mkGroupOption mkServiceOptions;
inherit (lib) mkOption mkEnableOption;
inherit (lib.types) enum;
inherit (config.ooknet) server;
cfg = server.ookflix;
in {
options.ooknet.server.ookflix = {
enable = mkEnableOption "Enable ookflix a container based media server module";
gpuAcceleration = {
enable = mkEnableOption "Enable GPU acceleration for video streamers";
type = mkOption {
type = enum ["nvidia" "intel" "amd"];
default = config.ooknet.hardware.gpu.type;
description = ''
What GPU type to use for GPU acceleration.
Defaults to system GPU type (ooknet.hardware.gpu.type)
'';
};
};
volumes = {
state.root = mkVolumeOption "root" "/var/lib/ookflix";
content.root = mkVolumeOption "root" "/jellyfin";
downloads = {
root = mkVolumeOption "${cfg.content.root}/downloads";
incomplete = mkVolumeOption "downloads" "incomplete";
complete = mkVolumeOption "downloads" "complete";
watch = mkVolumeOption "downloads" "watch";
};
media = {
root = mkVolumeOption "root" "${cfg.volumes.content.root}/media";
movies = mkVolumeOption "media" "movies";
tv = mkVolumeOption "media" "tv";
};
};
# Shared groups
groups = {
media = mkGroupOption "media" 992;
downloader = mkGroupOption "downloader" 981;
};
services = {
jellyfin = mkServiceOptions "jellyfin" {
port = 8096;
uid = 994;
gid = 994;
};
plex = mkServiceOptions "plex" {
port = 32400;
uid = 195;
gid = 195;
};
sonarr = mkServiceOptions "sonarr" {
port = 8989;
uid = 274;
gid = 274;
};
radarr = mkServiceOptions "radarr" {
port = 7878;
uid = 275;
gid = 275;
};
prowlarr = mkServiceOptions "prowlarr" {
port = 9696;
uid = 982;
gid = 987;
};
transmission = mkServiceOptions "transmission" {
port = 9091;
uid = 70;
gid = 70;
};
jellyseer = mkServiceOptions "jellyseer" {
port = 5055;
uid = 345;
gid = 345;
};
tautulli = mkServiceOptions "tautulli" {
port = 8181;
uid = 355;
gid = 355;
};
};
};
}

View file

@ -0,0 +1,66 @@
{
config,
lib,
ook,
...
}: let
ookflixLib = import ./lib.nix {inherit lib config;};
inherit (ookflixLib) mkServiceUser mkServiceStateDir;
inherit (lib) mkIf optionalAttrs;
inherit (ook.lib.container) mkContainerLabel mkContainerEnvironment mkContainerPort;
inherit (config.ooknet.server.ookflix) gpuAcceleration services volumes groups;
inherit (config.ooknet.server.ookflix.services) plex;
in {
config = mkIf plex.enable {
# not sure if this is needed for podman
hardware.nvidia-container-toolkit.enable = gpuAcceleration.enable && gpuAcceleration.type == "nvidia";
# users/group/directories configuration, see lib.nix
users = mkServiceUser plex.user.name;
systemd.tmpfiles = mkServiceStateDir "plex" plex.stateDir;
# container configuration
virtualisation.oci-containers.containers = {
# media streaming server
plex = {
image = "lscr.io/linuxserver/plex:latest";
autoStart = true;
hostname = "plex";
ports = [(mkContainerPort plex.port)];
volumes = [
"${volumes.media.movies}:/data/movies"
"${volumes.media.tv}:/data/tv"
"${plex.stateDir}:/config"
];
labels = mkContainerLabel {
name = "plex";
inherit (plex) domain port;
homepage = {
group = "media";
description = "media-server streamer";
};
};
extraOptions = optionalAttrs gpuAcceleration.enable (
if gpuAcceleration.type == "nvidia"
then [
"--runtime=nvidia"
]
else if gpuAcceleration.type == "intel"
then [
"--device=/dev/dri:/dev/dri"
]
else if gpuAcceleration.type == "amd"
then [
"--device=/dev/dri:/dev/dri"
]
else []
);
environment =
mkContainerEnvironment plex.user.id groups.media.id
// optionalAttrs (gpuAcceleration.enable && gpuAcceleration.type == "nvidia") {
NVIDIA_VISIBLE_DEVICES = "all";
};
};
};
};
}

View file

@ -0,0 +1,37 @@
{
config,
lib,
ook,
...
}: let
ookflixLib = import ./lib.nix {inherit lib config;};
inherit (ookflixLib) mkServiceUser mkServiceStateDir;
inherit (lib) mkIf;
inherit (ook.lib.container) mkContainerLabel mkContainerEnvironment mkContainerPort;
inherit (config.ooknet.server.ookflix) groups;
inherit (config.ooknet.server.services) tautulli;
in {
config = mkIf tautulli.enable {
users = mkServiceUser tautulli.user.name;
systemd.tmpfiles = mkServiceStateDir "tautulli" tautulli.stateDir;
virtualisation.oci-containers.containers = {
# plex monitoring service
tautulli = {
image = "lscr.io/linuxserver/tautulli:latest";
autoStart = true;
hostname = "tautulli";
ports = [(mkContainerPort tautulli.port)];
volumes = ["${tautulli.stateDir}:/config"];
labels = mkContainerLabel {
name = "tautulli";
inherit (tautulli) port domain;
homepage = {
group = "monitoring";
description = "media-server monitoring";
};
};
environment = mkContainerEnvironment tautulli.user.id groups.media.id;
};
};
};
}

View file

@ -0,0 +1,38 @@
{
lib,
config,
...
}: let
inherit (lib) mkIf mkMerge;
inherit (builtins) mapAttrs;
inherit (config.ooknet.server) ookflix;
inherit (config.ooknet.server.ookflix) services storage users groups;
mkServiceUser = name: user: {
isSystemUser = true;
group = groups.${name}.name;
uid = users.${name}.id;
home = storage.state.${name};
};
generateUsers = mapAttrs mkServiceUser users;
in {
config = mkIf ookflix.enable {
users = {
users = mkMerge [
# media service users
(mkIf services.jellyfin.enable {
${users.jellyfin.name} = mkServiceUser users.jellyfin.name groups.media.name;
})
(mkIf services.plex.enable {
${users.plex.name} = mkServiceUser users.plex.name groups.media.name;
})
(mkIf (services.jellyfin.enable || services.jellyseer.enable) {
${users.jellyseer.name} = mkServiceUser users.jellyseer.name groups.media.name;
})
];
groups = {
};
};
};
}

View file

@ -1,4 +1,8 @@
{lib, ...}: let
{
lib,
config,
...
}: let
inherit (builtins) isBool;
inherit (lib) toUpper optionalAttrs mapAttrs' nameValuePair;
@ -33,7 +37,7 @@
in
commonLabels // (processWidget widget);
mkContainerLabels = {name, ...} @ args: let
mkContainerLabel = {name, ...} @ args: let
homepage = args.homepage or {};
baseWidget = homepage.widget or {};
in
@ -52,16 +56,25 @@
# homepage labels
// (optionalAttrs (args ? homepage) (mkHomepageLabels {
inherit name;
inherit (args) domain;
domain = "https://${args.domain}";
group = args.homepage.group or name;
widget =
baseWidget
// {
type = name;
url = args.domain;
url = "https://${args.domain}";
key = "{{HOMEPAGE_FILE_${toUpper name}}}";
};
}));
mkContainerEnvironment = user: group: {
PUID = toString user;
PGID = toString group;
# TODO: I dont want to hard code this
TZ = "Antarctica/Macquarie";
};
mkContainerPort = port: "${toString port}:${toString port}";
in {
inherit mkContainerLabels;
inherit mkContainerLabel mkContainerEnvironment mkContainerPort;
}

View file

@ -2,6 +2,7 @@
lib,
self,
inputs,
config,
...
}: let
# my scuffed lib
@ -9,6 +10,7 @@
builders = import ./builders.nix {inherit self lib inputs;};
mkNeovim = import ./mkNeovim.nix {inherit inputs;};
math = import ./math.nix {inherit lib;};
container = import ./containers.nix {inherit lib config;};
color = let
check = import ./color/check.nix {inherit lib;};
types = import ./color/types.nix {