{ config, lib, pkgs, shb, ... }: let cfg = config.shb.open-webui; roleClaim = "openwebui_groups"; oauthScopes = [ "openid" "email" "profile" "groups" "${roleClaim}" ]; in { imports = [ ../../lib/module.nix ../blocks/nginx.nix ]; options.shb.open-webui = { enable = lib.mkEnableOption "the Open-WebUI service"; subdomain = lib.mkOption { type = lib.types.str; description = "Subdomain under which Open-WebUI will be served."; default = "open-webui"; }; domain = lib.mkOption { type = lib.types.str; description = "domain under which Open-WebUI will be served."; example = "mydomain.com"; }; ssl = lib.mkOption { description = "Path to SSL files"; type = lib.types.nullOr shb.contracts.ssl.certs; default = null; }; port = lib.mkOption { type = lib.types.port; description = "Port Open-WebUI listens to incoming requests."; default = 12444; }; environment = lib.mkOption { type = lib.types.attrsOf lib.types.str; description = "Extra environment variables. See https://docs.openwebui.com/getting-started/env-configuration"; default = { }; example = '' { WEBUI_NAME = "SelfHostBlocks"; OLLAMA_BASE_URL = "http://127.0.0.1:''${toString config.services.ollama.port}"; RAG_EMBEDDING_MODEL = "nomic-embed-text:v1.5"; ENABLE_OPENAI_API = "True"; OPENAI_API_BASE_URL = "http://127.0.0.1:''${toString config.services.llama-cpp.port}"; ENABLE_WEB_SEARCH = "True"; RAG_EMBEDDING_ENGINE = "openai"; } ''; }; ldap = lib.mkOption { description = '' Setup LDAP integration. ''; default = { }; type = lib.types.submodule { options = { userGroup = lib.mkOption { type = lib.types.str; description = "Group users must belong to to be able to login."; default = "open-webui_user"; }; adminGroup = lib.mkOption { type = lib.types.str; description = "Group users must belong to to have administrator privileges."; default = "open-webui_admin"; }; }; }; }; sso = lib.mkOption { description = '' Setup SSO integration. ''; default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption "SSO integration."; authEndpoint = lib.mkOption { type = lib.types.str; description = "Endpoint to the SSO provider."; example = "https://authelia.example.com"; }; clientID = lib.mkOption { type = lib.types.str; description = "Client ID for the OIDC endpoint."; default = "open-webui"; }; 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 Open-WebUI."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { owner = "open-webui"; restartUnits = [ "open-webui.service" ]; }; }; }; sharedSecretForAuthelia = lib.mkOption { description = "OIDC shared secret for Authelia. Must be the same as `sharedSecret`"; 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 state directory. ''; default = { }; type = lib.types.submodule { options = shb.contracts.backup.mkRequester { user = "open-webui"; sourceDirectories = [ config.services.open-webui.stateDir ]; sourceDirectoriesText = "[ config.services.open-webui.stateDir ]"; }; }; }; 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.open-webui.subdomain}.\${config.shb.open-webui.domain}"; internalUrl = "http://127.0.0.1:${toString cfg.port}"; }; }; }; }; config = ( lib.mkMerge [ (lib.mkIf cfg.enable { users.users.open-webui = { isSystemUser = true; group = "open-webui"; }; users.groups.open-webui = { }; services.open-webui = { enable = true; host = "127.0.0.1"; inherit (cfg) port; environment = { WEBUI_URL = "https://${cfg.subdomain}.${cfg.domain}"; ENABLE_PERSISTENT_CONFIG = "False"; ANONYMIZED_TELEMETRY = "False"; DO_NOT_TRACK = "True"; SCARF_NO_ANALYTICS = "True"; ENABLE_VERSION_UPDATE_CHECK = "False"; } // cfg.environment; }; systemd.services.open-webui.path = [ pkgs.ffmpeg-headless ]; shb.nginx.vhosts = [ { inherit (cfg) subdomain domain ssl; upstream = "http://127.0.0.1:${toString cfg.port}/"; extraConfig = '' proxy_read_timeout 300s; proxy_send_timeout 300s; ''; } ]; }) (lib.mkIf (cfg.enable && cfg.sso.enable) { shb.lldap.ensureGroups = { ${cfg.ldap.userGroup} = { }; ${cfg.ldap.adminGroup} = { }; }; services.open-webui = { package = pkgs.open-webui.overrideAttrs (finalAttrs: { patches = [ ../../patches/0001-selfhostblocks-never-onboard.patch ]; }); environment = { ENABLE_SIGNUP = "False"; WEBUI_AUTH = "True"; ENABLE_FORWARD_USER_INFO_HEADERS = "True"; ENABLE_OAUTH_SIGNUP = "True"; OAUTH_UPDATE_PICTURE_ON_LOGIN = "True"; OAUTH_CLIENT_ID = cfg.sso.clientID; OPENID_PROVIDER_URL = "${cfg.sso.authEndpoint}/.well-known/openid-configuration"; OAUTH_PROVIDER_NAME = "Single Sign-On"; OAUTH_USERNAME_CLAIM = "preferred_username"; ENABLE_OAUTH_ROLE_MANAGEMENT = "True"; OAUTH_ALLOWED_ROLES = "user,admin"; OAUTH_ADMIN_ROLES = "admin"; OAUTH_ROLES_CLAIM = roleClaim; OAUTH_SCOPES = lib.concatStringsSep " " oauthScopes; }; }; shb.authelia.extraDefinitions = { user_attributes.${roleClaim}.expression = ''"${cfg.ldap.adminGroup}" in groups ? ["admin"] : ("${cfg.ldap.userGroup}" in groups ? ["user"] : [""])''; }; shb.authelia.extraOidcClaimsPolicies.${roleClaim} = { custom_claims = { "${roleClaim}" = { }; }; }; shb.authelia.extraOidcScopes."${roleClaim}" = { claims = [ "${roleClaim}" ]; }; shb.authelia.oidcClients = [ { client_id = cfg.sso.clientID; client_name = "Open WebUI"; 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}/oauth/oidc/callback" ]; scopes = oauthScopes; } ]; systemd.services.open-webui.serviceConfig.EnvironmentFile = "/run/open-webui/secrets.env"; systemd.tmpfiles.rules = [ "d '/run/open-webui' 0750 root root - -" ]; systemd.services.open-webui-pre = { script = shb.replaceSecrets { userConfig = { OAUTH_CLIENT_SECRET.source = cfg.sso.sharedSecret.result.path; }; resultPath = "/run/open-webui/secrets.env"; generator = shb.toEnvVar; }; serviceConfig.Type = "oneshot"; wantedBy = [ "multi-user.target" ]; before = [ "open-webui.service" ]; requiredBy = [ "open-webui.service" ]; }; }) ] ); }