1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 inherit (lib) types;
9
10 cfg = config.services.open-webui;
11in
12{
13 options = {
14 services.open-webui = {
15 enable = lib.mkEnableOption "Open-WebUI server";
16 package = lib.mkPackageOption pkgs "open-webui" { };
17
18 stateDir = lib.mkOption {
19 type = types.path;
20 default = "/var/lib/open-webui";
21 example = "/home/foo";
22 description = "State directory of Open-WebUI.";
23 };
24
25 host = lib.mkOption {
26 type = types.str;
27 default = "127.0.0.1";
28 example = "0.0.0.0";
29 description = ''
30 The host address which the Open-WebUI server HTTP interface listens to.
31 '';
32 };
33
34 port = lib.mkOption {
35 type = types.port;
36 default = 8080;
37 example = 11111;
38 description = ''
39 Which port the Open-WebUI server listens to.
40 '';
41 };
42
43 environment = lib.mkOption {
44 type = types.attrsOf types.str;
45 default = {
46 SCARF_NO_ANALYTICS = "True";
47 DO_NOT_TRACK = "True";
48 ANONYMIZED_TELEMETRY = "False";
49 };
50 example = ''
51 {
52 OLLAMA_API_BASE_URL = "http://127.0.0.1:11434";
53 # Disable authentication
54 WEBUI_AUTH = "False";
55 }
56 '';
57 description = ''
58 Extra environment variables for Open-WebUI.
59 For more details see <https://docs.openwebui.com/getting-started/advanced-topics/env-configuration/>
60 '';
61 };
62
63 environmentFile = lib.mkOption {
64 description = ''
65 Environment file to be passed to the systemd service.
66 Useful for passing secrets to the service to prevent them from being
67 world-readable in the Nix store.
68 '';
69 type = lib.types.nullOr lib.types.path;
70 default = null;
71 example = "/var/lib/secrets/openWebuiSecrets";
72 };
73
74 openFirewall = lib.mkOption {
75 type = types.bool;
76 default = false;
77 description = ''
78 Whether to open the firewall for Open-WebUI.
79 This adds `services.open-webui.port` to `networking.firewall.allowedTCPPorts`.
80 '';
81 };
82 };
83 };
84
85 config = lib.mkIf cfg.enable {
86 systemd.services.open-webui = {
87 description = "User-friendly WebUI for LLMs";
88 wantedBy = [ "multi-user.target" ];
89 after = [ "network.target" ];
90
91 environment = {
92 STATIC_DIR = "${cfg.stateDir}/static";
93 DATA_DIR = "${cfg.stateDir}/data";
94 HF_HOME = "${cfg.stateDir}/hf_home";
95 SENTENCE_TRANSFORMERS_HOME = "${cfg.stateDir}/transformers_home";
96 WEBUI_URL = "http://localhost:${toString cfg.port}";
97 }
98 // cfg.environment;
99
100 # backwards compatability migration
101 preStart = ''
102 if [ -d "${cfg.stateDir}/data" ] && [ -n "$(ls -A "${cfg.stateDir}/data" 2>/dev/null)" ]; then
103 exit 0
104 fi
105
106 mkdir -p "${cfg.stateDir}/data"
107
108 [ -f "${cfg.stateDir}/webui.db" ] && mv "${cfg.stateDir}/webui.db" "${cfg.stateDir}/data/"
109
110 for dir in cache uploads vector_db; do
111 [ -d "${cfg.stateDir}/$dir" ] && mv "${cfg.stateDir}/$dir" "${cfg.stateDir}/data/"
112 done
113
114 exit 0
115 '';
116
117 serviceConfig = {
118 ExecStart = "${lib.getExe cfg.package} serve --host \"${cfg.host}\" --port ${toString cfg.port}";
119 EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
120 WorkingDirectory = cfg.stateDir;
121 StateDirectory = "open-webui";
122 RuntimeDirectory = "open-webui";
123 RuntimeDirectoryMode = "0755";
124 PrivateTmp = true;
125 DynamicUser = true;
126 DevicePolicy = "closed";
127 LockPersonality = true;
128 MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Permission Denied
129 PrivateUsers = true;
130 ProtectHome = true;
131 ProtectHostname = true;
132 ProtectKernelLogs = true;
133 ProtectKernelModules = true;
134 ProtectKernelTunables = true;
135 ProtectControlGroups = true;
136 ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
137 RestrictNamespaces = true;
138 RestrictRealtime = true;
139 SystemCallArchitectures = "native";
140 UMask = "0077";
141 CapabilityBoundingSet = "";
142 RestrictAddressFamilies = [
143 "AF_INET"
144 "AF_INET6"
145 "AF_UNIX"
146 ];
147 ProtectClock = true;
148 ProtectProc = "invisible";
149 SystemCallFilter = [
150 "@system-service"
151 "~@privileged"
152 ];
153 SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices
154 DeviceAllow = [
155 # CUDA
156 # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
157 "char-nvidiactl"
158 "char-nvidia-caps"
159 "char-nvidia-frontend"
160 "char-nvidia-uvm"
161 # ROCm
162 "char-drm"
163 "char-fb"
164 "char-kfd"
165 # WSL (Windows Subsystem for Linux)
166 "/dev/dxg"
167 ];
168 };
169 };
170
171 networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
172 };
173
174 meta.maintainers = with lib.maintainers; [ shivaraj-bh ];
175}