authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-05 20:33:28-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-08 02:03:19-08:00
logd7aaed393d5ab61468014ee56d2e3393d23beeb7
tree7fe161b043ab877e2a61d70cbfb97792e90ca16c
parent475b7d66a8d2e212107ca5ed624b515b45c90e5e
signaturelock-open Commit is signed but in an unrecognized format.

feat: jellyfin with sso

so this doesn't setup automatic installation of the provider within jellyfin. i frankly do not care enough to get that part of the auth implemented.

5 files changed, 66 insertions(+), 8 deletions(-)

.env.example-1
...@@ -21,7 +21,6 @@ POSTGRES_PASSWORD=...@@ -21,7 +21,6 @@ POSTGRES_PASSWORD=
21POSTGRES_PASSWORD_AUTHELIA=21POSTGRES_PASSWORD_AUTHELIA=
22POSTGRES_PASSWORD_FORGEJO=22POSTGRES_PASSWORD_FORGEJO=
23POSTGRES_PASSWORD_DAWARICH=23POSTGRES_PASSWORD_DAWARICH=
24POSTGRES_PASSWORD_JELLYFIN=
25POSTGRES_PASSWORD_LLDAP=24POSTGRES_PASSWORD_LLDAP=
2625
27CLOVER_SOT_KEY=26CLOVER_SOT_KEY=
compose.yaml+3-3
...@@ -56,8 +56,6 @@ services:...@@ -56,8 +56,6 @@ services:
56 condition: service_started56 condition: service_started
57 postgres:57 postgres:
58 condition: service_healthy58 condition: service_healthy
59 redis:
60 condition: service_healthy
61 healthcheck:59 healthcheck:
62 test:60 test:
63 - "CMD-SHELL"61 - "CMD-SHELL"
...@@ -242,6 +240,9 @@ services:...@@ -242,6 +240,9 @@ services:
242 net.paperclover.list.domain: jelly240 net.paperclover.list.domain: jelly
243 net.paperclover.list.priority: 110241 net.paperclover.list.priority: 110
244 net.paperclover.list.access: media242 net.paperclover.list.access: media
243 depends_on:
244 keycloak:
245 condition: service_healthy
245 navidrome: # port 4533246 navidrome: # port 4533
246 container_name: navidrome247 container_name: navidrome
247 image: deluan/navidrome:latest248 image: deluan/navidrome:latest
...@@ -384,7 +385,6 @@ services:...@@ -384,7 +385,6 @@ services:
384 POSTGRES_PASSWORD_FORGEJO: "${POSTGRES_PASSWORD_FORGEJO:?}"385 POSTGRES_PASSWORD_FORGEJO: "${POSTGRES_PASSWORD_FORGEJO:?}"
385 POSTGRES_PASSWORD_KEYCLOAK: "${POSTGRES_PASSWORD_KEYCLOAK:?}"386 POSTGRES_PASSWORD_KEYCLOAK: "${POSTGRES_PASSWORD_KEYCLOAK:?}"
386 POSTGRES_PASSWORD_DAWARICH: "${POSTGRES_PASSWORD_DAWARICH:?}"387 POSTGRES_PASSWORD_DAWARICH: "${POSTGRES_PASSWORD_DAWARICH:?}"
387 POSTGRES_PASSWORD_JELLYFIN: "${POSTGRES_PASSWORD_JELLYFIN:?}"
388 volumes:388 volumes:
389 - "${APP_ROOT}/pg_data:/var/lib/postgresql/data/"389 - "${APP_ROOT}/pg_data:/var/lib/postgresql/data/"
390 healthcheck:390 healthcheck:
config/jellyfin/branding.xml+29-2
...@@ -1,7 +1,34 @@...@@ -1,7 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<BrandingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">2<BrandingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3 <LoginDisclaimer>and then we knew, just like paper airplanes: that we could fly.</LoginDisclaimer>3 <LoginDisclaimer>&lt;form action=&quot;/sso/OID/start/snow&quot; id=&quot;snow-sign-on-form&quot;&gt;
4 <CustomCss></CustomCss>4 &lt;h2&gt;you are not logged in...&lt;/h2&gt;
5 &lt;button class=&quot;raised block emby-button button-submit snow-sign-on&quot;&gt;
6 snow sign on
7 &lt;/button&gt;
8&lt;/form&gt;
9
10and then we knew, just like paper airplanes: that we could fly.</LoginDisclaimer>
11 <CustomCss>.loginDisclaimer {width:100%}
12.manualLoginForm, .visualLoginForm, .btnForgotPassword { display: none; }
13.btnQuick {text-transform:lowercase;margin-top: 150px;display:flex}
14#loginPage .emby-button { background: var(--hover-background); width: 100%!important; max-width: 400px!important; margin-left: auto; margin-right: auto; border-radius: 8px; }
15#snow-sign-on-form {transform: translateY(-208px) }
16.snow-sign-on { display: flex; align-items; center; justify-content: center }
17.snow-sign-on:before, .btnQuick:before {
18display: block;
19content: &quot; &quot;;
20width:24px;
21height:24px;
22margin-right: 0.5rem;
23}
24.snow-sign-on:before{
25background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDIwTDguNzUgMTcuNUw2IDE4TTEwIDRMOC43NSA2LjVMNiA2TTE0IDIwTDE1LjI1IDE3LjVMMTggMThNMTQgNEwxNS4yNSA2LjVMMTggNk0xNyAyMUwxNCAxNU0xNCAxNUgxME0xNCAxNUwxNS41IDEyTTEwIDE1TDcgMjFNMTAgMTVMOC41IDEyTTE3IDNMMTQgOU0xNCA5TDE1LjUgMTJNMTQgOUgxME0xNS41IDEySDIyTTIgMTJIOC41TTguNSAxMkwxMCA5TTEwIDlMNyAzTTIwIDEwTDE4LjUgMTJMMjAgMTRNNCAxMEw1LjUgMTJMNCAxNCIgc3Ryb2tlPSJ1cmwoI3BhaW50MF9saW5lYXJfNTQ4XzE5KSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEwIDIwTDguNzUgMTcuNUw2IDE4TTEwIDRMOC43NSA2LjVMNiA2TTE0IDIwTDE1LjI1IDE3LjVMMTggMThNMTQgNEwxNS4yNSA2LjVMMTggNk0xNyAyMUwxNCAxNU0xNCAxNUgxME0xNCAxNUwxNS41IDEyTTEwIDE1TDcgMjFNMTAgMTVMOC41IDEyTTE3IDNMMTQgOU0xNCA5TDE1LjUgMTJNMTQgOUgxME0xNS41IDEySDIyTTIgMTJIOC41TTguNSAxMkwxMCA5TTEwIDlMNyAzTTIwIDEwTDE4LjUgMTJMMjAgMTRNNCAxMEw1LjUgMTJMNCAxNCIgc3Ryb2tlPSJ1cmwoI3BhaW50MV9saW5lYXJfNTQ4XzE5KSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl81NDhfMTkiIHgxPSI0IiB5MT0iLTIiIHgyPSIxOC41IiB5Mj0iMjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzIyM0Q5OSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMxNTQzOTIiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzU0OF8xOSIgeDE9IjEwIiB5MT0iLTMiIHgyPSIxOCIgeTI9IjI3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGMkUzRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRjJGOEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+);
26}
27.btnQuick:before{
28mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNoaWVsZC1lbGxpcHNpcy1pY29uIGx1Y2lkZS1zaGllbGQtZWxsaXBzaXMiPjxwYXRoIGQ9Ik0yMCAxM2MwIDUtMy41IDcuNS03LjY2IDguOTVhMSAxIDAgMCAxLS42Ny0uMDFDNy41IDIwLjUgNCAxOCA0IDEzVjZhMSAxIDAgMCAxIDEtMWMyIDAgNC41LTEuMiA2LjI0LTIuNzJhMS4xNyAxLjE3IDAgMCAxIDEuNTIgMEMxNC41MSAzLjgxIDE3IDUgMTkgNWExIDEgMCAwIDEgMSAxeiIvPjxwYXRoIGQ9Ik04IDEyaC4wMSIvPjxwYXRoIGQ9Ik0xMiAxMmguMDEiLz48cGF0aCBkPSJNMTYgMTJoLjAxIi8+PC9zdmc+);
29mask-size:cover;
30background:var(--main-text);
31}</CustomCss>
5 <SplashscreenEnabled>true</SplashscreenEnabled>32 <SplashscreenEnabled>true</SplashscreenEnabled>
6 <SplashscreenLocation>/config/upload/splashscreen-upload.jpg</SplashscreenLocation>33 <SplashscreenLocation>/config/upload/splashscreen-upload.jpg</SplashscreenLocation>
7</BrandingOptions>34</BrandingOptions>
config/keycloak/init.py+34-1
...@@ -39,7 +39,7 @@ realm_config = {...@@ -39,7 +39,7 @@ realm_config = {
39 "replyToDisplayName": "clover caruso",39 "replyToDisplayName": "clover caruso",
40 "envelopeFrom": "",40 "envelopeFrom": "",
41 "host": env("MAILER_ADDRESS"),41 "host": env("MAILER_ADDRESS"),
42 "port": "587",42 "port": "465",
43 "ssl": "true",43 "ssl": "true",
44 "starttls": "true",44 "starttls": "true",
45 "auth": "true",45 "auth": "true",
...@@ -161,6 +161,39 @@ def configure():...@@ -161,6 +161,39 @@ def configure():
161 client_data = kc_admin.get_client(client_id)161 client_data = kc_admin.get_client(client_id)
162 _ = Path("/shared/forgejo").write_text(client_data["secret"])162 _ = Path("/shared/forgejo").write_text(client_data["secret"])
163163
164 # jellyfin
165 client_id = kc_admin.create_client(
166 {
167 "protocol": "openid-connect",
168 "clientId": "jellyfin",
169 "name": "Jellyfin",
170 "description": "",
171 "publicClient": False,
172 "authorizationServicesEnabled": False,
173 "serviceAccountsEnabled": False,
174 "implicitFlowEnabled": False,
175 "directAccessGrantsEnabled": False,
176 "standardFlowEnabled": True,
177 "frontchannelLogout": True,
178 "attributes": {
179 "saml_idp_initiated_sso_url_name": "",
180 "standard.token.exchange.enabled": False,
181 "oauth2.device.authorization.grant.enabled": False,
182 "oidc.ciba.grant.enabled": False,
183 "pkce.code.challenge.method": "",
184 "dpop.bound.access.tokens": "false",
185 "post.logout.redirect.uris": "*",
186 },
187 "alwaysDisplayInConsole": False,
188 "rootUrl": "",
189 "baseUrl": "",
190 "redirectUris": ["*"],
191 },
192 skip_exists=True,
193 )
194 client_data = kc_admin.get_client(client_id)
195 _ = Path("/shared/jellyfin").write_text(client_data["secret"])
196
164 # forward auth197 # forward auth
165 client_id = kc_admin.create_client(198 client_id = kc_admin.create_client(
166 {199 {
config/postgres/init.sh-1
...@@ -45,7 +45,6 @@ ensure_db_user "clover" "$POSTGRES_PASSWORD"...@@ -45,7 +45,6 @@ ensure_db_user "clover" "$POSTGRES_PASSWORD"
45ensure_db_user "keycloak" "$POSTGRES_PASSWORD_KEYCLOAK"45ensure_db_user "keycloak" "$POSTGRES_PASSWORD_KEYCLOAK"
46ensure_db_user "dawarich" "$POSTGRES_PASSWORD_DAWARICH"46ensure_db_user "dawarich" "$POSTGRES_PASSWORD_DAWARICH"
47ensure_db_user "forgejo" "$POSTGRES_PASSWORD_FORGEJO"47ensure_db_user "forgejo" "$POSTGRES_PASSWORD_FORGEJO"
48ensure_db_user "jellyfin" "$POSTGRES_PASSWORD_JELLYFIN"
4948
50printf "Everything is ready!"49printf "Everything is ready!"
5150