{ config, lib, shb, ... }: let cfg = config.shb.audiobookshelf; fqdn = "${cfg.subdomain}.${cfg.domain}"; roleClaim = "audiobookshelf_groups"; in { options.shb.audiobookshelf = { enable = lib.mkEnableOption "selfhostblocks.audiobookshelf"; subdomain = lib.mkOption { type = lib.types.str; description = "Subdomain under which audiobookshelf will be served."; example = "abs"; }; domain = lib.mkOption { type = lib.types.str; description = "domain under which audiobookshelf will be served."; example = "mydomain.com"; }; webPort = lib.mkOption { type = lib.types.int; description = "Audiobookshelf web port"; default = 8113; }; ssl = lib.mkOption { description = "Path to SSL files"; type = lib.types.nullOr shb.contracts.ssl.certs; default = null; }; extraServiceConfig = lib.mkOption { type = lib.types.attrsOf lib.types.str; description = "Extra configuration given to the systemd service file."; default = { }; example = lib.literalExpression '' { MemoryHigh = "512M"; MemoryMax = "900M"; } ''; }; sso = lib.mkOption { description = "SSO configuration."; default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption "SSO"; provider = lib.mkOption { type = lib.types.str; description = "OIDC provider name"; default = "Authelia"; }; endpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO"; example = "https://authelia.example.com"; }; clientID = lib.mkOption { type = lib.types.str; description = "Client ID for the OIDC endpoint"; default = "audiobookshelf"; }; adminUserGroup = lib.mkOption { type = lib.types.str; description = "OIDC admin group"; default = "audiobookshelf_admin"; }; userGroup = lib.mkOption { type = lib.types.str; description = "OIDC user group"; default = "audiobookshelf_user"; }; authorization_policy = lib.mkOption { type = lib.types.enum [ "one_factor" "two_factor" ]; description = "Require one factor (password) or two factor (device) authentication."; default = "one_factor"; }; sharedSecret = lib.mkOption { description = "OIDC shared secret for Audiobookshelf."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0440"; owner = "audiobookshelf"; group = "audiobookshelf"; restartUnits = [ "audiobookshelfd.service" ]; }; }; }; sharedSecretForAuthelia = lib.mkOption { description = "OIDC shared secret for Authelia."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; ownerText = "config.shb.authelia.autheliaUser"; owner = config.shb.authelia.autheliaUser; }; }; }; }; }; }; backup = lib.mkOption { description = '' Backup configuration. ''; type = lib.types.submodule { options = shb.contracts.backup.mkRequester { user = "audiobookshelf"; sourceDirectories = [ "/var/lib/audiobookshelf" ]; }; }; }; logLevel = lib.mkOption { type = lib.types.nullOr ( lib.types.enum [ "critical" "error" "warning" "info" "debug" ] ); description = "Enable logging."; default = false; example = true; }; dashboard = lib.mkOption { description = '' Dashboard contract consumer ''; default = { }; type = lib.types.submodule { options = shb.contracts.dashboard.mkRequester { externalUrl = "https://${cfg.subdomain}.${cfg.domain}"; externalUrlText = "https://\${config.shb.audiobookshelf.subdomain}.\${config.shb.audiobookshelf.domain}"; internalUrl = "http://127.0.0.1:${toString cfg.webPort}"; }; }; }; }; config = lib.mkIf cfg.enable ( lib.mkMerge [ { services.audiobookshelf = { enable = true; openFirewall = true; dataDir = "audiobookshelf"; host = "127.0.0.1"; port = cfg.webPort; }; services.nginx.enable = true; services.nginx.virtualHosts."${fqdn}" = { http2 = true; forceSSL = !(isNull cfg.ssl); sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert; sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key; # https://github.com/advplyr/audiobookshelf#nginx-reverse-proxy extraConfig = '' set $audiobookshelf 127.0.0.1; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; proxy_pass http://$audiobookshelf:${builtins.toString cfg.webPort}; proxy_redirect http:// https://; } ''; }; shb.authelia.extraDefinitions = { user_attributes.${roleClaim}.expression = ''"${cfg.sso.adminUserGroup}" in groups ? ["admin"] : ("${cfg.sso.userGroup}" in groups ? ["user"] : [""])''; }; shb.authelia.extraOidcClaimsPolicies.${roleClaim} = { custom_claims = { "${roleClaim}" = { }; }; }; shb.authelia.extraOidcScopes."${roleClaim}" = { claims = [ "${roleClaim}" ]; }; shb.authelia.oidcClients = lib.lists.optionals cfg.sso.enable [ { client_id = cfg.sso.clientID; client_name = "Audiobookshelf"; client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path; claims_policy = "${roleClaim}"; public = false; authorization_policy = cfg.sso.authorization_policy; redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/auth/openid/callback" "https://${cfg.subdomain}.${cfg.domain}/auth/openid/mobile-redirect" ]; scopes = [ "openid" "profile" "email" "groups" "${roleClaim}" ]; require_pkce = true; pkce_challenge_method = "S256"; userinfo_signed_response_alg = "none"; token_endpoint_auth_method = "client_secret_basic"; } ]; } { systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig; } ] ); }