Add jellyfin

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-07-05 16:41:54 +01:00
parent e24997677d
commit bf49eac272
43 changed files with 1666 additions and 86 deletions

View File

@@ -10,7 +10,10 @@
storage.settings.storage.driver = "btrfs";
};
quadlet.autoEscape = true;
quadlet = {
enable = true;
autoEscape = true;
};
};
environment = {

View File

@@ -19,7 +19,10 @@
settings.storage.storage.driver = "btrfs";
};
virtualisation.quadlet.autoEscape = true;
virtualisation.quadlet = {
enable = true;
autoEscape = true;
};
home = {
packages = with pkgs; [

View File

@@ -52,7 +52,8 @@
'';
};
users.groups.storage = { };
# echo $(( (0x$(echo -n "storage" | sha256sum | cut -c1-8) % 999 ) + 1 ))
users.groups.storage.gid = 694;
systemd.tmpfiles.rules = [
"v /mnt/storage/public 2770 root storage - -"

View File

@@ -2,11 +2,14 @@
config,
pkgs,
lib,
inputs,
...
}:
{
imports = [ ./display.nix ];
nixpkgs.overlays = [ inputs.nvidia-patch.overlays.default ];
hardware = {
enableAllFirmware = true;
@@ -17,6 +20,11 @@
};
nvidia = {
# TODO: Enable
# package = pkgs.nvidia-patch.patch-nvenc (
# pkgs.nvidia-patch.patch-fbc config.boot.kernelPackages.nvidiaPackages.stable
# );
open = true;
powerManagement.enable = true;
dynamicBoost.enable = true;
@@ -42,7 +50,7 @@
};
nvidia-container-toolkit.enable =
config.virtualisation.containerd.enable || config.virtualisation.docker.enable;
config.virtualisation.containerd.enable || config.virtualisation.podman.enable;
};
boot = {

View File

@@ -68,7 +68,7 @@ in
identity_validation.reset_password.jwt_secret =
hmConfig.sops.placeholder."authelia/resetPasswordJwt";
definitions.user_attributes.is_admin.expression = "\"admins\" in groups";
definitions.user_attributes.is_admin.expression = "\"admin\" in groups";
identity_providers.oidc = {
hmac_secret = hmConfig.sops.placeholder."authelia/oidcHmac";
@@ -81,7 +81,7 @@ in
rules = [
{
policy = "two_factor";
subject = [ "group:admins" ];
subject = [ "group:admin" ];
}
];
};
@@ -91,7 +91,7 @@ in
rules = [
{
policy = "one_factor";
subject = [ "group:admins" ];
subject = [ "group:admin" ];
}
];
};
@@ -134,10 +134,11 @@ in
password = hmConfig.sops.placeholder."authelia/users/karaolidis";
email = "nick@karaolidis.com";
groups = [
"admins"
"admin"
"media"
"vaultwarden"
"nextcloud"
"media"
"jellyfin"
"gitea"
"outline"
"shlink"
@@ -149,7 +150,7 @@ in
};
virtualisation.quadlet = {
networks.authelia.networkConfig.internal = true;
networks.authelia = { };
volumes = {
authelia-redis = { };
@@ -159,24 +160,24 @@ in
containers = {
authelia-init = {
containerConfig =
let
entrypoint = pkgs.writeTextFile {
name = "entrypoint.sh";
executable = true;
text = builtins.readFile ./init-entrypoint.sh;
};
in
{
image = "docker-archive:${selfPkgs.docker-yq}";
volumes = [
containerConfig = {
image = "docker-archive:${selfPkgs.docker-yq}";
volumes =
let
entrypoint = pkgs.writeTextFile {
name = "entrypoint.sh";
executable = true;
text = builtins.readFile ./init-entrypoint.sh;
};
in
[
"${volumes.authelia.ref}:/etc/authelia"
"${hmConfig.sops.templates.authelia-users.path}:/etc/authelia/users.yaml.default:ro"
"${hmConfig.sops.templates.authelia.path}:/etc/authelia/conf.d/authelia.yaml:ro"
"${entrypoint}:/entrypoint.sh:ro"
];
entrypoint = "/entrypoint.sh";
};
entrypoint = "/entrypoint.sh";
};
serviceConfig = {
Type = "oneshot";

View File

@@ -1,5 +1,8 @@
#!/bin/sh
set -o errexit
set -o nounset
touch /etc/authelia/users.yaml
# shellcheck disable=SC2016
yq eval-all '. as $item ireduce ({}; . * $item)' /etc/authelia/users.yaml /etc/authelia/users.yaml.default -i

View File

@@ -11,6 +11,7 @@ in
(import ./authelia { inherit user home; })
(import ./gitea { inherit user home; })
(import ./grafana { inherit user home; })
(import ./jellyfin { inherit user home; })
(import ./nextcloud { inherit user home; })
(import ./ntfy { inherit user home; })
(import ./outline { inherit user home; })

View File

@@ -199,7 +199,7 @@ in
];
virtualisation.quadlet = {
networks.gitea.networkConfig.internal = true;
networks.gitea = { };
volumes = {
gitea-postgresql = { };

View File

@@ -12,6 +12,6 @@ gitea admin auth add-oauth \
--scopes='openid email profile groups' \
--skip-local-2fa \
--group-claim-name=groups \
--admin-group=admins 2>&1 || true
--admin-group=admin 2>&1 || true
exec gitea web -c /etc/gitea/app.ini

View File

@@ -87,9 +87,9 @@ in
groups_attribute_path = "groups";
allow_assign_grafana_admin = true;
role_attribute_strict = true;
role_attribute_path = "contains(groups, 'admins') && 'GrafanaAdmin' || 'Viewer'";
role_attribute_path = "contains(groups, 'admin') && 'GrafanaAdmin' || 'Viewer'";
org_attribute_path = "groups";
org_mapping = "admins:1:Admin *:1:Viewer";
org_mapping = "admin:1:Admin *:1:Viewer";
};
smtp = {
@@ -122,7 +122,7 @@ in
};
virtualisation.quadlet = {
networks.grafana.networkConfig.internal = true;
networks.grafana = { };
containers = {
grafana = {

View File

@@ -0,0 +1,132 @@
{
user ? throw "user argument is required",
home ? throw "home argument is required",
}:
{
config,
inputs,
pkgs,
system,
...
}:
let
selfPkgs = inputs.self.packages.${system};
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes networks;
autheliaClientId = "59TRpNutxEeRRCAZbDsK7rsnrA5NC69HAdAO45CEfc740xl4hgIacDy2u03oiFc89Exb67udBQvmfwxgeAQtJPiNAJxA5OzGmdQf";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"jellyfin/admin".sopsFile = ../../../../../../secrets/secrets.yaml;
"jellyfin/authelia/password".sopsFile = ../../../../../../secrets/secrets.yaml;
"jellyfin/authelia/digest".sopsFile = ../../../../../../secrets/secrets.yaml;
};
templates = {
jellyfin-env.content = ''
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
JELLYFIN_OIDC_SECRET=${hmConfig.sops.placeholder."jellyfin/authelia/password"}
'';
authelia-jellyfin.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "jellyfin.yaml" {
identity_providers.oidc = {
authorization_policies.jellyfin = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:jellyfin";
}
];
};
clients = [
{
client_id = autheliaClientId;
client_name = "Jellyfin";
client_secret = hmConfig.sops.placeholder."jellyfin/authelia/digest";
redirect_uris = [ "https://media.karaolidis.com/sso/OID/redirect/authelia" ];
authorization_policy = "jellyfin";
require_pkce = true;
pkce_challenge_method = "S256";
scopes = [
"openid"
"profile"
"groups"
];
token_endpoint_auth_method = "client_secret_post";
}
];
};
}
);
};
};
systemd.user.tmpfiles.rules = [
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data 700 storm storm"
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/films 755 storm storm"
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/shows 755 storm storm"
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/anime-films 755 storm storm"
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/anime-shows 755 storm storm"
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/music 755 storm storm"
];
virtualisation.quadlet = {
networks.jellyfin = { };
volumes = {
jellyfin-config = { };
jellyfin-data = { };
jellyfin-log = { };
jellyfin-cache = { };
};
containers = {
jellyfin = {
containerConfig = {
image = "docker-archive:${selfPkgs.docker-jellyfin}";
networks = [
networks.jellyfin.ref
networks.traefik.ref
];
volumes =
let
setup = pkgs.writeTextFile {
name = "setup.sh";
executable = true;
text = builtins.readFile ./setup.sh;
};
in
[
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
"${setup}:/etc/jellyfin/setup.sh:ro"
"${./libraries}:/etc/jellyfin/libraries:ro"
"${volumes.jellyfin-config.ref}:/etc/jellyfin"
"${volumes.jellyfin-data.ref}:/var/lib/jellyfin"
"${volumes.jellyfin-log.ref}:/var/log/jellyfin"
"${volumes.jellyfin-cache.ref}:/tmp/jellyfin"
];
environments.JELLYFIN_OIDC_CLIENT_ID = autheliaClientId;
environmentFiles = [ hmConfig.sops.templates.jellyfin-env.path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.jellyfin.rule=Host(`media.karaolidis.com`)"
];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
unitConfig.After = [ "sops-nix.service" ];
};
authelia-init.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-jellyfin.path}:/etc/authelia/conf.d/jellyfin.yaml:ro"
];
};
};
};
}

View File

@@ -0,0 +1,128 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": false,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "JP",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": true,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Movie",
"MetadataFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"MetadataFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"ImageFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/anime-films"
}
]
}
}

View File

@@ -0,0 +1,128 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": false,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "US",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": true,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Movie",
"MetadataFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"MetadataFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"ImageFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/films"
}
]
}
}

View File

@@ -0,0 +1,229 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": false,
"EnableTrickplayImageExtraction": false,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": false,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": false,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "US",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": true,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": false,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "MusicArtist",
"MetadataFetchers": ["MusicBrainz", "TheAudioDB"],
"MetadataFetcherOrder": ["MusicBrainz", "TheAudioDB"],
"ImageFetchers": ["TheAudioDB"],
"ImageFetcherOrder": ["TheAudioDB"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
},
{
"Type": "MusicAlbum",
"MetadataFetchers": ["MusicBrainz", "TheAudioDB"],
"MetadataFetcherOrder": ["MusicBrainz", "TheAudioDB"],
"ImageFetchers": ["TheAudioDB"],
"ImageFetcherOrder": ["TheAudioDB"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "0",
"MinWidth": "1280"
}
]
},
{
"Type": "Audio",
"ImageFetchers": ["Image Extractor"],
"ImageFetcherOrder": ["Image Extractor"]
},
{
"Type": "MusicVideo",
"ImageFetchers": ["Embedded Image Extractor", "Screen Grabber"],
"ImageFetcherOrder": ["Embedded Image Extractor", "Screen Grabber"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/music"
}
]
}
}

View File

@@ -0,0 +1,204 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": true,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "JP",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": false,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Series",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
},
{
"Type": "Season",
"MetadataFetchers": ["TheTVDB", "TheMovieDb"],
"MetadataFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "0",
"MinWidth": "1280"
}
]
},
{
"Type": "Episode",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"ImageFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/anime-shows"
}
]
}
}

View File

@@ -0,0 +1,204 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": true,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "US",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": false,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Series",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
},
{
"Type": "Season",
"MetadataFetchers": ["TheTVDB", "TheMovieDb"],
"MetadataFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "0",
"MinWidth": "1280"
}
]
},
{
"Type": "Episode",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"ImageFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/shows"
}
]
}
}

View File

@@ -0,0 +1,182 @@
# shellcheck shell=sh
curl -sf "$JELLYFIN_HOST/Startup/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}'
curl -sf "$JELLYFIN_HOST/Startup/User"
curl -sf "$JELLYFIN_HOST/Startup/User" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"Name":"'"$JELLYFIN_ADMIN_USERNAME"'","Password":"'"$JELLYFIN_ADMIN_PASSWORD"'"}'
curl -sf "$JELLYFIN_HOST/Startup/RemoteAccess" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"EnableRemoteAccess":true,"EnableAutomaticPortMapping":false}'
curl -sf "$JELLYFIN_HOST/Startup/Complete" \
-X POST
token="$(curl -sf "$JELLYFIN_HOST/Users/AuthenticateByName" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Client="jellyfin-init", Device="sh", DeviceId="sh", Version="1.0"' \
--data-raw '{"Username":"'"$JELLYFIN_ADMIN_USERNAME"'","Pw":"'"$JELLYFIN_ADMIN_PASSWORD"'"}' \
| jq -r '.AccessToken')"
curl -sf "$JELLYFIN_HOST/System/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.EnableMetrics = true
| .ServerName = "jupiter"
| .EnableGroupingIntoCollections = true
| .RemoteClientBitrateLimit = 1024000000
| .TrickplayOptions.EnableHwAcceleration = true
| .TrickplayOptions.EnableHwEncoding = true' \
| curl -sf "$JELLYFIN_HOST/System/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/System/Configuration/encoding" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.EnableThrottling = true
| .HardwareAccelerationType = "nvenc"
| .EnableTonemapping = true
| .EnableDecodingColorDepth12HevcRext = true
| .AllowHevcEncoding = true
| .HardwareDecodingCodecs = ["h264", "hevc", "mpeg2video", "mpeg4", "vc1", "vp8", "vp9", "av1"]' \
| curl -sf "$JELLYFIN_HOST/System/Configuration/encoding" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/c83d86bb-a1e0-4c35-a113-e2101cf4ee6b/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.AnalyzeSeasonZero = true
| .AnalyzeMovies = true' \
| curl -sf "$JELLYFIN_HOST/Plugins/c83d86bb-a1e0-4c35-a113-e2101cf4ee6b/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/b8715ed1-6c47-4528-9ad3-f72deb539cd4/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.IncludeAdult = true' \
| curl -sf "$JELLYFIN_HOST/Plugins/b8715ed1-6c47-4528-9ad3-f72deb539cd4/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
for filepath in /etc/jellyfin/libraries/*/*.json; do
collectionType=$(jq -rn --arg s "$(basename "$(dirname "$filepath")")" '$s|@uri')
name=$(jq -rn --arg s "$(basename "$filepath" .json)" '$s|@uri')
curl -sf "${JELLYFIN_HOST}/Library/VirtualFolders?collectionType=${collectionType}&name=${name}" \
-X POST \
-H "Content-Type: application/json" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @"${filepath}"
done
curl -sf "${JELLYFIN_HOST}/Plugins/505ce9d1-d916-42fa-86ca-673ef241d7df/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @- <<EOF
{
"SamlConfigs": {},
"OidConfigs": {
"authelia": {
"OidProviderName": "authelia",
"OidEndpoint": "https://id.karaolidis.com",
"OidClientId": "$JELLYFIN_OIDC_CLIENT_ID",
"OidSecret": "$JELLYFIN_OIDC_SECRET",
"RoleClaim": "groups",
"DefaultUsernameClaim": "preferred_username",
"Enabled": true,
"EnableAuthorization": true,
"EnableAllFolders": true,
"EnableFolderRoles": false,
"EnableLiveTvRoles": false,
"EnableLiveTv": false,
"EnableLiveTvManagement": false,
"DisableHttps": false,
"DoNotValidateEndpoints": false,
"DoNotValidateIssuerName": false,
"Roles": [
"jellyfin"
],
"AdminRoles": [
"admin"
],
"LiveTvRoles": [],
"LiveTvManagementRoles": [],
"OidScopes": [
"groups"
],
"EnabledFolders": [],
"FolderRoleMapping": [],
"SchemeOverride": "https"
}
}
}
EOF
# https://github.com/9p4/jellyfin-plugin-sso/issues/16#issuecomment-2953811762
custom_css=$(cat <<EOF
a.raised.emby-button,
.loginDisclaimerContainer,
.loginDisclaimer,
.manualLoginForm {
all: unset;
}
.btnQuick,
.btnSelectServer,
.btnForgotPassword,
a.raised.emby-button,
.emby-button.block,
.loginDisclaimerContainer,
.loginDisclaimer {
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
color: inherit !important;
}
.btnForgotPassword {
display: none !important;
}
.manualLoginForm > :not(:first-child) {
display: none !important;
}
EOF
)
login_disclaimer=$(cat <<EOF
<form action="https://media.karaolidis.com/sso/OID/start/authelia">
<button class="raised block emby-button button-submit">
Sign in with Authelia
</button>
</form>
EOF
)
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
-H "Authorization: MediaBrowser Token=$token" |
jq --arg custom_css "$custom_css" \
--arg login_disclaimer "$login_disclaimer" \
'.CustomCss = $custom_css | .LoginDisclaimer = $login_disclaimer' |
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: MediaBrowser Token=$token" \
--data-binary @-

View File

@@ -156,7 +156,7 @@ in
];
virtualisation.quadlet = {
networks.nextcloud.networkConfig.internal = true;
networks.nextcloud = { };
volumes = {
nextcloud-postgresql = { };
@@ -173,21 +173,26 @@ in
networks.nextcloud.ref
networks.traefik.ref
];
volumes = [
"/mnt/storage/private/storm/containers/storage/volumes/nextcloud-data/_data:/var/lib/nextcloud"
"${volumes.nextcloud-log.ref}:/var/log/nextcloud"
"${volumes.nextcloud-config.ref}:/var/www/nextcloud/config"
"${volumes.nextcloud-apps.ref}:/var/www/nextcloud/apps"
"${hmConfig.sops.templates.nextcloud.path}:/var/www/nextcloud/config/override.config.php:ro"
];
volumes =
let
post-setup = pkgs.writeTextFile {
name = "post-setup.sh";
executable = true;
text = builtins.readFile ./post-setup.sh;
};
in
[
"${post-setup}:/etc/nextcloud/post-setup.sh:ro"
"/mnt/storage/private/storm/containers/storage/volumes/nextcloud-data/_data:/var/lib/nextcloud"
"${volumes.nextcloud-log.ref}:/var/log/nextcloud"
"${volumes.nextcloud-config.ref}:/var/www/nextcloud/config"
"${volumes.nextcloud-apps.ref}:/var/www/nextcloud/apps"
"${hmConfig.sops.templates.nextcloud.path}:/var/www/nextcloud/config/override.config.php:ro"
];
environments = {
POSTGRES_HOST = "nextcloud-postgresql";
POSTGRES_DB = "nextcloud";
POSTGRES_USER = "nextcloud";
EXTRA_INIT = ''
occ config:app:set core shareapi_allow_custom_tokens --value true --type boolean --no-interaction
occ theming:config url https://cloud.karaolidis.com
'';
};
environmentFiles = [ hmConfig.sops.templates.nextcloud-env.path ];
labels = [

View File

@@ -0,0 +1,26 @@
# shellcheck shell=bash
occ user:delete admin
occ app:disable \
app_api \
contactsinteraction \
dashboard \
federation \
firstrunwizard \
photos \
recommendations \
sharebymail \
support \
survey_client \
user_status \
weather_status
occ app:install \
oidc_login
occ config:app:set \
core shareapi_allow_custom_tokens --value true --type boolean --no-interaction
occ theming:config \
url https://cloud.karaolidis.com

View File

@@ -84,7 +84,7 @@ in
};
virtualisation.quadlet = {
networks.ntfy.networkConfig.internal = true;
networks.ntfy = { };
volumes.ntfy = { };

View File

@@ -68,6 +68,7 @@ in
"email"
"offline_access"
];
response_types = [ "code" ];
token_endpoint_auth_method = "client_secret_post";
}
];
@@ -82,7 +83,7 @@ in
];
virtualisation.quadlet = {
networks.outline.networkConfig.internal = true;
networks.outline = { };
volumes = {
outline-redis = { };

View File

@@ -84,10 +84,7 @@ in
in
{
virtualisation.quadlet = {
networks = {
prometheus.networkConfig.internal = true;
prometheus-ext = { };
};
networks.prometheus = { };
volumes = {
prometheus-data = { };
@@ -269,10 +266,8 @@ in
"${volumes.prometheus-data.ref}:/var/lib/prometheus"
];
networks = [
networks.grafana.ref
networks.prometheus.ref
# Access to root exporters
networks.prometheus-ext.ref
networks.grafana.ref
];
exec = [
"--log.level=warn"

View File

@@ -41,7 +41,7 @@ in
};
virtualisation.quadlet = {
networks.shlink.networkConfig.internal = true;
networks.shlink = { };
volumes = {
shlink-postgresql = { };

View File

@@ -21,7 +21,7 @@ in
sops.secrets."sish/ssh/key".sopsFile = ../../../../../../secrets/secrets.yaml;
virtualisation.quadlet = {
networks.sish.networkConfig.internal = true;
networks.sish = { };
containers.sish = {
containerConfig = {

View File

@@ -140,7 +140,7 @@ in
{
domain = "proxy.karaolidis.com";
policy = "two_factor";
subject = [ "group:admins" ];
subject = [ "group:admin" ];
}
];
};

View File

@@ -24,7 +24,7 @@ in
virtualisation.quadlet = {
# Not internal, we need network access for obvious reasons
networks.transmission = { };
networks.transmission-ext = { };
volumes.transmission-config = { };
@@ -33,7 +33,7 @@ in
containerConfig = {
image = "docker-archive:${selfPkgs.docker-transmission-protonvpn}";
networks = [
networks.transmission.ref
networks.transmission-ext.ref
networks.traefik.ref
];
addCapabilities = [ "NET_ADMIN" ];

View File

@@ -71,6 +71,7 @@ in
"profile"
"offline_access"
];
response_types = [ "code" ];
}
];
};
@@ -80,7 +81,7 @@ in
};
virtualisation.quadlet = {
networks.vaultwarden.networkConfig.internal = true;
networks.vaultwarden = { };
volumes = {
vaultwarden-postgresql = { };

View File

@@ -15,7 +15,7 @@ let
in
{
home-manager.users.${user}.virtualisation.quadlet = {
networks.whoami.networkConfig.internal = true;
networks.whoami = { };
containers.whoami.containerConfig = {
image = "docker-archive:${selfPkgs.docker-whoami}";