Files
open-webui-docs/docs/features/extensibility/open-terminal/index.md
2026-02-25 22:58:03 +01:00

28 KiB
Raw Blame History

sidebar_position, title
sidebar_position title
3 Open Terminal

Open Terminal

:::info

This page is up-to-date with Open Terminal release version v0.2.6.

:::

Overview

Open Terminal is a lightweight API for running shell commands remotely — with background process management, file operations, and secure access. When connected to Open WebUI as a Tool, it gives models full shell access, a complete file system toolkit, and the ability to execute arbitrary commands in an isolated environment.

Unlike Pyodide (browser-based, limited libraries) or Jupyter (shared environment, no per-user isolation), Open Terminal runs in its own Docker container with full OS-level capabilities. This makes it ideal for tasks that require:

  • Installing and running any software package or language
  • Working with system tools like ffmpeg, pandoc, git, etc.
  • Reading, writing, and editing files directly
  • Running multi-step build, analysis, or data processing pipelines
  • Interacting with REPLs and interactive commands via stdin

:::warning Open Terminal provides unrestricted shell access to the environment it runs in. In production deployments, always run it inside a Docker container with appropriate resource limits. Never run it on bare metal in a shared or untrusted environment. :::

Getting Started

docker run -d --name open-terminal --restart unless-stopped -p 8000:8000 -v open-terminal:/home/user -e OPEN_TERMINAL_API_KEY=your-secret-key ghcr.io/open-webui/open-terminal

The -v open-terminal:/home/user flag creates a named volume so files in the user's home directory survive container restarts. If no API key is provided, one is auto-generated and printed on startup (docker logs open-terminal).

The container runs as a non-root user (user) with passwordless sudo available when elevated privileges are needed. The working directory is /home/user.

The Docker image is based on Python 3.12 and comes pre-installed with a rich set of tools:

Category Included
Core utilities coreutils, findutils, grep, sed, gawk, diffutils, patch, less, file, tree, bc
Networking curl, wget, net-tools, iputils-ping, dnsutils, netcat, socat, telnet, openssh-client, rsync
Editors vim, nano
Version control git
Build tools build-essential, cmake, make
Languages Python 3.12, Perl, Ruby, Lua 5.4
Data processing jq, xmlstarlet, sqlite3
Compression zip, unzip, tar, gzip, bzip2, xz, zstd, p7zip
System procps, htop, lsof, strace, sysstat, sudo, tmux, screen
Python libraries numpy, pandas, scipy, scikit-learn, matplotlib, seaborn, plotly, jupyter, ipython, requests, beautifulsoup4, lxml, sqlalchemy, psycopg2, pyyaml, toml, jsonlines, tqdm, rich

Build from Source

docker build -t open-terminal .
docker run -p 8000:8000 open-terminal

pip Install (Bare Metal)

# One-liner with uvx (no install needed)
uvx open-terminal run --host 0.0.0.0 --port 8000 --api-key your-secret-key

# Or install globally with pip
pip install open-terminal
open-terminal run --host 0.0.0.0 --port 8000 --api-key your-secret-key

# Set a custom working directory
open-terminal run --cwd /path/to/project

:::warning Running bare metal gives the model shell access to your actual machine. Only use this for local development or testing. :::

MCP Server Mode

Open Terminal can also run as an MCP (Model Context Protocol) server, exposing all its endpoints as MCP tools. This requires an additional dependency:

pip install open-terminal[mcp]

Then start the MCP server:

# stdio transport (default — for local MCP clients)
open-terminal mcp

# streamable-http transport (for remote/networked MCP clients)
open-terminal mcp --transport streamable-http --host 0.0.0.0 --port 8000
Option Default Description
--transport stdio Transport mode: stdio or streamable-http
--host 0.0.0.0 Bind address (streamable-http only)
--port 8000 Bind port (streamable-http only)
--cwd Server's current directory Working directory for the server process

Under the hood, this uses FastMCP to automatically convert every FastAPI endpoint into an MCP tool — no manual tool definitions needed.

Docker Compose (with Open WebUI)

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: open-webui
    ports:
      - "3000:8080"
    volumes:
      - open-webui:/app/backend/data

  open-terminal:
    image: ghcr.io/open-webui/open-terminal
    container_name: open-terminal
    ports:
      - "8000:8000"
    volumes:
      - open-terminal:/home/user
    environment:
      - OPEN_TERMINAL_API_KEY=your-secret-key
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2.0"

volumes:
  open-webui:
  open-terminal:

Configuration

CLI Option Default Environment Variable Description
--host 0.0.0.0 Bind address
--port 8000 Bind port
--cwd Current directory Working directory for the server process
--api-key Auto-generated OPEN_TERMINAL_API_KEY Bearer API key for authentication
~/.open-terminal/logs OPEN_TERMINAL_LOG_DIR Directory for process JSONL log files
image OPEN_TERMINAL_BINARY_MIME_PREFIXES Comma-separated MIME type prefixes for binary files that read_file returns as raw binary responses (e.g. image,audio or image/png,image/jpeg)

When no API key is provided, Open Terminal generates a random key using a cryptographically secure token and prints it to the console on startup.

Process output is persisted to JSONL log files under OPEN_TERMINAL_LOG_DIR/processes/. These files provide a full audit trail that survives process cleanup and server restarts.

:::note Performance All file and upload endpoints use fully async I/O via aiofiles. Directory listing and file search operations run in a background thread via asyncio.to_thread. This means the server's event loop is never blocked by filesystem operations, even on large directories or slow storage. As of v0.2.5, all file endpoints gracefully handle permission errors and OS-level exceptions instead of crashing with HTTP 500. :::

Connecting to Open WebUI

There are two ways to connect Open Terminal to Open WebUI: the native integration (recommended) and the generic OpenAPI Tool Server method.

:::tip Experimental The native Open Terminal integration is currently marked as experimental. It provides a tighter experience than the generic OpenAPI approach, with features like the built-in file browser. :::

Open WebUI has built-in support for Open Terminal connections under Settings → Integrations. This gives you:

  • Always-on tools — When enabled, all Open Terminal endpoints are automatically injected as tools into every chat. No need to manually select them.
  • Built-in file browser — A Files tab appears in the chat controls panel (alongside Controls), letting you browse, preview, download, upload, and attach files from the terminal directly in the chat UI.
  • Active terminal indicator — The message input area shows which terminal is connected.

Setup

  1. Go to Settings → Integrations
  2. Scroll to the Open Terminal section
  3. Click + to add a new connection
  4. Enter the URL (e.g. http://open-terminal:8000) and API key
  5. Toggle the connection on

Only one terminal connection can be active at a time. Enabling a new connection automatically disables the previous one.

File Browser

When a terminal is connected, the chat controls panel gains a Files tab:

  • Browse directories on the remote terminal filesystem
  • Preview text files, images, and PDFs inline
  • Create folders using the new folder button in the breadcrumb bar
  • Delete files and folders via the context menu (⋯) on each entry
  • Download any file to your local machine
  • Upload files by dragging and dropping them onto the directory listing
  • Attach files to the current chat by downloading them through the file browser

The file browser remembers your last-visited directory between panel open/close cycles and automatically syncs the terminal's working directory to match.

Generic OpenAPI Tool Server

Open Terminal is also a standard FastAPI application that exposes an OpenAPI specification at /openapi.json. This means it works as a generic OpenAPI Tool Server — useful when you want more control over which tools are enabled per-chat.

  • As a User Tool Server: Add it in Settings → Tools to connect directly from your browser.
  • As a Global Tool Server: Add it in Admin Settings → Tools to make it available to all users.

For step-by-step instructions with screenshots, see the OpenAPI Tool Server Integration Guide.

API Reference

All endpoints except /health and temporary download/upload links require Bearer token authentication.

Interactive API documentation (Swagger UI) is available at http://localhost:8000/docs when the server is running.

Command Execution

Execute a Command

POST /execute

Runs a shell command as a background process and returns a process ID. All output is persisted to a JSONL log file. Supports pipes, chaining (&&, ||, ;), and redirections.

:::tip The /execute endpoint description in the OpenAPI spec automatically includes live system metadata — OS, hostname, current user, default shell, Python version, and working directory. When Open WebUI discovers this tool via the OpenAPI spec, models see this context in the tool description and can adapt their commands accordingly. :::

Request body:

Field Type Default Description
command string (required) Shell command to execute
cwd string Server's working directory Working directory for the command
env object null Extra environment variables merged into the subprocess environment

Query parameters:

Parameter Default Description
stream false If true, stream output as JSONL instead of waiting for completion
tail (all) Return only the last N output entries. Useful to limit response size for AI agents.
wait number null
tail integer null

Example — fire and forget:

curl -X POST http://localhost:8000/execute \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"command": "echo hello"}'
{
  "id": "a1b2c3d4e5f6",
  "command": "echo hello",
  "status": "running",
  "exit_code": null,
  "output": [],
  "truncated": false,
  "next_offset": 0,
  "log_path": "/home/user/.open-terminal/logs/processes/a1b2c3d4e5f6.jsonl"
}

Example — wait for completion:

curl -X POST "http://localhost:8000/execute?wait=5" \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"command": "echo hello"}'
{
  "id": "a1b2c3d4e5f6",
  "command": "echo hello",
  "status": "done",
  "exit_code": 0,
  "output": [
    {"type": "stdout", "data": "hello\n"}
  ],
  "truncated": false,
  "next_offset": 1,
  "log_path": "/home/user/.open-terminal/logs/processes/a1b2c3d4e5f6.jsonl"
}

:::info File-Backed Process Output All background process output (stdout/stderr) is persisted to JSONL log files under ~/.open-terminal/logs/processes/. This means output is never lost, even if the server restarts. The response includes next_offset for stateless incremental polling — pass it as the offset query parameter on subsequent status requests to get only new output. The log_path field shows the path to the raw JSONL log file. :::

Grep Search (File Contents)

GET /files/grep

Search for a text pattern across files in a directory. Returns structured matches with file paths, line numbers, and matching lines. Skips binary files automatically.

:::note Renamed in v0.2.6 This endpoint was renamed from /files/search to /files/grep to clearly distinguish content-level search from filename-level search (/files/glob). :::

Query parameters:

Parameter Type Default Description
query string (required) Text or regex pattern to search for
path string . Directory or file to search in
regex boolean false Treat query as a regex pattern
case_insensitive boolean false Perform case-insensitive matching
include string[] (all files) Glob patterns to filter files (e.g. *.py). Files must match at least one pattern.
match_per_line boolean true If true, return each matching line with line numbers. If false, return only matching filenames.
max_results integer 50 Maximum number of matches to return (1500)
curl "http://localhost:8000/files/grep?query=TODO&include=*.py&case_insensitive=true" \

#### Get Command Status

**`GET /execute/{process_id}/status`**

Returns process status, exit code, and output since the last poll. Use `offset` and `next_offset` for stateless incremental reads — multiple clients can independently track the same process without data loss.

| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `wait` | number | `null` | Seconds to wait for the process to finish before returning. Returns early if the process exits. |
| `offset` | integer | `0` | Number of output entries to skip. Use `next_offset` from the previous response to get only new output. |
| `tail` | integer | `null` | Return only the last N output entries. |

```bash
curl "http://localhost:8000/execute/a1b2c3d4e5f6/status?offset=0&wait=10" \
  -H "Authorization: Bearer <api-key>"
{
  "id": "a1b2c3d4e5f6",
  "command": "echo hello",
  "status": "done",
  "exit_code": 0,
  "output": [
    {"type": "stdout", "data": "hello\n"}
  ],
  "truncated": false,
  "next_offset": 1,
  "log_path": "/home/user/.open-terminal/logs/processes/a1b2c3d4e5f6.jsonl"
}

List Processes

GET /execute

Returns a list of all tracked background processes, including running, done, and killed.

curl http://localhost:8000/execute \
  -H "Authorization: Bearer <api-key>"
[
  {
    "id": "a1b2c3d4e5f6",
    "command": "python train.py",
    "status": "running",
    "exit_code": null,
    "log_path": "/home/user/.open-terminal/logs/processes/a1b2c3d4e5f6.jsonl"
  }
]

Finished processes are automatically cleaned up after 5 minutes. Their JSONL log files are retained on disk for audit purposes.

Send Input

POST /execute/{process_id}/input

Sends text to a running process's stdin. Useful for interacting with REPLs, interactive commands, or any process waiting for input.

Request body:

Field Type Description
input string Text to send to stdin. Include newline characters as needed.
curl -X POST http://localhost:8000/execute/a1b2c3d4e5f6/input \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"input": "print(42)\n"}'

Kill a Process

DELETE /execute/{process_id}

Terminates a running process. Sends SIGTERM by default for graceful shutdown.

Parameter Type Default Description
force boolean false Send SIGKILL instead of SIGTERM.
curl -X DELETE "http://localhost:8000/execute/a1b2c3d4e5f6?force=true" \
  -H "Authorization: Bearer <api-key>"

File Operations

List Directory Contents

GET /files/list

Returns a structured listing of files and directories at the given path, including name, type, size, and modification time.

Parameter Type Default Description
directory string . Directory path to list.
curl "http://localhost:8000/files/list?directory=/home/user" \
  -H "Authorization: Bearer <api-key>"
{
  "dir": "/home/user",
  "entries": [
    {"name": "data.csv", "type": "file", "size": 1024, "modified": 1707955200.0},
    {"name": "scripts", "type": "directory", "size": 4096, "modified": 1707955200.0}
  ]
}

Read a File

GET /files/read

Returns the contents of a file. Text files return a JSON object with a content string. Supported binary types (configurable, default: image/*) return the raw binary with the appropriate Content-Type header. Unsupported binary types are rejected with HTTP 415.

Parameter Type Default Description
path string (required) Path to the file to read.
start_line integer null First line to return (1-indexed, inclusive).
end_line integer null Last line to return (1-indexed, inclusive).
curl "http://localhost:8000/files/read?path=/home/user/script.py&start_line=1&end_line=10" \
  -H "Authorization: Bearer <api-key>"
{
  "path": "/home/user/script.py",
  "total_lines": 50,
  "content": "#!/usr/bin/env python3\nimport sys\n..."
}

For binary files like images, the response is the raw file content with the detected MIME type. Control which binary types are allowed via the OPEN_TERMINAL_BINARY_MIME_PREFIXES environment variable (default: image).

Write a File

POST /files/write

Writes text content to a file. Creates parent directories automatically. Overwrites if the file already exists.

Request body:

Field Type Description
path string Absolute or relative path. Parent directories are created automatically.
content string Text content to write.
curl -X POST http://localhost:8000/files/write \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"path": "/home/user/hello.py", "content": "print(\"hello\")\n"}'

Replace Content in a File

POST /files/replace

Find and replace exact strings in a file. Supports multiple replacements in one call with optional line range narrowing for precision.

Request body:

Field Type Description
path string Path to the file to modify.
replacements array List of find-and-replace operations (applied sequentially).

Each replacement chunk:

Field Type Default Description
target string (required) Exact string to find. Must match precisely, including whitespace.
replacement string (required) Content to replace the target with.
start_line integer null Narrow the search to lines at or after this (1-indexed).
end_line integer null Narrow the search to lines at or before this (1-indexed).
allow_multiple boolean false If true, replaces all occurrences. If false, errors when multiple matches are found.
curl -X POST http://localhost:8000/files/replace \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/home/user/config.yaml",
    "replacements": [
      {"target": "debug: false", "replacement": "debug: true"}
    ]
  }'

Grep Search (File Contents)

GET /files/grep

Search for a text pattern across files in a directory. Returns structured matches with file paths, line numbers, and matching lines. Skips binary files.

Parameter Type Default Description
query string (required) Text or regex pattern to search for.
path string . Directory or file to search in.
regex boolean false Treat query as a regex pattern.
case_insensitive boolean false Perform case-insensitive matching.
include string[] null Glob patterns to filter files (e.g. *.py). Files must match at least one pattern.
match_per_line boolean true If true, return each matching line. If false, return only filenames.
max_results integer 50 Maximum number of matches to return (1500).
curl "http://localhost:8000/files/grep?query=TODO&path=/home/user/project&include=*.py" \
  -H "Authorization: Bearer <api-key>"
{
  "query": "TODO",
  "path": "/root",
  "matches": [
    {"file": "/root/app.py", "line": 42, "content": "# TODO: refactor this"},
    {"file": "/root/utils.py", "line": 7, "content": "# TODO: add tests"}
  ],
  "truncated": false
}

Glob Search (Filenames)

GET /files/glob

Search for files and subdirectories by name within a directory using glob patterns. Returns relative paths, type, size, and modification time.

Parameter Type Default Description
pattern string (required) Glob pattern to search for (e.g. *.py).
path string . Directory to search within.
exclude string[] null Glob patterns to exclude from results.
type string any Type filter: file, directory, or any.
max_results integer 50 Maximum number of matches to return (1500).
curl "http://localhost:8000/files/glob?pattern=*.py&path=/home/user/project&type=file" \
  -H "Authorization: Bearer <api-key>"
{
  "pattern": "*.py",
  "path": "/home/user/project",
  "matches": [
    {"path": "app.py", "type": "file", "size": 2048, "modified": 1707955200.0},
    {"path": "utils/helpers.py", "type": "file", "size": 512, "modified": 1707955200.0}
  ],
  "truncated": false
}

Create a Directory

POST /files/mkdir

Creates a directory at the specified path. Parent directories are created automatically if they don't exist.

Request body:

Field Type Description
path string Path of the directory to create.
curl -X POST http://localhost:8000/files/mkdir \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"path": "/home/user/project/src"}'
{"path": "/home/user/project/src"}

Delete a File or Directory

DELETE /files/delete

Deletes a file or directory. Directories are removed recursively.

Parameter Type Description
path string Path to the file or directory to delete.
curl -X DELETE "http://localhost:8000/files/delete?path=/home/user/old-file.txt" \
  -H "Authorization: Bearer <api-key>"
{"path": "/home/user/old-file.txt", "type": "file"}

Get or Set Working Directory

GET /files/cwd — Returns the server's current working directory.

POST /files/cwd — Changes the server's working directory.

# Get current working directory
curl "http://localhost:8000/files/cwd" \
  -H "Authorization: Bearer <api-key>"

# Set working directory
curl -X POST http://localhost:8000/files/cwd \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"path": "/home/user/project"}'
{"cwd": "/home/user/project"}

File Transfer

Upload a File

POST /files/upload

Save a file to the container filesystem. Provide either a url to fetch remotely, or send the file directly via multipart form data.

Parameter Type Description
directory string Destination directory for the file.
url string (Optional) URL to download the file from. If omitted, expects a multipart file upload.

From URL:

curl -X POST "http://localhost:8000/files/upload?directory=/home/user&url=https://example.com/data.csv" \
  -H "Authorization: Bearer <api-key>"

Direct upload:

curl -X POST "http://localhost:8000/files/upload?directory=/home/user" \
  -H "Authorization: Bearer <api-key>" \
  -F "file=@local_file.csv"

Via temporary upload link (no auth needed to upload):

# 1. Generate an upload link
curl -X POST "http://localhost:8000/files/upload/link?directory=/home/user" \
  -H "Authorization: Bearer <api-key>"
# → {"url": "http://localhost:8000/files/upload/a1b2c3d4..."}

# 2. Upload to the link (no auth required)
curl -X POST "http://localhost:8000/files/upload/a1b2c3d4..." \
  -F "file=@local_file.csv"

Opening a temporary upload link in a browser shows a simple file picker form — useful for manual uploads without curl.

The filename is automatically derived from the uploaded file or the URL.

Download a File

GET /files/download/link

Returns a temporary download URL for a file. The link expires after 5 minutes and requires no authentication to use.

curl "http://localhost:8000/files/download/link?path=/home/user/output.csv" \
  -H "Authorization: Bearer <api-key>"
{"url": "http://localhost:8000/files/download/a1b2c3d4..."}

Process Status (Background)

GET /processes/{process_id}/status

Poll the output of a running or finished background process. Uses offset-based pagination so agents can retrieve only new output since the last poll.

Query parameters:

Parameter Default Description
wait 0 Seconds to wait for the process to finish before returning.
offset 0 Number of output entries to skip. Use next_offset from the previous response.
tail (all) Return only the last N output entries. Useful to limit response size.
curl "http://localhost:8000/processes/a1b2c3d4/status?offset=0&tail=20" \
  -H "Authorization: Bearer <api-key>"
{
  "id": "a1b2c3d4",
  "command": "make build",
  "status": "running",
  "exit_code": null,
  "output": [{"type": "stdout", "data": "Building...\n"}],
  "truncated": false,
  "next_offset": 1,
  "log_path": "/root/.open-terminal/logs/processes/a1b2c3d4.jsonl"
}

Health Check

GET /health

Returns service status. No authentication required.

{"status": "ok"}

Security Considerations

  • Always use Docker in production. Running Open Terminal on bare metal exposes the host system to any command the model generates.
  • Non-root by default. The container runs as a non-root user (user) with passwordless sudo available when elevated privileges are needed. This adds a layer of safety while preserving full capability.
  • Set an API key. Without one, anyone who can reach the port has full shell access. If you don't provide one, the auto-generated key is printed once at startup — save it.
  • Use resource limits. Apply --memory and --cpus flags in Docker to prevent runaway processes from consuming host resources.
  • Network isolation. Place the Open Terminal container on an internal Docker network that only Open WebUI can reach, rather than exposing it to the public internet.
  • Use named volumes for persistence. Files inside the container are lost when the container is removed. The default docker run command mounts a named volume at /home/user for persistence.
  • Log files. Process output is persisted as JSONL files under the configured log directory. Review these files periodically and apply retention policies as needed.

Further Reading