Skip to content

Commit 499aaa6

Browse files
authored
feat: add open-webui module (#580)
This module installs and runs Open WebUI using Python and pip within your Coder workspace.
1 parent 3ae8c7d commit 499aaa6

File tree

6 files changed

+417
-0
lines changed

6 files changed

+417
-0
lines changed

.icons/openwebui.svg

Lines changed: 5 additions & 0 deletions
Loading
407 KB
Loading
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
display_name: Open WebUI
3+
description: A self-hosted AI chat interface supporting various LLM providers
4+
icon: ../../../../.icons/openwebui.svg
5+
verified: false
6+
tags: [ai, llm, chat, web, python]
7+
---
8+
9+
# Open WebUI
10+
11+
Open WebUI is a user-friendly web interface for interacting with Large Language Models. It provides a ChatGPT-like interface that can connect to various LLM providers including OpenAI, Ollama, and more.
12+
13+
```tf
14+
module "open-webui" {
15+
count = data.coder_workspace.me.start_count
16+
source = "registry.coder.com/coder-labs/open-webui/coder"
17+
version = "1.0.0"
18+
agent_id = coder_agent.main.id
19+
}
20+
```
21+
22+
![Open WebUI](../../.images/openwebui.png)
23+
24+
## Prerequisites
25+
26+
- **Python 3.11 or higher** must be installed in your image (with `venv` module)
27+
- Port 7800 (default) or your custom port must be available
28+
29+
For Ubuntu/Debian, you can install Python 3.11 from [deadsnakes PPA](https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa):
30+
31+
```shell
32+
sudo add-apt-repository -y ppa:deadsnakes/ppa
33+
sudo apt-get update
34+
sudo apt-get install -y python3.11 python3.11-venv
35+
```
36+
37+
## Examples
38+
39+
### With OpenAI API Key
40+
41+
```tf
42+
module "open-webui" {
43+
count = data.coder_workspace.me.start_count
44+
source = "registry.coder.com/coder-labs/open-webui/coder"
45+
version = "1.0.0"
46+
agent_id = coder_agent.main.id
47+
48+
openai_api_key = var.openai_api_key
49+
}
50+
```
51+
52+
### Custom Port and Data Directory
53+
54+
```tf
55+
module "open-webui" {
56+
count = data.coder_workspace.me.start_count
57+
source = "registry.coder.com/coder-labs/open-webui/coder"
58+
version = "1.0.0"
59+
agent_id = coder_agent.main.id
60+
61+
http_server_port = 8080
62+
data_dir = "/home/coder/open-webui-data"
63+
}
64+
```
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.5"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "http_server_log_path" {
18+
type = string
19+
description = "The path to log Open WebUI to."
20+
default = "/tmp/open-webui.log"
21+
}
22+
23+
variable "http_server_port" {
24+
type = number
25+
description = "The port to run Open WebUI on."
26+
default = 7800
27+
}
28+
29+
variable "open_webui_version" {
30+
type = string
31+
description = "The version of Open WebUI to install"
32+
default = "latest"
33+
}
34+
35+
variable "data_dir" {
36+
type = string
37+
description = "The directory where Open WebUI stores its data (database, uploads, vector_db, cache)."
38+
default = ".open-webui"
39+
}
40+
41+
variable "openai_api_key" {
42+
type = string
43+
description = "OpenAI API key for accessing OpenAI models. If not provided, OpenAI integration will need to be configured manually in the UI."
44+
default = ""
45+
sensitive = true
46+
}
47+
48+
variable "share" {
49+
type = string
50+
description = "The sharing level for the Open WebUI app. Set to 'owner' for private access, 'authenticated' for access by any authenticated user, or 'public' for public access."
51+
default = "owner"
52+
validation {
53+
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
54+
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
55+
}
56+
}
57+
58+
variable "order" {
59+
type = number
60+
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
61+
default = null
62+
}
63+
64+
variable "group" {
65+
type = string
66+
description = "The name of a group that this app belongs to."
67+
default = null
68+
}
69+
70+
resource "coder_script" "open-webui" {
71+
agent_id = var.agent_id
72+
display_name = "open-webui"
73+
icon = "/icon/openwebui.svg"
74+
script = templatefile("${path.module}/run.sh", {
75+
HTTP_SERVER_LOG_PATH : var.http_server_log_path,
76+
HTTP_SERVER_PORT : var.http_server_port,
77+
VERSION : var.open_webui_version,
78+
DATA_DIR : var.data_dir,
79+
OPENAI_API_KEY : var.openai_api_key,
80+
})
81+
run_on_start = true
82+
}
83+
84+
resource "coder_app" "open-webui" {
85+
agent_id = var.agent_id
86+
slug = "open-webui"
87+
display_name = "Open WebUI"
88+
url = "http://localhost:${var.http_server_port}"
89+
icon = "/icon/openwebui.svg"
90+
subdomain = true
91+
share = var.share
92+
order = var.order
93+
group = var.group
94+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
mock_provider "coder" {}
2+
3+
run "test_defaults" {
4+
command = plan
5+
6+
variables {
7+
agent_id = "test-agent-123"
8+
}
9+
10+
assert {
11+
condition = var.http_server_port == 7800
12+
error_message = "Default port should be 7800"
13+
}
14+
15+
assert {
16+
condition = var.http_server_log_path == "/tmp/open-webui.log"
17+
error_message = "Default log path should be /tmp/open-webui.log"
18+
}
19+
20+
assert {
21+
condition = var.share == "owner"
22+
error_message = "Default share should be 'owner'"
23+
}
24+
25+
assert {
26+
condition = var.open_webui_version == "latest"
27+
error_message = "Default version should be 'latest'"
28+
}
29+
30+
assert {
31+
condition = coder_app.open-webui.subdomain == true
32+
error_message = "App should use subdomain"
33+
}
34+
35+
assert {
36+
condition = coder_app.open-webui.display_name == "Open WebUI"
37+
error_message = "App display name should be 'Open WebUI'"
38+
}
39+
}
40+
41+
run "test_custom_port" {
42+
command = plan
43+
44+
variables {
45+
agent_id = "test-agent-456"
46+
http_server_port = 9000
47+
}
48+
49+
assert {
50+
condition = var.http_server_port == 9000
51+
error_message = "Custom port should be 9000"
52+
}
53+
54+
assert {
55+
condition = coder_app.open-webui.url == "http://localhost:9000"
56+
error_message = "App URL should use custom port"
57+
}
58+
}
59+
60+
run "test_custom_log_path" {
61+
command = plan
62+
63+
variables {
64+
agent_id = "test-agent-789"
65+
http_server_log_path = "/var/log/open-webui.log"
66+
}
67+
68+
assert {
69+
condition = var.http_server_log_path == "/var/log/open-webui.log"
70+
error_message = "Custom log path should be set"
71+
}
72+
}
73+
74+
run "test_share_authenticated" {
75+
command = plan
76+
77+
variables {
78+
agent_id = "test-agent-auth"
79+
share = "authenticated"
80+
}
81+
82+
assert {
83+
condition = coder_app.open-webui.share == "authenticated"
84+
error_message = "Share should be 'authenticated'"
85+
}
86+
}
87+
88+
run "test_share_public" {
89+
command = plan
90+
91+
variables {
92+
agent_id = "test-agent-public"
93+
share = "public"
94+
}
95+
96+
assert {
97+
condition = coder_app.open-webui.share == "public"
98+
error_message = "Share should be 'public'"
99+
}
100+
}
101+
102+
run "test_order_and_group" {
103+
command = plan
104+
105+
variables {
106+
agent_id = "test-agent-order"
107+
order = 10
108+
group = "AI Tools"
109+
}
110+
111+
assert {
112+
condition = coder_app.open-webui.order == 10
113+
error_message = "Order should be 10"
114+
}
115+
116+
assert {
117+
condition = coder_app.open-webui.group == "AI Tools"
118+
error_message = "Group should be 'AI Tools'"
119+
}
120+
}
121+
122+
run "test_custom_version" {
123+
command = plan
124+
125+
variables {
126+
agent_id = "test-agent-version"
127+
open_webui_version = "0.5.0"
128+
}
129+
130+
assert {
131+
condition = var.open_webui_version == "0.5.0"
132+
error_message = "Custom version should be '0.5.0'"
133+
}
134+
}
135+
136+
run "test_custom_data_dir" {
137+
command = plan
138+
139+
variables {
140+
agent_id = "test-agent-data"
141+
data_dir = "/home/coder/open-webui-data"
142+
}
143+
144+
assert {
145+
condition = var.data_dir == "/home/coder/open-webui-data"
146+
error_message = "Custom data_dir should be set"
147+
}
148+
}
149+
150+
run "test_default_data_dir" {
151+
command = plan
152+
153+
variables {
154+
agent_id = "test-agent-data-default"
155+
}
156+
157+
assert {
158+
condition = var.data_dir == ".open-webui"
159+
error_message = "Default data_dir should be '.open-webui'"
160+
}
161+
}
162+
163+
run "test_openai_api_key" {
164+
command = plan
165+
166+
variables {
167+
agent_id = "test-agent-openai"
168+
openai_api_key = "sk-test-key-123"
169+
}
170+
171+
assert {
172+
condition = var.openai_api_key == "sk-test-key-123"
173+
error_message = "OpenAI API key should be set"
174+
}
175+
}
176+
177+
run "test_default_openai_api_key" {
178+
command = plan
179+
180+
variables {
181+
agent_id = "test-agent-openai-default"
182+
}
183+
184+
assert {
185+
condition = var.openai_api_key == ""
186+
error_message = "Default OpenAI API key should be empty"
187+
}
188+
}

0 commit comments

Comments
 (0)