feat: Jellyfin media server

This commit is contained in:
Madeleine Sydney
2025-02-02 14:51:04 -07:00
parent 41029d72a0
commit 1b0d348533
6 changed files with 148 additions and 37 deletions

View File

@@ -219,6 +219,8 @@ CLOSED: [2025-02-02 Sun 14:06]
**** TODO [[https://github.com/promethial/paxedit][Paxedit]] **** TODO [[https://github.com/promethial/paxedit][Paxedit]]
Seems old and broken?
**** TODO Evil stuff **** TODO Evil stuff
**** TODO Text objects **** TODO Text objects

View File

@@ -10,6 +10,7 @@
users.users = [ users.users = [
"lain" "lain"
"besties"
]; ];
impermanence = { impermanence = {
@@ -46,17 +47,32 @@
../../public-keys/lain-at-deertopia.pub ../../public-keys/lain-at-deertopia.pub
]; ];
repos = { repos = {
"/persist/vault/jellyfin/Documents" = {
managed = true;
symlinkToAnnexHome = "documents";
remotes = {
"guix-rebound" = "crumb@guix-rebound:Documents";
};
};
"/persist/vault/jellyfin/Music" = {
managed = true;
symlinkToAnnexHome = "music";
remotes = {
"guix-rebound" = "crumb@guix-rebound:Music";
};
};
"/persist/deertopia.net/dav/org" = { "/persist/deertopia.net/dav/org" = {
managed = true; managed = true;
symlinkToAnnexHome = "org";
remotes = { remotes = {
"guix-rebound" = "crumb@guix-rebound:org"; "guix-rebound" = "crumb@guix-rebound:org";
# "fruitbook" = "crumble@fruitbook:org";
}; };
}; };
}; };
}; };
deertopia = { deertopia = {
jellyfin.enable = true;
nginx.enable = true; nginx.enable = true;
webdav.enable = true; webdav.enable = true;
bepasty.enable = true; bepasty.enable = true;

View File

@@ -0,0 +1,39 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.sydnix.deertopia.jellyfin;
in {
options = {
sydnix.deertopia.jellyfin = {
enable = mkEnableOption "Deertopia's Jellyfin media server";
};
};
config = mkIf cfg.enable {
sydnix.impermanence =
let jcfg = config.services.jellyfin;
in {
directories = [
jcfg.dataDir
jcfg.configDir
];
cache.directories = [
jcfg.cacheDir
];
};
services.jellyfin = {
enable = true;
openFirewall = true;
};
sydnix.deertopia.nginx.vhosts."watch".vhost = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:8096"; # Uses default port.
};
};
};
}

View File

@@ -31,6 +31,11 @@ in {
default = true; default = true;
}; };
symlinkToAnnexHome = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
};
path = lib.mkOption { path = lib.mkOption {
description = '' description = ''
Path to the repo. Path to the repo.
@@ -47,6 +52,9 @@ in {
user = lib.mkOption { user = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "annex"; default = "annex";
description = ''
The user that the Git-annex assistant will be run as.
'';
}; };
group = lib.mkOption { group = lib.mkOption {
@@ -81,11 +89,21 @@ in {
# Necessary to enable cloning over SSH. # Necessary to enable cloning over SSH.
isNormalUser = true; isNormalUser = true;
group = "annex"; group = "annex";
# TODO: Don't hardcode extra groups!
extraGroups = [ "jellyfin" ];
home = "/var/sydnix/annex"; home = "/var/sydnix/annex";
homeMode = "770";
createHome = true; createHome = true;
openssh.authorizedKeys.keyFiles = cfg.keyFiles; openssh.authorizedKeys.keyFiles = cfg.keyFiles;
}; };
# HACK: Some Jellyfin libraries are served by Git-annex. Give Jellyfin
# permission to symlink some of those libraries into the annex user's home
# dir.
users.users.${config.services.jellyfin.user}.extraGroups =
lib.mkIf config.sydnix.deertopia.jellyfin.enable
[ "annex" ];
system.activationScripts.initialiseUserAnnex = system.activationScripts.initialiseUserAnnex =
let gitconfig-file = pkgs.writeText "gitconfig" '' let gitconfig-file = pkgs.writeText "gitconfig" ''
[user] [user]
@@ -95,23 +113,16 @@ in {
defaultBranch = main defaultBranch = main
[core] [core]
symlinks = true symlinks = true
[safe]
${lib.strings.concatMapStrings
(repo: "\tdirectory = ${repo.path}\n")
(builtins.attrValues cfg.repos)}
''; '';
in '' in ''
set -e set -e
annexHome="${config.users.users.annex.home}" annexHome="${config.users.users.annex.home}"
ln -sf "${gitconfig-file}" "$annexHome/.gitconfig" ln -sf "${gitconfig-file}" "$annexHome/.gitconfig"
# Symlink repos into annex's home for easy access. This is particularly nice for cloning:
# git clone annex@deertopia.net:org
# instead of
# git clone annex@deertopia.net:/persist/deertopia.net/org
# Less assumptions about the host's file system!
${lib.strings.toShellVar "repos" (builtins.attrNames cfg.repos)}
for repoPath in ''${!repos[@]}; do
target="$annexHome/$(basename "$repoPath")"
ln -sf "$repoPath" "$target"
done
''; '';
systemd.services = systemd.services =
@@ -124,8 +135,18 @@ in {
[ -e .git ] || git init [ -e .git ] || git init
[ -e .git/annex ] || git annex init [ -e .git/annex ] || git annex init
# Symlink repo into user `annex` for easy access. # git config set user.name "${cfg.user.name}"
ln -sf "$(pwd)" "$HOME/" # git config set user.email "${cfg.user.email}"
# Symlink repos into annex's home for easy access. This is
# particularly nice for cloning:
# git clone annex@deertopia.net:org
# instead of
# git clone annex@deertopia.net:/persist/deertopia.net/dav/org
# Less assumptions about the host's file system!
annexHome="${config.users.users.annex.home}"
${lib.optionalString (repo.symlinkToAnnexHome != null)
''ln -sf "$PWD" "$annexHome/${repo.symlinkToAnnexHome}"''}
${lib.strings.toShellVar "remotes" repo.remotes} ${lib.strings.toShellVar "remotes" repo.remotes}
for remoteName in ''${!remotes[@]}; do for remoteName in ''${!remotes[@]}; do

View File

@@ -38,6 +38,28 @@ in {
Name of the group whose members have access to the persist directory. Name of the group whose members have access to the persist directory.
''; '';
}; };
cache = {
directories = mkOption {
description = ''
While functionally identical to `directories` (at the moment),
`cache.directories` carries additional semantics: these directories
/can/ be erased, but typically /shouldn't/ be.
'';
default = [];
type = types.listOf types.anything;
};
files = mkOption {
description = ''
While functionally identical to `files` (at the moment),
`cache.files` carries additional semantics: these files /can/ be
erased, but typically /shouldn't/ be.
'';
default = [];
type = types.listOf types.anything;
};
};
}; };
}; };
@@ -72,8 +94,8 @@ in {
programs.fuse.userAllowOther = true; programs.fuse.userAllowOther = true;
environment.persistence."${cfg.persistDirectory}/root" = { environment.persistence."${cfg.persistDirectory}/root" = {
directories = cfg.directories; directories = cfg.directories ++ cfg.cache.directories;
files = cfg.files; files = cfg.files ++ cfg.cache.files;
}; };
}; };
} }

View File

@@ -3,7 +3,18 @@
isNormalUser = true; isNormalUser = true;
# TODO: Don't hard-code `persist`. Use # TODO: Don't hard-code `persist`. Use
# config.sydnix.impermanence.persistGroupName. # config.sydnix.impermanence.persistGroupName.
extraGroups = [ "wheel" "persist" "nginx" "annex" ]; extraGroups = [
# Admin account.
"wheel"
# Default permissions to modify /persist.
"persist"
# Can modify the files served by Nginx.
"nginx"
# Can modify Deertopia's git-annex repos.
"annex"
# Can modify Deertopia's Jellyfin libraries.
"jellyfin"
];
initialHashedPassword = initialHashedPassword =
"$y$j9T$aEFDDwdTZbAc6VQRXrkBJ0$K8wxTGTWDihyX1wxJ.ZMH//wmQFfrGGUkLkxIU0Lyq8"; "$y$j9T$aEFDDwdTZbAc6VQRXrkBJ0$K8wxTGTWDihyX1wxJ.ZMH//wmQFfrGGUkLkxIU0Lyq8";