All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
- ℹ️ Conditional
/infoendpoint — newOPEN_TERMINAL_INFOenvironment variable (orinfoin config.toml) registers aGET /infoendpoint that returns operator-provided context to the AI. Use it to describe the environment (e.g. container base OS, available tools, GPU access). When the variable is unset, the endpoint is not registered.
- 🐛 Terminal PTY warnings — wrapped multi-user terminal sessions with
script -qcfor proper PTY allocation, eliminatingcannot set terminal process groupandno job controlwarnings. - 🐛 Stale home directory ownership — added
chownafteruseraddto handle pre-existing home directories with mismatched UID/GID from previous container runs.
- 📖 README — updated multi-user documentation with accurate description and production warning.
- 🔌 Per-user port visibility — in multi-user mode,
/portsnow filters by socket UID so each user only sees their own listening ports.
- 📁 Module reorganization — moved
runner.py,notebooks.py, anduser_isolation.pyintoopen_terminal/utils/for a cleaner package layout.
- 🔒 Cross-user file API isolation — file endpoints now block access to other users' home directories via path validation, returning
403 Forbidden. System paths (/etc,/usr, etc.) remain accessible. - 🐛 Terminal spawn directory — interactive terminals now start in the user's home directory instead of
/home/user(sudo -i -u).
- ♻️ Native Python I/O for writes — replaced
sudo tee,sudo mkdir -p,sudo rm -rf,sudo mvwith nativeaiofiles/os/shutil. The only remaining subprocess issudo chownfor ownership fixup after writes. Home directories usechmod 2770(setgid + group rwx).
- ♻️ Native Python I/O for multi-user reads — replaced subprocess-based file reads (
cat,find -printf,stat -c,test) with nativeaiofiles/oscalls. Home directories now usechmod 750with group membership so the server can read directly. Writes still usesudo -ufor correct ownership. Cross-user isolation preserved via Unix group permissions. - 🐳 Dockerfile — grants
CAP_SETGIDto the Python binary viasetcapso the server can refresh supplementary groups at runtime when provisioning new users.
- 🐛 Multi-user file operations — all file endpoints (list, read, view, display, replace, grep, glob, upload) now correctly run as the provisioned user. Previously only write/delete/move were handled, causing
PermissionErroron reads in user home directories.
- ♻️ UserFS abstraction (
open_terminal/utils/fs.py) — unified filesystem interface that transparently routes I/O throughsudo -uin multi-user mode. Endpoints receive aUserFSinstance via dependency injection and no longer branch on mode. Replaces per-endpoint sudo wrappers.
- 👥 Multi-user mode (
OPEN_TERMINAL_MULTI_USER=true) — per-user OS accounts inside a single container, with standard Unix permissions (chmod 700) providing kernel-enforced isolation between users. When enabled, Open Terminal reads theX-User-Idheader (set by the Open WebUI proxy), provisions a dedicated Linux user on first access viauseradd, and runs all commands, file operations, and terminal sessions as that user viasudo -u. No Docker socket, no per-user containers, no enterprise license required. Fails fast with a clear error on non-Linux platforms. (#38) - ⚙️
OPEN_TERMINAL_UVICORN_LOOP— environment variable (oruvicorn_loopin config.toml) to configure the Uvicorn event loop implementation. Defaults toauto.
- 🐳 Docker CLI, Compose, and Buildx bundled in the container image via get.docker.com. Mount the host's Docker socket (
-v /var/run/docker.sock:/var/run/docker.sock) to let agents clone repos, build images, and run containers. The entrypoint automatically fixes socket group permissions sodockercommands work withoutsudo.
- 🌐 UTF-8 encoding on Windows — all text file I/O now explicitly uses UTF-8 encoding instead of the system default. Fixes Chinese (and other non-ASCII) content being written as GB2312 on Chinese Windows, which broke tool-call chaining and produced garbled files. (#21)
- 📓 Notebook execution (
/notebooks) — multi-session Jupyter notebook execution via REST endpoints. Each session gets its own kernel vianbclient. Supports per-cell execution with rich outputs (images, HTML, LaTeX).nbclientandipykernelare now core dependencies. - ⚙️
OPEN_TERMINAL_ENABLE_NOTEBOOKS— environment variable (orenable_notebooksin config.toml) to enable/disable notebook execution endpoints. Defaults totrue. Exposed inGET /api/configfeatures.
- 📓 Notebook execution support — new
notebooksoptional extra (pip install open-terminal[notebooks]) addsnbclientandipykernelfor running Jupyter notebooks with per-cell execution and full rich output (images, HTML, LaTeX). Keeps the core package lightweight for users who don't need notebook support.
- 📝 Custom execute description — new
OPEN_TERMINAL_EXECUTE_DESCRIPTIONenvironment variable (orexecute_descriptionin config.toml) appends custom text to the execute endpoint's OpenAPI description, letting you tell AI models about installed tools, capabilities, or conventions.
- 📦 Startup package installation — new
OPEN_TERMINAL_PACKAGESandOPEN_TERMINAL_PIP_PACKAGESenvironment variables install additional apt and pip packages automatically when the Docker container starts. No need to fork the Dockerfile for common customizations.
- 🔍 Port detection (
GET /ports) — discovers TCP ports listening on localhost, scoped to descendant processes of open-terminal (servers started via the terminal or/execute). Cross-platform: parses/proc/net/tcpon Linux,lsofon macOS,netstaton Windows. Zero new dependencies. - 🔀 Port proxy (
/proxy/{port}/{path}) — reverse-proxies HTTP requests tolocalhost:{port}, enabling browser access to servers running inside the terminal environment. Supports all HTTP methods, forwards headers and body, returns 502 on connection refused. Uses the existinghttpxdependency. - 📦
utils.portmodule — port detection and process-tree utilities extracted intoopen_terminal/utils/port.pyfor reusability.
- ⏱️ Default execute timeout — new
OPEN_TERMINAL_EXECUTE_TIMEOUTenvironment variable (orexecute_timeoutin config.toml) sets a default wait duration for command execution. Smaller models that don't set timeouts now get command output inline instead of assuming failure.
- 🎨 Terminal color support — terminal sessions now set the
TERMenvironment variable (defaultxterm-256color) so programs emit ANSI color codes. Configurable viaOPEN_TERMINAL_TERMenvironment variable ortermin config.toml.
- ⚙️ Configurable terminal feature — new
OPEN_TERMINAL_ENABLE_TERMINALenvironment variable (orenable_terminalin config.toml) to enable or disable the interactive terminal. When disabled, all/api/terminalsroutes and the WebSocket endpoint are not mounted. Defaults totrue. - 🔍 Config discovery endpoint (
GET /api/config) — returns server feature flags so clients like Open WebUI can discover whether the terminal is enabled and adapt the UI accordingly.
- 🪟 Windows PTY support — terminal sessions and command execution now work on Windows via pywinpty (ConPTY).
pywinptyis auto-installed on Windows. Interactive terminals (/api/terminals), colored output, and TUI apps now work on Windows instead of returning 503. - 🏭 WinPtyRunner — new
ProcessRunnerimplementation usingwinpty.PtyProcessfor full PTY semantics on Windows, including resize support. Thecreate_runnerfactory now prefers Unix PTY → WinPTY → pipe fallback.
- 🔒 Terminal session limit — new
OPEN_TERMINAL_MAX_SESSIONSenvironment variable (default16) caps the number of concurrent interactive terminal sessions. Dead sessions are automatically pruned before the limit is checked. Returns429when the limit is reached.
- 🐳 PTY device exhaustion — fixed
OSError: out of pty devicesby closing leaked file descriptors when subprocess creation fails afterpty.openpty(). BothPtyRunner(command execution) andcreate_terminal(interactive sessions) now properly clean up on error paths. - 🛡️ Graceful PTY error handling —
create_terminalnow returns a clear503with a descriptive message when the system runs out of PTY devices, instead of an unhandled server error.
- 🐳 Docker terminal shell — fixed
can't access tty; job control turned offerror by setting the default shell to/bin/bashfor the container user. Previously the user was created with/bin/sh(dash), which does not support interactive job control in a PTY.
- 🖥️ Interactive terminal sessions — full PTY-based terminal accessible via WebSocket, following the JupyterLab/Kubernetes resource pattern.
POST /api/terminalsto create a session,GET /api/terminalsto list,DELETE /api/terminals/{id}to kill, andWS /api/terminals/{id}to attach. Non-blocking I/O ensures the terminal never starves other API requests. Sessions are automatically cleaned up on disconnect.
- 📄 Configuration file support — settings can now be loaded from TOML config files at /etc/open-terminal/config.toml (system-wide) and $XDG_CONFIG_HOME/open-terminal/config.toml (per-user, defaults to ~/.config/open-terminal/config.toml). Supports host, port, api_key, cors_allowed_origins, log_dir, and binary_mime_prefixes. CLI flags and environment variables still take precedence. Use --config to point to a custom config file. This keeps the API key out of ps / htop output.
- 📂 XDG Base Directory support — the default log directory moved from ~/.open-terminal/logs to the XDG-compliant path $XDG_STATE_HOME/open-terminal/logs (defaults to ~/.local/state/open-terminal/logs when XDG_STATE_HOME is not set). The OPEN_TERMINAL_LOG_DIR environment variable still overrides the default.
- 🔐 Docker secrets support — set OPEN_TERMINAL_API_KEY_FILE to load the API key from a file (e.g. /run/secrets/...), following the convention used by the official PostgreSQL Docker image.
- 📦 Move endpoint (POST /files/move) for moving and renaming files and directories. Uses shutil.move for cross-filesystem support. Hidden from OpenAPI schema.
- 🙈 Hidden upload_file from OpenAPI schema — the /files/upload endpoint is now excluded from the public API docs, consistent with other internal-only file endpoints.
- 📥 Temporary download links (GET /files/download/link and GET /files/download/{token}) — deprecated in favour of direct file navigation built into Open WebUI.
- 🔗 Temporary upload links (POST /files/upload/link, GET /files/upload/{token}, and POST /files/upload/{token}) — deprecated in favour of direct file navigation built into Open WebUI.
- 🖥️ Pseudo-terminal (PTY) execution — commands now run under a real PTY by default, enabling colored output, interactive programs (REPLs, TUI apps), and proper isatty() detection. Falls back to pipe-based execution on Windows.
- 🏭 Process runner abstraction — new ProcessRunner factory pattern (PtyRunner / PipeRunner) in runner.py for clean, extensible process management.
- 🔡 Escape sequence conversion in send_process_input — literal escape strings from LLMs (\n, \x03 for Ctrl-C, \x04 for Ctrl-D, etc.) are automatically converted to real characters.
- 📦 Merged output stream — PTY output is logged as type "output" (merged stdout/stderr) instead of separate streams, matching real terminal behavior.
- 📺 Display file endpoint (GET /files/display) — a signaling endpoint that lets AI agents request a file be shown to the user. The consuming client is responsible for handling the response and presenting the file in its own UI.
- ⏳ Improved wait behavior — wait=0 on the status endpoint now correctly triggers a wait instead of being treated as falsy, so commands that finish quickly return immediately rather than requiring a non-zero wait value.
- 📄 PDF text extraction in read_file — PDF files are now automatically converted to text using pypdf and returned in the standard text-file JSON format, making them readable by LLMs. Supports start_line/end_line range selection.
- 👁️ File view endpoint (GET /files/view) for serving raw binary content of any file type with the correct Content-Type. Designed for UI previewing (PDFs, images, etc.) without the MIME restrictions of read_file.
- 📂 --cwd CLI option for both run and mcp commands to set the server's working directory on startup.
- 📍 Working directory endpoints — GET /files/cwd and POST /files/cwd to query and change the current working directory at runtime.
- 📁 mkdir endpoint (POST /files/mkdir) to create directories with automatic parent directory creation.
- 🗑️ delete endpoint (DELETE /files/delete) to remove files and directories.
- 📄 Binary-aware read_file returns raw binary responses for supported file types (images, etc.) and rejects unsupported binary files with a descriptive error. Configurable via OPEN_TERMINAL_BINARY_MIME_PREFIXES env var.
- 🔍 File Search Endpoints: Added a new /files/glob endpoint (alias glob_search) to search for files by name/pattern using wildcards.
- 🔄 Alias Update: Renamed and aliased the existing /files/search endpoint to /files/grep (alias grep_search) to establish a clear distinction between content-level search (grep) and filename-level search (glob).
- 🛡️ Graceful permission error handling across all file endpoints (write_file, replace_file_content, upload_file). PermissionError and other OSError exceptions now return HTTP 400 with a descriptive message instead of crashing with HTTP 500.
- 🐳 Docker volume permissions via entrypoint.sh that automatically fixes /home/user ownership on startup when a host volume is mounted with mismatched permissions.
- 🔧 Background process resilience — _log_process no longer crashes if the log directory is unwritable; commands still execute and complete normally.
- ⚡ Fully async I/O across all file and upload endpoints. Replaced blocking os.* and open() calls with aiofiles and aiofiles.os so the event loop is never blocked by filesystem operations. search_files and list_files inner loops use asyncio.to_thread for os.walk/os.listdir workloads.
- 🤖 Optional MCP server mode via open-terminal mcp, exposing all endpoints as MCP tools for LLM agent integration. Supports stdio and streamable-http transports. Install with pip install open-terminal[mcp].
- 🛡️ Null query parameter tolerance via HTTP middleware that strips query parameters with the literal value "null". Prevents 422 errors when clients serialize null into query strings (e.g. ?wait=null) instead of omitting the parameter.
- 📁 File-backed process output persisted to JSONL log files under 'logs/processes/', configurable via 'OPEN_TERMINAL_LOG_DIR'. Full audit trail survives process cleanup and server restarts.
- 📍 Offset-based polling on the status endpoint with 'offset' and 'next_offset' for stateless incremental reads. Multiple clients can independently track the same process without data loss.
- ✂️ Tail parameter on both execute and status endpoints to return only the last N output entries, keeping AI agent responses bounded.
- 🗑️ Removed in-memory output buffer in favor of reading directly from the JSONL log file as the single source of truth.
- 📂 Organized log directory with process logs namespaced under 'logs/processes/' to accommodate future log types.
- 🔄 Bounded output buffers and the 'OPEN_TERMINAL_MAX_OUTPUT_LINES' environment variable, no longer needed without in-memory buffering.
- 📂 File operations for reading, writing, listing, and find-and-replace, with optional line-range selection for large files.
- 📤 File upload by URL or multipart form data.
- 📥 Temporary download links that work without authentication, making it easy to retrieve files from the container.
- 🔗 Temporary upload links with a built-in drag-and-drop page for sharing with others.
- ⌨️ Stdin input to send text to running processes, enabling interaction with REPLs and interactive commands.
- 📋 Process listing to view all tracked background processes and their current status at a glance.
- ⏳ Synchronous mode with an optional 'wait' parameter to block until a command finishes and get output inline.
- 🔄 Bounded output buffers to prevent memory issues on long-running commands, configurable via 'OPEN_TERMINAL_MAX_OUTPUT_LINES'.
- 🛠️ Rich toolbox pre-installed in the container, including Python data science libraries, networking utilities, editors, and build tools.
- 👤 Non-root user with passwordless 'sudo' available when elevated privileges are needed.
- 🚀 CI/CD pipeline for automated multi-arch Docker image builds and publishing via GitHub Actions.
- 💾 Named volume in the default 'docker run' command so your files survive container restarts.
- 🐳 Expanded container image with system packages and Python libraries for a batteries-included experience.
- 🎉 Initial release of Open Terminal, a lightweight API that turns any container into a remote shell for AI agents and automation workflows.
▶️ Background command execution with async process tracking, supporting shell features like pipes, chaining, and redirections.- 🔑 Bearer token authentication to secure your instance using the 'OPEN_TERMINAL_API_KEY' environment variable.
- 🔐 Zero-config setup with an auto-generated API key printed to container logs when none is provided.
- 💚 Health check endpoint at '/health' for load balancer and orchestrator integration.
- 🌐 CORS enabled by default for seamless integration with web-based AI tools and dashboards.