All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
- 🌐 Path-based file serving — new
GET /files/serve/{path}endpoint enables HTML files loaded in iframes to resolve relative CSS, JS, and image references to sibling files. Used by Open WebUI's FileNav to render multi-file websites.
grep_searchnow defaults to regex mode, matching actual grep behaviour. Patterns likefoo|baranderr.*timeoutwork as expected without needing to setregex=trueexplicitly.
- 🗂️ Session-aware relative path resolution — all LLM-exposed file endpoints (
list_files,read_file,write_file,display_file,replace_file_content,grep_search,glob_search) now read theX-Session-Idheader and resolve relative paths (e.g..) against the session's working directory instead of always defaulting tofs.home. Added an optionalcwdparameter toUserFS.resolve_path()to support this. Absolute paths are unaffected.
- 🗂️ Per-session working directory — replaced the process-global
os.chdir()with an in-memory, session-aware dictionary keyed byX-Session-Idheader. Multiple concurrent chat sessions now maintain independent working directories.GET/POST /files/cwd,POST /execute, andPOST /api/terminalsall read the header to resolve the correct cwd. Sessions without a header fall back tofs.home. Entries expire after 7 days of inactivity (sliding TTL), configurable viaOPEN_TERMINAL_SESSION_CWD_TTL(orsession_cwd_ttlin config.toml).
- 🔑 Stronger auto-generated API keys — increased from 192-bit (32 chars) to 384-bit (64 chars) entropy, making brute-force attacks computationally infeasible.
- 🔒 API key required to start — the server now refuses to start without an API key configured. The CLI auto-generates one as before, but running via
uvicorndirectly without settingOPEN_TERMINAL_API_KEYis no longer allowed. - 🛡️ Constant-time API key comparison — both HTTP and WebSocket authentication now use
hmac.compare_digest()instead of!=, preventing timing-based key extraction. ⚠️ CORS default warning — a prominent yellow box is printed at startup when--cors-allowed-originsis left at the default*, warning operators to restrict it for production.
- 🔒 File save fails in multi-user mode —
_chown()transferred file ownership to the provisioned user but didn't set group-write permission, leaving files at644. The server process (a group member) could create new files but couldn't overwrite them on subsequent saves, returningPermissionError. Now setschmod g+wafterchown, matching the2770treatment already applied to directories. (#93)
- 📦 Compressed directory download (
POST /files/archive) — bundle one or more files and directories into a ZIP archive for download. Cross-platform compatible (Windows, macOS, Linux). Multi-user access control enforced.
- 🧠 System prompt endpoint (
GET /system) — returns a structured system prompt for LLM integration, grounding the model in the environment (OS, hostname, user, shell, Python version) with directives for tool usage. Gated byOPEN_TERMINAL_ENABLE_SYSTEM_PROMPT(defaulttrue); advertised viafeatures.systeminGET /api/configso consumers can check support before fetching. - ⚙️
OPEN_TERMINAL_ENABLE_SYSTEM_PROMPT— environment variable (orenable_system_promptin config.toml) to enable/disable the/systemendpoint and feature flag. Defaults totrue. - ⚙️
OPEN_TERMINAL_SYSTEM_PROMPT— environment variable (orsystem_promptin config.toml) to fully override the generated system prompt with custom content.
- ♻️
utils/documents.py— extracted all document text extraction logic fromread_fileinto a dedicated module with anEXTRACTORSregistry. Adding a new format now requires only a single function and a one-line registry entry. No behaviour changes.
- 📄 Extended document text extraction in
read_file—.rtf,.xls,.odt,.ods,.odp,.epub, and.emlfiles are now automatically converted to text and returned in the standard JSON format. RTF strips formatting to plain text, legacy Excel (.xls) renders sheets as tab-separated values, OpenDocument formats (.odt/.ods/.odp) parse the underlying XML, EPUB extracts body text in reading order, and.emlextracts headers and body (with HTML tag stripping). All supportstart_line/end_linerange selection.
- 📄 Office document text extraction in
read_file—.docx,.xlsx, and.pptxfiles are now automatically converted to text and returned in the standard JSON format, making them readable by LLMs. Word documents extract paragraphs and table contents, Excel spreadsheets render all sheets as tab-separated values, and PowerPoint presentations extract text from all slides. Supportsstart_line/end_linerange selection.
- 🔐
_FILEmutual exclusivity bypassed by empty env vars — setting e.g.OPEN_TERMINAL_API_KEY=""alongsideOPEN_TERMINAL_API_KEY_FILEsilently skipped the conflict check because empty strings are falsy. The Python helper (_resolve_file_env),entrypoint.sh, andentrypoint-slim.shnow test whether the variable is set (not merely non-empty), so any explicit assignment — including=""— correctly triggers the mutual-exclusivity error.
- 🐛
/portsreturns 500 in multi-user mode on restricted runtimes — the endpoint triggered full user provisioning (useradd) just to filter ports by UID. On container runtimes that rejectuseradd(e.g. Azure Container Apps), this crashed with an unhandled exception. Now returns an empty port list when provisioning fails — an unprovisioned user has no ports to show. (#80) - 🐳 Docker-in-Docker broken in multi-user mode — mounting the Docker socket (
-v /var/run/docker.sock:/var/run/docker.sock) withOPEN_TERMINAL_MULTI_USER=truefailed because only the defaultuseraccount was added to the socket's group. Dynamically provisioned users now automatically inherit Docker socket group membership when the socket is mounted. (#83)
- 🔍 Port detection broken since v0.11.2 —
setcap cap_setgid+epon the system Python binary (added for multi-useros.setgroups()) made all Python processes non-dumpable, blocking/proc/[pid]/fd/access needed to resolve socket inodes to PIDs. Ports from user-spawned Python servers were silently filtered out. Fixed by copying the Python binary and grantingcap_setgidonly to the copy (python3-ot), used exclusively by the open-terminal server. The systempython3stays capability-free so user processes remain dumpable. Slim and Alpine images hadsetcapremoved entirely since they don't support multi-user mode. (#85, #63) - 📖 README — Image Variants table incorrectly listed multi-user mode as supported on slim and alpine images. Multi-user mode requires
sudo, which only the full image includes.
- 👥 Multi-user mode works when running as root —
ensure_os_user()no longer unconditionally requiressudo; when the process is already root (e.g.user: "0:0"in Docker Compose), user provisioning commands run directly.check_environment()now only requiressudowhen not running as root, with an actionable error message pointing to the standard image or Terminals when neither root nor sudo is available. (#60)
- 🔒
write_filepermission denied in multi-user subdirectories — file writes into directories created byrun_commandfailed becausemkdir -p(viasudo -u) creates directories with default755permissions, leaving the server process without group-write access.UserFSnow creates parent directories as the provisioned user (sudo -u mkdir -p) and sets2770(setgid + group rwx) on the entire directory chain, matching the home directory's permissions. Writing to the home root was unaffected. (#70)
- ⚡ Configurable log flush strategy — new
OPEN_TERMINAL_LOG_FLUSH_INTERVALandOPEN_TERMINAL_LOG_FLUSH_BUFFERenvironment variables (orlog_flush_interval/log_flush_bufferin config.toml) control how frequently process output is flushed to disk. Default0preserves the existing per-chunk flush behaviour. SettingOPEN_TERMINAL_LOG_FLUSH_INTERVAL=1reduces fsyncs from ~250/sec to ~1/sec for high-output commands, preventing I/O storms that can make ARM/eMMC systems unresponsive. (#65)
- 🔧 Centralized flush control — per-chunk
flush()calls removed fromPtyRunner,PipeRunner, andWinPtyRunner; flushing is now managed entirely byBoundedLogWriterbased on the configured interval and buffer settings. An explicit final flush is performed before writing the process end marker.
- 🌐 Global pip packages in multi-user mode —
OPEN_TERMINAL_PIP_PACKAGESnow installs to the system-wide site-packages (sudo pip install) whenOPEN_TERMINAL_MULTI_USER=true, so all provisioned users share the same packages. Previously, packages were installed to/home/user/.local/and only accessible to the default user. (#68)
- 🧹 Removed experimental
urlparameter from/files/upload— this feature was never used by any known consumer (Open WebUI uses multipart uploads). The endpoint now only accepts direct file uploads.
- 🖥️ Redesigned startup output — the CLI now displays Local and Network URLs, the generated API key, and a bind warning in a clean, modern key-value layout with color-coded labels. Network URL auto-detects your LAN IP when binding to
0.0.0.0. - 🔒 Bind warning — a yellow warning is printed at startup when binding to
0.0.0.0, nudging bare-metal users to restrict access with--host 127.0.0.1.
- 🏠 Multi-user home directory hints —
get_system_info()no longer includesas user 'user'in the OpenAPI description when multi-user mode is active, removing a misleading hint that caused smaller LLMs to write to/home/userinstead of their assigned directory. (#57) - 🔄
/home/usrpath rewrite —resolve_path()now also rewrites/home/usr(a common LLM hallucination) to the provisioned user's home directory, matching the existing/home/userrewrite.
- 🐛 Recursive home directory ownership fix —
chowninensure_os_user()now uses-Rto recursively fix ownership of all files within a user's home directory when the OS user is recreated with a different UID (e.g. after container recreation with a persistent volume). Previously only the top-level directory was re-owned, leaving files inside with a mismatched UID. (#62)
- 🔒 Network egress filtering (Docker only) — restrict which domains the container can access via the
OPEN_TERMINAL_ALLOWED_DOMAINSenv var. Supports wildcards (e.g.*.github.com). Set to empty string to block all outbound traffic; omit for full access. Skips gracefully on bare-metal installs.
- 🔒 Upload path traversal —
/files/uploadnow resolves thedirectoryparameter throughfs.resolve_path()and sanitizes the uploaded filename withos.path.basename(), preventing path traversal attacks (e.g.../../etc/passwd) that could escape the user's home directory in multi-user mode. The composed path is normalized withos.path.normpath()and validated by_check_pathbefore writing. All other file endpoints already had these protections.
- 🧟 Zombie process cleanup — process runner
kill()methods now useos.killpg()to signal the entire process group instead of just the direct child PID. Background processes started inside terminals or/executesessions (e.g.sleep 100 &) are now properly terminated on cleanup._cleanup_session()always callsprocess.wait()after force-killing to prevent zombie entries in the process table. - 🐳 Docker PID 1 reaping — added
tinias the container's init process (ENTRYPOINT ["/usr/bin/tini", "--", ...]). Python/uvicorn no longer runs as PID 1, so orphaned grandchild processes are automatically reaped instead of accumulating as zombies.
- ⏰ Timestamp-sortable process IDs — process IDs now use a
YYYYMMDD-HHMMSS-<random>format so log files sort chronologically in the filesystem. The most recent log is always at the bottom ofls. (#54) - ⚙️
OPEN_TERMINAL_MAX_LOG_SIZE— environment variable (ormax_log_sizein config.toml) to set the per-process log file size limit in bytes. Default: 50 MB. - ⚙️
OPEN_TERMINAL_LOG_RETENTION— environment variable (orlog_retentionin config.toml) to set how long finished-process log files are kept on disk before automatic cleanup. Default: 7 days. - 📁
utils/log.py— extracted process log management code (BoundedLogWriter,log_process,read_log,tail_log) into a dedicated module to reducemain.pysize.
- 🐛 Memory leak — unbounded process log growth — JSONL log files for background processes now rotate when they exceed a configurable size limit (
OPEN_TERMINAL_MAX_LOG_SIZE, default 50 MB). When the limit is reached, the oldest half of the file is discarded and writing continues, so the most recent output is always available. Previously, a long-running process could grow its log file without limit, and_read_log()loaded the entire file into memory on every status poll — causing the container to consume all available host RAM (~26 GB) and trigger the OOM killer. (#52)
- 🔒 Multi-user search isolation —
glob_search,grep_search,listdir, andwalknow filter out entries belonging to other users' home directories during traversal. Previously, searching a parent directory like/homewould expose all users' files. Addedis_path_allowed()toUserFSfor per-entry validation duringos.walk. (#46)
- 🐛 Multi-user relative path resolution — relative paths (e.g.
abcdef.txt,.) now resolve against the provisioned user's home directory instead of the server process's/home/user. All file, search, and execute endpoints use the newUserFS.resolve_path()method. (#47) - 🔄 Auto-swap
/home/userpaths — in multi-user mode, absolute paths under/home/userare automatically rewritten to the provisioned user's home directory, handling LLMs that hardcode the default path from the system description.
- ℹ️ 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.