| author | |
| committer | |
| log | 914005dab4ce28f6ba6f52c236f3c621549630ce |
| tree | e783f54310637f193eae649546902d99d36b15b5 |
| parent | 4f9fc9b6a085ffbb5f78850f91023a01ed4c410f |
| signature |
33 files changed, 2106 insertions(+), 234 deletions(-)
.env.example+13-4| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | # shellcheck disable | 1 | # shellcheck disable |
| 2 | HOME_DOMAIN=local.test | 2 | HOME_DOMAIN=local.test |
| 3 | ADMIN_EMAIL= | 3 | ADMIN_EMAIL= |
| 4 | USER_ID="501" | 4 | USER_ID="1000" |
| 5 | GROUP_ID="20" | 5 | GROUP_ID="1000" |
| 6 | 6 | ||
| 7 | APP_ROOT=./.apps | 7 | APP_ROOT=./.apps |
| 8 | STORE_ROOT=/Volumes | 8 | STORE_ROOT=/Volumes |
| ... | @@ -11,16 +11,25 @@ MEDIA_ROOT=/Volumes/media | ... | @@ -11,16 +11,25 @@ MEDIA_ROOT=/Volumes/media |
| 11 | 11 | ||
| 12 | MAILER_ADDRESS=smtp.example.com | 12 | MAILER_ADDRESS=smtp.example.com |
| 13 | MAILER_USERNAME=me@paperclover.net | 13 | MAILER_USERNAME=me@paperclover.net |
| 14 | MAILER_PASSWORD=yolo | 14 | MAILER_PASSWORD= |
| 15 | |||
| 16 | KEYCLOAK_ADMIN_PASSWORD= | ||
| 17 | FORWARD_AUTH_KEY= | ||
| 15 | 18 | ||
| 16 | POSTGRES_PASSWORD= | 19 | POSTGRES_PASSWORD= |
| 17 | POSTGRES_PASSWORD_AUTHELIA= | 20 | POSTGRES_PASSWORD_AUTHELIA= |
| 18 | POSTGRES_PASSWORD_FORGEJO= | 21 | POSTGRES_PASSWORD_FORGEJO= |
| 19 | POSTGRES_PASSWORD_DAWARICH= | 22 | POSTGRES_PASSWORD_DAWARICH= |
| 20 | POSTGRES_PASSWORD_JELLYFIN= | 23 | POSTGRES_PASSWORD_JELLYFIN= |
| 24 | POSTGRES_PASSWORD_LLDAP= | ||
| 21 | 25 | ||
| 22 | CLOVER_SOT_KEY= | 26 | CLOVER_SOT_KEY= |
| 23 | 27 | ||
| 28 | ANUBIS_PRIVATE_KEY= | ||
| 29 | |||
| 24 | FORGEJO_SERVER_LFS_JWT_SECRET= | 30 | FORGEJO_SERVER_LFS_JWT_SECRET= |
| 25 | FORGEJO_OAUTH2_JWT_SECRET= | 31 | FORGEJO_OAUTH2_JWT_SECRET= |
| 26 | FORGEJO_SECURITY_INTERNAL_TOKEN= | 32 | FORGEJO_OPENID_SECRET= |
| 33 | FORGEJO_SECURITY_SECRET_KEY= | ||
| 34 | FORGEJO_SECURITY_INTERNAL_TOKEN=(jwt) | ||
| 35 |
compose.yaml+189-40| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | services: | 1 | services: |
| 2 | # web server | 2 | # web server |
| 3 | caddy: | 3 | caddy: # port 80, 443 |
| 4 | container_name: caddy | 4 | container_name: caddy |
| 5 | image: caddy:latest | 5 | image: caddy:2.11 |
| 6 | ports: | 6 | ports: |
| 7 | - 80:80 | 7 | - 80:80 |
| 8 | - 443:443 | 8 | - 443:443 |
| ... | @@ -12,46 +12,110 @@ services: | ... | @@ -12,46 +12,110 @@ services: |
| 12 | - "./config:/etc/caddy:ro" | 12 | - "./config:/etc/caddy:ro" |
| 13 | - "${APP_ROOT}/caddy:/data/caddy:rw" | 13 | - "${APP_ROOT}/caddy:/data/caddy:rw" |
| 14 | environment: | 14 | environment: |
| 15 | - HOME_DOMAIN | 15 | HOME_DOMAIN: "${HOME_DOMAIN}" |
| 16 | develop: | 16 | develop: |
| 17 | watch: | 17 | watch: |
| 18 | - path: ./config/Caddyfile | 18 | - path: ./config/Caddyfile |
| 19 | action: restart | 19 | action: restart |
| 20 | # SSO | 20 | # SSO |
| 21 | authelia: | 21 | keycloak: # port 80, 443 |
| 22 | image: "authelia/authelia" | 22 | container_name: keycloak |
| 23 | container_name: authelia | 23 | build: |
| 24 | context: ./config/keycloak | ||
| 25 | dockerfile: Dockerfile | ||
| 26 | pull_policy: build | ||
| 24 | volumes: | 27 | volumes: |
| 25 | - "./config/authelia:/config" | 28 | - "${APP_ROOT}/keycloak:/shared:rw" |
| 26 | restart: "unless-stopped" | 29 | - "./config/keycloak/theme:/opt/keycloak/themes/snow:ro" |
| 30 | - "./config/font:/opt/keycloak/themes/snow/login/resources/font:ro" | ||
| 27 | secrets: | 31 | secrets: |
| 28 | - "authelia-jwt-secret" | 32 | - keycloak-crt |
| 29 | - "authelia-session-secret" | 33 | - keycloak-key |
| 30 | - "authelia-encryption-key" | ||
| 31 | environment: | 34 | environment: |
| 32 | PUID: "$USER_ID" | 35 | KC_BOOTSTRAP_ADMIN_USERNAME: admin |
| 33 | PGID: "$GROUP_ID" | 36 | KC_BOOTSTRAP_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD:?}" |
| 34 | TZ: America/Los_Angelas | 37 | KC_DB: postgres |
| 35 | AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: "/run/secrets/authelia-jwt-secret" | 38 | KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak |
| 36 | AUTHELIA_SESSION_SECRET_FILE: "/run/secrets/authelia-session-secret" | 39 | KC_DB_USERNAME: keycloak |
| 37 | AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: "/run/secrets/authelia-encryption-key" | 40 | KC_DB_PASSWORD: "${POSTGRES_PASSWORD_KEYCLOAK:?}" |
| 38 | AUTHELIA_STORAGE_POSTGRES_ADDRESS: postgres:5432 | 41 | KC_HOSTNAME: "auth.${HOME_DOMAIN:?}" |
| 39 | AUTHELIA_STORAGE_POSTGRES_DATABASE: authelia | 42 | KC_PROXY_HEADERS: xforwarded |
| 40 | AUTHELIA_STORAGE_POSTGRES_USERNAME: authelia | 43 | KC_HEALTH_ENABLED: true |
| 41 | AUTHELIA_STORAGE_POSTGRES_PASSWORD: "${POSTGRES_PASSWORD_AUTHELIA}" | 44 | KC_HTTP_ENABLED: true |
| 42 | AUTHELIA_NOTIFIER_SMTP_ADDRESS: "smtp://${MAILER_ADDRESS}" | 45 | KC_HTTP_MANAGEMENT_SCHEME: http |
| 43 | AUTHELIA_NOTIFIER_SMTP_USERNAME: "${MAILER_USERNAME}" | 46 | ADMIN_EMAIL: "${ADMIN_EMAIL:?}" |
| 44 | AUTHELIA_NOTIFIER_SMTP_PASSWORD: "${MAILER_PASSWORD}" | 47 | MAILER_ADDRESS: "${MAILER_ADDRESS:?}" |
| 48 | MAILER_USERNAME: "${MAILER_USERNAME:?}" | ||
| 49 | MAILER_PASSWORD: "${MAILER_PASSWORD:?}" | ||
| 50 | HOME_DOMAIN: "${HOME_DOMAIN:?}" | ||
| 51 | KC_HTTP_PORT: 80 | ||
| 52 | KC_HTTPS_PORT: 443 | ||
| 53 | restart: unless-stopped | ||
| 54 | read_only: false | ||
| 55 | depends_on: | ||
| 56 | caddy: | ||
| 57 | condition: service_started | ||
| 58 | postgres: | ||
| 59 | condition: service_healthy | ||
| 60 | redis: | ||
| 61 | condition: service_healthy | ||
| 62 | healthcheck: | ||
| 63 | test: | ||
| 64 | - "CMD-SHELL" | ||
| 65 | - "test -f /shared/forward-auth" | ||
| 66 | interval: 10s | ||
| 67 | timeout: 5s | ||
| 68 | retries: 3 | ||
| 69 | start_period: 120s | ||
| 70 | forward-auth: # port 80 | ||
| 71 | container_name: forward-auth | ||
| 72 | build: | ||
| 73 | context: config/keycloak/forward-auth | ||
| 74 | dockerfile: Dockerfile | ||
| 75 | pull_policy: build | ||
| 76 | restart: unless-stopped | ||
| 77 | user: "0:0" | ||
| 78 | environment: | ||
| 79 | OAUTH2_PROXY_CLIENT_ID: "forward-auth" | ||
| 80 | OAUTH2_PROXY_COOKIE_SECRET: "${FORWARD_AUTH_KEY:?}" | ||
| 81 | OAUTH2_PROXY_OIDC_ISSUER_URL: "https://auth.${HOME_DOMAIN:?}/realms/master" | ||
| 82 | OAUTH2_PROXY_CODE_CHALLENGE_METHOD: "S256" | ||
| 83 | OAUTH2_PROXY_PROVIDER: "keycloak-oidc" | ||
| 84 | OAUTH2_PROXY_EMAIL_DOMAINS: "*" | ||
| 85 | OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:80" | ||
| 86 | OAUTH2_PROXY_PINING_PATH: "/ping" | ||
| 87 | OAUTH2_PROXY_READY_PATH: "/ready" | ||
| 88 | OAUTH2_PROXY_PROXY_PREFIX: "/snow.oauth2" | ||
| 89 | OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "TRUE" | ||
| 90 | OAUTH2_PROXY_REVERSE_PROXY: "TRUE" | ||
| 91 | OAUTH2_PROXY_WHITELIST_DOMAINS: "*.${HOME_DOMAIN:?}" | ||
| 92 | OAUTH2_PROXY_SET_XAUTHREQUEST: "TRUE" | ||
| 93 | OAUTH2_PROXY_PASS_USER_HEADERS: "TRUE" | ||
| 94 | OAUTH2_PROXY_BACKEND_LOGOUT_URL: "https://auth.${HOME_DOMAIN:?}/realms/master/protocol/openid-connect/logout?id_token_hint={id_token}" | ||
| 95 | OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: "TRUE" | ||
| 96 | HOME_DOMAIN: "${HOME_DOMAIN:?}" | ||
| 97 | depends_on: | ||
| 98 | keycloak: | ||
| 99 | condition: service_healthy | ||
| 100 | volumes: | ||
| 101 | - "${APP_ROOT}/keycloak/forward-auth:/shared/forward-auth:ro" | ||
| 45 | # file manager | 102 | # file manager |
| 46 | copyparty: # port 3923 | 103 | copyparty: # port 80 |
| 47 | image: copyparty/ac | 104 | image: copyparty/ac |
| 48 | container_name: copyparty | 105 | container_name: copyparty |
| 49 | user: "$USER_ID:$GROUP_ID" | 106 | user: "$USER_ID:$GROUP_ID" |
| 50 | volumes: | 107 | volumes: |
| 51 | - "${STORE_ROOT}:/w" | 108 | - "${STORE_ROOT}:/w" |
| 52 | - "${APP_ROOT}/copyparty:/cfg" | 109 | - "${APP_ROOT}/copyparty:/cfg" |
| 53 | - ./config/copyparty.conf:/cfg/copyparty.conf | 110 | - ./config/copyparty.conf:/cfg/copyparty.conf:ro |
| 54 | restart: unless-stopped | 111 | restart: unless-stopped |
| 112 | labels: | ||
| 113 | net.paperclover.list.name: Copyparty File Storage | ||
| 114 | net.paperclover.list.domain: nas | ||
| 115 | net.paperclover.list.priority: 103 | ||
| 116 | depends_on: | ||
| 117 | forward-auth: | ||
| 118 | condition: service_started | ||
| 55 | # paper clover's database service | 119 | # paper clover's database service |
| 56 | clover-source-of-truth: | 120 | clover-source-of-truth: |
| 57 | build: | 121 | build: |
| ... | @@ -75,7 +139,10 @@ services: | ... | @@ -75,7 +139,10 @@ services: |
| 75 | interval: 10s | 139 | interval: 10s |
| 76 | timeout: 5s | 140 | timeout: 5s |
| 77 | retries: 3 | 141 | retries: 3 |
| 78 | start_period: 40s | 142 | start_period: 10s |
| 143 | depends_on: | ||
| 144 | caddy: | ||
| 145 | condition: service_healthy | ||
| 79 | # git hosting | 146 | # git hosting |
| 80 | forgejo: # port 3000 | 147 | forgejo: # port 3000 |
| 81 | container_name: forgejo | 148 | container_name: forgejo |
| ... | @@ -101,14 +168,25 @@ services: | ... | @@ -101,14 +168,25 @@ services: |
| 101 | - "/etc/localtime:/etc/localtime:ro" | 168 | - "/etc/localtime:/etc/localtime:ro" |
| 102 | - ./config/forgejo:/custom:ro | 169 | - ./config/forgejo:/custom:ro |
| 103 | - ./config/font:/custom/public/assets/font:ro | 170 | - ./config/font:/custom/public/assets/font:ro |
| 171 | labels: | ||
| 172 | net.paperclover.list.name: Clover Git Forge | ||
| 173 | net.paperclover.list.domain: git | ||
| 174 | net.paperclover.list.priority: 105 | ||
| 175 | depends_on: | ||
| 176 | keycloak: | ||
| 177 | condition: service_healthy | ||
| 178 | # forgejo-anubis: | ||
| 179 | # condition: service_healthy | ||
| 104 | forgejo-anubis: | 180 | forgejo-anubis: |
| 105 | container_name: forgejo-anubis | 181 | container_name: forgejo-anubis |
| 106 | image: ghcr.io/techarohq/anubis:latest | 182 | image: ghcr.io/techarohq/anubis:latest |
| 107 | user: "$USER_ID:$GROUP_ID" | 183 | user: "${USER_ID:?}:${GROUP_ID:?}" |
| 108 | restart: unless-stopped | 184 | restart: unless-stopped |
| 109 | environment: | 185 | environment: |
| 110 | BIND: :80 | 186 | BIND: :80 |
| 111 | TARGET: http://forgejo:3000 | 187 | TARGET: http://forgejo:3000 |
| 188 | COOKIE_DOMAIN: "${HOME_DOMAIN:?}" | ||
| 189 | ED25519_PRIVATE_KEY_HEX: "${ANUBIS_PRIVATE_KEY:?}" | ||
| 112 | forgejo-runner: | 190 | forgejo-runner: |
| 113 | container_name: forgejo-runner | 191 | container_name: forgejo-runner |
| 114 | image: code.forgejo.org/forgejo/runner:12 | 192 | image: code.forgejo.org/forgejo/runner:12 |
| ... | @@ -151,6 +229,11 @@ services: | ... | @@ -151,6 +229,11 @@ services: |
| 151 | - "./config/jellyfin:/config/custom:ro" | 229 | - "./config/jellyfin:/config/custom:ro" |
| 152 | - "./config/jellyfin/branding.xml:/config/config/branding.xml:ro" | 230 | - "./config/jellyfin/branding.xml:/config/config/branding.xml:ro" |
| 153 | - "./config/font:/usr/local/share/fonts/custom:ro" | 231 | - "./config/font:/usr/local/share/fonts/custom:ro" |
| 232 | labels: | ||
| 233 | net.paperclover.list.name: Cloverfin | ||
| 234 | net.paperclover.list.domain: jelly | ||
| 235 | net.paperclover.list.priority: 110 | ||
| 236 | net.paperclover.list.access: media | ||
| 154 | navidrome: # port 4533 | 237 | navidrome: # port 4533 |
| 155 | container_name: navidrome | 238 | container_name: navidrome |
| 156 | image: deluan/navidrome:latest | 239 | image: deluan/navidrome:latest |
| ... | @@ -163,6 +246,11 @@ services: | ... | @@ -163,6 +246,11 @@ services: |
| 163 | - ND_SCANNER_GROUPALBUMRELEASES=1 | 246 | - ND_SCANNER_GROUPALBUMRELEASES=1 |
| 164 | - ND_BASEURL=https://nd.${HOME_DOMAIN}/ | 247 | - ND_BASEURL=https://nd.${HOME_DOMAIN}/ |
| 165 | - ND_PID_ALBUM=folder | 248 | - ND_PID_ALBUM=folder |
| 249 | labels: | ||
| 250 | net.paperclover.list.name: Navidrome Music | ||
| 251 | net.paperclover.list.domain: nd | ||
| 252 | net.paperclover.list.priority: 109 | ||
| 253 | net.paperclover.list.access: media | ||
| 166 | # media aquisition | 254 | # media aquisition |
| 167 | qbittorrent: # webui on port 23938 | 255 | qbittorrent: # webui on port 23938 |
| 168 | platform: linux/amd64 # not available for aarch64 | 256 | platform: linux/amd64 # not available for aarch64 |
| ... | @@ -199,6 +287,11 @@ services: | ... | @@ -199,6 +287,11 @@ services: |
| 199 | - "${APP_ROOT}/qbittorrent:/config" | 287 | - "${APP_ROOT}/qbittorrent:/config" |
| 200 | - "./config/openvpn:/config/openvpn:ro" | 288 | - "./config/openvpn:/config/openvpn:ro" |
| 201 | - "/etc/localtime:/etc/localtime:ro" | 289 | - "/etc/localtime:/etc/localtime:ro" |
| 290 | labels: | ||
| 291 | net.paperclover.list.name: qBittorrent | ||
| 292 | net.paperclover.list.domain: qbt | ||
| 293 | net.paperclover.list.priority: 52 | ||
| 294 | net.paperclover.list.access: media-manage | ||
| 202 | # jackett: # port 9117 | 295 | # jackett: # port 9117 |
| 203 | # container_name: jackett | 296 | # container_name: jackett |
| 204 | # environment: | 297 | # environment: |
| ... | @@ -209,6 +302,11 @@ services: | ... | @@ -209,6 +302,11 @@ services: |
| 209 | # restart: unless-stopped | 302 | # restart: unless-stopped |
| 210 | # volumes: | 303 | # volumes: |
| 211 | # - "${APP_ROOT}/jackett:/config/Jackett" | 304 | # - "${APP_ROOT}/jackett:/config/Jackett" |
| 305 | # labels: | ||
| 306 | # net.paperclover.list.name: Jackett API Adapter | ||
| 307 | # net.paperclover.list.domain: jkt | ||
| 308 | # net.paperclover.list.priority: 40 | ||
| 309 | # net.paperclover.list.access: media-manage | ||
| 212 | # sonarr: # port 8989 | 310 | # sonarr: # port 8989 |
| 213 | # container_name: sonarr | 311 | # container_name: sonarr |
| 214 | # environment: | 312 | # environment: |
| ... | @@ -221,6 +319,11 @@ services: | ... | @@ -221,6 +319,11 @@ services: |
| 221 | # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" | 319 | # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" |
| 222 | # - "${MEDIA_ROOT}:/data/media" | 320 | # - "${MEDIA_ROOT}:/data/media" |
| 223 | # restart: unless-stopped | 321 | # restart: unless-stopped |
| 322 | # labels: | ||
| 323 | # net.paperclover.list.name: Sonarr TV PVR | ||
| 324 | # net.paperclover.list.domain: jkt | ||
| 325 | # net.paperclover.list.priority: 51 | ||
| 326 | # net.paperclover.list.access: media-manage | ||
| 224 | # radarr: # port 7878 | 327 | # radarr: # port 7878 |
| 225 | # container_name: radarr | 328 | # container_name: radarr |
| 226 | # environment: | 329 | # environment: |
| ... | @@ -233,6 +336,11 @@ services: | ... | @@ -233,6 +336,11 @@ services: |
| 233 | # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" | 336 | # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" |
| 234 | # - "${MEDIA_ROOT}:/data/media" | 337 | # - "${MEDIA_ROOT}:/data/media" |
| 235 | # restart: unless-stopped | 338 | # restart: unless-stopped |
| 339 | # labels: | ||
| 340 | # net.paperclover.list.name: Radarr Movie Organizer | ||
| 341 | # net.paperclover.list.domain: jkt | ||
| 342 | # net.paperclover.list.priority: 50 | ||
| 343 | # net.paperclover.list.access: media-manage | ||
| 236 | # language models | 344 | # language models |
| 237 | opencode: # port 4096 | 345 | opencode: # port 4096 |
| 238 | container_name: opencode | 346 | container_name: opencode |
| ... | @@ -246,6 +354,11 @@ services: | ... | @@ -246,6 +354,11 @@ services: |
| 246 | - "${APP_ROOT}/opencode/.config:/home/clover/.config" | 354 | - "${APP_ROOT}/opencode/.config:/home/clover/.config" |
| 247 | - "${APP_ROOT}/opencode/.local:/home/clover/.local" | 355 | - "${APP_ROOT}/opencode/.local:/home/clover/.local" |
| 248 | - "${APP_ROOT}/opencode/code:/home/clover/code" | 356 | - "${APP_ROOT}/opencode/code:/home/clover/code" |
| 357 | labels: | ||
| 358 | net.paperclover.list.name: OpenCode | ||
| 359 | net.paperclover.list.domain: opencode | ||
| 360 | net.paperclover.list.priority: 70 | ||
| 361 | net.paperclover.list.access: personal | ||
| 249 | # databases | 362 | # databases |
| 250 | postgres: | 363 | postgres: |
| 251 | container_name: postgres | 364 | container_name: postgres |
| ... | @@ -260,33 +373,41 @@ services: | ... | @@ -260,33 +373,41 @@ services: |
| 260 | user: "$USER_ID:$GROUP_ID" | 373 | user: "$USER_ID:$GROUP_ID" |
| 261 | environment: | 374 | environment: |
| 262 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" | 375 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" |
| 263 | POSTGRES_PASSWORD_FORGEJO: "${POSTGRES_PASSWORD_FORGEJO}" | 376 | POSTGRES_PASSWORD_FORGEJO: "${POSTGRES_PASSWORD_FORGEJO:?}" |
| 264 | POSTGRES_PASSWORD_AUTHELIA: "${POSTGRES_PASSWORD_AUTHELIA}" | 377 | POSTGRES_PASSWORD_KEYCLOAK: "${POSTGRES_PASSWORD_KEYCLOAK:?}" |
| 265 | POSTGRES_PASSWORD_DAWARICH: "${POSTGRES_PASSWORD_DAWARICH}" | 378 | POSTGRES_PASSWORD_DAWARICH: "${POSTGRES_PASSWORD_DAWARICH:?}" |
| 266 | POSTGRES_PASSWORD_JELLYFIN: "${POSTGRES_PASSWORD_JELLYFIN}" | 379 | POSTGRES_PASSWORD_JELLYFIN: "${POSTGRES_PASSWORD_JELLYFIN:?}" |
| 267 | volumes: | 380 | volumes: |
| 268 | - "${APP_ROOT}/pg_data:/var/lib/postgresql/data/" | 381 | - "${APP_ROOT}/pg_data:/var/lib/postgresql/data/" |
| 269 | healthcheck: | 382 | healthcheck: |
| 270 | test: ["CMD-SHELL", "pg_isready -U authelia -d authelia"] | 383 | test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"] |
| 271 | interval: 10s | 384 | interval: 10s |
| 272 | timeout: 5s | 385 | timeout: 5s |
| 273 | retries: 5 | 386 | retries: 5 |
| 274 | start_period: 10s | 387 | start_period: 10s |
| 275 | pgadmin: # port 80 | 388 | pgadmin: # port 80 |
| 276 | container_name: pgadmin | 389 | container_name: pgadmin |
| 390 | entrypoint: ["/bin/sh", "/init/pgadmin-init.sh"] | ||
| 277 | image: "docker.io/dpage/pgadmin4:latest" | 391 | image: "docker.io/dpage/pgadmin4:latest" |
| 278 | user: "5050:5050" | 392 | user: "0:0" |
| 279 | environment: | 393 | environment: |
| 280 | PGADMIN_DEFAULT_EMAIL: "pgadmin4@pgadmin.org" | 394 | PGADMIN_DEFAULT_EMAIL: "pgadmin4@pgadmin.org" |
| 281 | PGADMIN_DEFAULT_PASSWORD: "secret" | 395 | PGADMIN_DEFAULT_PASSWORD: "secret" |
| 282 | PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" | 396 | PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" |
| 283 | PGADMIN_CONFIG_SERVER_MODE: "False" | 397 | PGADMIN_CONFIG_SERVER_MODE: "False" |
| 398 | POSTGRES_PASSWORD: "$POSTGRES_PASSWORD" | ||
| 284 | volumes: | 399 | volumes: |
| 285 | - "${APP_ROOT}/pg_admin:/var/lib/pgadmin" | 400 | - "${APP_ROOT}/pg_admin:/var/lib/pgadmin" |
| 401 | - "./config/postgres:/init" | ||
| 286 | restart: unless-stopped | 402 | restart: unless-stopped |
| 287 | depends_on: | 403 | depends_on: |
| 288 | postgres: | 404 | postgres: |
| 289 | condition: service_healthy | 405 | condition: service_healthy |
| 406 | labels: | ||
| 407 | net.paperclover.list.name: pgAdmin | ||
| 408 | net.paperclover.list.domain: pg | ||
| 409 | net.paperclover.list.priority: 15 | ||
| 410 | net.paperclover.list.access: personal | ||
| 290 | redis: # port 6379 | 411 | redis: # port 6379 |
| 291 | container_name: redis | 412 | container_name: redis |
| 292 | image: redis:8 | 413 | image: redis:8 |
| ... | @@ -294,12 +415,23 @@ services: | ... | @@ -294,12 +415,23 @@ services: |
| 294 | restart: unless-stopped | 415 | restart: unless-stopped |
| 295 | volumes: | 416 | volumes: |
| 296 | - "${APP_ROOT}/redis:/data" | 417 | - "${APP_ROOT}/redis:/data" |
| 418 | healthcheck: | ||
| 419 | test: ["CMD", "redis-cli", "ping"] | ||
| 420 | interval: 1s | ||
| 421 | timeout: 3s | ||
| 422 | retries: 30 | ||
| 423 | start_period: 5s | ||
| 297 | redis-insight: # port 5540 | 424 | redis-insight: # port 5540 |
| 298 | container_name: redis-insight | 425 | container_name: redis-insight |
| 299 | image: redis/redisinsight:latest | 426 | image: redis/redisinsight:latest |
| 300 | environment: | 427 | environment: |
| 301 | RI_REDIS_HOST: redis | 428 | RI_REDIS_HOST: redis |
| 302 | restart: unless-stopped | 429 | restart: unless-stopped |
| 430 | labels: | ||
| 431 | net.paperclover.list.name: Redis Insight | ||
| 432 | net.paperclover.list.domain: redis | ||
| 433 | net.paperclover.list.priority: 14 | ||
| 434 | net.paperclover.list.access: personal | ||
| 303 | # location history | 435 | # location history |
| 304 | dawarich-app: # port 30161 | 436 | dawarich-app: # port 30161 |
| 305 | image: freikin/dawarich:latest | 437 | image: freikin/dawarich:latest |
| ... | @@ -340,6 +472,11 @@ services: | ... | @@ -340,6 +472,11 @@ services: |
| 340 | limits: | 472 | limits: |
| 341 | cpus: "0.50" | 473 | cpus: "0.50" |
| 342 | memory: 4G | 474 | memory: 4G |
| 475 | labels: | ||
| 476 | net.paperclover.list.name: Dawarich Location History | ||
| 477 | net.paperclover.list.domain: dawarich | ||
| 478 | net.paperclover.list.priority: 30 | ||
| 479 | net.paperclover.list.access: personal | ||
| 343 | dawarich-sidekiq: | 480 | dawarich-sidekiq: |
| 344 | image: freikin/dawarich:latest | 481 | image: freikin/dawarich:latest |
| 345 | container_name: dawarich-sidekiq | 482 | container_name: dawarich-sidekiq |
| ... | @@ -387,12 +524,21 @@ services: | ... | @@ -387,12 +524,21 @@ services: |
| 387 | - 22000:22000/udp | 524 | - 22000:22000/udp |
| 388 | - 21027:21027/udp | 525 | - 21027:21027/udp |
| 389 | restart: unless-stopped | 526 | restart: unless-stopped |
| 527 | labels: | ||
| 528 | net.paperclover.list.name: Syncthing | ||
| 529 | net.paperclover.list.domain: sync | ||
| 530 | net.paperclover.list.priority: 31 | ||
| 531 | net.paperclover.list.access: personal | ||
| 390 | # administration | 532 | # administration |
| 391 | openspeedtest: # port 3000 | 533 | openspeedtest: # port 3000 |
| 392 | restart: unless-stopped | 534 | restart: unless-stopped |
| 393 | container_name: openspeedtest | 535 | container_name: openspeedtest |
| 394 | image: openspeedtest/latest | 536 | image: openspeedtest/latest |
| 395 | user: "$USER_ID:$GROUP_ID" | 537 | user: "$USER_ID:$GROUP_ID" |
| 538 | labels: | ||
| 539 | net.paperclover.list.name: Open Speed Test | ||
| 540 | net.paperclover.list.domain: speedtest | ||
| 541 | net.paperclover.list.priority: 10 | ||
| 396 | technitium-dns: | 542 | technitium-dns: |
| 397 | image: technitium/dns-server | 543 | image: technitium/dns-server |
| 398 | container_name: technitium-dns | 544 | container_name: technitium-dns |
| ... | @@ -411,13 +557,16 @@ services: | ... | @@ -411,13 +557,16 @@ services: |
| 411 | restart: unless-stopped | 557 | restart: unless-stopped |
| 412 | sysctls: | 558 | sysctls: |
| 413 | - net.ipv4.ip_local_port_range=1024 65000 | 559 | - net.ipv4.ip_local_port_range=1024 65000 |
| 560 | labels: | ||
| 561 | net.paperclover.list.name: DNS | ||
| 562 | net.paperclover.list.domain: dns | ||
| 563 | net.paperclover.list.priority: 9 | ||
| 564 | net.paperclover.list.access: personal | ||
| 414 | 565 | ||
| 415 | secrets: | 566 | secrets: |
| 416 | openvpn-credentials: | 567 | openvpn-credentials: |
| 417 | file: ./secrets/openvpn-credentials.txt | 568 | file: ./secrets/openvpn-credentials.txt |
| 418 | authelia-jwt-secret: | 569 | keycloak-crt: |
| 419 | file: ./secrets/authelia-jwt-secret.txt | 570 | file: ./secrets/keycloak.crt |
| 420 | authelia-session-secret: | 571 | keycloak-key: |
| 421 | file: ./secrets/authelia-session-secret.txt | 572 | file: ./secrets/keycloak.key |
| 422 | authelia-encryption-key: | ||
| 423 | file: ./secrets/authelia-encryption-key.txt |
config/Caddyfile+112-76| ... | @@ -1,99 +1,133 @@ | ... | @@ -1,99 +1,133 @@ |
| 1 | local.test, *.local.test { | 1 | # (auth_require) { |
| 2 | tls internal | 2 | # 	@not_websocket not header Connection Upgrade |
| 3 | } | 3 | # 	handle /snow.oauth2/* { |
| 4 | # 		reverse_proxy "http://forward-auth" { | ||
| 5 | # 			header_up X-Real-IP {remote_host} | ||
| 6 | # 			header_up X-Forwarded-Uri {uri} | ||
| 7 | # 		} | ||
| 8 | # 	} | ||
| 9 | # | ||
| 10 | # 	handle { | ||
| 11 | # 		forward_auth @not_websocket "http://forward-auth" { | ||
| 12 | # 			uri /snow.oauth2/auth | ||
| 13 | # 			header_up X-Real-IP {remote_host} | ||
| 14 | # 			copy_headers X-Forwarded-User X-Forwarded-Groups X-Forwarded-Email | ||
| 15 | # 			@error status 401 | ||
| 16 | # 			handle_response @error { | ||
| 17 | # 				redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri} | ||
| 18 | # 			} | ||
| 19 | # 		} | ||
| 20 | # | ||
| 21 | # 		reverse_proxy {args[:]} | ||
| 22 | # } | ||
| 23 | # } | ||
| 24 | (auth-optional) { | ||
| 25 | 	handle /snow.oauth2/* { | ||
| 26 | 		reverse_proxy "http://forward-auth" { | ||
| 27 | 			header_up X-Real-IP {remote_host} | ||
| 28 | 			header_up X-Forwarded-Uri {uri} | ||
| 29 | 		} | ||
| 30 | 	} | ||
| 31 | |||
| 32 | @not_websocket not header Connection Upgrade | ||
| 33 | reverse_proxy @not_websocket "http://forward-auth" { | ||
| 34 | method GET | ||
| 35 | rewrite /snow.oauth2/auth | ||
| 4 | 36 | ||
| 5 | (auth) { | 37 | header_up X-Forwarded-Method {method} |
| 6 | 	@not_websocket not header Connection Upgrade | 38 | header_up X-Forwarded-Uri {uri} |
| 7 | 	forward_auth @not_websocket authelia:9091 { | 39 | |
| 8 | uri /api/authz/forward-auth | 40 | @good status 2xx |
| 9 | copy_headers Remote-User Remote-Groups Remote-Email Remote-Name | 41 | handle_response @good { |
| 42 | request_header User-Id {rp.header.X-Auth-Request-User} | ||
| 43 | request_header User-Groups {rp.header.X-Auth-Request-Groups} | ||
| 44 | request_header User-Email {rp.header.X-Auth-Request-Email} | ||
| 45 | request_header User-Name {rp.header.X-Auth-Request-Preferred-Username} | ||
| 46 | } | ||
| 47 | @bad status 4xx | ||
| 48 | handle_response @bad { | ||
| 49 | request_header User-Id "" | ||
| 50 | request_header User-Groups "" | ||
| 51 | request_header User-Email "" | ||
| 52 | request_header User-Name "" | ||
| 53 | } | ||
| 10 | } | 54 | } |
| 55 | |||
| 56 | reverse_proxy {args[:]} | ||
| 11 | } | 57 | } |
| 12 | 58 | ||
| 13 | # jelly.{$HOME_DOMAIN} | ||
| 14 | import ./jellyfin/Caddyfile | ||
| 15 | 59 | ||
| 60 | # services are sorted alphabetically | ||
| 16 | auth.{$HOME_DOMAIN} { | 61 | auth.{$HOME_DOMAIN} { |
| 17 | 	reverse_proxy "authelia:9091" | 62 | 	reverse_proxy "http://keycloak" |
| 18 | } | 63 | } |
| 19 | dawarich.{$HOME_DOMAIN} { | 64 | dawarich.{$HOME_DOMAIN} { |
| 20 | 	reverse_proxy "dawarich:30161" | 65 | 	reverse_proxy "http://dawarich:30161" |
| 21 | } | 66 | } |
| 22 | db.{$HOME_DOMAIN} { | 67 | db.{$HOME_DOMAIN} { |
| 23 | 	reverse_proxy "clover-source-of-truth:80" | 68 | 	reverse_proxy "http://clover-source-of-truth" |
| 24 | } | ||
| 25 | nas.{$HOME_DOMAIN} { | ||
| 26 | forward_auth authelia:9091 { | ||
| 27 | uri /api/authz/forward-auth | ||
| 28 | copy_headers Remote-User Remote-Groups Remote-Email Remote-Name | ||
| 29 | } | ||
| 30 | route { | ||
| 31 | @login path /login | ||
| 32 | redir @login https://auth.{$HOME_DOMAIN}?rd=https%3A%2F%2Fnas.{$HOME_DOMAIN}{?rd} | ||
| 33 | |||
| 34 | @logout path /logout | ||
| 35 | redir @logout https://auth.{$HOME_DOMAIN}/logout?rd=https://nas.{$HOME_DOMAIN} | ||
| 36 | |||
| 37 | reverse_proxy copyparty:3923 | ||
| 38 | } | ||
| 39 | } | 69 | } |
| 40 | git.{$HOME_DOMAIN} { | 70 | git.{$HOME_DOMAIN} { |
| 41 | 	reverse_proxy "forgejo-anubis:80" { | 71 | 	reverse_proxy "http://forgejo-anubis" { |
| 42 | 		header_up X-Real-Ip {remote_host} | 72 | 		header_up X-Real-Ip {remote_host} |
| 43 | 		header_up X-Http-Version {http.request.proto} | 73 | 		header_up X-Http-Version {http.request.proto} |
| 44 | 	} | 74 | 	} |
| 45 | } | 75 | } |
| 76 | import ./jellyfin/Caddyfile | ||
| 77 | # jkt.{$HOME_DOMAIN} { | ||
| 78 | # 	import auth | ||
| 79 | # 	reverse_proxy "http://jackett:9117" { | ||
| 80 | # 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | ||
| 81 | # 	} | ||
| 82 | # } | ||
| 46 | music.{$HOME_DOMAIN} { | 83 | music.{$HOME_DOMAIN} { |
| 47 | 	reverse_proxy "navidrome:4533" | 84 | 	reverse_proxy "http://navidrome:4533" |
| 48 | } | 85 | } |
| 49 | qbt.{$HOME_DOMAIN} { | 86 | nas.{$HOME_DOMAIN} { |
| 50 | 	import auth | 87 | import auth-optional "http://copyparty" |
| 51 | 	reverse_proxy "qbittorrent:23938" | 88 | } |
| 52 | } | 89 | # opencode.{$HOME_DOMAIN} { |
| 53 | snr.{$HOME_DOMAIN} { | 90 | # 	import auth |
| 54 | 	import auth | 91 | # 	reverse_proxy "http://opencode" |
| 55 | 	reverse_proxy "sonarr:8989" { | 92 | # } |
| 56 | 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | 93 | # pg.{$HOME_DOMAIN} { |
| 57 | 	} | 94 | # 	import auth |
| 58 | } | 95 | # 	reverse_proxy "http://pgadmin" |
| 59 | rdr.{$HOME_DOMAIN} { | 96 | # } |
| 60 | 	import auth | 97 | # qbt.{$HOME_DOMAIN} { |
| 61 | 	reverse_proxy "radarr:7878" { | 98 | # 	import auth |
| 62 | 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | 99 | # 	reverse_proxy "qbittorrent:23938" |
| 63 | 	} | 100 | # } |
| 64 | } | 101 | # redis.{$HOME_DOMAIN} { |
| 65 | jkt.{$HOME_DOMAIN} { | 102 | # 	import auth |
| 66 | 	import auth | 103 | # 	reverse_proxy "redis-insight:5540" |
| 67 | 	reverse_proxy "jackett:9117" { | 104 | # } |
| 68 | 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | 105 | # rdr.{$HOME_DOMAIN} { |
| 69 | 	} | 106 | # 	import auth |
| 70 | } | 107 | # 	reverse_proxy "radarr:7878" { |
| 108 | # 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | ||
| 109 | # 	} | ||
| 110 | # } | ||
| 111 | # snr.{$HOME_DOMAIN} { | ||
| 112 | # 	import auth | ||
| 113 | # 	reverse_proxy "sonarr:8989" { | ||
| 114 | # 		header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" | ||
| 115 | # 	} | ||
| 116 | # } | ||
| 71 | speedtest.{$HOME_DOMAIN} { | 117 | speedtest.{$HOME_DOMAIN} { |
| 72 | 	reverse_proxy "openspeedtest:3000" | 118 | 	reverse_proxy "openspeedtest:3000" |
| 73 | } | 119 | } |
| 74 | opencode.{$HOME_DOMAIN} { | 120 | # sync.{$HOME_DOMAIN} { |
| 75 | 	import auth | 121 | # 	import auth |
| 76 | 	reverse_proxy "opencode:3923" | 122 | # 	reverse_proxy "syncthing:8384" |
| 77 | } | 123 | # } |
| 78 | pg.{$HOME_DOMAIN} { | ||
| 79 | 	import auth | ||
| 80 | 	reverse_proxy "pgadmin:80" | ||
| 81 | } | ||
| 82 | redis.{$HOME_DOMAIN} { | ||
| 83 | 	import auth | ||
| 84 | 	reverse_proxy "redis-insight:5540" | ||
| 85 | } | ||
| 86 | sync.{$HOME_DOMAIN} { | ||
| 87 | 	import auth | ||
| 88 | 	reverse_proxy "syncthing:8384" | ||
| 89 | } | ||
| 90 | 124 | ||
| 91 | # redirections | 125 | # redirections |
| 92 | paperclover.dev { | 126 | paperclover.dev { |
| 93 | redir "https://git.paperclover.net{uri}" | 127 | 	redir "https://git.paperclover.net{uri}" |
| 94 | tls { | 128 | 	tls { |
| 95 | on_demand | 129 | 		on_demand |
| 96 | } | 130 | 	} |
| 97 | } | 131 | } |
| 98 | 132 | ||
| 99 | :80, :443 { | 133 | :80, :443 { |
| ... | @@ -105,11 +139,13 @@ paperclover.dev { | ... | @@ -105,11 +139,13 @@ paperclover.dev { |
| 105 | 		} | 139 | 		} |
| 106 | 	} | 140 | 	} |
| 107 | 141 | ||
| 108 | 	handle_path /generate_204 { | ||
| 109 | 		respond "" 204 | ||
| 110 | 	} | ||
| 111 | 	handle { | 142 | 	handle { |
| 112 | 		respond "the 'Not Found' page was not found: {http.request.host}" 404 | 143 | 		respond "the 'Not Found' page was not found: {http.request.host}" 404 |
| 113 | 	} | 144 | 	} |
| 114 | } | 145 | } |
| 115 | 146 | ||
| 147 | local.test, *.local.test { | ||
| 148 | 	tls internal | ||
| 149 | } | ||
| 150 | |||
| 151 |
config/authelia/configuration.yml deleted-46| ... | @@ -1,46 +0,0 @@ | ||
| 1 | storage: | ||
| 2 | postgres: | ||
| 3 | address: "tcp://postgres:5432" | ||
| 4 | database: "authelia" | ||
| 5 | schema: "public" | ||
| 6 | username: "authelia" | ||
| 7 | timeout: "5s" | ||
| 8 | authentication_backend: | ||
| 9 | refresh_interval: "5m" | ||
| 10 | file: | ||
| 11 | watch: true | ||
| 12 | path: "/config/users.yaml" | ||
| 13 | search: | ||
| 14 | email: true | ||
| 15 | case_insensitive: true | ||
| 16 | totp: | ||
| 17 | issuer: "local.test" | ||
| 18 | session: | ||
| 19 | name: "authelia_session" | ||
| 20 | same_site: "lax" | ||
| 21 | inactivity: "5m" | ||
| 22 | expiration: "1h" | ||
| 23 | remember_me: "3M" | ||
| 24 | cookies: | ||
| 25 | - name: "clover-authelia" | ||
| 26 | domain: "local.test" | ||
| 27 | authelia_url: "https://auth.local.test" | ||
| 28 | notifier: | ||
| 29 | disable_startup_check: true | ||
| 30 | smtp: | ||
| 31 | address: "smtp://127.0.0.1:25" | ||
| 32 | timeout: "5s" | ||
| 33 | username: "test" | ||
| 34 | password: "password" | ||
| 35 | sender: "clover bot <noreply.auth@paperclover.net>" | ||
| 36 | identifier: "localhost" | ||
| 37 | subject: "[Authelia] {title}" | ||
| 38 | access_control: | ||
| 39 | default_policy: one_factor | ||
| 40 | rules: | ||
| 41 | - domain: "nas.local.test" | ||
| 42 | policy: bypass # optional | ||
| 43 | regulation: | ||
| 44 | max_retries: 10 | ||
| 45 | find_time: "2 minutes" | ||
| 46 | ban_time: "5 minutes" | ||
config/authelia/users.yaml deleted-11| ... | @@ -1,11 +0,0 @@ | ||
| 1 | users: | ||
| 2 | snow: | ||
| 3 | disabled: false | ||
| 4 | displayname: "snow" | ||
| 5 | password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" | ||
| 6 | email: "account@paperclover.net" | ||
| 7 | fish: | ||
| 8 | disabled: false | ||
| 9 | displayname: "fish" | ||
| 10 | password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" | ||
| 11 | email: "julia@paperclover.net" | ||
config/copyparty.conf+9-7| ... | @@ -2,19 +2,21 @@ | ... | @@ -2,19 +2,21 @@ |
| 2 | # e2dsa # enable file indexing and filesystem scanning | 2 | # e2dsa # enable file indexing and filesystem scanning |
| 3 | # e2ts # enable multimedia indexing | 3 | # e2ts # enable multimedia indexing |
| 4 | ansi # enable colors in log messages | 4 | ansi # enable colors in log messages |
| 5 | p: 80 # port | ||
| 5 | shr: /shr # set the virtual path for shares | 6 | shr: /shr # set the virtual path for shares |
| 6 | df: 16 # stop accepting uploads if less than 16 GB free disk space | 7 | df: 16 # stop accepting uploads if less than 16 GB free disk space |
| 7 | ver # show copyparty version in the controlpanel | 8 | ver # show copyparty version in the controlpanel |
| 8 | theme: 2 # monokai | 9 | theme: 2 # monokai |
| 9 | name: clover nas # change the server-name that's displayed in the browser | 10 | name: clover's nas |
| 10 | stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) | 11 | stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) |
| 11 | 12 | ||
| 12 | # authelia | 13 | # keycloak |
| 13 | xff-src: lan # accept X-Forwarded-For from `lan` | 14 | xff-src: lan # accept X-Forwarded-For from `lan` |
| 14 | idp-h-usr: remote-user | 15 | rproxy: 1 |
| 15 | idp-h-grp: remote-groups | 16 | idp-h-usr: user-name |
| 16 | idp-login: /login?rd={dst} # caddy redirect to avoid hardcoding a domain here | 17 | idp-h-grp: user-groups |
| 17 | idp-logout: /logout | 18 | idp-login: /snow.oauth2/sign_in?rd={dst} # caddy redirect to avoid hardcoding a domain here |
| 19 | idp-logout: /snow.oauth2/sign_out | ||
| 18 | idp-login-t: with sso (snow sign on) | 20 | idp-login-t: with sso (snow sign on) |
| 19 | 21 | ||
| 20 | [/] # webroot | 22 | [/] # webroot |
config/jellyfin/Caddyfile+40-45| ... | @@ -3,69 +3,64 @@ jelly.{$HOME_DOMAIN} { | ... | @@ -3,69 +3,64 @@ jelly.{$HOME_DOMAIN} { |
| 3 | 		header Content-Type "text/css" | 3 | 		header Content-Type "text/css" |
| 4 | 		respond 200 { | 4 | 		respond 200 { |
| 5 | 			body ` | 5 | 			body ` |
| 6 | @import url("/original{http.request.uri.path}") layer(base); | 6 | 			@import url("/original{http.request.uri.path}") layer(base); |
| 7 | 			@import url("/original/web/themes/dark/theme.css") layer(basetheme); | 7 | 			@import url("/original/web/themes/dark/theme.css") layer(basetheme); |
| 8 | 			@import url("https://jellyfin.catppuccin.com/theme.css") layer(cat); | 8 | 			@import url("https://jellyfin.catppuccin.com/theme.css") layer(cat); |
| 9 | 			@import url("https://jellyfin.catppuccin.com/catppuccin-macchiato.css"); | 9 | 			@import url("https://jellyfin.catppuccin.com/catppuccin-macchiato.css"); |
| 10 | 			@import url("https://jellyfin.catppuccin.com/catppuccin-latte.css") (prefers-color-scheme: light); | 10 | 			@import url("https://jellyfin.catppuccin.com/catppuccin-latte.css") (prefers-color-scheme: light); |
| 11 | 			@font-face { | 11 | 			@font-face { |
| 12 | font-family: 'Name Sans'; | 12 | 			font-family: 'Name Sans'; |
| 13 | src: url('/web/extra/font/ATNameSansVariable-Regular.woff2') format('woff2') tech(variations); | 13 | 			src: url('/web/extra/font/ATNameSansVariable-Regular.woff2') format('woff2') tech(variations); |
| 14 | font-weight: 100 900; | 14 | 			font-weight: 100 900; |
| 15 | font-style: normal; | 15 | 			font-style: normal; |
| 16 | font-display: swap; | 16 | 			font-display: swap; |
| 17 | font-stretch: 75% 125%; | 17 | 			font-stretch: 75% 125%;} |
| 18 | } | ||
| 19 | 18 | ||
| 20 | 			@font-face { | 19 | 			@font-face { |
| 21 | font-family: 'Name Sans'; | 20 | 			font-family: 'Name Sans'; |
| 22 | src: url('/web/extra/font/ATNameSansVariable-Italic.woff2') format('woff2') tech(variations); | 21 | 			src: url('/web/extra/font/ATNameSansVariable-Italic.woff2') format('woff2') tech(variations); |
| 23 | font-weight: 100 900; | 22 | 			font-weight: 100 900; |
| 24 | font-style: italic; | 23 | 			font-style: italic; |
| 25 | font-display: swap; | 24 | 			font-display: swap; |
| 26 | font-stretch: 75% 125%; | 25 | 			font-stretch: 75% 125%;} |
| 27 | } | ||
| 28 | 26 | ||
| 29 | 			body { font-family: "Name Sans", sans-serif;} | 27 | 			body { font-family: "Name Sans", sans-serif;} |
| 30 | 			.preload, .touch-menu-la, .mainDrawer-scrollContainer { | 28 | 			.preload, .touch-menu-la, .mainDrawer-scrollContainer { |
| 31 | background-color: var(--main-background); | 29 | 			background-color: var(--main-background); |
| 32 | color: var(--main-text); | 30 | 			color: var(--main-text);} |
| 33 | } | 31 | |
| 34 | 			:root { | 32 | 			:root { |
| 35 | --main-color: var(--sapphire); | 33 | 			--main-color: var(--sapphire); |
| 36 | --jf-font-button: 500 0.875rem / 1.75 "Name Sans", sans-serif; | 34 | 			--jf-font-button: 500 0.875rem / 1.75 "Name Sans", sans-serif; |
| 37 | --jf-font-h1: 300 1.8rem / 1.167 "Name Sans", sans-serif; | 35 | 			--jf-font-h1: 300 1.8rem / 1.167 "Name Sans", sans-serif; |
| 38 | --jf-font-h2: 300 1.5rem / 1.2 "Name Sans", sans-serif; | 36 | 			--jf-font-h2: 300 1.5rem / 1.2 "Name Sans", sans-serif; |
| 39 | --jf-font-h3: 400 1.17rem / 1.167 "Name Sans", sans-serif; | 37 | 			--jf-font-h3: 400 1.17rem / 1.167 "Name Sans", sans-serif; |
| 40 | --jf-font-h4: 400 2.125rem / 1.235 "Name Sans", sans-serif; | 38 | 			--jf-font-h4: 400 2.125rem / 1.235 "Name Sans", sans-serif; |
| 41 | --jf-font-h5: 400 1.5rem / 1.334 "Name Sans", sans-serif; | 39 | 			--jf-font-h5: 400 1.5rem / 1.334 "Name Sans", sans-serif; |
| 42 | --jf-font-h6: 500 1.25rem / 1.6 "Name Sans", sans-serif; | 40 | 			--jf-font-h6: 500 1.25rem / 1.6 "Name Sans", sans-serif; |
| 43 | --jf-font-subtitle1: 400 1rem / 1.75 "Name Sans", sans-serif; | 41 | 			--jf-font-subtitle1: 400 1rem / 1.75 "Name Sans", sans-serif; |
| 44 | --jf-font-subtitle2: 500 0.875rem / 1.57 "Name Sans", sans-serif; | 42 | 			--jf-font-subtitle2: 500 0.875rem / 1.57 "Name Sans", sans-serif; |
| 45 | --jf-font-body1: 400 1rem / 1.5 "Name Sans", sans-serif; | 43 | 			--jf-font-body1: 400 1rem / 1.5 "Name Sans", sans-serif; |
| 46 | --jf-font-body2: 400 0.875rem / 1.43 "Name Sans", sans-serif; | 44 | 			--jf-font-body2: 400 0.875rem / 1.43 "Name Sans", sans-serif; |
| 47 | --jf-font-caption: 400 0.75rem / 1.66 "Name Sans", sans-serif; | 45 | 			--jf-font-caption: 400 0.75rem / 1.66 "Name Sans", sans-serif; |
| 48 | --jf-font-overline: 400 0.75rem / 2.66 "Name Sans", sans-serif; | 46 | 			--jf-font-overline: 400 0.75rem / 2.66 "Name Sans", sans-serif; |
| 49 | --jf-font-inherit: inherit inherit / inherit inherit; | 47 | 			--jf-font-inherit: inherit inherit / inherit inherit;} |
| 50 | } | ||
| 51 | 48 | ||
| 52 | .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner.png)!important; } | 49 | 			.pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner.png)!important;} |
| 53 | .layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon.png)!important; } | 50 | 			.layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon.png)!important;} |
| 54 | .splashLogo { background-image: url(/web/extra/cloverfin-icon.png)!important; } | 51 | 			.splashLogo { background-image: url(/web/extra/cloverfin-icon.png)!important;} |
| 55 | 52 | ||
| 56 | 			@media (min-device-width: 992px) { | 53 | 			@media (min-device-width: 992px) { |
| 57 | .splashLogo { background-image: url(/web/extra/cloverfin-banner.png)!important; } | 54 | 			.splashLogo { background-image: url(/web/extra/cloverfin-banner.png)!important;}} |
| 58 | } | ||
| 59 | 55 | ||
| 60 | 			@media (prefers-color-scheme: light) { | 56 | 			@media (prefers-color-scheme: light) { |
| 61 | .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important;} | 57 | 			.pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important;} |
| 62 | .layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important;} | 58 | 			.layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important;} |
| 63 | .splashLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important; } | 59 | 			.splashLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important;}} |
| 64 | } | ||
| 65 | 60 | ||
| 66 | 			@media (min-device-width: 992px) and (prefers-color-scheme: light) { | 61 | 			@media (min-device-width: 992px) and (prefers-color-scheme: light) { |
| 67 | .splashLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important; } | 62 | 			.splashLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important;}} |
| 68 | } | 63 | |
| 69 | 			` | 64 | 			` |
| 70 | 		} | 65 | 		} |
| 71 | 	} | 66 | 	} |
config/keycloak/.gitignore created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | .venv | ||
| 2 | .python-version | ||
config/keycloak/Dockerfile created+20| ... | @@ -0,0 +1,20 @@ | ||
| 1 | FROM registry.access.redhat.com/ubi9/python-311 AS python | ||
| 2 | |||
| 3 | USER root | ||
| 4 | RUN pip3 install --no-cache-dir --target=/install python-keycloak requests | ||
| 5 | |||
| 6 | FROM quay.io/keycloak/keycloak:latest | ||
| 7 | |||
| 8 | USER root | ||
| 9 | COPY --from=python /usr/bin/python3.11 /usr/bin/python3 | ||
| 10 | COPY --from=python /usr/lib64/libpython3.11.so.1.0 /usr/lib64/ | ||
| 11 | COPY --from=python /usr/lib64/libcrypto.so.3 /usr/lib64/ | ||
| 12 | COPY --from=python /usr/lib64/libssl.so.3 /usr/lib64/ | ||
| 13 | COPY --from=python /usr/lib64/python3.11 /usr/lib64/python3.11 | ||
| 14 | COPY --from=python /install /usr/lib64/python3.11/site-packages | ||
| 15 | RUN ln -s /usr/lib64/libpython3.11.so.1.0 /usr/lib64/libpython3.11.so | ||
| 16 | |||
| 17 | COPY init.py /opt/keycloak/init.py | ||
| 18 | COPY entry.sh /opt/keycloak/entry.sh | ||
| 19 | |||
| 20 | ENTRYPOINT ["/opt/keycloak/entry.sh"] | ||
config/keycloak/entry.sh created+16| ... | @@ -0,0 +1,16 @@ | ||
| 1 | #!/bin/bash | ||
| 2 | set -e | ||
| 3 | |||
| 4 | THEME_DEV_ARGS="" | ||
| 5 | # THEME_DEV_ARGS='--spi-theme--static-max-age=-1 --spi-theme--cache-themes=false --spi-theme--cache-templates=false' | ||
| 6 | |||
| 7 | /opt/keycloak/bin/kc.sh start --verbose \ | ||
| 8 | --https-certificate-file=/run/secrets/keycloak-crt \ | ||
| 9 | --https-certificate-key-file=/run/secrets/keycloak-key \ | ||
| 10 | $THEME_DEV_ARGS & | ||
| 11 | |||
| 12 | PID="$!" | ||
| 13 | |||
| 14 | /opt/keycloak/init.py | ||
| 15 | |||
| 16 | wait "$PID" | ||
config/keycloak/forward-auth/Dockerfile created+5| ... | @@ -0,0 +1,5 @@ | ||
| 1 | FROM busybox:musl AS busybox | ||
| 2 | FROM quay.io/oauth2-proxy/oauth2-proxy:v7.13.0 | ||
| 3 | COPY ./entry.sh /init/entry.sh | ||
| 4 | COPY --from=busybox /bin /bin | ||
| 5 | ENTRYPOINT "/init/entry.sh" | ||
config/keycloak/forward-auth/entry.sh created+11| ... | @@ -0,0 +1,11 @@ | ||
| 1 | #!/bin/sh | ||
| 2 | set -e | ||
| 3 | |||
| 4 | CADDY_IP="$(nslookup caddy | awk '/^Address: / { print $2 }')" | ||
| 5 | echo "${CADDY_IP} auth.${HOME_DOMAIN}" >>/etc/hosts | ||
| 6 | rm -f /etc/ssl/certs/caddy.crt | ||
| 7 | ln -s /caddy/pki/authorities/root.crt /etc/ssl/certs/caddy.crt | ||
| 8 | |||
| 9 | export OAUTH2_PROXY_CLIENT_SECRET="$(cat /shared/forward-auth)" | ||
| 10 | |||
| 11 | exec /bin/oauth2-proxy | ||
config/keycloak/init.py created+173| ... | @@ -0,0 +1,173 @@ | ||
| 1 | #!/usr/bin/env python3 | ||
| 2 | # declarative configuration for keycloak | ||
| 3 | # pyright: reportUnknownMemberType=false | ||
| 4 | # pyright: reportUnknownVariableType=false | ||
| 5 | # pyright: reportUnknownArgumentType=false | ||
| 6 | import logging | ||
| 7 | import os | ||
| 8 | import requests | ||
| 9 | import sys | ||
| 10 | import time | ||
| 11 | from keycloak import KeycloakAdmin, KeycloakOpenIDConnection | ||
| 12 | from pathlib import Path | ||
| 13 | |||
| 14 | |||
| 15 | def env(key: str): | ||
| 16 | val = os.environ.get(key) | ||
| 17 | if not val: | ||
| 18 | raise Exception(f"Missing ${key}") | ||
| 19 | return val | ||
| 20 | |||
| 21 | |||
| 22 | groups_to_create = ["snow", "media", "media-manage", "git", "file"] | ||
| 23 | realm_config = { | ||
| 24 | "realm": "master", | ||
| 25 | "displayName": "snow sign on", | ||
| 26 | "displayNameHtml": '<div class="kc-logo-text"><span>snow sign on</span></div>', | ||
| 27 | "editUsernameAllowed": True, | ||
| 28 | "resetPasswordAllowed": True, | ||
| 29 | "rememberMe": True, | ||
| 30 | "loginWithEmailAllowed": True, | ||
| 31 | "registrationAllowed": False, | ||
| 32 | "webAuthnPolicyRpEntityName": "snow sign on", | ||
| 33 | "webAuthnPolicyPasswordlessPasskeysEnabled": True, | ||
| 34 | "loginTheme": "snow", | ||
| 35 | "smtpServer": { | ||
| 36 | "from": f"noreply.auth@{env('HOME_DOMAIN')}", | ||
| 37 | "fromDisplayName": "clover bot", | ||
| 38 | "replyTo": f"me@{env('HOME_DOMAIN')}", | ||
| 39 | "replyToDisplayName": "clover caruso", | ||
| 40 | "envelopeFrom": "", | ||
| 41 | "host": env("MAILER_ADDRESS"), | ||
| 42 | "port": "587", | ||
| 43 | "ssl": "true", | ||
| 44 | "starttls": "true", | ||
| 45 | "auth": "true", | ||
| 46 | "user": env("MAILER_USERNAME"), | ||
| 47 | "authType": "basic", | ||
| 48 | "password": env("MAILER_PASSWORD"), | ||
| 49 | }, | ||
| 50 | } | ||
| 51 | |||
| 52 | |||
| 53 | # Configure logging | ||
| 54 | logging.basicConfig( | ||
| 55 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" | ||
| 56 | ) | ||
| 57 | logger = logging.getLogger(__name__) | ||
| 58 | |||
| 59 | |||
| 60 | def wait_for_keycloak(timeout: int = 120) -> bool: | ||
| 61 | start_time = time.time() | ||
| 62 | |||
| 63 | while time.time() - start_time < timeout: | ||
| 64 | try: | ||
| 65 | response = requests.get(f"http://localhost:9000/health/ready", timeout=5) | ||
| 66 | if response.status_code == 200: | ||
| 67 | return True | ||
| 68 | except requests.exceptions.RequestException: | ||
| 69 | pass | ||
| 70 | |||
| 71 | time.sleep(2) | ||
| 72 | |||
| 73 | logger.error(f"Timeout waiting for Keycloak to be ready after {timeout} seconds") | ||
| 74 | return False | ||
| 75 | |||
| 76 | |||
| 77 | def configure(): | ||
| 78 | admin_user = env("KC_BOOTSTRAP_ADMIN_USERNAME") | ||
| 79 | admin_pass = env("KC_BOOTSTRAP_ADMIN_PASSWORD") | ||
| 80 | connection = KeycloakOpenIDConnection( | ||
| 81 | server_url="http://localhost:80", | ||
| 82 | username=admin_user, | ||
| 83 | password=admin_pass, | ||
| 84 | realm_name="master", | ||
| 85 | client_id="admin-cli", | ||
| 86 | ) | ||
| 87 | kc_admin = KeycloakAdmin(connection=connection) | ||
| 88 | |||
| 89 | _ = kc_admin.update_realm("master", realm_config) | ||
| 90 | |||
| 91 | # admin | ||
| 92 | admin_email = env("ADMIN_EMAIL") | ||
| 93 | users = kc_admin.get_users({"username": "snow"}) | ||
| 94 | if not users: | ||
| 95 | snow_user_id = kc_admin.create_user( | ||
| 96 | { | ||
| 97 | "username": "snow", | ||
| 98 | "firstName": "Snow", | ||
| 99 | "email": admin_email, | ||
| 100 | "enabled": True, | ||
| 101 | "emailVerified": True, | ||
| 102 | "requiredActions": [ | ||
| 103 | "UPDATE_PASSWORD", | ||
| 104 | "webauthn-register-passwordless", | ||
| 105 | ], | ||
| 106 | } | ||
| 107 | ) | ||
| 108 | _ = kc_admin.set_user_password(snow_user_id, admin_pass, temporary=True) | ||
| 109 | |||
| 110 | roles = kc_admin.get_realm_roles() | ||
| 111 | admin_role = next((role for role in roles if role["name"] == "admin"), None) | ||
| 112 | if not admin_role: | ||
| 113 | raise Exception("Missing admin role") | ||
| 114 | |||
| 115 | _ = kc_admin.assign_realm_roles(snow_user_id, [admin_role]) | ||
| 116 | logger.info("Admin role assigned to snow :3") | ||
| 117 | |||
| 118 | # groups | ||
| 119 | existing_groups = kc_admin.get_groups() | ||
| 120 | existing_group_names = {group["name"] for group in existing_groups} | ||
| 121 | |||
| 122 | for group_name in groups_to_create: | ||
| 123 | if group_name not in existing_group_names: | ||
| 124 | logger.info(f"Creating group: {group_name}") | ||
| 125 | _ = kc_admin.create_group({"name": group_name}) | ||
| 126 | else: | ||
| 127 | logger.info(f"Group {group_name} already exists, skipping") | ||
| 128 | |||
| 129 | client_id = kc_admin.create_client( | ||
| 130 | { | ||
| 131 | "protocol": "openid-connect", | ||
| 132 | "clientId": "forward-auth", | ||
| 133 | "name": "Forward-Auth", | ||
| 134 | "description": "", | ||
| 135 | "publicClient": False, | ||
| 136 | "authorizationServicesEnabled": False, | ||
| 137 | "serviceAccountsEnabled": False, | ||
| 138 | "implicitFlowEnabled": False, | ||
| 139 | "directAccessGrantsEnabled": False, | ||
| 140 | "standardFlowEnabled": True, | ||
| 141 | "frontchannelLogout": True, | ||
| 142 | "attributes": { | ||
| 143 | "saml_idp_initiated_sso_url_name": "", | ||
| 144 | "standard.token.exchange.enabled": False, | ||
| 145 | "oauth2.device.authorization.grant.enabled": False, | ||
| 146 | "oidc.ciba.grant.enabled": False, | ||
| 147 | "pkce.code.challenge.method": "", | ||
| 148 | "dpop.bound.access.tokens": "false", | ||
| 149 | "post.logout.redirect.uris": "*", | ||
| 150 | }, | ||
| 151 | "alwaysDisplayInConsole": False, | ||
| 152 | "rootUrl": "", | ||
| 153 | "baseUrl": "", | ||
| 154 | "redirectUris": ["*"], | ||
| 155 | }, | ||
| 156 | skip_exists=True, | ||
| 157 | ) | ||
| 158 | client_data = kc_admin.get_client(client_id) | ||
| 159 | fw_secret_path = Path("/shared/forward-auth") | ||
| 160 | fw_secret_path.parent.mkdir(parents=True, exist_ok=True) | ||
| 161 | _ = fw_secret_path.write_text(client_data["secret"]) | ||
| 162 | |||
| 163 | |||
| 164 | def main(): | ||
| 165 | if not wait_for_keycloak(): | ||
| 166 | sys.exit(1) | ||
| 167 | |||
| 168 | configure() | ||
| 169 | logger.info("initial data upserted <3") | ||
| 170 | |||
| 171 | |||
| 172 | if __name__ == "__main__": | ||
| 173 | main() | ||
config/keycloak/pyproject.toml created+10| ... | @@ -0,0 +1,10 @@ | ||
| 1 | [project] | ||
| 2 | name = "keycloak" | ||
| 3 | version = "0.1.0" | ||
| 4 | description = "Add your description here" | ||
| 5 | readme = "README.md" | ||
| 6 | requires-python = ">=3.12" | ||
| 7 | dependencies = [ | ||
| 8 | "python-keycloak>=6.0.0", | ||
| 9 | "requests>=2.32.5", | ||
| 10 | ] | ||
config/keycloak/theme/login/resources/css/styles.css created+360| ... | @@ -0,0 +1,360 @@ | ||
| 1 | @font-face { | ||
| 2 | font-family: "Name Mono"; | ||
| 3 | src: url("../font/ATNameMono-Italic.woff2") format("woff2"); | ||
| 4 | font-weight: 400; | ||
| 5 | font-style: italic; | ||
| 6 | font-display: swap; | ||
| 7 | } | ||
| 8 | |||
| 9 | @font-face { | ||
| 10 | font-family: "Name Sans"; | ||
| 11 | src: url("../font/nsv.woff2") format("woff2") tech(variations); | ||
| 12 | font-weight: 100 900; | ||
| 13 | font-style: normal; | ||
| 14 | font-display: swap; | ||
| 15 | font-stretch: 75% 125%; | ||
| 16 | } | ||
| 17 | |||
| 18 | @font-face { | ||
| 19 | font-family: "Name Sans"; | ||
| 20 | src: url("../font/nsvi.woff2") format("woff2") tech(variations); | ||
| 21 | font-weight: 100 900; | ||
| 22 | font-style: italic; | ||
| 23 | font-display: swap; | ||
| 24 | font-stretch: 75% 125%; | ||
| 25 | } | ||
| 26 | |||
| 27 | /* reset */ | ||
| 28 | html, | ||
| 29 | body { | ||
| 30 | height: 100%; | ||
| 31 | } | ||
| 32 | |||
| 33 | h1, | ||
| 34 | h2, | ||
| 35 | h3, | ||
| 36 | h4, | ||
| 37 | h5, | ||
| 38 | h6 { | ||
| 39 | font-size: unset; | ||
| 40 | font-weight: unset; | ||
| 41 | } | ||
| 42 | |||
| 43 | :where( | ||
| 44 | html, | ||
| 45 | body, | ||
| 46 | p, | ||
| 47 | ol, | ||
| 48 | ul, | ||
| 49 | li, | ||
| 50 | dl, | ||
| 51 | dt, | ||
| 52 | dd, | ||
| 53 | blockquote, | ||
| 54 | figure, | ||
| 55 | fieldset, | ||
| 56 | legend, | ||
| 57 | textarea, | ||
| 58 | pre, | ||
| 59 | iframe, | ||
| 60 | hr, | ||
| 61 | h1, | ||
| 62 | h2, | ||
| 63 | h3, | ||
| 64 | h4, | ||
| 65 | h5, | ||
| 66 | h6 | ||
| 67 | ) { | ||
| 68 | margin: 0; | ||
| 69 | padding: 0; | ||
| 70 | } | ||
| 71 | |||
| 72 | *, :after, :before { | ||
| 73 | box-sizing: border-box; | ||
| 74 | font: unset; | ||
| 75 | } | ||
| 76 | |||
| 77 | /* root */ | ||
| 78 | body { | ||
| 79 | color-scheme: light dark; | ||
| 80 | background-color: #f9feff; | ||
| 81 | background-image: url(../img/miku-light.png); | ||
| 82 | background-size: auto 100%; | ||
| 83 | background-position: right top; | ||
| 84 | background-repeat: no-repeat; | ||
| 85 | color: black; | ||
| 86 | --text: black; | ||
| 87 | font-family: "Name Sans", sans-serif; | ||
| 88 | |||
| 89 | --header: light-dark(#1a46cd, #938cff); | ||
| 90 | --primary: light-dark(#00238f, #938cff); | ||
| 91 | } | ||
| 92 | @media (prefers-color-scheme: dark) { | ||
| 93 | body { | ||
| 94 | background-image: url(../img/miku-dark.png); | ||
| 95 | background-color: #2f4b67; | ||
| 96 | color: white; | ||
| 97 | --text: white; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | @media (max-width: 1313px) { | ||
| 101 | body { | ||
| 102 | background-position: right calc(-100px + 50%) top; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | /* sidebar */ | ||
| 107 | .pf-v5-c-login__main { | ||
| 108 | max-width: 30rem; | ||
| 109 | padding: 2rem; | ||
| 110 | height: 100%; | ||
| 111 | display: flex; | ||
| 112 | flex-direction: column; | ||
| 113 | justify-content: center; | ||
| 114 | --bg: light-dark(rgba(30, 30, 30, 0.1), rgba(0, 0, 0, 0.3)); | ||
| 115 | background-color: var(--bg); | ||
| 116 | border-style: solid; | ||
| 117 | border-right-width: 4px; | ||
| 118 | border-color: var(--bg); | ||
| 119 | backdrop-filter: blur(4px); | ||
| 120 | overflow-y: auto; | ||
| 121 | } | ||
| 122 | .kc-logo-text { | ||
| 123 | font-size: 3rem; | ||
| 124 | text-align: center; | ||
| 125 | color: var(--header); | ||
| 126 | } | ||
| 127 | #kc-page-title, #kc-info-wrapper { | ||
| 128 | text-align: center; | ||
| 129 | color: rgb(from var(--text) r g b / 0.8); | ||
| 130 | } | ||
| 131 | #kc-page-title { | ||
| 132 | margin-bottom: 1rem; | ||
| 133 | } | ||
| 134 | #kc-info-wrapper { | ||
| 135 | margin-top: 1rem; | ||
| 136 | } | ||
| 137 | #kc-form-options { | ||
| 138 | margin-bottom: 1rem; | ||
| 139 | } | ||
| 140 | a { | ||
| 141 | color: var(--header); | ||
| 142 | text-decoration: dotted underline; | ||
| 143 | } | ||
| 144 | a:hover { | ||
| 145 | text-decoration: underline; | ||
| 146 | background-color: rgb(from var(--header) r g b / 0.2); | ||
| 147 | } | ||
| 148 | |||
| 149 | /* branding */ | ||
| 150 | .kc-logo-text::before { | ||
| 151 | display: block; | ||
| 152 | content: " "; | ||
| 153 | width: 30%; | ||
| 154 | aspect-ratio: 1; | ||
| 155 | margin: auto; | ||
| 156 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDIwTDguNzUgMTcuNUw2IDE4TTEwIDRMOC43NSA2LjVMNiA2TTE0IDIwTDE1LjI1IDE3LjVMMTggMThNMTQgNEwxNS4yNSA2LjVMMTggNk0xNyAyMUwxNCAxNU0xNCAxNUgxME0xNCAxNUwxNS41IDEyTTEwIDE1TDcgMjFNMTAgMTVMOC41IDEyTTE3IDNMMTQgOU0xNCA5TDE1LjUgMTJNMTQgOUgxME0xNS41IDEySDIyTTIgMTJIOC41TTguNSAxMkwxMCA5TTEwIDlMNyAzTTIwIDEwTDE4LjUgMTJMMjAgMTRNNCAxMEw1LjUgMTJMNCAxNCIgc3Ryb2tlPSJ1cmwoI3BhaW50MF9saW5lYXJfNTQ4XzE5KSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEwIDIwTDguNzUgMTcuNUw2IDE4TTEwIDRMOC43NSA2LjVMNiA2TTE0IDIwTDE1LjI1IDE3LjVMMTggMThNMTQgNEwxNS4yNSA2LjVMMTggNk0xNyAyMUwxNCAxNU0xNCAxNUgxME0xNCAxNUwxNS41IDEyTTEwIDE1TDcgMjFNMTAgMTVMOC41IDEyTTE3IDNMMTQgOU0xNCA5TDE1LjUgMTJNMTQgOUgxME0xNS41IDEySDIyTTIgMTJIOC41TTguNSAxMkwxMCA5TTEwIDlMNyAzTTIwIDEwTDE4LjUgMTJMMjAgMTRNNCAxMEw1LjUgMTJMNCAxNCIgc3Ryb2tlPSJ1cmwoI3BhaW50MV9saW5lYXJfNTQ4XzE5KSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl81NDhfMTkiIHgxPSI0IiB5MT0iLTIiIHgyPSIxOC41IiB5Mj0iMjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzIyM0Q5OSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMxNTQzOTIiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzU0OF8xOSIgeDE9IjEwIiB5MT0iLTMiIHgyPSIxOCIgeTI9IjI3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGMkUzRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRjJGOEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==); | ||
| 157 | background-size: cover; | ||
| 158 | } | ||
| 159 | #kc-page-title, | ||
| 160 | #kc-info-wrapper, | ||
| 161 | .checkbox, | ||
| 162 | a, | ||
| 163 | #kc-form-buttons, | ||
| 164 | .pf-v5-c-helper-text__item-text, | ||
| 165 | .pf-v5-c-alert__title, | ||
| 166 | input[type="submit"], | ||
| 167 | input[type="button"], | ||
| 168 | button { | ||
| 169 | text-transform: lowercase; | ||
| 170 | } | ||
| 171 | |||
| 172 | /* text input */ | ||
| 173 | .pf-v5-c-form__group { | ||
| 174 | margin-bottom: 1rem; | ||
| 175 | } | ||
| 176 | .pf-v5-c-form__group input:not([type="checkbox"]) { | ||
| 177 | width: 100%; | ||
| 178 | } | ||
| 179 | .pf-v5-c-form__label { | ||
| 180 | margin-bottom: 0.25rem; | ||
| 181 | text-transform: lowercase; | ||
| 182 | user-select: none; | ||
| 183 | display: block; | ||
| 184 | } | ||
| 185 | .pf-v5-c-input-group { | ||
| 186 | display: flex; | ||
| 187 | border-radius: 8px; | ||
| 188 | } | ||
| 189 | .pf-v5-c-form-control { | ||
| 190 | background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15)); | ||
| 191 | padding: 2px; | ||
| 192 | appearance: none; | ||
| 193 | border: 2px solid var(--text); | ||
| 194 | font-size: inherit; | ||
| 195 | color: var(--text); | ||
| 196 | text-indent: 8px; | ||
| 197 | height: 38px; | ||
| 198 | border-radius: 8px; | ||
| 199 | flex: 1; | ||
| 200 | } | ||
| 201 | .pf-v5-c-form-control:has(+ button) { | ||
| 202 | border-top-right-radius: 0; | ||
| 203 | border-bottom-right-radius: 0; | ||
| 204 | } | ||
| 205 | .pf-v5-c-form-control + button { | ||
| 206 | appearance: none; | ||
| 207 | border-top-right-radius: 8px; | ||
| 208 | border-bottom-right-radius: 8px; | ||
| 209 | width: 38px; | ||
| 210 | border: 2px solid var(--text); | ||
| 211 | border-left: none; | ||
| 212 | background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15)); | ||
| 213 | transition: background-color 0.1s linear; | ||
| 214 | } | ||
| 215 | .pf-v5-c-form-control + button:hover { | ||
| 216 | background-color: light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2)); | ||
| 217 | } | ||
| 218 | .pf-v5-c-form__group:has(input:focus-visible) | ||
| 219 | > :is(input, .pf-v5-c-input-group) { | ||
| 220 | outline: 2px solid rgb(from var(--primary) r g b / 0.5); | ||
| 221 | } | ||
| 222 | .pf-v5-c-form__group:has(input:focus-visible) * { | ||
| 223 | border-color: var(--primary); | ||
| 224 | outline: none; | ||
| 225 | } | ||
| 226 | .pf-v5-c-form__group:has(input:focus-visible) .pf-v5-c-form__label { | ||
| 227 | color: var(--header); | ||
| 228 | } | ||
| 229 | .pf-v5-c-helper-text__item-text { | ||
| 230 | display: block; | ||
| 231 | margin-top: 0.5rem; | ||
| 232 | } | ||
| 233 | .pf-m-error { | ||
| 234 | color: light-dark(#c80000, #f56666); | ||
| 235 | } | ||
| 236 | |||
| 237 | /* checkbox */ | ||
| 238 | .checkbox input { | ||
| 239 | display: none; | ||
| 240 | } | ||
| 241 | .checkbox label { | ||
| 242 | display: flex; | ||
| 243 | align-items: center; | ||
| 244 | user-select: none; | ||
| 245 | cursor: pointer; | ||
| 246 | } | ||
| 247 | .checkbox label:before { | ||
| 248 | content: " "; | ||
| 249 | display: block; | ||
| 250 | width: 24px; | ||
| 251 | height: 24px; | ||
| 252 | margin-right: 0.5rem; | ||
| 253 | margin-left: -2px; | ||
| 254 | background-color: var(--text); | ||
| 255 | |||
| 256 | mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNxdWFyZS1pY29uIGx1Y2lkZS1zcXVhcmUiPjxyZWN0IHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgeD0iMyIgeT0iMyIgcng9IjIiLz48L3N2Zz4=); | ||
| 257 | mask-size: cover; | ||
| 258 | } | ||
| 259 | .checkbox label:has(:checked):before { | ||
| 260 | background-color: var(--primary); | ||
| 261 | mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNxdWFyZS1jaGVjay1iaWctaWNvbiBsdWNpZGUtc3F1YXJlLWNoZWNrLWJpZyI+PHBhdGggZD0iTTIxIDEwLjY1NlYxOWEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMlY1YTIgMiAwIDAgMSAyLTJoMTIuMzQ0Ii8+PHBhdGggZD0ibTkgMTEgMyAzTDIyIDQiLz48L3N2Zz4=); | ||
| 262 | } | ||
| 263 | .checkbox label:has(:checked) { | ||
| 264 | color: var(--primary); | ||
| 265 | } | ||
| 266 | |||
| 267 | /* buttons */ | ||
| 268 | .pf-v5-c-button.pf-m-primary { | ||
| 269 | background-color: var(--primary); | ||
| 270 | display: block; | ||
| 271 | border: none; | ||
| 272 | height: 30px; | ||
| 273 | border-radius: 8px; | ||
| 274 | color: white; | ||
| 275 | } | ||
| 276 | .pf-v5-c-button.pf-m-block { | ||
| 277 | display: block; | ||
| 278 | width: 100%; | ||
| 279 | } | ||
| 280 | |||
| 281 | /* alert */ | ||
| 282 | .pf-v5-c-alert { | ||
| 283 | border-radius: 8px; | ||
| 284 | padding: 8px; | ||
| 285 | align-items: center; | ||
| 286 | margin-bottom: 1rem; | ||
| 287 | text-align: center; | ||
| 288 | text-wrap: balance; | ||
| 289 | } | ||
| 290 | .alert-error { | ||
| 291 | background-color: light-dark(#ff010182, #f56666a1); | ||
| 292 | } | ||
| 293 | .alert-warning, .alert-info { | ||
| 294 | background-color: rgb(from var(--primary) r g b / 0.5); | ||
| 295 | } | ||
| 296 | |||
| 297 | /* icons */ | ||
| 298 | [data-password-toggle] { | ||
| 299 | display: grid; | ||
| 300 | align-items: center; | ||
| 301 | justify-content: center; | ||
| 302 | } | ||
| 303 | [data-password-toggle] i { | ||
| 304 | display: block; | ||
| 305 | width: 24px; | ||
| 306 | height: 24px; | ||
| 307 | background-color: var(--text); | ||
| 308 | mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWV5ZS1pY29uIGx1Y2lkZS1leWUiPjxwYXRoIGQ9Ik0yLjA2MiAxMi4zNDhhMSAxIDAgMCAxIDAtLjY5NiAxMC43NSAxMC43NSAwIDAgMSAxOS44NzYgMCAxIDEgMCAwIDEgMCAuNjk2IDEwLjc1IDEwLjc1IDAgMCAxLTE5Ljg3NiAwIi8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIvPjwvc3ZnPg==); | ||
| 309 | } | ||
| 310 | input[type="text"] + [data-password-toggle] i { | ||
| 311 | mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWV5ZS1vZmYtaWNvbiBsdWNpZGUtZXllLW9mZiI+PHBhdGggZD0iTTEwLjczMyA1LjA3NmExMC43NDQgMTAuNzQ0IDAgMCAxIDExLjIwNSA2LjU3NSAxIDEgMCAwIDEgMCAuNjk2IDEwLjc0NyAxMC43NDcgMCAwIDEtMS40NDQgMi40OSIvPjxwYXRoIGQ9Ik0xNC4wODQgMTQuMTU4YTMgMyAwIDAgMS00LjI0Mi00LjI0MiIvPjxwYXRoIGQ9Ik0xNy40NzkgMTcuNDk5YTEwLjc1IDEwLjc1IDAgMCAxLTE1LjQxNy01LjE1MSAxIDEgMCAwIDEgMC0uNjk2IDEwLjc1IDEwLjc1IDAgMCAxIDQuNDQ2LTUuMTQzIi8+PHBhdGggZD0ibTIgMiAyMCAyMCIvPjwvc3ZnPg==); | ||
| 312 | } | ||
| 313 | |||
| 314 | @media (max-width: 799px) { | ||
| 315 | body { | ||
| 316 | background-position: left 65% bottom 60%; | ||
| 317 | background-size: auto 150%; | ||
| 318 | display: flex; | ||
| 319 | flex-direction: column; | ||
| 320 | align-items: center; | ||
| 321 | justify-content: flex-end; | ||
| 322 | } | ||
| 323 | body:before { | ||
| 324 | display: block; | ||
| 325 | content: ""; | ||
| 326 | flex: 1.5; | ||
| 327 | } | ||
| 328 | .pf-v5-c-login__main { | ||
| 329 | flex: 1 1 40%; | ||
| 330 | max-width: 25rem; | ||
| 331 | min-width: 80%; | ||
| 332 | border-width: 4px; | ||
| 333 | border-bottom-width: 0; | ||
| 334 | border-top-left-radius: 32px; | ||
| 335 | border-top-right-radius: 32px; | ||
| 336 | corner-shape: superellipse(0); | ||
| 337 | justify-content: space-between; | ||
| 338 | --bg: light-dark(rgba(255, 255, 255, 0.7), rgba(0.2, 0, 0.2, 0.6)); | ||
| 339 | } | ||
| 340 | .pf-v5-c-login__main:after { | ||
| 341 | display: block; | ||
| 342 | content: ""; | ||
| 343 | } | ||
| 344 | .kc-logo-text::before { | ||
| 345 | position: absolute; | ||
| 346 | left: 50%; | ||
| 347 | width: 100px; | ||
| 348 | transform: translate(-50%, calc(-50% - 35px)); | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | #credit { | ||
| 353 | position: fixed; | ||
| 354 | bottom: 2px; | ||
| 355 | right: 2px; | ||
| 356 | font-size: 14px; | ||
| 357 | } | ||
| 358 | #credit a:not(:hover) { | ||
| 359 | opacity: 0.5; | ||
| 360 | } | ||
config/keycloak/theme/login/resources/font/.gitignoreconfig/keycloak/theme/login/resources/img/license.txt created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | https://safebooru.org/index.php?page=post&s=view&id=4405346 | ||
| 2 | dark mode by paper clover | ||
config/keycloak/theme/login/resources/img/miku-dark.png| Binary files /dev/null and b/config/keycloak/theme/login/resources/img/miku-dark.png differ | |||
config/keycloak/theme/login/resources/img/miku-light.png| Binary files /dev/null and b/config/keycloak/theme/login/resources/img/miku-light.png differ | |||
config/keycloak/theme/login/resources/js/stars.js created+105| ... | @@ -0,0 +1,105 @@ | ||
| 1 | const shader = ` | ||
| 2 | // Heavily modified off of https://www.shadertoy.com/view/3sKGWw | ||
| 3 | // big thanks to these resources as well: | ||
| 4 | // https://youtu.be/3CycKKJiwis | ||
| 5 | // https://www.shadertoy.com/view/4djSRW | ||
| 6 | // https://www.shadertoy.com/view/3s3GDn | ||
| 7 | |||
| 8 | precision mediump float; | ||
| 9 | uniform vec2 u_resolution; | ||
| 10 | uniform float u_time; | ||
| 11 | uniform bool u_mask; | ||
| 12 | |||
| 13 | float random(vec2 p){ | ||
| 14 | vec3 p3 = fract(vec3(p.xyx) * .1031); | ||
| 15 | p3 += dot(p3, p3.yzx + 33.33); | ||
| 16 | return fract((p3.x + p3.y) * p3.z); | ||
| 17 | } | ||
| 18 | |||
| 19 | vec2 random2(vec2 p){ | ||
| 20 | vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); | ||
| 21 | p3 += dot(p3, p3.yzx+33.33); | ||
| 22 | return fract((p3.xx+p3.yz)*p3.zy); | ||
| 23 | } | ||
| 24 | |||
| 25 | void main() { | ||
| 26 | float glow; | ||
| 27 | vec2 uv; | ||
| 28 | vec2 fl; | ||
| 29 | vec2 local_uv; | ||
| 30 | vec2 seed; | ||
| 31 | vec2 centre; | ||
| 32 | vec2 cell; | ||
| 33 | float t = u_time * 0.00015; | ||
| 34 | float blinkTime = u_time * 0.0068; | ||
| 35 | float rotationAngle = -t * 21.0; | ||
| 36 | mat2 rotation = mat2(cos(rotationAngle), -sin(rotationAngle), | ||
| 37 | sin(rotationAngle), cos(rotationAngle)); | ||
| 38 | vec3 col = vec3(0); | ||
| 39 | |||
| 40 | centre = vec2(cos(t), sin(t)) * 5.0 + 0.5; | ||
| 41 | |||
| 42 | // Get uv from the fragment coordinates, rotation and depth | ||
| 43 | uv = (centre - (gl_FragCoord.xy - u_resolution / 2.0) / (1600.0 / 25.0)) * rotation; | ||
| 44 | fl = floor(uv); | ||
| 45 | // The local cell coordinates. uv-fl == frac(uv) | ||
| 46 | local_uv = uv - fl - 0.5; | ||
| 47 | |||
| 48 | // For a 3x3 group of cells around the fragment, find the distance from the points | ||
| 49 | // of each to the current fragment and draw an accumulative glow accordingly | ||
| 50 | // The local cell is (0, 0) | ||
| 51 | for(float j = -1.0; j <= 1.0; j++){ | ||
| 52 | for(float k = -1.0; k <= 1.0; k++){ | ||
| 53 | cell = vec2(j, k); | ||
| 54 | seed = 128.0 + (fl + cell); | ||
| 55 | glow = | ||
| 56 | ((0.6 + 0.5 * sin((128.0 * random(seed)) * blinkTime))) * 25.0 | ||
| 57 | * pow(0.005 / length(local_uv - (cell + 0.9 * (random2(seed) - 0.5))), 2.0); | ||
| 58 | col += vec3(0.2 * glow) + 0.1 * glow; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | col = 1.0 - exp(-col); | ||
| 63 | col = pow(col, vec3(0.45)); | ||
| 64 | |||
| 65 | if (u_mask) { | ||
| 66 | float mask = smoothstep(100.0, 300.0, length(vec2(gl_FragCoord.x - u_resolution.x/2.0, gl_FragCoord.y - 200.0))); | ||
| 67 | col *= mask; | ||
| 68 | } | ||
| 69 | |||
| 70 | gl_FragColor = vec4(col * (u_mask ? 0.6 : 0.25), 0); | ||
| 71 | } | ||
| 72 | `; | ||
| 73 | |||
| 74 | function createShader(gl, type, source) { | ||
| 75 | const shader = gl.createShader(type); | ||
| 76 | gl.shaderSource(shader, source); | ||
| 77 | gl.compileShader(shader); | ||
| 78 | const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); | ||
| 79 | if (success) { | ||
| 80 | return shader; | ||
| 81 | } | ||
| 82 | |||
| 83 | // eslint-disable-next-line no-console | ||
| 84 | console.error(gl.getShaderInfoLog(shader)); | ||
| 85 | gl.deleteShader(shader); | ||
| 86 | throw new Error("Failed to compile shader"); | ||
| 87 | } | ||
| 88 | |||
| 89 | function createProgram(gl, vertexShader, fragmentShader) { | ||
| 90 | const program = gl.createProgram(); | ||
| 91 | |||
| 92 | gl.attachShader(program, vertexShader); | ||
| 93 | gl.attachShader(program, fragmentShader); | ||
| 94 | gl.linkProgram(program); | ||
| 95 | |||
| 96 | const success = gl.getProgramParameter(program, gl.LINK_STATUS); | ||
| 97 | if (success) { | ||
| 98 | return program; | ||
| 99 | } | ||
| 100 | |||
| 101 | // eslint-disable-next-line no-console | ||
| 102 | console.error(gl.getProgramInfoLog(program)); | ||
| 103 | gl.deleteProgram(program); | ||
| 104 | throw new Error("Failed to link program"); | ||
| 105 | } | ||
config/keycloak/theme/login/resources/js/webauthnRegister.js created+159| ... | @@ -0,0 +1,159 @@ | ||
| 1 | import { base64url } from "rfc4648"; | ||
| 2 | |||
| 3 | export async function registerByWebAuthn(input) { | ||
| 4 | // Check if WebAuthn is supported by this browser | ||
| 5 | if (!window.PublicKeyCredential) { | ||
| 6 | returnFailure(input.errmsg); | ||
| 7 | return; | ||
| 8 | } | ||
| 9 | |||
| 10 | const publicKey = { | ||
| 11 | challenge: base64url.parse(input.challenge, { loose: true }), | ||
| 12 | rp: { id: input.rpId, name: input.rpEntityName }, | ||
| 13 | user: { | ||
| 14 | id: base64url.parse(input.userid, { loose: true }), | ||
| 15 | name: input.username, | ||
| 16 | displayName: input.username, | ||
| 17 | }, | ||
| 18 | pubKeyCredParams: getPubKeyCredParams(input.signatureAlgorithms), | ||
| 19 | }; | ||
| 20 | |||
| 21 | if (input.attestationConveyancePreference !== "not specified") { | ||
| 22 | publicKey.attestation = input.attestationConveyancePreference; | ||
| 23 | } | ||
| 24 | |||
| 25 | const authenticatorSelection = {}; | ||
| 26 | let isAuthenticatorSelectionSpecified = false; | ||
| 27 | |||
| 28 | if (input.authenticatorAttachment !== "not specified") { | ||
| 29 | authenticatorSelection.authenticatorAttachment = | ||
| 30 | input.authenticatorAttachment; | ||
| 31 | isAuthenticatorSelectionSpecified = true; | ||
| 32 | } | ||
| 33 | |||
| 34 | if (input.requireResidentKey !== "not specified") { | ||
| 35 | if (input.requireResidentKey === "Yes") { | ||
| 36 | authenticatorSelection.requireResidentKey = true; | ||
| 37 | } else { | ||
| 38 | authenticatorSelection.requireResidentKey = false; | ||
| 39 | } | ||
| 40 | isAuthenticatorSelectionSpecified = true; | ||
| 41 | } | ||
| 42 | |||
| 43 | if (input.userVerificationRequirement !== "not specified") { | ||
| 44 | authenticatorSelection.userVerification = input.userVerificationRequirement; | ||
| 45 | isAuthenticatorSelectionSpecified = true; | ||
| 46 | } | ||
| 47 | |||
| 48 | if (isAuthenticatorSelectionSpecified) { | ||
| 49 | publicKey.authenticatorSelection = authenticatorSelection; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (input.createTimeout !== 0) { | ||
| 53 | publicKey.timeout = input.createTimeout * 1000; | ||
| 54 | } | ||
| 55 | |||
| 56 | const excludeCredentials = getExcludeCredentials(input.excludeCredentialIds); | ||
| 57 | if (excludeCredentials.length > 0) { | ||
| 58 | publicKey.excludeCredentials = excludeCredentials; | ||
| 59 | } | ||
| 60 | |||
| 61 | try { | ||
| 62 | const result = await doRegister(publicKey); | ||
| 63 | returnSuccess(result, input.initLabel, input.initLabelPrompt); | ||
| 64 | } catch (error) { | ||
| 65 | returnFailure(error); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | function doRegister(publicKey) { | ||
| 70 | return navigator.credentials.create({ publicKey }); | ||
| 71 | } | ||
| 72 | |||
| 73 | function getPubKeyCredParams(signatureAlgorithmsList) { | ||
| 74 | const pubKeyCredParams = []; | ||
| 75 | if (signatureAlgorithmsList.length === 0) { | ||
| 76 | pubKeyCredParams.push({ type: "public-key", alg: -7 }); | ||
| 77 | return pubKeyCredParams; | ||
| 78 | } | ||
| 79 | |||
| 80 | for (const entry of signatureAlgorithmsList) { | ||
| 81 | pubKeyCredParams.push({ | ||
| 82 | type: "public-key", | ||
| 83 | alg: entry, | ||
| 84 | }); | ||
| 85 | } | ||
| 86 | |||
| 87 | return pubKeyCredParams; | ||
| 88 | } | ||
| 89 | |||
| 90 | function getExcludeCredentials(excludeCredentialIds) { | ||
| 91 | const excludeCredentials = []; | ||
| 92 | if (excludeCredentialIds === "") { | ||
| 93 | return excludeCredentials; | ||
| 94 | } | ||
| 95 | |||
| 96 | for (const entry of excludeCredentialIds.split(",")) { | ||
| 97 | excludeCredentials.push({ | ||
| 98 | type: "public-key", | ||
| 99 | id: base64url.parse(entry, { loose: true }), | ||
| 100 | }); | ||
| 101 | } | ||
| 102 | |||
| 103 | return excludeCredentials; | ||
| 104 | } | ||
| 105 | |||
| 106 | function getTransportsAsString(transportsList) { | ||
| 107 | if (!Array.isArray(transportsList)) { | ||
| 108 | return ""; | ||
| 109 | } | ||
| 110 | |||
| 111 | return transportsList.join(); | ||
| 112 | } | ||
| 113 | |||
| 114 | function returnSuccess(result, initLabel, initLabelPrompt) { | ||
| 115 | document.getElementById("clientDataJSON").value = base64url.stringify( | ||
| 116 | new Uint8Array(result.response.clientDataJSON), | ||
| 117 | { pad: false }, | ||
| 118 | ); | ||
| 119 | document.getElementById("attestationObject").value = base64url.stringify( | ||
| 120 | new Uint8Array(result.response.attestationObject), | ||
| 121 | { pad: false }, | ||
| 122 | ); | ||
| 123 | document.getElementById("publicKeyCredentialId").value = base64url.stringify( | ||
| 124 | new Uint8Array(result.rawId), | ||
| 125 | { pad: false }, | ||
| 126 | ); | ||
| 127 | |||
| 128 | if (typeof result.response.getTransports === "function") { | ||
| 129 | const transports = result.response.getTransports(); | ||
| 130 | if (transports) { | ||
| 131 | document.getElementById("transports").value = getTransportsAsString( | ||
| 132 | transports, | ||
| 133 | ); | ||
| 134 | } | ||
| 135 | } else { | ||
| 136 | console.log( | ||
| 137 | "Your browser is not able to recognize supported transport media for the authenticator.", | ||
| 138 | ); | ||
| 139 | } | ||
| 140 | |||
| 141 | // let labelResult = window.prompt(initLabelPrompt, initLabel); | ||
| 142 | // if (labelResult === null) { | ||
| 143 | // labelResult = initLabel; | ||
| 144 | // } | ||
| 145 | document.getElementById("authenticatorLabel").value = "Passkey " + | ||
| 146 | new Date().toString(); | ||
| 147 | |||
| 148 | document.getElementById("register").requestSubmit(); | ||
| 149 | } | ||
| 150 | |||
| 151 | function returnFailure(err) { | ||
| 152 | if (err.name === "NotAllowedError") { | ||
| 153 | document.getElementById("error").value = | ||
| 154 | "the operation was cancelled / browser does not support passkey"; | ||
| 155 | } else { | ||
| 156 | document.getElementById("error").value = err; | ||
| 157 | } | ||
| 158 | document.getElementById("register").requestSubmit(); | ||
| 159 | } | ||
config/keycloak/theme/login/template.ftl created+209| ... | @@ -0,0 +1,209 @@ | ||
| 1 | <#import "footer.ftl" as loginFooter> | ||
| 2 | <#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false> | ||
| 3 | <!DOCTYPE html> | ||
| 4 | <html class="${properties.kcHtmlClass!}" lang="${lang}"<#if realm.internationalizationEnabled> dir="${(locale.rtl)?then('rtl','ltr')}"</#if>> | ||
| 5 | |||
| 6 | <head> | ||
| 7 | <meta charset="utf-8"> | ||
| 8 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
| 9 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| 10 | |||
| 11 | <#if properties.meta?has_content> | ||
| 12 | <#list properties.meta?split(' ') as meta> | ||
| 13 | <meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/> | ||
| 14 | </#list> | ||
| 15 | </#if> | ||
| 16 | <title>sso (snow sign on)</title> | ||
| 17 | <link rel="icon" href="${url.resourcesPath}/img/favicon.ico" /> | ||
| 18 | <#if properties.stylesCommon?has_content> | ||
| 19 | <#list properties.stylesCommon?split(' ') as style> | ||
| 20 | <link href="${url.resourcesCommonPath}/${style}" rel="stylesheet" /> | ||
| 21 | </#list> | ||
| 22 | </#if> | ||
| 23 | <#if properties.styles?has_content> | ||
| 24 | <#list properties.styles?split(' ') as style> | ||
| 25 | <link href="${url.resourcesPath}/${style}" rel="stylesheet" /> | ||
| 26 | </#list> | ||
| 27 | </#if> | ||
| 28 | <#if properties.scripts?has_content> | ||
| 29 | <#list properties.scripts?split(' ') as script> | ||
| 30 | <script src="${url.resourcesPath}/${script}" type="text/javascript"></script> | ||
| 31 | </#list> | ||
| 32 | </#if> | ||
| 33 | <script type="importmap"> | ||
| 34 | { | ||
| 35 | "imports": { | ||
| 36 | "rfc4648": "${url.resourcesCommonPath}/vendor/rfc4648/rfc4648.js" | ||
| 37 | } | ||
| 38 | } | ||
| 39 | </script> | ||
| 40 | <script src="${url.resourcesPath}/js/menu-button-links.js" type="module"></script> | ||
| 41 | <#if scripts??> | ||
| 42 | <#list scripts as script> | ||
| 43 | <script src="${script}" type="text/javascript"></script> | ||
| 44 | </#list> | ||
| 45 | </#if> | ||
| 46 | <script type="module"> | ||
| 47 | import { startSessionPolling } from "${url.resourcesPath}/js/authChecker.js"; | ||
| 48 | |||
| 49 | startSessionPolling( | ||
| 50 | "${url.ssoLoginInOtherTabsUrl?no_esc}" | ||
| 51 | ); | ||
| 52 | </script> | ||
| 53 | <script type="module"> | ||
| 54 | document.addEventListener("click", (event) => { | ||
| 55 | const link = event.target.closest("a[data-once-link]"); | ||
| 56 | |||
| 57 | if (!link) { | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (link.getAttribute("aria-disabled") === "true") { | ||
| 62 | event.preventDefault(); | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | |||
| 66 | const { disabledClass } = link.dataset; | ||
| 67 | |||
| 68 | if (disabledClass) { | ||
| 69 | link.classList.add(...disabledClass.trim().split(/\s+/)); | ||
| 70 | } | ||
| 71 | |||
| 72 | link.setAttribute("role", "link"); | ||
| 73 | link.setAttribute("aria-disabled", "true"); | ||
| 74 | }); | ||
| 75 | </script> | ||
| 76 | <#if authenticationSession??> | ||
| 77 | <script type="module"> | ||
| 78 | import { checkAuthSession } from "${url.resourcesPath}/js/authChecker.js"; | ||
| 79 | |||
| 80 | checkAuthSession( | ||
| 81 | "${authenticationSession.authSessionIdHash}" | ||
| 82 | ); | ||
| 83 | </script> | ||
| 84 | </#if> | ||
| 85 | </head> | ||
| 86 | |||
| 87 | <body class="${properties.kcBodyClass!}" data-page-id="login-${pageId}"> | ||
| 88 | <div class="${properties.kcLoginClass!}"> | ||
| 89 | <div id="kc-header" class="${properties.kcHeaderClass!}"> | ||
| 90 | <div id="kc-header-wrapper" | ||
| 91 | class="${properties.kcHeaderWrapperClass!}">${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</div> | ||
| 92 | </div> | ||
| 93 | <div class="${properties.kcFormCardClass!}"> | ||
| 94 | <header class="${properties.kcFormHeaderClass!}"> | ||
| 95 | <#if realm.internationalizationEnabled && locale.supported?size gt 1> | ||
| 96 | <div class="${properties.kcLocaleMainClass!}" id="kc-locale"> | ||
| 97 | <div id="kc-locale-wrapper" class="${properties.kcLocaleWrapperClass!}"> | ||
| 98 | <div id="kc-locale-dropdown" class="menu-button-links ${properties.kcLocaleDropDownClass!}"> | ||
| 99 | <button tabindex="1" id="kc-current-locale-link" aria-label="${msg("languages")}" aria-haspopup="true" aria-expanded="false" aria-controls="language-switch1">${locale.current}</button> | ||
| 100 | <ul role="menu" tabindex="-1" aria-labelledby="kc-current-locale-link" aria-activedescendant="" id="language-switch1" class="${properties.kcLocaleListClass!}"> | ||
| 101 | <#assign i = 1> | ||
| 102 | <#list locale.supported as l> | ||
| 103 | <li class="${properties.kcLocaleListItemClass!}" role="none"> | ||
| 104 | <a role="menuitem" id="language-${i}" class="${properties.kcLocaleItemClass!}" href="${l.url}">${l.label}</a> | ||
| 105 | </li> | ||
| 106 | <#assign i++> | ||
| 107 | </#list> | ||
| 108 | </ul> | ||
| 109 | </div> | ||
| 110 | </div> | ||
| 111 | </div> | ||
| 112 | </#if> | ||
| 113 | <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> | ||
| 114 | <#if displayRequiredFields> | ||
| 115 | <div class="${properties.kcContentWrapperClass!}"> | ||
| 116 | <div class="${properties.kcLabelWrapperClass!} subtitle"> | ||
| 117 | <span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span> | ||
| 118 | </div> | ||
| 119 | <div class="col-md-10"> | ||
| 120 | <h1 id="kc-page-title"><#nested "header"></h1> | ||
| 121 | </div> | ||
| 122 | </div> | ||
| 123 | <#else> | ||
| 124 | <h1 id="kc-page-title"><#nested "header"></h1> | ||
| 125 | </#if> | ||
| 126 | <#else> | ||
| 127 | <#if displayRequiredFields> | ||
| 128 | <div class="${properties.kcContentWrapperClass!}"> | ||
| 129 | <div class="${properties.kcLabelWrapperClass!} subtitle"> | ||
| 130 | <span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span> | ||
| 131 | </div> | ||
| 132 | <div class="col-md-10"> | ||
| 133 | <#nested "show-username"> | ||
| 134 | <div id="kc-username" class="${properties.kcFormGroupClass!}"> | ||
| 135 | <label id="kc-attempted-username">${auth.attemptedUsername}</label> | ||
| 136 | <a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}"> | ||
| 137 | <div class="kc-login-tooltip"> | ||
| 138 | <i class="${properties.kcResetFlowIcon!}"></i> | ||
| 139 | <span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span> | ||
| 140 | </div> | ||
| 141 | </a> | ||
| 142 | </div> | ||
| 143 | </div> | ||
| 144 | </div> | ||
| 145 | <#else> | ||
| 146 | <#nested "show-username"> | ||
| 147 | <div id="kc-username" class="${properties.kcFormGroupClass!}"> | ||
| 148 | <label id="kc-attempted-username">${auth.attemptedUsername}</label> | ||
| 149 | <a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}"> | ||
| 150 | <div class="kc-login-tooltip"> | ||
| 151 | <i class="${properties.kcResetFlowIcon!}"></i> | ||
| 152 | <span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span> | ||
| 153 | </div> | ||
| 154 | </a> | ||
| 155 | </div> | ||
| 156 | </#if> | ||
| 157 | </#if> | ||
| 158 | </header> | ||
| 159 | <div id="kc-content"> | ||
| 160 | <div id="kc-content-wrapper"> | ||
| 161 | |||
| 162 | <#-- App-initiated actions should not see warning messages about the need to complete the action --> | ||
| 163 | <#-- during login. --> | ||
| 164 | <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)> | ||
| 165 | <div class="alert-${message.type} ${properties.kcAlertClass!} pf-m-<#if message.type = 'error'>danger<#else>${message.type}</#if>"> | ||
| 166 | <div class="pf-c-alert__icon"> | ||
| 167 | <#if message.type = 'success'><span class="${properties.kcFeedbackSuccessIcon!}"></span></#if> | ||
| 168 | <#if message.type = 'warning'><span class="${properties.kcFeedbackWarningIcon!}"></span></#if> | ||
| 169 | <#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if> | ||
| 170 | <#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if> | ||
| 171 | </div> | ||
| 172 | <span class="${properties.kcAlertTitleClass!}">${kcSanitize(message.summary)?no_esc}</span> | ||
| 173 | </div> | ||
| 174 | </#if> | ||
| 175 | |||
| 176 | <#nested "form"> | ||
| 177 | |||
| 178 | <#if auth?has_content && auth.showTryAnotherWayLink()> | ||
| 179 | <form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post"> | ||
| 180 | <div class="${properties.kcFormGroupClass!}"> | ||
| 181 | <input type="hidden" name="tryAnotherWay" value="on"/> | ||
| 182 | <a href="#" id="try-another-way" | ||
| 183 | onclick="document.forms['kc-select-try-another-way-form'].requestSubmit();return false;">${msg("doTryAnotherWay")}</a> | ||
| 184 | </div> | ||
| 185 | </form> | ||
| 186 | </#if> | ||
| 187 | |||
| 188 | <#nested "socialProviders"> | ||
| 189 | |||
| 190 | <#if displayInfo> | ||
| 191 | <div id="kc-info" class="${properties.kcSignUpClass!}"> | ||
| 192 | <div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}"> | ||
| 193 | <#nested "info"> | ||
| 194 | </div> | ||
| 195 | </div> | ||
| 196 | </#if> | ||
| 197 | </div> | ||
| 198 | </div> | ||
| 199 | |||
| 200 | |||
| 201 | <@loginFooter.content/> | ||
| 202 | </div> | ||
| 203 | </div> | ||
| 204 | <div id='credit'> | ||
| 205 | <a rel="noopener noreferrer" target="_blank" href="https://www.pixiv.net/en/artworks/109102745">art by 紺屋</a> | ||
| 206 | </div> | ||
| 207 | </body> | ||
| 208 | </html> | ||
| 209 | </#macro> | ||
config/keycloak/theme/login/theme.properties created+128| ... | @@ -0,0 +1,128 @@ | ||
| 1 | parent=base | ||
| 2 | import=common/keycloak | ||
| 3 | |||
| 4 | styles=css/styles.css | ||
| 5 | stylesCommon= | ||
| 6 | |||
| 7 | darkMode=false | ||
| 8 | |||
| 9 | kcFormGroupClass=pf-v5-c-form__group | ||
| 10 | kcFormGroupLabelClass=pf-v5-c-form__group-label pf-v5-u-pb-xs | ||
| 11 | kcFormLabelClass=pf-v5-c-form__label | ||
| 12 | kcFormLabelTextClass=pf-v5-c-form__label-text | ||
| 13 | |||
| 14 | kcLabelClass=pf-v5-c-form__label | ||
| 15 | kcInputClass=pf-v5-c-form-control | ||
| 16 | kcInputGroup=pf-v5-c-input-group | ||
| 17 | kcFormHelperTextClass=pf-v5-c-form__helper-text | ||
| 18 | kcInputHelperTextClass=pf-v5-c-helper-text pf-v5-u-display-flex pf-v5-u-justify-content-space-between | ||
| 19 | kcInputHelperTextItemClass=pf-v5-c-helper-text__item | ||
| 20 | kcInputHelperTextItemTextClass=pf-v5-c-helper-text__item-text | ||
| 21 | kcInputGroupItemClass=pf-v5-c-input-group__item | ||
| 22 | kcFill=pf-m-fill | ||
| 23 | kcError=pf-m-error | ||
| 24 | kcLoginFooterBand=pf-v5-c-login__main-footer-band | ||
| 25 | kcLoginFooterBandItem=pf-v5-c-login__main-footer-band-item | ||
| 26 | |||
| 27 | kcCheckboxClass=pf-v5-c-check | ||
| 28 | kcCheckboxInputClass=pf-v5-c-check__input | ||
| 29 | kcCheckboxLabelClass=pf-v5-c-check__label | ||
| 30 | kcCheckboxLabelRequiredClass=pf-v5-c-check__label-required | ||
| 31 | |||
| 32 | kcInputRequiredClass=pf-v5-c-form__label-required | ||
| 33 | kcInputErrorMessageClass=pf-v5-c-helper-text__item-text pf-m-error kc-feedback-text | ||
| 34 | kcFormControlUtilClass=pf-v5-c-form-control__utilities | ||
| 35 | kcInputErrorIconStatusClass=pf-v5-c-form-control__icon pf-m-status | ||
| 36 | kcInputErrorIconClass=fas fa-exclamation-circle | ||
| 37 | kcAlertClass=pf-v5-c-alert pf-m-inline pf-v5-u-mb-md | ||
| 38 | kcAlertIconClass=pf-v5-c-alert__icon | ||
| 39 | kcAlertTitleClass=pf-v5-c-alert__title | ||
| 40 | kcAlertDescriptionClass=pf-v5-c-alert__description | ||
| 41 | kcFormPasswordVisibilityButtonClass=pf-v5-c-button pf-m-control | ||
| 42 | kcFormControlToggleIcon=pf-v5-c-form-control__toggle-icon | ||
| 43 | kcFormActionGroupClass=pf-v5-c-form__actions pf-v5-u-pt-xs | ||
| 44 | kcFormReadOnlyClass=pf-m-readonly | ||
| 45 | |||
| 46 | kcPanelClass=pf-v5-c-panel pf-m-raised | ||
| 47 | kcPanelMainClass=pf-v5-c-panel__main | ||
| 48 | kcPanelMainBodyClass=pf-v5-c-panel__main-body | ||
| 49 | kcListClass=pf-v5-c-list | ||
| 50 | |||
| 51 | kcButtonClass=pf-v5-c-button | ||
| 52 | kcButtonPrimaryClass=pf-v5-c-button pf-m-primary | ||
| 53 | kcButtonSecondaryClass=pf-v5-c-button pf-m-secondary | ||
| 54 | kcButtonBlockClass=pf-m-block | ||
| 55 | kcButtonLinkClass=pf-v5-c-button pf-m-link | ||
| 56 | kcCommonLogoIdP=pf-v5-c-login__main-footer-links-item | ||
| 57 | kcFormSocialAccountListClass=pf-v5-c-login__main-body pf-v5-u-pl-0 pf-v5-u-pr-0 | ||
| 58 | kcFormSocialAccountListItemClass=pf-v5-u-pb-sm | ||
| 59 | kcFormSocialAccountNameClass=pf-v5-u-m-auto | ||
| 60 | kcFormSocialAccountListButtonClass=pf-v5-c-button pf-m-secondary pf-m-block pf-v5-u-display-flex pf-v5-u-align-items-center pf-v5-u-justify-content-space-between | ||
| 61 | kcFormSocialAccountListButtonDisabledClass=pf-m-aria-disabled | ||
| 62 | kcFormSocialAccountListGridClass=pf-v5-l-grid pf-m-gutter pf-m-all-6-col-on-xl pf-m-all-6-col-on-sm | ||
| 63 | kcFormSocialAccountGridItem=pf-v5-l-grid__item | ||
| 64 | |||
| 65 | kcLoginClass=pf-v5-c-login__main | ||
| 66 | kcFormClass=pf-v5-c-form pf-v5-u-w-100 | ||
| 67 | kcFormCardClass=card-pf | ||
| 68 | |||
| 69 | kcResetFlowIcon=pf-icon fas fa-share-square | ||
| 70 | |||
| 71 | kcSelectAuthListClass=pf-v5-c-data-list select-auth-container | ||
| 72 | kcSelectAuthListItemWrapperClass=pf-v5-c-data-list__item pf-m-clickable | ||
| 73 | kcSelectAuthListItemClass=pf-v5-c-data-list__item-row select-auth-box-parent | ||
| 74 | kcSelectAuthListItemHeadingClass=pf-v5-u-font-family-heading select-auth-box-headline | ||
| 75 | kcSelectAuthListItemBodyClass=pf-v5-c-data-list__cell pf-m-no-fill pf-v5-u-pt-md pf-v5-u-pb-md | ||
| 76 | kcSelectAuthListItemIconClass=pf-v5-c-data-list__cell pf-m-icon pf-v5-u-display-flex pf-v5-u-pt-0 pf-v5-u-align-items-center | ||
| 77 | kcSelectAuthListItemFillClass=pf-v5-c-data-list__item-action | ||
| 78 | kcSelectAuthListItemDescriptionClass=pf-v5-c-data-list__cell pf-m-no-fill select-auth-box-desc | ||
| 79 | |||
| 80 | kcRecoveryCodesWarning=pf-v5-c-alert pf-m-warning pf-m-inline pf-v5-u-mb-md kc-recovery-codes-warning | ||
| 81 | kcLogin=pf-v5-c-login | ||
| 82 | kcLoginContainer=pf-v5-c-login__container | ||
| 83 | kcLoginMain=pf-v5-c-login__main | ||
| 84 | kcLoginMainHeader=pf-v5-c-login__main-header | ||
| 85 | kcLoginMainFooter=pf-v5-c-login__main-footer | ||
| 86 | kcLoginMainFooterBand=pf-v5-c-login__main-footer-band | ||
| 87 | kcLoginMainFooterBandItem=pf-v5-c-login__main-footer-band-item | ||
| 88 | kcLoginMainFooterHelperText=pf-v5-u-font-size-sm pf-v5-u-color-200 | ||
| 89 | kcLoginMainTitle=pf-v5-c-title pf-m-3xl | ||
| 90 | kcLoginMainHeaderUtilities=pf-v5-c-login__main-header-utilities | ||
| 91 | kcLoginMainBody=pf-v5-c-login__main-body | ||
| 92 | |||
| 93 | kcContentWrapperClass=pf-v5-u-mb-md-on-md | ||
| 94 | kcWebAuthnDefaultIcon=pf-v5-c-icon pf-m-lg | ||
| 95 | kcMarginTopClass=pf-v5-u-mt-md-on-md | ||
| 96 | |||
| 97 | kcLoginOTPListClass=pf-v5-c-tile | ||
| 98 | kcLoginOTPListItemHeaderClass=pf-v5-c-tile__header pf-m-stacked | ||
| 99 | kcLoginOTPListItemIconBodyClass=pf-v5-c-tile__icon | ||
| 100 | kcLoginOTPListItemIconClass=fa fa-mobile | ||
| 101 | kcLoginOTPListItemTitleClass=pf-v5-c-tile__title | ||
| 102 | kcLoginOTPListSelectedClass=pf-m-selected | ||
| 103 | |||
| 104 | kcCopyIconClass=fas fa-copy | ||
| 105 | kcCheckIconClass=fas fa-check | ||
| 106 | kcAngleRightIconClass=fas fa-angle-right | ||
| 107 | kcAngleDownIconClass=fas fa-angle-down | ||
| 108 | kcExpandedClass=pf-m-expanded | ||
| 109 | |||
| 110 | kcCodeClipboardCopyClass=pf-v5-c-clipboard-copy | ||
| 111 | kcCodeClipboardCopyGroupClass=pf-v5-c-clipboard-copy__group | ||
| 112 | kcCodeClipboardCopyContentClass=pf-v5-c-clipboard-copy__expandable-content | ||
| 113 | |||
| 114 | kcDarkModeClass=pf-v5-theme-dark | ||
| 115 | |||
| 116 | kcHtmlClass=login-pf | ||
| 117 | kcLogoIdP-facebook= | ||
| 118 | kcLogoIdP-google= | ||
| 119 | kcLogoIdP-github= | ||
| 120 | kcLogoIdP-linkedin= | ||
| 121 | kcLogoIdP-instagram= | ||
| 122 | kcLogoIdP-microsoft= | ||
| 123 | kcLogoIdP-bitbucket= | ||
| 124 | kcLogoIdP-gitlab= | ||
| 125 | kcLogoIdP-paypal= | ||
| 126 | kcLogoIdP-stackoverflow= | ||
| 127 | kcLogoIdP-twitter= | ||
| 128 | kcLogoIdP-openshift-v4= | ||
config/keycloak/theme/login/webauthn-error.ftl created+36| ... | @@ -0,0 +1,36 @@ | ||
| 1 | <#import "template.ftl" as layout> | ||
| 2 | <@layout.registrationLayout displayMessage=true; section> | ||
| 3 | <#if section = "header"> | ||
| 4 | ${kcSanitize(msg("webauthn-error-title"))?no_esc} | ||
| 5 | <#elseif section = "form"> | ||
| 6 | |||
| 7 | <script type="text/javascript"> | ||
| 8 | refreshPage = () => { | ||
| 9 | document.getElementById('isSetRetry').value = 'retry'; | ||
| 10 | document.getElementById('executionValue').value = '${execution}'; | ||
| 11 | document.getElementById('kc-error-credential-form').requestSubmit(); | ||
| 12 | } | ||
| 13 | </script> | ||
| 14 | |||
| 15 | <form id="kc-error-credential-form" class="${properties.kcFormClass!}" action="${url.loginAction}" | ||
| 16 | method="post"> | ||
| 17 | <input type="hidden" id="executionValue" name="authenticationExecution"/> | ||
| 18 | <input type="hidden" id="isSetRetry" name="isSetRetry"/> | ||
| 19 | </form> | ||
| 20 | |||
| 21 | <input tabindex="4" onclick="refreshPage()" type="button" | ||
| 22 | class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" | ||
| 23 | name="try-again" id="kc-try-again" value="${kcSanitize(msg("doTryAgain"))?no_esc}" | ||
| 24 | /> | ||
| 25 | |||
| 26 | <#if isAppInitiatedAction??> | ||
| 27 | <form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-webauthn-settings-form" method="post"> | ||
| 28 | <button type="submit" | ||
| 29 | class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" | ||
| 30 | id="cancelWebAuthnAIA" name="cancel-aia" value="true">${msg("doCancel")} | ||
| 31 | </button> | ||
| 32 | </form> | ||
| 33 | </#if> | ||
| 34 | |||
| 35 | </#if> | ||
| 36 | </@layout.registrationLayout> | ||
| \ No newline at end of file | |||
config/keycloak/theme/login/webauthn-register.ftl created+72| ... | @@ -0,0 +1,72 @@ | ||
| 1 | <#import "template.ftl" as layout> | ||
| 2 | <#import "password-commons.ftl" as passwordCommons> | ||
| 3 | |||
| 4 | <@layout.registrationLayout; section> | ||
| 5 | <#if section = "title"> | ||
| 6 | title | ||
| 7 | <#elseif section = "header"> | ||
| 8 | <div class="alert-info pf-v5-c-alert pf-m-inline pf-v5-u-mb-md pf-m-danger"> | ||
| 9 | <div class="pf-c-alert__icon"> | ||
| 10 | <span class=""></span> | ||
| 11 | </div> | ||
| 12 | <span class="pf-v5-c-alert__title"> | ||
| 13 | you will be prompted to create a passkey. please contact clover if | ||
| 14 | this doesn't work in your browser. | ||
| 15 | </span> | ||
| 16 | </div> | ||
| 17 | <#elseif section = "form"> | ||
| 18 | |||
| 19 | <form id="register" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> | ||
| 20 | <div class="${properties.kcFormGroupClass!}"> | ||
| 21 | <input type="hidden" id="clientDataJSON" name="clientDataJSON"/> | ||
| 22 | <input type="hidden" id="attestationObject" name="attestationObject"/> | ||
| 23 | <input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId"/> | ||
| 24 | <input type="hidden" id="authenticatorLabel" name="authenticatorLabel"/> | ||
| 25 | <input type="hidden" id="transports" name="transports"/> | ||
| 26 | <input type="hidden" id="error" name="error"/> | ||
| 27 | </div> | ||
| 28 | </form> | ||
| 29 | |||
| 30 | <script type="module"> | ||
| 31 | import { registerByWebAuthn } from "${url.resourcesPath}/js/webauthnRegister.js"; | ||
| 32 | function init() { | ||
| 33 | const input = { | ||
| 34 | challenge : '${challenge}', | ||
| 35 | userid : '${userid}', | ||
| 36 | username : '${username}', | ||
| 37 | signatureAlgorithms : [<#list signatureAlgorithms as sigAlg>${sigAlg?c},</#list>], | ||
| 38 | rpEntityName : '${rpEntityName}', | ||
| 39 | rpId : '${rpId}', | ||
| 40 | attestationConveyancePreference : '${attestationConveyancePreference}', | ||
| 41 | authenticatorAttachment : '${authenticatorAttachment}', | ||
| 42 | requireResidentKey : '${requireResidentKey}', | ||
| 43 | userVerificationRequirement : '${userVerificationRequirement}', | ||
| 44 | createTimeout : ${createTimeout?c}, | ||
| 45 | excludeCredentialIds : '${excludeCredentialIds}', | ||
| 46 | initLabel : "${msg("webauthn-registration-init-label")?no_esc}", | ||
| 47 | initLabelPrompt : "${msg("webauthn-registration-init-label-prompt")?no_esc}", | ||
| 48 | errmsg : "${msg("webauthn-unsupported-browser-text")?no_esc}" | ||
| 49 | }; | ||
| 50 | registerByWebAuthn(input); | ||
| 51 | } | ||
| 52 | init(); | ||
| 53 | </script> | ||
| 54 | |||
| 55 | <!-- | ||
| 56 | <input type="submit" | ||
| 57 | class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" | ||
| 58 | id="registerWebAuthn" value="${msg("doRegisterSecurityKey")}"/> | ||
| 59 | --> | ||
| 60 | |||
| 61 | <#if !isSetRetry?has_content && isAppInitiatedAction?has_content> | ||
| 62 | <form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-webauthn-settings-form" | ||
| 63 | method="post"> | ||
| 64 | <button type="submit" | ||
| 65 | class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" | ||
| 66 | id="cancelWebAuthnAIA" name="cancel-aia" value="true">${msg("doCancel")} | ||
| 67 | </button> | ||
| 68 | </form> | ||
| 69 | </#if> | ||
| 70 | |||
| 71 | </#if> | ||
| 72 | </@layout.registrationLayout> | ||
config/keycloak/uv.lock created+380| ... | @@ -0,0 +1,380 @@ | ||
| 1 | version = 1 | ||
| 2 | revision = 3 | ||
| 3 | requires-python = ">=3.12" | ||
| 4 | |||
| 5 | [[package]] | ||
| 6 | name = "aiofiles" | ||
| 7 | version = "25.1.0" | ||
| 8 | source = { registry = "https://pypi.org/simple" } | ||
| 9 | sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } | ||
| 10 | wheels = [ | ||
| 11 | { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, | ||
| 12 | ] | ||
| 13 | |||
| 14 | [[package]] | ||
| 15 | name = "anyio" | ||
| 16 | version = "4.12.0" | ||
| 17 | source = { registry = "https://pypi.org/simple" } | ||
| 18 | dependencies = [ | ||
| 19 | { name = "idna" }, | ||
| 20 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, | ||
| 21 | ] | ||
| 22 | sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } | ||
| 23 | wheels = [ | ||
| 24 | { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, | ||
| 25 | ] | ||
| 26 | |||
| 27 | [[package]] | ||
| 28 | name = "async-property" | ||
| 29 | version = "0.2.2" | ||
| 30 | source = { registry = "https://pypi.org/simple" } | ||
| 31 | sdist = { url = "https://files.pythonhosted.org/packages/a7/12/900eb34b3af75c11b69d6b78b74ec0fd1ba489376eceb3785f787d1a0a1d/async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380", size = 16523, upload-time = "2023-07-03T17:21:55.688Z" } | ||
| 32 | wheels = [ | ||
| 33 | { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546, upload-time = "2023-07-03T17:21:54.293Z" }, | ||
| 34 | ] | ||
| 35 | |||
| 36 | [[package]] | ||
| 37 | name = "certifi" | ||
| 38 | version = "2026.1.4" | ||
| 39 | source = { registry = "https://pypi.org/simple" } | ||
| 40 | sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } | ||
| 41 | wheels = [ | ||
| 42 | { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, | ||
| 43 | ] | ||
| 44 | |||
| 45 | [[package]] | ||
| 46 | name = "cffi" | ||
| 47 | version = "2.0.0" | ||
| 48 | source = { registry = "https://pypi.org/simple" } | ||
| 49 | dependencies = [ | ||
| 50 | { name = "pycparser", marker = "implementation_name != 'PyPy'" }, | ||
| 51 | ] | ||
| 52 | sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } | ||
| 53 | wheels = [ | ||
| 54 | { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, | ||
| 55 | { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, | ||
| 56 | { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, | ||
| 57 | { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, | ||
| 58 | { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, | ||
| 59 | { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, | ||
| 60 | { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, | ||
| 61 | { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, | ||
| 62 | { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, | ||
| 63 | { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, | ||
| 64 | { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, | ||
| 65 | { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, | ||
| 66 | { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, | ||
| 67 | { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, | ||
| 68 | { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, | ||
| 69 | { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, | ||
| 70 | { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, | ||
| 71 | { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, | ||
| 72 | { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, | ||
| 73 | { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, | ||
| 74 | { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, | ||
| 75 | { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, | ||
| 76 | { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, | ||
| 77 | { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, | ||
| 78 | { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, | ||
| 79 | { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, | ||
| 80 | { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, | ||
| 81 | { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, | ||
| 82 | { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, | ||
| 83 | { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, | ||
| 84 | { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, | ||
| 85 | { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, | ||
| 86 | { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, | ||
| 87 | { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, | ||
| 88 | { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, | ||
| 89 | { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, | ||
| 90 | { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, | ||
| 91 | { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, | ||
| 92 | { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, | ||
| 93 | { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, | ||
| 94 | { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, | ||
| 95 | { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, | ||
| 96 | { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, | ||
| 97 | { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, | ||
| 98 | { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, | ||
| 99 | { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, | ||
| 100 | ] | ||
| 101 | |||
| 102 | [[package]] | ||
| 103 | name = "charset-normalizer" | ||
| 104 | version = "3.4.4" | ||
| 105 | source = { registry = "https://pypi.org/simple" } | ||
| 106 | sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } | ||
| 107 | wheels = [ | ||
| 108 | { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, | ||
| 109 | { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, | ||
| 110 | { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, | ||
| 111 | { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, | ||
| 112 | { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, | ||
| 113 | { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, | ||
| 114 | { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, | ||
| 115 | { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, | ||
| 116 | { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, | ||
| 117 | { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, | ||
| 118 | { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, | ||
| 119 | { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, | ||
| 120 | { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, | ||
| 121 | { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, | ||
| 122 | { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, | ||
| 123 | { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, | ||
| 124 | { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, | ||
| 125 | { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, | ||
| 126 | { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, | ||
| 127 | { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, | ||
| 128 | { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, | ||
| 129 | { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, | ||
| 130 | { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, | ||
| 131 | { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, | ||
| 132 | { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, | ||
| 133 | { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, | ||
| 134 | { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, | ||
| 135 | { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, | ||
| 136 | { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, | ||
| 137 | { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, | ||
| 138 | { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, | ||
| 139 | { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, | ||
| 140 | { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, | ||
| 141 | { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, | ||
| 142 | { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, | ||
| 143 | { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, | ||
| 144 | { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, | ||
| 145 | { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, | ||
| 146 | { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, | ||
| 147 | { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, | ||
| 148 | { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, | ||
| 149 | { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, | ||
| 150 | { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, | ||
| 151 | { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, | ||
| 152 | { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, | ||
| 153 | { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, | ||
| 154 | { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, | ||
| 155 | { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, | ||
| 156 | { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, | ||
| 157 | ] | ||
| 158 | |||
| 159 | [[package]] | ||
| 160 | name = "cryptography" | ||
| 161 | version = "46.0.3" | ||
| 162 | source = { registry = "https://pypi.org/simple" } | ||
| 163 | dependencies = [ | ||
| 164 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, | ||
| 165 | ] | ||
| 166 | sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } | ||
| 167 | wheels = [ | ||
| 168 | { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, | ||
| 169 | { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, | ||
| 170 | { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, | ||
| 171 | { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, | ||
| 172 | { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, | ||
| 173 | { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, | ||
| 174 | { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, | ||
| 175 | { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, | ||
| 176 | { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, | ||
| 177 | { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, | ||
| 178 | { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, | ||
| 179 | { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, | ||
| 180 | { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, | ||
| 181 | { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, | ||
| 182 | { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, | ||
| 183 | { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, | ||
| 184 | { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, | ||
| 185 | { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, | ||
| 186 | { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, | ||
| 187 | { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, | ||
| 188 | { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, | ||
| 189 | { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, | ||
| 190 | { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, | ||
| 191 | { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, | ||
| 192 | { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, | ||
| 193 | { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, | ||
| 194 | { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, | ||
| 195 | { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, | ||
| 196 | { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, | ||
| 197 | { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, | ||
| 198 | { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, | ||
| 199 | { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, | ||
| 200 | { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, | ||
| 201 | { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, | ||
| 202 | { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, | ||
| 203 | { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, | ||
| 204 | { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, | ||
| 205 | { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, | ||
| 206 | { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, | ||
| 207 | { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, | ||
| 208 | { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, | ||
| 209 | { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, | ||
| 210 | { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, | ||
| 211 | { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, | ||
| 212 | { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, | ||
| 213 | ] | ||
| 214 | |||
| 215 | [[package]] | ||
| 216 | name = "deprecation" | ||
| 217 | version = "2.1.0" | ||
| 218 | source = { registry = "https://pypi.org/simple" } | ||
| 219 | dependencies = [ | ||
| 220 | { name = "packaging" }, | ||
| 221 | ] | ||
| 222 | sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } | ||
| 223 | wheels = [ | ||
| 224 | { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, | ||
| 225 | ] | ||
| 226 | |||
| 227 | [[package]] | ||
| 228 | name = "h11" | ||
| 229 | version = "0.16.0" | ||
| 230 | source = { registry = "https://pypi.org/simple" } | ||
| 231 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } | ||
| 232 | wheels = [ | ||
| 233 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, | ||
| 234 | ] | ||
| 235 | |||
| 236 | [[package]] | ||
| 237 | name = "httpcore" | ||
| 238 | version = "1.0.9" | ||
| 239 | source = { registry = "https://pypi.org/simple" } | ||
| 240 | dependencies = [ | ||
| 241 | { name = "certifi" }, | ||
| 242 | { name = "h11" }, | ||
| 243 | ] | ||
| 244 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } | ||
| 245 | wheels = [ | ||
| 246 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, | ||
| 247 | ] | ||
| 248 | |||
| 249 | [[package]] | ||
| 250 | name = "httpx" | ||
| 251 | version = "0.28.1" | ||
| 252 | source = { registry = "https://pypi.org/simple" } | ||
| 253 | dependencies = [ | ||
| 254 | { name = "anyio" }, | ||
| 255 | { name = "certifi" }, | ||
| 256 | { name = "httpcore" }, | ||
| 257 | { name = "idna" }, | ||
| 258 | ] | ||
| 259 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } | ||
| 260 | wheels = [ | ||
| 261 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, | ||
| 262 | ] | ||
| 263 | |||
| 264 | [[package]] | ||
| 265 | name = "idna" | ||
| 266 | version = "3.11" | ||
| 267 | source = { registry = "https://pypi.org/simple" } | ||
| 268 | sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } | ||
| 269 | wheels = [ | ||
| 270 | { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, | ||
| 271 | ] | ||
| 272 | |||
| 273 | [[package]] | ||
| 274 | name = "jwcrypto" | ||
| 275 | version = "1.5.6" | ||
| 276 | source = { registry = "https://pypi.org/simple" } | ||
| 277 | dependencies = [ | ||
| 278 | { name = "cryptography" }, | ||
| 279 | { name = "typing-extensions" }, | ||
| 280 | ] | ||
| 281 | sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168, upload-time = "2024-03-06T19:58:31.831Z" } | ||
| 282 | wheels = [ | ||
| 283 | { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" }, | ||
| 284 | ] | ||
| 285 | |||
| 286 | [[package]] | ||
| 287 | name = "keycloak" | ||
| 288 | version = "0.1.0" | ||
| 289 | source = { virtual = "." } | ||
| 290 | dependencies = [ | ||
| 291 | { name = "python-keycloak" }, | ||
| 292 | { name = "requests" }, | ||
| 293 | ] | ||
| 294 | |||
| 295 | [package.metadata] | ||
| 296 | requires-dist = [ | ||
| 297 | { name = "python-keycloak", specifier = ">=6.0.0" }, | ||
| 298 | { name = "requests", specifier = ">=2.32.5" }, | ||
| 299 | ] | ||
| 300 | |||
| 301 | [[package]] | ||
| 302 | name = "packaging" | ||
| 303 | version = "25.0" | ||
| 304 | source = { registry = "https://pypi.org/simple" } | ||
| 305 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } | ||
| 306 | wheels = [ | ||
| 307 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, | ||
| 308 | ] | ||
| 309 | |||
| 310 | [[package]] | ||
| 311 | name = "pycparser" | ||
| 312 | version = "2.23" | ||
| 313 | source = { registry = "https://pypi.org/simple" } | ||
| 314 | sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } | ||
| 315 | wheels = [ | ||
| 316 | { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, | ||
| 317 | ] | ||
| 318 | |||
| 319 | [[package]] | ||
| 320 | name = "python-keycloak" | ||
| 321 | version = "6.0.0" | ||
| 322 | source = { registry = "https://pypi.org/simple" } | ||
| 323 | dependencies = [ | ||
| 324 | { name = "aiofiles" }, | ||
| 325 | { name = "async-property" }, | ||
| 326 | { name = "deprecation" }, | ||
| 327 | { name = "httpx" }, | ||
| 328 | { name = "jwcrypto" }, | ||
| 329 | { name = "requests" }, | ||
| 330 | { name = "requests-toolbelt" }, | ||
| 331 | ] | ||
| 332 | sdist = { url = "https://files.pythonhosted.org/packages/c4/95/df72437403eba9f96902ff3b3049a3e06d0f0e47e5bd71ab1b364d440cef/python_keycloak-6.0.0.tar.gz", hash = "sha256:87271d381639f4179a9add88afbb3dbbe9689509cf240d1cf883e44beff4d8f9", size = 71107, upload-time = "2025-12-28T17:57:52.208Z" } | ||
| 333 | wheels = [ | ||
| 334 | { url = "https://files.pythonhosted.org/packages/44/7e/f51774cb30db61f04dc0920f27b3a2c50ac05c8819dc4086436decf95010/python_keycloak-6.0.0-py3-none-any.whl", hash = "sha256:6f6697a0a894d6ded077aa86e12aa41433a0afffa27aa0b9e38395dd69a94009", size = 80167, upload-time = "2025-12-28T17:57:50.869Z" }, | ||
| 335 | ] | ||
| 336 | |||
| 337 | [[package]] | ||
| 338 | name = "requests" | ||
| 339 | version = "2.32.5" | ||
| 340 | source = { registry = "https://pypi.org/simple" } | ||
| 341 | dependencies = [ | ||
| 342 | { name = "certifi" }, | ||
| 343 | { name = "charset-normalizer" }, | ||
| 344 | { name = "idna" }, | ||
| 345 | { name = "urllib3" }, | ||
| 346 | ] | ||
| 347 | sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } | ||
| 348 | wheels = [ | ||
| 349 | { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, | ||
| 350 | ] | ||
| 351 | |||
| 352 | [[package]] | ||
| 353 | name = "requests-toolbelt" | ||
| 354 | version = "1.0.0" | ||
| 355 | source = { registry = "https://pypi.org/simple" } | ||
| 356 | dependencies = [ | ||
| 357 | { name = "requests" }, | ||
| 358 | ] | ||
| 359 | sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } | ||
| 360 | wheels = [ | ||
| 361 | { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, | ||
| 362 | ] | ||
| 363 | |||
| 364 | [[package]] | ||
| 365 | name = "typing-extensions" | ||
| 366 | version = "4.15.0" | ||
| 367 | source = { registry = "https://pypi.org/simple" } | ||
| 368 | sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } | ||
| 369 | wheels = [ | ||
| 370 | { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, | ||
| 371 | ] | ||
| 372 | |||
| 373 | [[package]] | ||
| 374 | name = "urllib3" | ||
| 375 | version = "2.6.2" | ||
| 376 | source = { registry = "https://pypi.org/simple" } | ||
| 377 | sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } | ||
| 378 | wheels = [ | ||
| 379 | { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, | ||
| 380 | ] | ||
config/postgres/init.sh+13-1| ... | @@ -7,6 +7,18 @@ until pg_isready -U postgres; do | ... | @@ -7,6 +7,18 @@ until pg_isready -U postgres; do |
| 7 | sleep 1 | 7 | sleep 1 |
| 8 | done | 8 | done |
| 9 | 9 | ||
| 10 | psql -v ON_ERROR_STOP=1 -U postgres postgres <<-SQL | ||
| 11 | DO \$\$ | ||
| 12 | BEGIN | ||
| 13 | IF EXISTS (SELECT FROM pg_user WHERE usename = 'postgres') THEN | ||
| 14 | ALTER USER "postgres" WITH PASSWORD '$POSTGRES_PASSWORD'; | ||
| 15 | ELSE | ||
| 16 | CREATE USER "postgres" WITH PASSWORD '$POSTGRES_PASSWORD'; | ||
| 17 | END IF; | ||
| 18 | END | ||
| 19 | \$\$; | ||
| 20 | SQL | ||
| 21 | |||
| 10 | ensure_db_user() { | 22 | ensure_db_user() { |
| 11 | printf "Configuring Database %s\n" "$1" | 23 | printf "Configuring Database %s\n" "$1" |
| 12 | user="$1" | 24 | user="$1" |
| ... | @@ -30,7 +42,7 @@ SQL | ... | @@ -30,7 +42,7 @@ SQL |
| 30 | } | 42 | } |
| 31 | 43 | ||
| 32 | ensure_db_user "clover" "$POSTGRES_PASSWORD" | 44 | ensure_db_user "clover" "$POSTGRES_PASSWORD" |
| 33 | ensure_db_user "authelia" "$POSTGRES_PASSWORD_AUTHELIA" | 45 | ensure_db_user "keycloak" "$POSTGRES_PASSWORD_KEYCLOAK" |
| 34 | ensure_db_user "dawarich" "$POSTGRES_PASSWORD_DAWARICH" | 46 | ensure_db_user "dawarich" "$POSTGRES_PASSWORD_DAWARICH" |
| 35 | ensure_db_user "forgejo" "$POSTGRES_PASSWORD_FORGEJO" | 47 | ensure_db_user "forgejo" "$POSTGRES_PASSWORD_FORGEJO" |
| 36 | ensure_db_user "jellyfin" "$POSTGRES_PASSWORD_JELLYFIN" | 48 | ensure_db_user "jellyfin" "$POSTGRES_PASSWORD_JELLYFIN" |
config/postgres/pgadmin-init.sh created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | #!/bin/sh | ||
| 2 | set -e | ||
| 3 | |||
| 4 | export PGADMIN_SERVER_JSON_FILE=/init/pgadmin.json | ||
| 5 | |||
| 6 | mkdir -p /var/lib/pgadmin/storage/pgadmin4_pgadmin.org | ||
| 7 | echo "postgres:5432:*:postgres:$POSTGRES_PASSWORD" >/var/lib/pgadmin/storage/pgadmin4_pgadmin.org/pgpass | ||
| 8 | |||
| 9 | chmod 600 /var/lib/pgadmin/storage/*/pgpass | ||
| 10 | chown -R 5050:5050 /var/lib/pgadmin/storage/* | ||
| 11 | |||
| 12 | /entrypoint.sh python3 | ||
config/postgres/pgadmin.json created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | { | ||
| 2 | "Servers": { | ||
| 3 | "1": { | ||
| 4 | "Name": "Clover Home", | ||
| 5 | "Group": "Servers", | ||
| 6 | "Host": "postgres", | ||
| 7 | "Port": 5432, | ||
| 8 | "MaintenanceDB": "postgres", | ||
| 9 | "Username": "postgres", | ||
| 10 | "PassFile": "/var/lib/pgadmin/storage/pgadmin4_pgadmin.org/pgpass" | ||
| 11 | } | ||
| 12 | } | ||
| 13 | } | ||
local-dns.sh+3| ... | @@ -14,3 +14,6 @@ while ! [ -f .apps/caddy/pki/authorities/local/root.crt ]; do | ... | @@ -14,3 +14,6 @@ while ! [ -f .apps/caddy/pki/authorities/local/root.crt ]; do |
| 14 | done | 14 | done |
| 15 | 15 | ||
| 16 | sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .apps/caddy/pki/authorities/local/root.crt | 16 | sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .apps/caddy/pki/authorities/local/root.crt |
| 17 | |||
| 18 | echo | ||
| 19 | echo "If using OrbStack, you may need to restart it now" |
postgres-rescue.sh created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | #!/bin/bash | ||
| 2 | set -e | ||
| 3 | . ./.env | ||
| 4 | cd "$(dirname "${BASH_SOURCE[0]}")" | ||
| 5 | |||
| 6 | docker build -t postgres-rescue "config/postgres" --build-arg USER_ID="$USER_ID" --build-arg GROUP_ID="$GROUP_ID" | ||
| 7 | |||
| 8 | docker run -it --rm \ | ||
| 9 | --user "$(id -u):$(id -g)" \ | ||
| 10 | -v "${APP_ROOT}/pg_data:/var/lib/postgresql/data" \ | ||
| 11 | --entrypoint /bin/bash \ | ||
| 12 | postgres-rescue | ||
readme.md-3| ... | @@ -16,9 +16,6 @@ cp .env.example .env | ... | @@ -16,9 +16,6 @@ cp .env.example .env |
| 16 | 16 | ||
| 17 | # fill secrets | 17 | # fill secrets |
| 18 | vi .env | 18 | vi .env |
| 19 | vi secrets/authelia-encryption-key.txt | ||
| 20 | vi secrets/authelia-session-secret.txt | ||
| 21 | vi secrets/authelia-jwt-secret.txt | ||
| 22 | vi secrets/openvpn-credentials.txt | 19 | vi secrets/openvpn-credentials.txt |
| 23 | 20 | ||
| 24 | docker compose up -d | 21 | docker compose up -d |
secrets/.gitignore+2-1| ... | @@ -1 +1,2 @@ | ... | @@ -1 +1,2 @@ |
| 1 | *.txt | 1 | !.gitignore |
| 2 | * |