8
flake.lock
generated
8
flake.lock
generated
@@ -511,11 +511,11 @@
|
|||||||
"secrets": {
|
"secrets": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1757954517,
|
"lastModified": 1758562503,
|
||||||
"narHash": "sha256-fmcVzq/HGeXbMKsrW/NlLEuBZ+xYiRBTOkwQc80tzGk=",
|
"narHash": "sha256-jFnb0C2Jm3370vo0uUGHi+BEEfQJ9jOAoHnyVPYX2ZE=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "dab48ad2370acfa732987eef6f647bdd4f4362f8",
|
"rev": "54a416e3168179f24b6cda8f9a6ad54b1c388f34",
|
||||||
"revCount": 46,
|
"revCount": 47,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "ssh://git@karaolidis.com/karaolidis/nix-secrets.git"
|
"url": "ssh://git@karaolidis.com/karaolidis/nix-secrets.git"
|
||||||
},
|
},
|
||||||
|
@@ -132,7 +132,6 @@ in
|
|||||||
"media"
|
"media"
|
||||||
"vaultwarden"
|
"vaultwarden"
|
||||||
"nextcloud"
|
"nextcloud"
|
||||||
"jellyfin"
|
|
||||||
"gitea"
|
"gitea"
|
||||||
"outline"
|
"outline"
|
||||||
"shlink"
|
"shlink"
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
{ user, home }:
|
{ user, home }:
|
||||||
{ config, inputs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
inputs,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
hmConfig = config.home-manager.users.${user};
|
hmConfig = config.home-manager.users.${user};
|
||||||
|
|
||||||
@@ -17,7 +22,7 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(import ./jellyfin { inherit user home; })
|
(import ./plex { inherit user home; })
|
||||||
(import ./jellyseerr {
|
(import ./jellyseerr {
|
||||||
inherit
|
inherit
|
||||||
user
|
user
|
||||||
@@ -58,6 +63,32 @@ in
|
|||||||
|
|
||||||
sops.secrets."ntfy/tokens/jupiter/media".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
sops.secrets."ntfy/tokens/jupiter/media".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
||||||
|
|
||||||
virtualisation.quadlet.networks.media = { };
|
virtualisation.quadlet = {
|
||||||
|
networks.media = { };
|
||||||
|
|
||||||
|
containers.authelia.containerConfig.volumes =
|
||||||
|
let
|
||||||
|
mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" {
|
||||||
|
access_control.rules = [
|
||||||
|
{
|
||||||
|
domain = "beta.media.karaolidis.com";
|
||||||
|
policy = "one_factor";
|
||||||
|
resources = [ "^/manage([/?].*)?$" ];
|
||||||
|
subject = [ "group:media" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
domain = "beta.media.karaolidis.com";
|
||||||
|
policy = "deny";
|
||||||
|
resources = [ "^/manage([/?].*)?$" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
domain = "beta.media.karaolidis.com";
|
||||||
|
policy = "bypass";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
[ "${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,152 +0,0 @@
|
|||||||
{ user, home }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
inputs,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
hmConfig = config.home-manager.users.${user};
|
|
||||||
inherit (hmConfig.virtualisation.quadlet) volumes networks;
|
|
||||||
autheliaClientId = "59TRpNutxEeRRCAZbDsK7rsnrA5NC69HAdAO45CEfc740xl4hgIacDy2u03oiFc89Exb67udBQvmfwxgeAQtJPiNAJxA5OzGmdQf";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
home-manager.users.${user} = {
|
|
||||||
sops = {
|
|
||||||
secrets = {
|
|
||||||
"jellyfin/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
"jellyfin/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
"jellyfin/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
"opensubtitles/username".sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
|
|
||||||
"opensubtitles/password".sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
|
|
||||||
};
|
|
||||||
|
|
||||||
templates = {
|
|
||||||
jellyfin-env.content = ''
|
|
||||||
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
|
|
||||||
JELLYFIN_OIDC_SECRET=${hmConfig.sops.placeholder."jellyfin/authelia/password"}
|
|
||||||
OPENSUBTITLES_USERNAME=${hmConfig.sops.placeholder."opensubtitles/username"}
|
|
||||||
OPENSUBTITLES_PASSWORD=${hmConfig.sops.placeholder."opensubtitles/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://beta.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";
|
|
||||||
pre_configured_consent_duration = "1 month";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualisation.quadlet = {
|
|
||||||
networks.jellyfin = { };
|
|
||||||
|
|
||||||
volumes = {
|
|
||||||
jellyfin-config = { };
|
|
||||||
jellyfin-data = { };
|
|
||||||
jellyfin-metadata = { };
|
|
||||||
jellyfin-root = { };
|
|
||||||
jellyfin-log = { };
|
|
||||||
jellyfin-cache = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
containers = {
|
|
||||||
jellyfin = {
|
|
||||||
containerConfig = {
|
|
||||||
image = "docker-archive:${pkgs.dockerImages.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"
|
|
||||||
# FIXME: https://github.com/9p4/jellyfin-plugin-sso/issues/189#issuecomment-3262794524
|
|
||||||
"${./sso-button.js}:/etc/jellyfin/sso-button.js:ro"
|
|
||||||
"${./libraries}:/etc/jellyfin/libraries:ro"
|
|
||||||
"${volumes.jellyfin-config.ref}:/etc/jellyfin"
|
|
||||||
"${volumes.jellyfin-data.ref}:/var/lib/jellyfin/data"
|
|
||||||
"${volumes.jellyfin-metadata.ref}:/var/lib/jellyfin/metadata"
|
|
||||||
"${volumes.jellyfin-root.ref}:/var/lib/jellyfin/root"
|
|
||||||
"${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(`beta.media.karaolidis.com`)"
|
|
||||||
];
|
|
||||||
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
|
|
||||||
devices = [ "nvidia.com/gpu=all" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
unitConfig.After = [ "sops-nix.service" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
authelia.containerConfig.volumes =
|
|
||||||
let
|
|
||||||
mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" {
|
|
||||||
access_control.rules = [
|
|
||||||
{
|
|
||||||
domain = "beta.media.karaolidis.com";
|
|
||||||
policy = "one_factor";
|
|
||||||
resources = [ "^/manage([/?].*)?$" ];
|
|
||||||
subject = [ "group:media" ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
domain = "beta.media.karaolidis.com";
|
|
||||||
policy = "deny";
|
|
||||||
resources = [ "^/manage([/?].*)?$" ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
domain = "beta.media.karaolidis.com";
|
|
||||||
policy = "bypass";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
[
|
|
||||||
"${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro"
|
|
||||||
"${hmConfig.sops.templates.authelia-jellyfin.path}:/etc/authelia/conf.d/jellyfin.yaml:ro"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,128 +0,0 @@
|
|||||||
{
|
|
||||||
"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/libraries/anime/films"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,128 +0,0 @@
|
|||||||
{
|
|
||||||
"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/libraries/films"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,204 +0,0 @@
|
|||||||
{
|
|
||||||
"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/libraries/anime/shows"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,204 +0,0 @@
|
|||||||
{
|
|
||||||
"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/libraries/shows"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,222 +0,0 @@
|
|||||||
# shellcheck shell=sh
|
|
||||||
|
|
||||||
JELLYFIN_HOST="${JELLYFIN_HOST:-http://localhost:8096}"
|
|
||||||
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
|
|
||||||
|
|
||||||
until response="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
|
|
||||||
echo "Waiting for Jellyfin to be ready..."
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
setup="$(echo "$response" | jq -r '.StartupWizardCompleted')"
|
|
||||||
|
|
||||||
if [ "$setup" = "false" ]; then
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
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"
|
|
||||||
| .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/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
|
|
||||||
| jq --arg username "$OPENSUBTITLES_USERNAME" \
|
|
||||||
--arg password "$OPENSUBTITLES_PASSWORD" \
|
|
||||||
'.Username = $username
|
|
||||||
| .Password = $password' \
|
|
||||||
| curl -sf "$JELLYFIN_HOST/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/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 @-
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
|
|
||||||
-H "Authorization: MediaBrowser Token=$token" |
|
|
||||||
jq --arg custom_css "$custom_css" \
|
|
||||||
'.CustomCss = $custom_css' |
|
|
||||||
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
|
|
||||||
-X POST \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-H "Authorization: MediaBrowser Token=$token" \
|
|
||||||
--data-binary @-
|
|
||||||
|
|
||||||
jq -Rn --rawfile script /etc/jellyfin/sso-button.js '
|
|
||||||
{
|
|
||||||
CustomJavaScripts: [
|
|
||||||
{
|
|
||||||
Name: "SSO Button",
|
|
||||||
Script: $script,
|
|
||||||
Enabled: true,
|
|
||||||
RequiresAuthentication: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
' \
|
|
||||||
| curl -sf "$JELLYFIN_HOST/Plugins/f5a34f7b-2e8a-4e6a-a722-3a216a81b374/Configuration" \
|
|
||||||
-X POST \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
|
|
||||||
--data-binary @-
|
|
||||||
|
|
||||||
existing_libraries="$(curl -sf "$JELLYFIN_HOST/Library/VirtualFolders" \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
|
||||||
|
|
||||||
find /etc/jellyfin/libraries -name "*.json" | sort -V | while IFS= read -r filepath; do
|
|
||||||
collectionType=$(jq -rn --arg s "$(basename "$(dirname "$filepath")")" '$s|@uri')
|
|
||||||
name=$(jq -rn --arg s "$(basename "$filepath" .json)" '$s|@uri')
|
|
||||||
|
|
||||||
if echo "$existing_libraries" | jq -e --arg name "$name" 'any(.[]; .Name | @uri == $name)'; then
|
|
||||||
echo "Skipping existing virtual folder: $name"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Creating virtual folder: $name"
|
|
||||||
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
|
|
@@ -1,180 +0,0 @@
|
|||||||
const SSO_AUTH_URL = "https://beta.media.karaolidis.com/sso/OID/start/authelia";
|
|
||||||
|
|
||||||
// SSO provider customization. Available options are:
|
|
||||||
// generic, authentik, authelia, keycloak, zitadel
|
|
||||||
const PROVIDER = "authelia";
|
|
||||||
|
|
||||||
// Self-executing function that waits for the document body to be available
|
|
||||||
(function waitForBody() {
|
|
||||||
// If document.body doesn't exist yet, retry in 100ms
|
|
||||||
if (!document.body) {
|
|
||||||
return setTimeout(waitForBody, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the current page is a login page by checking multiple indicators
|
|
||||||
* @returns {boolean} True if this appears to be a login page
|
|
||||||
*/
|
|
||||||
function isLoginPage() {
|
|
||||||
const hash = location.hash.toLowerCase();
|
|
||||||
const pathname = location.pathname.toLowerCase();
|
|
||||||
// Check for URL patterns that typically indicate login pages
|
|
||||||
const hasLoginUrl =
|
|
||||||
hash === "" ||
|
|
||||||
hash === "#/" ||
|
|
||||||
hash === "#/home" ||
|
|
||||||
hash === "#/login" ||
|
|
||||||
hash.startsWith("#/login") ||
|
|
||||||
pathname.includes("/login");
|
|
||||||
|
|
||||||
// Check for DOM elements that indicate a login form is present
|
|
||||||
const hasLoginElements =
|
|
||||||
document.querySelector('input[type="password"]') !== null ||
|
|
||||||
document.querySelector(".loginPage") !== null ||
|
|
||||||
document.querySelector("#txtUserName") !== null;
|
|
||||||
|
|
||||||
return hasLoginUrl || hasLoginElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current page should be excluded from SSO button insertion
|
|
||||||
* These are typically pages where users are already authenticated
|
|
||||||
* @returns {boolean} True if this page should be excluded
|
|
||||||
*/
|
|
||||||
function shouldExcludePage() {
|
|
||||||
const hash = location.hash.toLowerCase();
|
|
||||||
// List of page patterns where we don't want to show the SSO button
|
|
||||||
const excludePatterns = [
|
|
||||||
"#/dashboard",
|
|
||||||
"#/home.html",
|
|
||||||
"#/movies",
|
|
||||||
"#/tv",
|
|
||||||
"#/music",
|
|
||||||
"#/livetv",
|
|
||||||
"#/search",
|
|
||||||
"#/settings",
|
|
||||||
"#/wizardstart",
|
|
||||||
"#/wizardfinish",
|
|
||||||
"#/mypreferencesmenu",
|
|
||||||
"#/userprofile",
|
|
||||||
];
|
|
||||||
|
|
||||||
return excludePatterns.some((pattern) => hash.startsWith(pattern));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the OAuth device ID in localStorage if it doesn't exist
|
|
||||||
* This is required for Jellyfin native apps to maintain device identification
|
|
||||||
*/
|
|
||||||
function oAuthInitDeviceId() {
|
|
||||||
// Only set device ID if it's not already set and we're in a native shell environment
|
|
||||||
if (
|
|
||||||
!localStorage.getItem("_deviceId2") &&
|
|
||||||
window.NativeShell?.AppHost?.deviceId
|
|
||||||
) {
|
|
||||||
localStorage.setItem("_deviceId2", window.NativeShell.AppHost.deviceId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and inserts the SSO login button into the login page
|
|
||||||
* Only runs if we're on a valid login page and the button doesn't already exist
|
|
||||||
*/
|
|
||||||
function insertSSOButton() {
|
|
||||||
// Safety check: ensure we're on the right page before proceeding
|
|
||||||
if (!isLoginPage() || shouldExcludePage()) return;
|
|
||||||
|
|
||||||
// Try to find a suitable container for the SSO button
|
|
||||||
const loginContainer =
|
|
||||||
document.querySelector(".readOnlyContent") ||
|
|
||||||
document.querySelector("form")?.parentNode ||
|
|
||||||
document.querySelector(".loginPage") ||
|
|
||||||
document.querySelector("#loginPage");
|
|
||||||
|
|
||||||
// Exit if no container found or button already exists
|
|
||||||
if (!loginContainer || document.querySelector("#custom-sso-button")) return;
|
|
||||||
|
|
||||||
switch (PROVIDER.toLowerCase()) {
|
|
||||||
case "authentik":
|
|
||||||
SSO_BUTTON_HTML =
|
|
||||||
'<img src="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.svg" width="21em"><span>Login with Authentik</span>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "authelia":
|
|
||||||
SSO_BUTTON_HTML =
|
|
||||||
'<img src="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authelia-light.svg" width="21em"><span>Login with Authelia</span>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "keycloak":
|
|
||||||
SSO_BUTTON_HTML =
|
|
||||||
'<img src="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/keycloak.svg" width="21em"><span>Login with Keycloak</span>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "zitadel":
|
|
||||||
SSO_BUTTON_HTML =
|
|
||||||
'<img src="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/zitadel.svg" width="21em"><span>Login with Zitadel</span>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
SSO_BUTTON_HTML =
|
|
||||||
'<span class="material-icons">shield</span><span>Login with SSO</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip insertion for Jellyfin Media Player (JMP) as it may have different auth handling
|
|
||||||
const isJMP = navigator.userAgent.includes("JellyfinMediaPlayer");
|
|
||||||
if (isJMP) return;
|
|
||||||
|
|
||||||
// Create the SSO button element
|
|
||||||
const button = document.createElement("button");
|
|
||||||
button.id = "custom-sso-button";
|
|
||||||
button.className = "raised block emby-button button-submit";
|
|
||||||
// Style the button to match Jellyfin's design while being visually distinct
|
|
||||||
button.style =
|
|
||||||
"display: flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 20px; font-size: 16px; background-color: #3949ab; color: white; margin-top: 16px;";
|
|
||||||
// Add icon and text content
|
|
||||||
button.innerHTML = SSO_BUTTON_HTML;
|
|
||||||
// Handle button click - prevent form submission and redirect to SSO
|
|
||||||
button.onclick = function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
oAuthInitDeviceId(); // Ensure device ID is set before SSO redirect
|
|
||||||
window.location.href = SSO_AUTH_URL;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the button to the login container
|
|
||||||
loginContainer.appendChild(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial setup: Check if we should insert the SSO button when script first loads
|
|
||||||
if (isLoginPage() && !shouldExcludePage()) {
|
|
||||||
// Delay insertion slightly to ensure all page elements are fully loaded
|
|
||||||
setTimeout(insertSSOButton, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a MutationObserver to watch for dynamic page changes
|
|
||||||
// This handles cases where Jellyfin loads content dynamically via JavaScript
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
if (isLoginPage() && !shouldExcludePage()) {
|
|
||||||
// Check if login elements are ready and button hasn't been inserted yet
|
|
||||||
const ready =
|
|
||||||
document.querySelector(".readOnlyContent") ||
|
|
||||||
document.querySelector("form") ||
|
|
||||||
document.querySelector(".loginPage");
|
|
||||||
if (ready && !document.querySelector("#custom-sso-button")) {
|
|
||||||
insertSSOButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing changes to the entire document body and its children
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
// Listen for hash changes (when navigating between pages in Jellyfin's SPA)
|
|
||||||
window.addEventListener("hashchange", () => {
|
|
||||||
// Small delay to allow page transition to complete
|
|
||||||
setTimeout(() => {
|
|
||||||
if (isLoginPage() && !shouldExcludePage()) {
|
|
||||||
insertSSOButton();
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
})();
|
|
@@ -15,22 +15,13 @@ let
|
|||||||
hmConfig = config.home-manager.users.${user};
|
hmConfig = config.home-manager.users.${user};
|
||||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||||
arrs = radarrs ++ sonarrs;
|
arrs = radarrs ++ sonarrs;
|
||||||
autheliaClientId = "s8QyVqBdiEStH5WXeEYNSrEh8ls2xHif0qyTGbC7V8nHNcqHi5NhqHUapCHuVFT4kEtngqgLry2SKOKepQl3AiqCWlhTjlIxr7LI";
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
sops = {
|
sops = {
|
||||||
secrets = {
|
secrets."jellyseerr/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
||||||
"jellyseerr/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
"jellyseerr/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
"jellyseerr/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
|
||||||
};
|
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
jellyseerr-env.content = ''
|
|
||||||
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
|
|
||||||
'';
|
|
||||||
|
|
||||||
jellyseerr.content = builtins.readFile (
|
jellyseerr.content = builtins.readFile (
|
||||||
(pkgs.formats.json { }).generate "setings.json" {
|
(pkgs.formats.json { }).generate "setings.json" {
|
||||||
main = {
|
main = {
|
||||||
@@ -42,38 +33,17 @@ in
|
|||||||
# 32 | 4194304 | 67108864
|
# 32 | 4194304 | 67108864
|
||||||
defaultPermissions = 71303200;
|
defaultPermissions = 71303200;
|
||||||
localLogin = false;
|
localLogin = false;
|
||||||
mediaServerLogin = false;
|
mediaServerLogin = true;
|
||||||
oidcLogin = true;
|
oidcLogin = false;
|
||||||
newPlexLogin = false;
|
newPlexLogin = true;
|
||||||
mediaServerType = 2;
|
mediaServerType = 1;
|
||||||
partialRequestsEnabled = true;
|
partialRequestsEnabled = true;
|
||||||
enableSpecialEpisodes = true;
|
enableSpecialEpisodes = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
jellyfin = {
|
plex = { };
|
||||||
name = "jupiter";
|
|
||||||
ip = "jellyfin";
|
|
||||||
port = 8096;
|
|
||||||
externalHostname = "https://beta.media.karaolidis.com";
|
|
||||||
jellyfinForgotPasswordUrl = "https://id.karaolidis.com/reset-password/step1";
|
|
||||||
};
|
|
||||||
|
|
||||||
oidc.providers = [
|
jellyfin = { };
|
||||||
{
|
|
||||||
slug = "authelia";
|
|
||||||
name = "Authelia";
|
|
||||||
issuerUrl = "https://id.karaolidis.com";
|
|
||||||
clientId = autheliaClientId;
|
|
||||||
clientSecret = hmConfig.sops.placeholder."jellyseerr/authelia/password";
|
|
||||||
scopes = lib.strings.concatStringsSep " " [
|
|
||||||
"openid"
|
|
||||||
"profile"
|
|
||||||
"email"
|
|
||||||
"groups"
|
|
||||||
];
|
|
||||||
newUserLogin = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
radarr = [ ];
|
radarr = [ ];
|
||||||
|
|
||||||
@@ -111,34 +81,6 @@ in
|
|||||||
network.trustProxy = true;
|
network.trustProxy = true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
authelia-jellyseerr.content = builtins.readFile (
|
|
||||||
(pkgs.formats.yaml { }).generate "jellyseerr.yaml" {
|
|
||||||
identity_providers.oidc = {
|
|
||||||
authorization_policies.jellyseerr = {
|
|
||||||
default_policy = "deny";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
policy = "one_factor";
|
|
||||||
subject = "group:jellyfin";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
clients = [
|
|
||||||
{
|
|
||||||
client_id = autheliaClientId;
|
|
||||||
client_name = "jellyseerr";
|
|
||||||
client_secret = hmConfig.sops.placeholder."jellyseerr/authelia/digest";
|
|
||||||
redirect_uris = [ "https://request.karaolidis.com/login?provider=authelia&callback=true" ];
|
|
||||||
authorization_policy = "jellyseerr";
|
|
||||||
token_endpoint_auth_method = "client_secret_post";
|
|
||||||
pre_configured_consent_duration = "1 month";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// builtins.listToAttrs (
|
// builtins.listToAttrs (
|
||||||
builtins.map (arr: {
|
builtins.map (arr: {
|
||||||
@@ -153,57 +95,49 @@ in
|
|||||||
virtualisation.quadlet = {
|
virtualisation.quadlet = {
|
||||||
volumes.jellyseerr = { };
|
volumes.jellyseerr = { };
|
||||||
|
|
||||||
containers = {
|
containers.jellyseerr = {
|
||||||
jellyseerr = {
|
containerConfig = {
|
||||||
containerConfig = {
|
image = "docker-archive:${pkgs.dockerImages.jellyseerr}";
|
||||||
image = "docker-archive:${pkgs.dockerImages.jellyseerr}";
|
networks = [
|
||||||
networks = [
|
networks.media.ref
|
||||||
networks.jellyfin.ref
|
networks.traefik.ref
|
||||||
networks.media.ref
|
];
|
||||||
networks.traefik.ref
|
volumes =
|
||||||
];
|
|
||||||
volumes =
|
|
||||||
let
|
|
||||||
preStart = pkgs.writeTextFile {
|
|
||||||
name = "pre-start.sh";
|
|
||||||
executable = true;
|
|
||||||
text = builtins.readFile ./pre-start.sh;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
[
|
|
||||||
"${preStart}:/etc/jellyseerr/pre-start.sh:ro"
|
|
||||||
"${hmConfig.sops.templates.jellyseerr.path}:/etc/jellyseerr/settings.default.json:ro"
|
|
||||||
"${volumes.jellyseerr.ref}:/var/lib/jellyseerr"
|
|
||||||
]
|
|
||||||
++ builtins.map (
|
|
||||||
radarr:
|
|
||||||
"${
|
|
||||||
hmConfig.sops.templates."jellyseerr-${radarr.hostName}".path
|
|
||||||
}:/etc/jellyseerr/apps/radarr/${radarr.hostName}.json:ro"
|
|
||||||
) radarrs
|
|
||||||
++ builtins.map (
|
|
||||||
sonarr:
|
|
||||||
"${
|
|
||||||
hmConfig.sops.templates."jellyseerr-${sonarr.hostName}".path
|
|
||||||
}:/etc/jellyseerr/apps/sonarr/${sonarr.hostName}.json:ro"
|
|
||||||
) sonarrs;
|
|
||||||
environmentFiles = [ hmConfig.sops.templates.jellyseerr-env.path ];
|
|
||||||
labels = [
|
|
||||||
"traefik.enable=true"
|
|
||||||
"traefik.http.routers.jellyseerr.rule=Host(`request.karaolidis.com`)"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
unitConfig.After =
|
|
||||||
let
|
let
|
||||||
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
|
preStart = pkgs.writeTextFile {
|
||||||
|
name = "pre-start.sh";
|
||||||
|
executable = true;
|
||||||
|
text = builtins.readFile ./pre-start.sh;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
[ "sops-nix.service" ] ++ arrServices;
|
[
|
||||||
|
"${preStart}:/etc/jellyseerr/pre-start.sh:ro"
|
||||||
|
"${hmConfig.sops.templates.jellyseerr.path}:/etc/jellyseerr/settings.default.json:ro"
|
||||||
|
"${volumes.jellyseerr.ref}:/var/lib/jellyseerr"
|
||||||
|
]
|
||||||
|
++ builtins.map (
|
||||||
|
radarr:
|
||||||
|
"${
|
||||||
|
hmConfig.sops.templates."jellyseerr-${radarr.hostName}".path
|
||||||
|
}:/etc/jellyseerr/apps/radarr/${radarr.hostName}.json:ro"
|
||||||
|
) radarrs
|
||||||
|
++ builtins.map (
|
||||||
|
sonarr:
|
||||||
|
"${
|
||||||
|
hmConfig.sops.templates."jellyseerr-${sonarr.hostName}".path
|
||||||
|
}:/etc/jellyseerr/apps/sonarr/${sonarr.hostName}.json:ro"
|
||||||
|
) sonarrs;
|
||||||
|
labels = [
|
||||||
|
"traefik.enable=true"
|
||||||
|
"traefik.http.routers.jellyseerr.rule=Host(`request.karaolidis.com`)"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
authelia.containerConfig.volumes = [
|
unitConfig.After =
|
||||||
"${hmConfig.sops.templates.authelia-jellyseerr.path}:/etc/authelia/conf.d/jellyseerr.yaml:ro"
|
let
|
||||||
];
|
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
|
||||||
|
in
|
||||||
|
[ "sops-nix.service" ] ++ arrServices;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,59 +1,5 @@
|
|||||||
# shellcheck shell=sh
|
# shellcheck shell=sh
|
||||||
|
|
||||||
JELLYFIN_HOST="${JELLYFIN_HOST:-http://jellyfin:8096}"
|
|
||||||
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
|
|
||||||
|
|
||||||
until public="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
|
|
||||||
echo "Waiting for Jellyfin to be ready..."
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
until [ "$(echo "$public" | jq -r '.StartupWizardCompleted')" = "true" ]; do
|
|
||||||
echo "Waiting for Jellyfin setup wizard to finish..."
|
|
||||||
sleep 1
|
|
||||||
public="$(curl -sf "${JELLYFIN_HOST}/System/Info/Public")"
|
|
||||||
done
|
|
||||||
|
|
||||||
token="$(curl -sf "$JELLYFIN_HOST/Users/AuthenticateByName" \
|
|
||||||
-X POST \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-H 'Authorization: MediaBrowser Client="jellyseerr-init", Device="sh", DeviceId="sh", Version="1.0"' \
|
|
||||||
--data-raw '{"Username":"'"$JELLYFIN_ADMIN_USERNAME"'","Pw":"'"$JELLYFIN_ADMIN_PASSWORD"'"}' \
|
|
||||||
| jq -r '.AccessToken')"
|
|
||||||
|
|
||||||
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
|
||||||
|
|
||||||
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
|
|
||||||
|
|
||||||
if [ -z "$jellyseerr_key" ] || [ "$jellyseerr_key" = "null" ]; then
|
|
||||||
curl -sf "$JELLYFIN_HOST/Auth/Keys?App=Jellyseerr" \
|
|
||||||
-X POST \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"'
|
|
||||||
|
|
||||||
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
|
||||||
|
|
||||||
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
serverId="$(echo "$public" | jq -r '.Id')"
|
|
||||||
|
|
||||||
libraries="$(curl -sf "$JELLYFIN_HOST/Library/VirtualFolders" \
|
|
||||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
|
||||||
|
|
||||||
libraries="$(
|
|
||||||
echo "$libraries" | jq '[
|
|
||||||
.[]
|
|
||||||
| {
|
|
||||||
id: .ItemId,
|
|
||||||
name: .Name,
|
|
||||||
enabled: .LibraryOptions.Enabled,
|
|
||||||
type: ( .CollectionType | if . == "movies" then "movie" elif . == "tvshows" then "show" else . end )
|
|
||||||
}
|
|
||||||
]'
|
|
||||||
)"
|
|
||||||
|
|
||||||
try_forever() {
|
try_forever() {
|
||||||
until "$@" 2>&1; do
|
until "$@" 2>&1; do
|
||||||
echo "Try failed: $* - retrying in 1s"
|
echo "Try failed: $* - retrying in 1s"
|
||||||
@@ -129,15 +75,9 @@ done
|
|||||||
|
|
||||||
tmpfile=$(mktemp)
|
tmpfile=$(mktemp)
|
||||||
jq -s \
|
jq -s \
|
||||||
--argjson libs "$libraries" \
|
|
||||||
--argjson radarr "$radarr_json" \
|
--argjson radarr "$radarr_json" \
|
||||||
--argjson sonarr "$sonarr_json" \
|
--argjson sonarr "$sonarr_json" \
|
||||||
--arg serverId "$serverId" \
|
|
||||||
--arg apiKey "$jellyseerr_key" \
|
|
||||||
'.[0] * .[1]
|
'.[0] * .[1]
|
||||||
| .jellyfin.serverId = $serverId
|
|
||||||
| .jellyfin.apiKey = $apiKey
|
|
||||||
| .jellyfin.libraries = $libs
|
|
||||||
| .radarr = $radarr
|
| .radarr = $radarr
|
||||||
| .sonarr = $sonarr' \
|
| .sonarr = $sonarr' \
|
||||||
/var/lib/jellyseerr/settings.json \
|
/var/lib/jellyseerr/settings.json \
|
||||||
|
@@ -0,0 +1,100 @@
|
|||||||
|
{ user, home }:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
inputs,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
hmConfig = config.home-manager.users.${user};
|
||||||
|
inherit (hmConfig.virtualisation.quadlet) volumes networks;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
networking.firewall.allowedTCPPorts = [ 32400 ];
|
||||||
|
|
||||||
|
home-manager.users.${user} = {
|
||||||
|
sops = {
|
||||||
|
secrets = {
|
||||||
|
"plex/machineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
||||||
|
"plex/processedMachineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
||||||
|
"plex/anonymousMachineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
|
||||||
|
};
|
||||||
|
|
||||||
|
templates.plex.content = ''
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Preferences
|
||||||
|
MachineIdentifier="${hmConfig.sops.placeholder."plex/machineIdentifier"}"
|
||||||
|
ProcessedMachineIdentifier="${hmConfig.sops.placeholder."plex/processedMachineIdentifier"}"
|
||||||
|
AnonymousMachineIdentifier="${hmConfig.sops.placeholder."plex/anonymousMachineIdentifier"}"
|
||||||
|
FriendlyName="jupiter"
|
||||||
|
PlexOnlineUsername="karaolidis"
|
||||||
|
PlexOnlineMail="nick@karaolidis.com"
|
||||||
|
PlexOnlineHome="1"
|
||||||
|
PublishServerOnPlexOnlineKey="1"
|
||||||
|
AcceptedEULA="1"
|
||||||
|
DlnaEnabled="0"
|
||||||
|
customConnections="https://beta.media.karaolidis.com:443"
|
||||||
|
secureConnections="1"
|
||||||
|
IPNetworkType="v4only"
|
||||||
|
PushNotificationsEnabled="1"
|
||||||
|
logDebug="0"
|
||||||
|
sendCrashReports="0"
|
||||||
|
WanTotalMaxUploadRate="1000000"
|
||||||
|
GdmEnabled="0"
|
||||||
|
RelayEnabled="0"
|
||||||
|
FSEventLibraryPartialScanEnabled="1"
|
||||||
|
FSEventLibraryUpdatesEnabled="1"
|
||||||
|
GenerateAdMarkerBehavior="asap"
|
||||||
|
GenerateBIFBehavior="asap"
|
||||||
|
GenerateChapterThumbBehavior="asap"
|
||||||
|
GenerateVADBehavior="asap"
|
||||||
|
LoudnessAnalysisBehavior="asap"
|
||||||
|
MusicAnalysisBehavior="asap"
|
||||||
|
ScheduledLibraryUpdatesEnabled="1"
|
||||||
|
watchMusicSections="1"
|
||||||
|
HardwareDevicePath="10de:24dd:17aa:3a54@0000:01:00.0"
|
||||||
|
OptimizerTranscodeCountLimit="0"
|
||||||
|
ButlerTaskRefreshLibraries="1"
|
||||||
|
CinemaTrailersFromBluRay="1"
|
||||||
|
CinemaTrailersFromTheater="1"
|
||||||
|
CinemaTrailersType="0"
|
||||||
|
/>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualisation.quadlet = {
|
||||||
|
networks.plex = { };
|
||||||
|
|
||||||
|
volumes = {
|
||||||
|
plex-data = { };
|
||||||
|
plex-cache = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
containers.plex = {
|
||||||
|
containerConfig = {
|
||||||
|
image = "docker-archive:${pkgs.dockerImages.plex}";
|
||||||
|
networks = [
|
||||||
|
networks.plex.ref
|
||||||
|
networks.traefik.ref
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"${hmConfig.sops.templates.plex.path}:/etc/plex/Preferences.xml:ro"
|
||||||
|
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
|
||||||
|
"${volumes.plex-data.ref}:/var/lib/plex"
|
||||||
|
"${volumes.plex-cache.ref}:/tmp/plex"
|
||||||
|
];
|
||||||
|
labels = [
|
||||||
|
"traefik.enable=true"
|
||||||
|
"traefik.http.routers.plex.rule=Host(`beta.media.karaolidis.com`)"
|
||||||
|
];
|
||||||
|
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
|
||||||
|
devices = [ "nvidia.com/gpu=all" ];
|
||||||
|
addCapabilities = [ "SYS_ADMIN" ];
|
||||||
|
publishPorts = [ "32400:32400/tcp" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
unitConfig.After = [ "sops-nix.service" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -24,7 +24,6 @@ final: prev:
|
|||||||
grafana-image-renderer = final.docker-image-grafana-image-renderer;
|
grafana-image-renderer = final.docker-image-grafana-image-renderer;
|
||||||
grafana-to-ntfy = final.docker-image-grafana-to-ntfy;
|
grafana-to-ntfy = final.docker-image-grafana-to-ntfy;
|
||||||
grafana = final.docker-image-grafana;
|
grafana = final.docker-image-grafana;
|
||||||
jellyfin = final.docker-image-jellyfin;
|
|
||||||
jellyseerr = final.docker-image-jellyseerr;
|
jellyseerr = final.docker-image-jellyseerr;
|
||||||
littlelink-server = final.docker-image-littlelink-server;
|
littlelink-server = final.docker-image-littlelink-server;
|
||||||
mariadb = final.docker-image-mariadb;
|
mariadb = final.docker-image-mariadb;
|
||||||
@@ -35,6 +34,7 @@ final: prev:
|
|||||||
ntfy = final.docker-image-ntfy;
|
ntfy = final.docker-image-ntfy;
|
||||||
oidcwarden = final.docker-image-oidcwarden;
|
oidcwarden = final.docker-image-oidcwarden;
|
||||||
outline = final.docker-image-outline;
|
outline = final.docker-image-outline;
|
||||||
|
plex = final.docker-image-plex;
|
||||||
postgresql = final.docker-image-postgresql;
|
postgresql = final.docker-image-postgresql;
|
||||||
prometheus = final.docker-image-prometheus;
|
prometheus = final.docker-image-prometheus;
|
||||||
prometheus-fail2ban-exporter = final.docker-image-prometheus-fail2ban-exporter;
|
prometheus-fail2ban-exporter = final.docker-image-prometheus-fail2ban-exporter;
|
||||||
@@ -53,20 +53,6 @@ final: prev:
|
|||||||
transmission-protonvpn = final.docker-image-transmission-protonvpn;
|
transmission-protonvpn = final.docker-image-transmission-protonvpn;
|
||||||
whoami = final.docker-image-whoami;
|
whoami = final.docker-image-whoami;
|
||||||
};
|
};
|
||||||
|
|
||||||
jellyfinPlugins = prev.jellyfinPlugins or { } // {
|
|
||||||
bookshelf = final.jellyfin-plugin-bookshelf-bin;
|
|
||||||
intro-skipper = final.jellyfin-plugin-intro-skipper-bin;
|
|
||||||
javascript-injector = final.jellyfin-plugin-javascript-injector-bin;
|
|
||||||
opensubtitles = final.jellyfin-plugin-opensubtitles-bin;
|
|
||||||
playbackreporting = final.jellyfin-plugin-playbackreporting-bin;
|
|
||||||
reports = final.jellyfin-plugin-reports-bin;
|
|
||||||
sso = final.jellyfin-plugin-sso-bin;
|
|
||||||
subtitleextract = final.jellyfin-plugin-subtitleextract-bin;
|
|
||||||
tmdbboxsets = final.jellyfin-plugin-tmdbboxsets-bin;
|
|
||||||
tvdb = final.jellyfin-plugin-tvdb-bin;
|
|
||||||
};
|
|
||||||
|
|
||||||
obsidianPlugins = prev.obsidianPlugins or { } // {
|
obsidianPlugins = prev.obsidianPlugins or { } // {
|
||||||
better-word-count = final.obsidian-plugin-better-word-count;
|
better-word-count = final.obsidian-plugin-better-word-count;
|
||||||
dataview = final.obsidian-plugin-dataview;
|
dataview = final.obsidian-plugin-dataview;
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
docker-image-grafana-image-renderer = import ./docker/grafana-image-renderer { inherit pkgs; };
|
docker-image-grafana-image-renderer = import ./docker/grafana-image-renderer { inherit pkgs; };
|
||||||
docker-image-grafana-to-ntfy = import ./docker/grafana-to-ntfy { inherit pkgs; };
|
docker-image-grafana-to-ntfy = import ./docker/grafana-to-ntfy { inherit pkgs; };
|
||||||
docker-image-grafana = import ./docker/grafana { inherit pkgs; };
|
docker-image-grafana = import ./docker/grafana { inherit pkgs; };
|
||||||
docker-image-jellyfin = import ./docker/jellyfin { inherit pkgs; };
|
|
||||||
docker-image-jellyseerr = import ./docker/jellyseerr { inherit pkgs; };
|
docker-image-jellyseerr = import ./docker/jellyseerr { inherit pkgs; };
|
||||||
docker-image-littlelink-server = import ./docker/littlelink-server { inherit pkgs; };
|
docker-image-littlelink-server = import ./docker/littlelink-server { inherit pkgs; };
|
||||||
docker-image-mariadb = import ./docker/mariadb { inherit pkgs; };
|
docker-image-mariadb = import ./docker/mariadb { inherit pkgs; };
|
||||||
@@ -28,6 +27,7 @@
|
|||||||
docker-image-ntfy = import ./docker/ntfy { inherit pkgs; };
|
docker-image-ntfy = import ./docker/ntfy { inherit pkgs; };
|
||||||
docker-image-oidcwarden = import ./docker/oidcwarden { inherit pkgs; };
|
docker-image-oidcwarden = import ./docker/oidcwarden { inherit pkgs; };
|
||||||
docker-image-outline = import ./docker/outline { inherit pkgs; };
|
docker-image-outline = import ./docker/outline { inherit pkgs; };
|
||||||
|
docker-image-plex = import ./docker/plex { inherit pkgs; };
|
||||||
docker-image-postgresql = import ./docker/postgresql { inherit pkgs; };
|
docker-image-postgresql = import ./docker/postgresql { inherit pkgs; };
|
||||||
docker-image-prometheus = import ./docker/prometheus { inherit pkgs; };
|
docker-image-prometheus = import ./docker/prometheus { inherit pkgs; };
|
||||||
docker-image-prometheus-fail2ban-exporter = import ./docker/prometheus-fail2ban-exporter {
|
docker-image-prometheus-fail2ban-exporter = import ./docker/prometheus-fail2ban-exporter {
|
||||||
@@ -52,21 +52,6 @@
|
|||||||
docker-image-transmission-protonvpn = import ./docker/transmission-protonvpn { inherit pkgs; };
|
docker-image-transmission-protonvpn = import ./docker/transmission-protonvpn { inherit pkgs; };
|
||||||
docker-image-whoami = import ./docker/whoami { inherit pkgs; };
|
docker-image-whoami = import ./docker/whoami { inherit pkgs; };
|
||||||
|
|
||||||
jellyfin-plugin-bookshelf-bin = import ./jellyfin/plugins/bookshelf { inherit pkgs; };
|
|
||||||
jellyfin-plugin-intro-skipper-bin = import ./jellyfin/plugins/intro-skipper { inherit pkgs; };
|
|
||||||
jellyfin-plugin-javascript-injector-bin = import ./jellyfin/plugins/javascript-injector {
|
|
||||||
inherit pkgs;
|
|
||||||
};
|
|
||||||
jellyfin-plugin-opensubtitles-bin = import ./jellyfin/plugins/opensubtitles { inherit pkgs; };
|
|
||||||
jellyfin-plugin-playbackreporting-bin = import ./jellyfin/plugins/playbackreporting {
|
|
||||||
inherit pkgs;
|
|
||||||
};
|
|
||||||
jellyfin-plugin-reports-bin = import ./jellyfin/plugins/reports { inherit pkgs; };
|
|
||||||
jellyfin-plugin-sso-bin = import ./jellyfin/plugins/sso { inherit pkgs; };
|
|
||||||
jellyfin-plugin-subtitleextract-bin = import ./jellyfin/plugins/subtitleextract { inherit pkgs; };
|
|
||||||
jellyfin-plugin-tmdbboxsets-bin = import ./jellyfin/plugins/tmdbboxsets { inherit pkgs; };
|
|
||||||
jellyfin-plugin-tvdb-bin = import ./jellyfin/plugins/tvdb { inherit pkgs; };
|
|
||||||
|
|
||||||
littlelink-server = import ./littlelink-server { inherit pkgs; };
|
littlelink-server = import ./littlelink-server { inherit pkgs; };
|
||||||
|
|
||||||
obsidian-plugin-better-word-count = import ./obsidian/plugins/better-word-count { inherit pkgs; };
|
obsidian-plugin-better-word-count = import ./obsidian/plugins/better-word-count { inherit pkgs; };
|
||||||
|
@@ -4,7 +4,7 @@ set -o errexit
|
|||||||
set -o nounset
|
set -o nounset
|
||||||
|
|
||||||
atticd "$@" &
|
atticd "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
|
|
||||||
if [ -f /etc/attic/post-start.sh ]; then
|
if [ -f /etc/attic/post-start.sh ]; then
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
|
@@ -1,77 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
let
|
|
||||||
jellyfin = pkgs.jellyfin.overrideAttrs (_: {
|
|
||||||
makeWrapperArgs = [
|
|
||||||
"--add-flags"
|
|
||||||
"--ffmpeg=${pkgs.jellyfin-ffmpeg}/bin/ffmpeg"
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
jellyfin-web = pkgs.runCommandLocal "jellyfin-web" { } ''
|
|
||||||
mkdir -p $out/var/www
|
|
||||||
cp -r ${pkgs.jellyfin-web}/share/jellyfin-web $out/var/www/jellyfin
|
|
||||||
'';
|
|
||||||
|
|
||||||
entrypoint = pkgs.writeTextFile {
|
|
||||||
name = "entrypoint";
|
|
||||||
executable = true;
|
|
||||||
destination = "/bin/entrypoint";
|
|
||||||
text = builtins.readFile ./entrypoint.sh;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
pkgs.dockerTools.buildImage {
|
|
||||||
name = "jellyfin";
|
|
||||||
fromImage = pkgs.docker-image-base;
|
|
||||||
|
|
||||||
copyToRoot = pkgs.buildEnv {
|
|
||||||
name = "root";
|
|
||||||
paths =
|
|
||||||
with pkgs;
|
|
||||||
[
|
|
||||||
entrypoint
|
|
||||||
jellyfin
|
|
||||||
jellyfin-web
|
|
||||||
jellyfin-ffmpeg
|
|
||||||
curl
|
|
||||||
jq
|
|
||||||
]
|
|
||||||
++ (with jellyfinPlugins; [
|
|
||||||
bookshelf
|
|
||||||
intro-skipper
|
|
||||||
javascript-injector
|
|
||||||
opensubtitles
|
|
||||||
playbackreporting
|
|
||||||
reports
|
|
||||||
sso
|
|
||||||
subtitleextract
|
|
||||||
tmdbboxsets
|
|
||||||
tvdb
|
|
||||||
]);
|
|
||||||
pathsToLink = [
|
|
||||||
"/bin"
|
|
||||||
"/lib"
|
|
||||||
"/var"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
Entrypoint = [ "entrypoint" ];
|
|
||||||
ExposedPorts = {
|
|
||||||
"8096/tcp" = { };
|
|
||||||
};
|
|
||||||
WorkingDir = "/var/lib/jellyfin";
|
|
||||||
Volumes = {
|
|
||||||
"/etc/jellyfin" = { };
|
|
||||||
"/var/lib/jellyfin/data" = { };
|
|
||||||
"/var/lib/jellyfin/metadata" = { };
|
|
||||||
"/var/lib/jellyfin/root" = { };
|
|
||||||
"/var/log/jellyfin" = { };
|
|
||||||
"/tmp/jellyfin" = { };
|
|
||||||
};
|
|
||||||
Env = [
|
|
||||||
# FIXME: https://github.com/NixOS/nixpkgs/issues/176081
|
|
||||||
"FONTCONFIG_FILE=${pkgs.fontconfig.out}/etc/fonts/fonts.conf"
|
|
||||||
"FONTCONFIG_PATH=${pkgs.fontconfig.out}/etc/fonts/"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
start() {
|
|
||||||
jellyfin \
|
|
||||||
-w /var/www/jellyfin \
|
|
||||||
-c /etc/jellyfin \
|
|
||||||
-d /var/lib/jellyfin \
|
|
||||||
-l /var/log/jellyfin \
|
|
||||||
-C /tmp/jellyfin \
|
|
||||||
"$@" &
|
|
||||||
|
|
||||||
PID=$!
|
|
||||||
}
|
|
||||||
|
|
||||||
start "$@"
|
|
||||||
|
|
||||||
if [ -f /etc/jellyfin/setup.sh ]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
. /etc/jellyfin/setup.sh
|
|
||||||
|
|
||||||
kill "$PID"
|
|
||||||
wait "$PID" 2>/dev/null || true
|
|
||||||
start "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
trap 'kill -INT "$PID"' INT TERM
|
|
||||||
wait "$PID"
|
|
||||||
exit $?
|
|
@@ -34,6 +34,6 @@ fi
|
|||||||
|
|
||||||
trap 'kill -QUIT "$PID"' INT TERM
|
trap 'kill -QUIT "$PID"' INT TERM
|
||||||
mariadbd --user=root --datadir="$DATADIR" "$@" &
|
mariadbd --user=root --datadir="$DATADIR" "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
wait "$PID"
|
wait "$PID"
|
||||||
exit $?
|
exit $?
|
||||||
|
@@ -34,6 +34,6 @@ fi
|
|||||||
|
|
||||||
trap 'kill -QUIT "$PID"' INT TERM
|
trap 'kill -QUIT "$PID"' INT TERM
|
||||||
mysqld --user=root --datadir="$DATADIR" "$@" &
|
mysqld --user=root --datadir="$DATADIR" "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
wait "$PID"
|
wait "$PID"
|
||||||
exit $?
|
exit $?
|
||||||
|
48
packages/docker/plex/default.nix
Normal file
48
packages/docker/plex/default.nix
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
entrypoint = pkgs.writeTextFile {
|
||||||
|
name = "entrypoint";
|
||||||
|
executable = true;
|
||||||
|
destination = "/bin/entrypoint";
|
||||||
|
text = builtins.readFile ./entrypoint.sh;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.dockerTools.buildImage {
|
||||||
|
name = "plex";
|
||||||
|
fromImage = pkgs.docker-image-base;
|
||||||
|
|
||||||
|
copyToRoot = pkgs.buildEnv {
|
||||||
|
name = "root";
|
||||||
|
paths = with pkgs; [
|
||||||
|
entrypoint
|
||||||
|
util-linux
|
||||||
|
plex
|
||||||
|
xmlstarlet
|
||||||
|
curl
|
||||||
|
jq
|
||||||
|
];
|
||||||
|
pathsToLink = [
|
||||||
|
"/bin"
|
||||||
|
"/lib"
|
||||||
|
"/var"
|
||||||
|
"/usr"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
Entrypoint = [ "entrypoint" ];
|
||||||
|
ExposedPorts = {
|
||||||
|
"32400/tcp" = { };
|
||||||
|
};
|
||||||
|
WorkingDir = "/var/lib/plex";
|
||||||
|
Volumes = {
|
||||||
|
"/var/lib/plex" = { };
|
||||||
|
"/tmp/plex" = { };
|
||||||
|
};
|
||||||
|
Env = [
|
||||||
|
"LD_LIBRARY_PATH=/run/opengl-driver/lib"
|
||||||
|
"PLEX_DATADIR=/var/lib/plex"
|
||||||
|
"PLEX_MEDIA_SERVER_TMPDIR=/tmp/plex"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
54
packages/docker/plex/entrypoint.sh
Normal file
54
packages/docker/plex/entrypoint.sh
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
PREFERENCES="/var/lib/plex/Plex Media Server/Preferences.xml"
|
||||||
|
TEMPLATE_PREFERENCES="/etc/plex/Preferences.xml"
|
||||||
|
|
||||||
|
getPref() {
|
||||||
|
xmlstarlet sel -t -v "/Preferences/@$1" "$PREFERENCES" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
setPref() {
|
||||||
|
name="$1"
|
||||||
|
value="$2"
|
||||||
|
|
||||||
|
xmlstarlet ed --inplace \
|
||||||
|
-d "/Preferences/@${name}" \
|
||||||
|
-i "/Preferences" -t attr -n "${name}" -v "${value}" \
|
||||||
|
"$PREFERENCES"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$PREFERENCES")"
|
||||||
|
if [ ! -f "$PREFERENCES" ]; then
|
||||||
|
echo '<?xml version="1.0" encoding="utf-8"?><Preferences/>' > "$PREFERENCES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$TEMPLATE_PREFERENCES" ]; then
|
||||||
|
ATTRS="$(xmlstarlet sel -t -m "/Preferences/@*" -v "concat(name(),'=',.)" -n "$TEMPLATE_PREFERENCES")"
|
||||||
|
|
||||||
|
if [ -n "$ATTRS" ]; then
|
||||||
|
set --
|
||||||
|
|
||||||
|
while IFS='=' read -r name value; do
|
||||||
|
[ -z "$name" ] && continue
|
||||||
|
set -- "$@" -d "/Preferences/@${name}"
|
||||||
|
set -- "$@" -i "/Preferences" -t attr -n "${name}" -v "${value}"
|
||||||
|
done <<EOF
|
||||||
|
$ATTRS
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
xmlstarlet ed --inplace "$@" "$PREFERENCES"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "/var/lib/plex/Plex Media Server/plexmediaserver.pid"
|
||||||
|
plexmediaserver &
|
||||||
|
PID="$!"
|
||||||
|
|
||||||
|
trap 'kill -QUIT "$PID"' INT TERM
|
||||||
|
wait "$PID"
|
||||||
|
exit $?
|
@@ -32,7 +32,7 @@ set_config_value "InstanceName" "${INSTANCE_NAME:-Prowlarr}"
|
|||||||
set_config_value "AnalyticsEnabled" "False"
|
set_config_value "AnalyticsEnabled" "False"
|
||||||
|
|
||||||
Prowlarr -data=/var/lib/prowlarr -nobrowser "$@" &
|
Prowlarr -data=/var/lib/prowlarr -nobrowser "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
|
|
||||||
if [ -f /etc/prowlarr/post-start.sh ]; then
|
if [ -f /etc/prowlarr/post-start.sh ]; then
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
|
@@ -32,7 +32,7 @@ set_config_value "InstanceName" "${INSTANCE_NAME:-Radarr}"
|
|||||||
set_config_value "AnalyticsEnabled" "False"
|
set_config_value "AnalyticsEnabled" "False"
|
||||||
|
|
||||||
Radarr -data=/var/lib/radarr -nobrowser "$@" &
|
Radarr -data=/var/lib/radarr -nobrowser "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
|
|
||||||
if [ -f /etc/radarr/post-start.sh ]; then
|
if [ -f /etc/radarr/post-start.sh ]; then
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
|
@@ -32,7 +32,7 @@ set_config_value "InstanceName" "${INSTANCE_NAME:-Sonarr}"
|
|||||||
set_config_value "AnalyticsEnabled" "False"
|
set_config_value "AnalyticsEnabled" "False"
|
||||||
|
|
||||||
Sonarr -data=/var/lib/sonarr -nobrowser "$@" &
|
Sonarr -data=/var/lib/sonarr -nobrowser "$@" &
|
||||||
PID=$!
|
PID="$!"
|
||||||
|
|
||||||
if [ -f /etc/sonarr/post-start.sh ]; then
|
if [ -f /etc/sonarr/post-start.sh ]; then
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
|
@@ -68,7 +68,7 @@ transmission-daemon -f \
|
|||||||
--bind-address-ipv4 "$BIND_IP" \
|
--bind-address-ipv4 "$BIND_IP" \
|
||||||
--bind-address-ipv6 "::1" \
|
--bind-address-ipv6 "::1" \
|
||||||
"$@" > "$PIPE" 2>&1 &
|
"$@" > "$PIPE" 2>&1 &
|
||||||
PID=$!
|
PID="$!"
|
||||||
|
|
||||||
CAT_PIPE=$(mktemp -u)
|
CAT_PIPE=$(mktemp -u)
|
||||||
GREP_PIPE=$(mktemp -u)
|
GREP_PIPE=$(mktemp -u)
|
||||||
@@ -108,7 +108,7 @@ rpc_url="http://127.0.0.1:9091${rpc_path}rpc/"
|
|||||||
sleep 45
|
sleep 45
|
||||||
done
|
done
|
||||||
) &
|
) &
|
||||||
NATPMP_PID=$!
|
NATPMP_PID="$!"
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-bookshelf-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "bookshelf";
|
|
||||||
version = "12";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-bookshelf/releases/download/v${finalAttrs.version}/bookshelf_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-P85SLXaJuFIv9AmAE6mPbxZDMBhqEt+88dZiPUKu2iQ=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/bookshelf
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,22 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-intro-skipper-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "intro-skipper";
|
|
||||||
version = "10.10/v1.10.10.23";
|
|
||||||
|
|
||||||
src =
|
|
||||||
let
|
|
||||||
parts = pkgs.lib.strings.splitString "/" finalAttrs.version;
|
|
||||||
full = builtins.elemAt parts 1;
|
|
||||||
in
|
|
||||||
pkgs.fetchzip {
|
|
||||||
url = "https://github.com/intro-skipper/intro-skipper/releases/download/${finalAttrs.version}/intro-skipper-${full}.zip";
|
|
||||||
sha256 = "sha256-r+syY/AlErws1xVkkiWm51aI+QxtefdLDc/sWC7oVo8=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/intro-skipper
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-javascript-injector-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "javascript-injector";
|
|
||||||
version = "2.0.0.0";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/n00bcodr/Jellyfin-JavaScript-Injector/releases/download/${finalAttrs.version}/javascript-injector-${finalAttrs.version}.zip";
|
|
||||||
sha256 = "sha256-BzT4Hk4ulHsnkV9eKyy2oK6su98Am0x6rydfjAY/AWY=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/javascript-injector
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-opensubtitles-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "opensubtitles";
|
|
||||||
version = "20";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-opensubtitles/releases/download/v${finalAttrs.version}/open-subtitles_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-U17wQn32GB4nh05ExYJhzRw4nDvYOCB4EJtDoaaUnjI=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/opensubtitles
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-playbackreporting-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "playbackreporting";
|
|
||||||
version = "16";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-playbackreporting/releases/download/v${finalAttrs.version}/playback-reporting_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-UrWxS0CpeeW4nYNyRNxnK0jqiAqXwfLv3YfFokfVH0A=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/playbackreporting
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-reports-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "reports";
|
|
||||||
version = "17";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-reports/releases/download/v${finalAttrs.version}/reports_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-kN1UDhx5/1sw3PO5co2YkfbZNiDj56F2YAT8S/0EdZM=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/reports
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-sso-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "sso";
|
|
||||||
version = "3.5.2.4";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/9p4/jellyfin-plugin-sso/releases/download/v${finalAttrs.version}/sso-authentication_${finalAttrs.version}.zip";
|
|
||||||
sha256 = "sha256-e+w5m6/7vRAynStDj34eBexfCIEgDJ09huHzi5gQEbo=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/sso
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-subtitleextract-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "subtitleextract";
|
|
||||||
version = "4";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-subtitleextract/releases/download/v${finalAttrs.version}/subtitle-extract_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-FstPWUYsZg416DNshIV4yOvbg6U21cRxKse8hITUyBY=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/subtitleextract
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-tmdbboxsets-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "tmdbboxsets";
|
|
||||||
version = "11";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-tmdbboxsets/releases/download/v${finalAttrs.version}/tmdb-box-sets_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-cO3hpjFacS62kdXn8ebS7oMtFT9LJAt8Q4b36aSxwCQ=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/tmdbboxsets
|
|
||||||
'';
|
|
||||||
})
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
# AUTO-UPDATE: nix-update --flake jellyfin-plugin-tvdb-bin
|
|
||||||
pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "tvdb";
|
|
||||||
version = "19";
|
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
|
||||||
url = "https://github.com/jellyfin/jellyfin-plugin-tvdb/releases/download/v${finalAttrs.version}/thetvdb_${finalAttrs.version}.0.0.0.zip";
|
|
||||||
sha256 = "sha256-011wpVwQy562XDAwAQ44GJTbu/ESHcyo5F/wrtNBAcs=";
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/var/lib/jellyfin/plugins
|
|
||||||
cp -r $src $out/var/lib/jellyfin/plugins/tvdb
|
|
||||||
'';
|
|
||||||
})
|
|
Submodule submodules/secrets updated: dab48ad237...54a416e316
Reference in New Issue
Block a user