diff --git a/docs/redirects.json b/docs/redirects.json index 0c4aa7a..019ccba 100644 --- a/docs/redirects.json +++ b/docs/redirects.json @@ -3320,6 +3320,159 @@ "services-nextcloudserver-usage-version": [ "services-nextcloud.html#services-nextcloudserver-usage-version" ], + "services-open-webui": [ + "services-open-webui.html#services-open-webui" + ], + "services-open-webui-features": [ + "services-open-webui.html#services-open-webui-features" + ], + "services-open-webui-ollama": [ + "services-open-webui.html#services-open-webui-ollama" + ], + "services-open-webui-options": [ + "services-open-webui.html#services-open-webui-options" + ], + "services-open-webui-options-shb.open-webui.backup": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup" + ], + "services-open-webui-options-shb.open-webui.backup.request": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request" + ], + "services-open-webui-options-shb.open-webui.backup.request.excludePatterns": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.excludePatterns" + ], + "services-open-webui-options-shb.open-webui.backup.request.hooks": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.hooks" + ], + "services-open-webui-options-shb.open-webui.backup.request.hooks.afterBackup": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.hooks.afterBackup" + ], + "services-open-webui-options-shb.open-webui.backup.request.hooks.beforeBackup": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.hooks.beforeBackup" + ], + "services-open-webui-options-shb.open-webui.backup.request.sourceDirectories": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.sourceDirectories" + ], + "services-open-webui-options-shb.open-webui.backup.request.user": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.request.user" + ], + "services-open-webui-options-shb.open-webui.backup.result": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.result" + ], + "services-open-webui-options-shb.open-webui.backup.result.backupService": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.result.backupService" + ], + "services-open-webui-options-shb.open-webui.backup.result.restoreScript": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.backup.result.restoreScript" + ], + "services-open-webui-options-shb.open-webui.domain": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.domain" + ], + "services-open-webui-options-shb.open-webui.enable": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.enable" + ], + "services-open-webui-options-shb.open-webui.environment": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.environment" + ], + "services-open-webui-options-shb.open-webui.ldap": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ldap" + ], + "services-open-webui-options-shb.open-webui.ldap.adminGroup": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ldap.adminGroup" + ], + "services-open-webui-options-shb.open-webui.ldap.userGroup": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ldap.userGroup" + ], + "services-open-webui-options-shb.open-webui.port": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.port" + ], + "services-open-webui-options-shb.open-webui.ssl": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ssl" + ], + "services-open-webui-options-shb.open-webui.ssl.paths": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ssl.paths" + ], + "services-open-webui-options-shb.open-webui.ssl.paths.cert": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ssl.paths.cert" + ], + "services-open-webui-options-shb.open-webui.ssl.paths.key": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ssl.paths.key" + ], + "services-open-webui-options-shb.open-webui.ssl.systemdService": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.ssl.systemdService" + ], + "services-open-webui-options-shb.open-webui.sso": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso" + ], + "services-open-webui-options-shb.open-webui.sso.authEndpoint": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.authEndpoint" + ], + "services-open-webui-options-shb.open-webui.sso.authorization_policy": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.authorization_policy" + ], + "services-open-webui-options-shb.open-webui.sso.clientID": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.clientID" + ], + "services-open-webui-options-shb.open-webui.sso.enable": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.enable" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.request": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.request" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.request.group": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.request.group" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.request.mode": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.request.mode" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.request.owner": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.request.owner" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.request.restartUnits": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.request.restartUnits" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.result": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.result" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecret.result.path": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecret.result.path" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.group": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.group" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.mode": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.mode" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.owner": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.owner" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.restartUnits": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.request.restartUnits" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.result": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.result" + ], + "services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.result.path": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.sso.sharedSecretForAuthelia.result.path" + ], + "services-open-webui-options-shb.open-webui.subdomain": [ + "services-open-webui.html#services-open-webui-options-shb.open-webui.subdomain" + ], + "services-open-webui-usage": [ + "services-open-webui.html#services-open-webui-usage" + ], + "services-open-webui-usage-backup": [ + "services-open-webui.html#services-open-webui-usage-backup" + ], "services-pinchflat": [ "services-pinchflat.html#services-pinchflat" ], diff --git a/docs/services.md b/docs/services.md index 1fdaec9..3c997e9 100644 --- a/docs/services.md +++ b/docs/services.md @@ -18,6 +18,7 @@ information is provided in the respective manual sections. | [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N | | [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N | | [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) | +| [Open WebUI][] | Y (1) | Y | Y | Y | Y (2) | N | | [Pinchflat][] | Y | Y | Y | Y (4) | Y (5) | N | | [Vaultwarden][] | Y (1) | Y | Y | Y | Y (2) | N | @@ -35,6 +36,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes [Home-Assistant]: services-home-assistant.html [Jellyfin]: services-jellyfin.html [Nextcloud Server]: services-nextcloud.html +[Open WebUI]: services-open-webui.html [Pinchflat]: services-pinchflat.html [Vaultwarden]: services-vaultwarden.html @@ -62,6 +64,10 @@ modules/services/nextcloud-server/docs/default.md modules/services/pinchflat/docs/default.md ``` +```{=include=} chapters html:into-file=//services-open-webui.html +modules/services/open-webui/docs/default.md +``` + ```{=include=} chapters html:into-file=//services-vaultwarden.html modules/services/vaultwarden/docs/default.md ``` diff --git a/flake.nix b/flake.nix index 03b4a7b..7bb6e17 100644 --- a/flake.nix +++ b/flake.nix @@ -41,6 +41,7 @@ }); pkgs = import patchedNixpkgs { inherit system; + config.allowUnfree = true; }; # The contract dummies are used to show options for contracts. @@ -82,6 +83,7 @@ self.nixosModules.${system}.home-assistant self.nixosModules.${system}.jellyfin self.nixosModules.${system}.nextcloud-server + self.nixosModules.${system}.open-webui self.nixosModules.${system}.pinchflat self.nixosModules.${system}.vaultwarden ]; @@ -117,6 +119,7 @@ nixosModules.home-assistant = modules/services/home-assistant.nix; nixosModules.jellyfin = modules/services/jellyfin.nix; nixosModules.nextcloud-server = modules/services/nextcloud-server.nix; + nixosModules.open-webui = modules/services/open-webui.nix; nixosModules.pinchflat = modules/services/pinchflat.nix; nixosModules.vaultwarden = modules/services/vaultwarden.nix; @@ -146,6 +149,7 @@ "services/home-assistant" = ./modules/services/home-assistant.nix; "services/jellyfin" = ./modules/services/jellyfin.nix; "services/nextcloud-server" = ./modules/services/nextcloud-server.nix; + "services/open-webui" = ./modules/services/open-webui.nix; "services/pinchflat" = ./modules/services/pinchflat.nix; "services/vaultwarden" = ./modules/services/vaultwarden.nix; "contracts/backup" = ./modules/contracts/backup/dummyModule.nix; @@ -263,6 +267,7 @@ // (vm_test "jellyfin" ./test/services/jellyfin.nix) // (vm_test "monitoring" ./test/services/monitoring.nix) // (vm_test "nextcloud" ./test/services/nextcloud.nix) + // (vm_test "open-webui" ./test/services/open-webui.nix) // (vm_test "pinchflat" ./test/services/pinchflat.nix) // (vm_test "vaultwarden" ./test/services/vaultwarden.nix) diff --git a/modules/services/open-webui.nix b/modules/services/open-webui.nix new file mode 100644 index 0000000..d540202 --- /dev/null +++ b/modules/services/open-webui.nix @@ -0,0 +1,244 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.shb.open-webui; + + contracts = pkgs.callPackage ../contracts {}; + shblib = pkgs.callPackage ../../lib {}; +in +{ + options.shb.open-webui = { + enable = lib.mkEnableOption "the Open-WebUI service"; + + subdomain = lib.mkOption { + type = lib.types.str; + description = "Subdomain under which Karakeep will be served."; + default = "pinchflat"; + }; + + domain = lib.mkOption { + type = lib.types.str; + description = "domain under which Karakeep will be served."; + example = "mydomain.com"; + }; + + ssl = lib.mkOption { + description = "Path to SSL files"; + type = lib.types.nullOr contracts.ssl.certs; + default = null; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "Port Karakeep 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"; + 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.nullOr lib.types.str; + default = null; + description = "Endpoint to the SSO provider. Leave null to not have SSO configured."; + 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 Audiobookshelf."; + type = lib.types.submodule { + options = 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 = 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 = contracts.backup.mkRequester { + user = "open-webui"; + sourceDirectories = [ + config.services.open-webui.stateDir + ]; + sourceDirectoriesText = "[ config.services.open-webui.stateDir ]"; + }; + }; + }; + }; + + 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"; + }; + }; + + 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} = {}; + }; + + shb.authelia.oidcClients = [ + { + client_id = cfg.sso.clientID; + client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path; + scopes = [ "openid" "email" "profile" ]; + redirect_uris = [ + "https://${cfg.subdomain}.${cfg.domain}/oauth/oidc/callback" + ]; + } + ]; + 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_SCOPES = "openid email profile"; + OAUTH_ALLOWED_ROLES = cfg.ldap.userGroup; + OAUTH_ADMIN_ROLES = cfg.ldap.adminGroup; + ENABLE_OAUTH_ROLE_MANAGEMENT = "True"; + }; + }; + + 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 = shblib.replaceSecrets { + userConfig = { + OAUTH_CLIENT_SECRET.source = cfg.sso.sharedSecret.result.path; + }; + resultPath = "/run/open-webui/secrets.env"; + generator = shblib.toEnvVar; + }; + serviceConfig.Type = "oneshot"; + wantedBy = [ "multi-user.target" ]; + before = [ "open-webui.service" ]; + requiredBy = [ "open-webui.service" ]; + }; + }) + ]); +} diff --git a/modules/services/open-webui/docs/default.md b/modules/services/open-webui/docs/default.md new file mode 100644 index 0000000..1303242 --- /dev/null +++ b/modules/services/open-webui/docs/default.md @@ -0,0 +1,121 @@ +# Open-WebUI Service {#services-open-webui} + +Defined in [`/modules/blocks/open-webui.nix`](@REPO@/modules/blocks/open-webui.nix), +found in the `selfhostblocks.nixosModules.open-webui` module. +See [the manual](usage.html#usage-flake) for how to import the module in your code. + +This service sets up [Open WebUI][] which provides a frontend to various LLMs. + +[Open WebUI]: https://docs.openwebui.com/ + +## Features {#services-open-webui-features} + +- Telemetry disabled. +- Declarative [LDAP](#services-open-webui-options-shb.open-webui.ldap) Configuration. + Needed LDAP groups are created automatically. +- Declarative [SSO](#services-open-webui-options-shb.open-webui.sso) Configuration. + When SSO is enabled, login with user and password is disabled. + Registration is enabled through SSO. +- Access through [subdomain](#services-open-webui-options-shb.open-webui.subdomain) using reverse proxy. +- Access through [HTTPS](#services-open-webui-options-shb.open-webui.ssl) using reverse proxy. +- [Backup](#services-open-webui-options-shb.open-webui.sso) through the [backup block](./blocks-backup.html). + +## Usage {#services-open-webui-usage} + +The following snippet assumes a few blocks have been setup already: + +- the [secrets block](usage.html#usage-secrets) with SOPS, +- the [`shb.ssl` block](blocks-ssl.html#usage), +- the [`shb.lldap` block](blocks-lldap.html#blocks-lldap-global-setup). +- the [`shb.authelia` block](blocks-authelia.html#blocks-sso-global-setup). + +```nix +{ + shb.open-webui = { + enable = true; + domain = "example.com"; + subdomain = "open-webui"; + + ssl = config.shb.certs.certs.letsencrypt.${domain}; + + sso = { + enable = true; + authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}"; + + sharedSecret.result = config.shb.sops.secret.oidcSecret.result; + sharedSecretForAuthelia.result = config.shb.sops.secret.oidcAutheliaSecret.result; + }; + }; + + shb.sops.secret.oidcSecret.request = config.shb.open-webui.sso.sharedSecret.request; + shb.sops.secret.oidcAutheliaSecret.request = config.shb.open-webui.sso.sharedSecretForAuthelia.request; +} +``` + +Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`. + +The [user](#services-open-webui-options-shb.open-webui.ldap.userGroup) +and [admin](#services-open-webui-options-shb.open-webui.ldap.adminGroup) +LDAP groups are created automatically. + +## Integration with OLLAMA {#services-open-webui-ollama} + +Assuming ollama is enabled, it will be available on port `config.services.ollama.port`. +The following snippet sets up acceleration using an AMD (i)GPU and loads some models. + +```nix +{ + services.ollama = { + enable = true; + + # https://wiki.nixos.org/wiki/Ollama#AMD_GPU_with_open_source_driver + acceleration = "rocm"; + + # https://ollama.com/library + loadModels = [ + "deepseek-r1:1.5b" + "llama3.2:3b" + "llava:7b" + "mxbai-embed-large:335m" + "nomic-embed-text:v1.5" + ]; + }; +} +``` + +Integrating with the ollama service is done with: + +```nix +{ + services.open-webui = { + environment.OLLAMA_BASE_URL = "http://127.0.0.1:${toString config.services.ollama.port}"; + }; +} +``` + +Notice we're using the upstream service here `services.open-webui`, not `shb.open-webui`. + +## Backup {#services-open-webui-usage-backup} + +Backing up Open-Webui using the [Restic block](blocks-restic.html) is done like so: + +```nix +shb.restic.instances."open-webui" = { + request = config.shb.open-webui.backup; + settings = { + enable = true; + }; +}; +``` + +The name `"open-webui"` in the `instances` can be anything. +The `config.shb.open-webui.backup` option provides what directories to backup. +You can define any number of Restic instances to backup Open WebUI multiple times. + +## Options Reference {#services-open-webui-options} + +```{=include=} options +id-prefix: services-open-webui-options- +list-id: selfhostblocks-services-open-webui-options +source: @OPTIONS_JSON@ +``` diff --git a/patches/0001-selfhostblocks-never-onboard.patch b/patches/0001-selfhostblocks-never-onboard.patch new file mode 100644 index 0000000..443a699 --- /dev/null +++ b/patches/0001-selfhostblocks-never-onboard.patch @@ -0,0 +1,29 @@ +From 6897dd86a41b336c7c03a466990f7e981c5c649c Mon Sep 17 00:00:00 2001 +From: ibizaman +Date: Tue, 23 Sep 2025 11:36:24 +0200 +Subject: [PATCH] selfhostblocks: never onboard + +--- + backend/open_webui/main.py | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py +index 5630a5883..5c7c88a64 100644 +--- a/backend/open_webui/main.py ++++ b/backend/open_webui/main.py +@@ -1654,11 +1654,9 @@ async def get_app_config(request: Request): + user = Users.get_user_by_id(data["id"]) + + user_count = Users.get_num_users() ++ # Never onboard + onboarding = False + +- if user is None: +- onboarding = user_count == 0 +- + return { + **({"onboarding": True} if onboarding else {}), + "status": True, +-- +2.50.1 + diff --git a/test/services/open-webui.nix b/test/services/open-webui.nix new file mode 100644 index 0000000..9ab7bb4 --- /dev/null +++ b/test/services/open-webui.nix @@ -0,0 +1,190 @@ +{ pkgs, ... }: +let + oidcSecret = "oidcSecret"; + + testLib = pkgs.callPackage ../common.nix {}; + + commonTestScript = testLib.mkScripts { + hasSSL = { node, ... }: !(isNull node.config.shb.open-webui.ssl); + waitForServices = { ... }: [ + "open-webui.service" + "nginx.service" + ]; + waitForPorts = { node, ... }: [ + node.config.shb.open-webui.port + ]; + }; + + basic = { config, ... }: { + imports = [ + testLib.baseModule + ../../modules/blocks/hardcodedsecret.nix + ../../modules/blocks/lldap.nix + ../../modules/services/open-webui.nix + ]; + + test = { + subdomain = "o"; + }; + + shb.open-webui = { + enable = true; + inherit (config.test) subdomain domain; + }; + + networking.hosts = { + "127.0.0.1" = [ "${config.test.subdomain}.${config.test.domain}" ]; + }; + }; + + https = { config, ... }: { + shb.open-webui = { + ssl = config.shb.certs.certs.selfsigned.n; + }; + + systemd.services.open-webui.environment = { + # Needed for open-webui to be able to talk to auth server. + SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; + }; + }; + + ldap = { config, ... }: { + shb.open-webui = { + ldap = { + userGroup = "user_group"; + adminGroup = "admin_group"; + }; + }; + }; + + clientLoginSso = { config, ... }: { + imports = [ + testLib.baseModule + testLib.clientLoginModule + ]; + virtualisation.memorySize = 4096; + test = { + subdomain = "o"; + }; + + test.login = { + startUrl = "https://${config.test.fqdn}/auth"; + beforeHook = '' + page.get_by_role("button", name="continue").click() + ''; + usernameFieldLabelRegex = "Username"; + passwordFieldLabelRegex = "Password"; + loginButtonNameRegex = "[sS]ign [iI]n"; + testLoginWith = [ + { username = "alice"; password = "NotAlicePassword"; nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()" + ]; } + { username = "alice"; password = "AlicePassword"; nextPageExpect = [ + "page.get_by_role('button', name=re.compile('Accept')).click()" + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()" + "expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()" + "expect(page.get_by_text('logged in')).to_be_visible()" + ]; } + { username = "bob"; password = "NotBobPassword"; nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()" + ]; } + { username = "bob"; password = "BobPassword"; nextPageExpect = [ + "page.get_by_role('button', name=re.compile('Accept')).click()" + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()" + "expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()" + "expect(page.get_by_text('logged in')).to_be_visible()" + ]; } + ]; + }; + }; + + sso = { config, ... }: { + shb.open-webui = { + sso = { + enable = true; + authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}"; + clientID = "open-webui"; + + sharedSecret.result = config.shb.hardcodedsecret.oidcSecret.result; + sharedSecretForAuthelia.result = config.shb.hardcodedsecret.oidcAutheliaSecret.result; + }; + }; + + shb.hardcodedsecret.oidcSecret = { + request = config.shb.open-webui.sso.sharedSecret.request; + settings.content = oidcSecret; + }; + shb.hardcodedsecret.oidcAutheliaSecret = { + request = config.shb.open-webui.sso.sharedSecretForAuthelia.request; + settings.content = oidcSecret; + }; + }; +in +{ + basic = pkgs.testers.runNixOSTest { + name = "open-webui_basic"; + + nodes.client = {}; + nodes.server = { + imports = [ + basic + ]; + }; + + testScript = commonTestScript.access; + }; + + backup = pkgs.testers.runNixOSTest { + name = "open-webui_backup"; + + nodes.server = { config, ... }: { + imports = [ + basic + (testLib.backup config.shb.open-webui.backup) + ]; + }; + + nodes.client = {}; + + testScript = commonTestScript.backup; + }; + + https = pkgs.testers.runNixOSTest { + name = "open-webui_https"; + + nodes.client = {}; + nodes.server = { + imports = [ + basic + testLib.certs + https + ]; + }; + + testScript = commonTestScript.access; + }; + + sso = pkgs.testers.runNixOSTest { + name = "open-webui_sso"; + interactive.sshBackdoor.enable = true; + + nodes.client = { + imports = [ + clientLoginSso + ]; + }; + nodes.server = { config, pkgs, ... }: { + imports = [ + basic + testLib.certs + https + testLib.ldap + ldap + (testLib.sso config.shb.certs.certs.selfsigned.n) + sso + ]; + }; + + testScript = commonTestScript.access; + }; +}