Commit graph

66 commits

Author SHA1 Message Date
Timothy Jaeryang Baek
9162e808c3 feat: add /files/serve/{path} endpoint for iframe relative URL resolution
Some checks failed
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
2026-04-08 14:49:49 -07:00
Timothy Jaeryang Baek
8c154351df fix: default grep_search to regex mode
Closes #101
2026-04-08 13:03:47 -07:00
Timothy Jaeryang Baek
96d8134e1e feat: resolve relative paths against session cwd in all file endpoints
Some checks failed
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
All LLM-exposed file endpoints (list_files, read_file, write_file,
display_file, replace_file_content, grep_search, glob_search) now
read the X-Session-Id header and resolve relative paths against the
session's working directory instead of always using fs.home.

- Added optional cwd parameter to UserFS.resolve_path()
- Relative paths join against cwd if provided, else fs.home
- Absolute paths are unaffected
- execute endpoint also resolves relative cwd param against session cwd
2026-03-29 21:34:44 -05:00
Timothy Jaeryang Baek
fbaf2e8e69 feat: per-session working directory tracking via X-Session-Id header
Replace the process-global os.chdir() with an in-memory dictionary
keyed by session ID (passed via X-Session-Id header). Multiple
concurrent chat sessions now maintain independent working directories.

- GET/POST /files/cwd read X-Session-Id to resolve per-session cwd
- POST /execute falls back to session cwd when no cwd param provided
- POST /api/terminals spawns PTY in the session's cwd
- 7-day sliding TTL for session entries
- Fully backward compatible: no header = fs.home default
2026-03-29 20:59:47 -05:00
Timothy Jaeryang Baek
004ee891e6 security: harden API key handling and add startup warnings
Some checks failed
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
- Increase auto-generated API key entropy from 192-bit to 384-bit
- Use hmac.compare_digest() for constant-time key comparison (HTTP + WS)
- Refuse to start without OPEN_TERMINAL_API_KEY configured
- Add prominent CORS wildcard warning at startup
- Bump version to 0.11.30
2026-03-25 17:15:15 -05:00
Timothy Jaeryang Baek
952c3f1314 fix: set group-write permission on files after chown in multi-user mode
Some checks are pending
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Blocked by required conditions
Release / release (push) Waiting to run
_chown() transferred file ownership to the provisioned user but didn't
set group-write permission, leaving files at 644. The server process
(a group member) could create new files but couldn't overwrite them on
subsequent saves, returning PermissionError. Now sets chmod g+w after
chown, matching the 2770 treatment already applied to directories.

Fixes #93
2026-03-24 16:53:28 -05:00
Timothy Jaeryang Baek
b18c15123b feat: ZIP archive download endpoint (POST /files/archive)
Some checks are pending
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Blocked by required conditions
Release / release (push) Waiting to run
Add a single POST /files/archive endpoint that bundles one or more
files and directories into a ZIP archive for download.

- Accepts a list of paths (files and/or directories)
- Directories are recursively included with relative paths preserved
- Multi-user access control enforced via UserFS
- Cross-platform ZIP format (Windows, macOS, Linux)
- Archive name derived from input (single item = item name, multiple = download.zip)

Closes #92
2026-03-24 05:38:30 -05:00
Timothy Jaeryang Baek
d5c9fc48e7 feat: add GET /system endpoint for LLM system prompt injection
Some checks are pending
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Blocked by required conditions
Release / release (push) Waiting to run
- GET /system returns a structured system prompt grounding the LLM in
  the environment (OS, hostname, user, shell, Python version) with
  directives for tool usage
- Gated by OPEN_TERMINAL_ENABLE_SYSTEM_PROMPT env var (default: true)
- Advertised via features.system in GET /api/config for consumer discovery
- OPEN_TERMINAL_SYSTEM_PROMPT env var for full prompt override
- Endpoint excluded from OpenAPI schema, auth-protected
2026-03-22 22:11:08 -05:00
Timothy Jaeryang Baek
a5e44c44a7 refactor: extract document parsers into utils/documents.py
Some checks failed
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Has been cancelled
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Has been cancelled
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Has been cancelled
Moved all document text extraction logic from read_file() into a
dedicated utils/documents.py module with an EXTRACTORS registry.
The read_file endpoint now iterates the registry in a 15-line loop
instead of inlining ~330 lines of per-format parsing.

Adding a new format requires only a single extract_* function and
a one-line registry entry. No behaviour changes.
2026-03-20 17:23:59 -05:00
Timothy Jaeryang Baek
cf3bb5c809 feat: add .rtf, .xls, .odt, .ods, .odp, .epub, .eml text extraction in read_file
Extended document text extraction to seven more formats:

- .rtf: strips formatting to plain text (striprtf, BSD)
- .xls: legacy Excel, renders sheets as tab-separated values (xlrd, BSD)
- .odt: OpenDocument text, extracts paragraphs (stdlib + lxml, BSD)
- .ods: OpenDocument spreadsheet, extracts all sheets (stdlib + lxml, BSD)
- .odp: OpenDocument presentation, extracts slide text (stdlib + lxml, BSD)
- .epub: extracts body text in spine reading order (stdlib + lxml, BSD)
- .eml: extracts headers and body with HTML stripping (stdlib + lxml, BSD)

All support start_line/end_line range selection.
2026-03-20 17:19:17 -05:00
Timothy Jaeryang Baek
2f786fcd92 feat: add .docx, .xlsx, .pptx text extraction in read_file
Office documents are now automatically converted to text and returned
in the standard JSON format, making them readable by LLMs.

- .docx: extracts paragraphs and table contents (python-docx, MIT)
- .xlsx: renders all sheets as tab-separated values (openpyxl, MIT)
- .pptx: extracts text from all slides (python-pptx, MIT)

Supports start_line/end_line range selection, consistent with the
existing PDF handler.
2026-03-20 17:13:10 -05:00
Timothy Jaeryang Baek
7738e7032a fix: enforce env/_FILE mutual exclusivity for empty values
Some checks failed
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Failing after 45s
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Failing after 52s
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim short_tag:slim suffix:-slim]) (push) Failing after 42s
Build Docker Image / merge (map[name:default short_tag:latest suffix:]) (push) Has been skipped
Build Docker Image / merge (map[name:slim short_tag:slim suffix:-slim]) (push) Has been skipped
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Failing after 1m5s
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine short_tag:alpine suffix:-alpine]) (push) Failing after 42s
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default short_tag:latest suffix:]) (push) Failing after 48s
Release / release (push) Failing after 12s
Build Docker Image / merge (map[name:alpine short_tag:alpine suffix:-alpine]) (push) Has been skipped
The _FILE mutual-exclusivity guard silently passed when the plain env
var was set to an empty string, because empty strings are falsy.

Python: changed `if value and file_path` to
`if value is not None and file_path is not None`, and the fallback
return from `value or default` to
`value if value is not None else default`.

entrypoint.sh: changed ${!var:-} to ${!var+set} to test whether the
variable is defined at all, not just non-empty.

entrypoint-slim.sh: changed -n tests to ${var+set} via eval, matching
the bash entrypoint semantics.
2026-03-19 17:53:56 -05:00
Timothy Jaeryang Baek
40e1225d53 fix: /ports returns 500 in multi-user mode on restricted runtimes (#80)
list_ports triggered full user provisioning (useradd) just to filter
ports by UID. On runtimes that reject useradd (e.g. Azure Container
Apps), this crashed with an unhandled CalledProcessError.

Now catches provisioning failures and returns an empty port list —
an unprovisioned user has no listening ports to show.
2026-03-19 17:51:24 -05:00
Timothy Jaeryang Baek
2da589100c fix: port detection broken since v0.11.2 (#85, #63)
setcap cap_setgid+ep on the system Python binary made all Python
processes non-dumpable, blocking /proc/[pid]/fd/ access needed by
_pid_from_inode() to resolve socket inodes to PIDs.

Fix: copy the Python binary to python3-ot and setcap only the copy.
The open-terminal server uses python3-ot (has CAP_SETGID for
multi-user os.setgroups()), while user-spawned python3 stays
capability-free and dumpable.

Slim/Alpine: removed setcap entirely (multi-user mode requires sudo,
which only the full image has). Kept libcap packages installed.

README: corrected Image Variants table — multi-user mode is
full-image only.
2026-03-19 17:42:17 -05:00
Timothy Jaeryang Baek
5a79b898b5 fix: multi-user mode works when running as root (#60)
ensure_os_user() no longer unconditionally requires sudo; when the
process is already root (e.g. user: '0:0' in Docker Compose), user
provisioning commands run directly. check_environment() now only
requires sudo when not running as root.
2026-03-15 18:14:55 -05:00
Timothy Jaeryang Baek
4ab451d27b fix: write_file permission denied in multi-user subdirectories (#70)
Some checks are pending
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default suffix:]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default suffix:]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / merge (map[name:alpine suffix:-alpine]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:default suffix:]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:slim suffix:-slim]) (push) Blocked by required conditions
Release / release (push) Waiting to run
Directories created by run_command (as the provisioned user via sudo -u)
get default 755 permissions, leaving the server process without group-write.
Both makedirs (creating subdirs) and aiofiles.open (creating files) fail
with EPERM.

UserFS._ensure_parents now:
- Creates directories as the provisioned user (sudo -u mkdir -p) to bypass
  755 restrictions on existing parent directories
- Sets 2770 (setgid + group rwx) on all intermediate directories up to the
  user's home, matching the home directory's permissions
- Falls through to plain makedirs in single-user mode (no behavior change)

Closes #70
2026-03-15 16:40:46 -05:00
Timothy Jaeryang Baek
674cf047fc feat: configurable log flush strategy (#65)
Add OPEN_TERMINAL_LOG_FLUSH_INTERVAL and OPEN_TERMINAL_LOG_FLUSH_BUFFER
env vars to control how frequently process output is flushed to disk.
Default 0 preserves existing per-chunk flush behaviour.

Setting FLUSH_INTERVAL=1 reduces fsyncs from ~250/sec to ~1/sec for
high-output commands, preventing I/O storms that can make ARM/eMMC
systems unresponsive.

- Centralize flush control in BoundedLogWriter
- Remove per-chunk flush() from PtyRunner, PipeRunner, WinPtyRunner
- Add explicit final flush before writing process end marker
- Bump version to 0.11.18
2026-03-15 16:24:23 -05:00
Timothy Jaeryang Baek
39ff40ccb8 fix: install pip packages globally in multi-user mode
When OPEN_TERMINAL_MULTI_USER=true, OPEN_TERMINAL_PIP_PACKAGES now uses
sudo pip install to install to the system-wide site-packages directory
instead of /home/user/.local/. This ensures all provisioned users share
the same packages without needing to reinstall individually.

Closes #68
2026-03-14 19:18:01 -05:00
Timothy Jaeryang Baek
83eb8218f2 remove unused url parameter from /files/upload
Some checks are pending
Build Docker Image / merge (map[name:default suffix:]) (push) Blocked by required conditions
Build Docker Image / merge (map[name:slim suffix:-slim]) (push) Blocked by required conditions
Build Docker Image / build (linux/amd64, map[file:Dockerfile name:default suffix:]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.alpine name:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/amd64, map[file:Dockerfile.slim name:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile name:default suffix:]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.alpine name:alpine suffix:-alpine]) (push) Waiting to run
Build Docker Image / build (linux/arm64, map[file:Dockerfile.slim name:slim suffix:-slim]) (push) Waiting to run
Build Docker Image / merge (map[name:alpine suffix:-alpine]) (push) Blocked by required conditions
Release / release (push) Waiting to run
The experimental url query parameter was never used by any consumer.
Open WebUI uses multipart file uploads exclusively. The endpoint now
only accepts direct file uploads, and the file parameter is required
instead of optional.
2026-03-13 21:15:02 -05:00
Timothy Jaeryang Baek
4e6e2739ce feat: redesign startup output with URLs, bind warning
- Display Local and Network URLs at startup (Vite-style)
- Auto-detect LAN IP when binding to 0.0.0.0
- Show generated API key inline with bold label
- Print yellow warning when listening on all interfaces
- Bump to 0.11.15
2026-03-13 17:43:16 -05:00
Timothy Jaeryang Baek
abbface95a fix: remove misleading user from system info in multi-user mode, catch /home/usr hallucination
- get_system_info() no longer includes 'as user user' when MULTI_USER is
  active, removing the hint that caused smaller LLMs to hardcode /home/user
- resolve_path() now rewrites /home/usr → actual user home, matching the
  existing /home/user rewrite

Closes #57
2026-03-13 17:21:05 -05:00
Timothy Jaeryang Baek
68b9380c94 fix: recursive chown on user recreation (UID mismatch) (#62) 2026-03-13 14:57:33 -05:00
Timothy Jaeryang Baek
9398d3028f feat: network egress filtering via dnsmasq + ipset + iptables + capsh (Docker only)
Some checks failed
Build Docker Image / build (linux/amd64) (push) Has been cancelled
Build Docker Image / build (linux/arm64) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (push) Has been cancelled
Add OPEN_TERMINAL_ALLOWED_DOMAINS env var for domain-level network
restriction. Uses dnsmasq as a local DNS resolver (NXDOMAIN for
non-whitelisted domains), ipset for dynamic IP tracking (dnsmasq
auto-populates resolved IPs), iptables to block external DNS and
non-whitelisted IPs, and capsh to permanently drop CAP_NET_ADMIN.

Supports wildcards (*.github.com), live DNS, fails closed.
Set to empty string to block all; omit for full access.
Gracefully skips on bare metal if iptables is unavailable.

Bump version 0.11.11 → 0.11.12
2026-03-12 16:17:34 -05:00
Timothy Jaeryang Baek
371a94cc45 fix: harden /files/upload path handling for multi-user isolation
Some checks are pending
Build Docker Image / build (linux/amd64) (push) Waiting to run
Build Docker Image / build (linux/arm64) (push) Waiting to run
Build Docker Image / merge (push) Blocked by required conditions
Release / release (push) Waiting to run
- Resolve directory parameter through fs.resolve_path()
- Sanitize uploaded filename with os.path.basename()
- Normalize composed path with os.path.normpath()
- Catch PermissionError and return 403 for cross-user access attempts
2026-03-11 16:49:32 -05:00
Timothy Jaeryang Baek
f4277c3210 fix: bounded process log rotation and timestamp-sortable IDs (#52, #54)
Co-Authored-By: wjss <a123edcrfv@live.cn>
2026-03-11 16:37:09 -05:00
Timothy Jaeryang Baek
fd5c5deef9 fix: bounded process log rotation and timestamp-sortable IDs (#52, #54)
- Add log rotation: when a process log exceeds MAX_PROCESS_LOG_SIZE (default
  50 MB), the oldest half is discarded and writing continues so the most
  recent output is always available. Prevents unbounded memory growth that
  caused ~26 GB OOM kills.

- Add tail-optimized log reads: _read_log with tail parameter now reads from
  the end of the file instead of loading the entire file into memory.

- Add OPEN_TERMINAL_MAX_LOG_SIZE (default 50 MB) and
  OPEN_TERMINAL_LOG_RETENTION (default 7 days) configuration.

- Use timestamp-prefixed process IDs (YYYYMMDD-HHMMSS-<random>) so log files
  sort chronologically in the filesystem.

- Extract log management into utils/log.py to reduce main.py size.
2026-03-11 16:22:46 -05:00
Timothy Jaeryang Baek
34054e5052 fix: multi-user search isolation for glob_search, grep_search, listdir, walk (#46)
Added is_path_allowed() to UserFS and wired it into all directory
traversal code paths. In multi-user mode, entries belonging to other
users' home directories are now filtered out during os.walk/os.listdir,
preventing file listing leaks when searching parent directories like
/home. Single-user mode is unaffected (early return on username=None).
2026-03-11 15:16:05 -05:00
Timothy Jaeryang Baek
eaf7cb7812 fix: resolve relative paths against user home in multi-user mode (#47)
- Add resolve_path() to UserFS that resolves relative paths against
  self.home instead of os.getcwd()
- Replace all os.path.abspath() calls in route handlers with
  fs.resolve_path()
- Auto-swap /home/user → provisioned user home for hardcoded paths
- Add fs dependency to grep_search and glob_search endpoints
2026-03-11 15:09:32 -05:00
Timothy Jaeryang Baek
9006401538 feat: conditional /info endpoint via OPEN_TERMINAL_INFO env var
Some checks are pending
Build Docker Image / build (linux/amd64) (push) Waiting to run
Build Docker Image / build (linux/arm64) (push) Waiting to run
Build Docker Image / merge (push) Blocked by required conditions
Release / release (push) Waiting to run
2026-03-10 17:13:36 -05:00
Timothy Jaeryang Baek
cead109816 fix: terminal PTY warnings, stale home ownership, docs update
Some checks are pending
Build Docker Image / build (linux/amd64) (push) Waiting to run
Build Docker Image / build (linux/arm64) (push) Waiting to run
Build Docker Image / merge (push) Blocked by required conditions
Release / release (push) Waiting to run
- Wrap multi-user terminals with 'script -qc' for proper PTY
- chown home dir after useradd for stale ownership from prior runs
- README: accurate multi-user description with production warning
- Bump version to 0.11.5
2026-03-09 18:41:15 -05:00
Timothy Jaeryang Baek
3fd9d3e1ac chore: bump version to 0.11.4, update changelog 2026-03-09 18:26:40 -05:00
Timothy Jaeryang Baek
a12ba2cd3d chore: bump version to 0.11.3, update changelog 2026-03-09 18:21:52 -05:00
Timothy Jaeryang Baek
bf1e3e7d6b chore: bump version to 0.11.2, update changelog 2026-03-09 18:07:18 -05:00
Timothy Jaeryang Baek
b503b97c19 chore: bump version to 0.11.1, update changelog 2026-03-09 17:56:54 -05:00
Timothy Jaeryang Baek
7a629e1feb feat: multi-user mode via OPEN_TERMINAL_MULTI_USER
When enabled, reads the X-User-Id header (set by the OWUI proxy),
provisions a per-user Linux account on first access (useradd -m),
and runs all commands, file operations, and terminal sessions as
that user via sudo -u. chmod 700 on home directories provides
kernel-enforced isolation between users.

New environment variables:
- OPEN_TERMINAL_MULTI_USER (default: false)
- OPEN_TERMINAL_USER_PREFIX (default: empty)
- OPEN_TERMINAL_UVICORN_LOOP (default: auto)

Closes #38
2026-03-09 17:26:27 -05:00
Timothy Jaeryang Baek
553db84542 feat: bundle Docker CLI, Compose, and Buildx in container image
Install Docker CLI, Compose, and Buildx via get.docker.com so agents can
build images and run containers when the host Docker socket is mounted.
The entrypoint automatically fixes socket group permissions.

Closes #19
2026-03-06 22:01:14 -06:00
Timothy Jaeryang Baek
d70da587ae fix: enforce UTF-8 encoding for all text file I/O on Windows (#21)
On Windows, Python defaults to the system locale encoding (e.g. GB2312
on Chinese systems). This caused files written via open-terminal to be
saved in the wrong encoding, breaking tool-call chaining with non-ASCII
content.

Added explicit encoding="utf-8" to all 9 text-mode open() calls in
main.py and notebooks.py.
2026-03-06 14:32:47 -06:00
Timothy Jaeryang Baek
79ba67cf00 feat: add notebook execution endpoints (/notebooks)
Some checks are pending
Build Docker Image / merge (push) Blocked by required conditions
Build Docker Image / build (linux/amd64) (push) Waiting to run
Build Docker Image / build (linux/arm64) (push) Waiting to run
Release / release (push) Waiting to run
Multi-session Jupyter notebook execution via REST endpoints.
Each session gets its own kernel via nbclient.
Supports per-cell execution with rich outputs (images, HTML, LaTeX).

- POST /notebooks — create session (starts kernel)
- POST /notebooks/{id}/execute — execute cell
- GET /notebooks/{id} — session status
- DELETE /notebooks/{id} — stop kernel

nbclient and ipykernel are now core dependencies.
OPEN_TERMINAL_ENABLE_NOTEBOOKS env var to enable/disable.

Bump version 0.9.3 → 0.10.0
2026-03-05 15:51:41 -06:00
Timothy Jaeryang Baek
59bfc4ff5e feat: add notebooks optional extra (nbclient + ipykernel)
pip install open-terminal[notebooks] adds nbclient and ipykernel
for Jupyter notebook per-cell execution with full rich output support.

Bump version 0.9.2 → 0.9.3
2026-03-05 15:27:18 -06:00
Timothy Jaeryang Baek
b37842f693 feat: custom execute description env var (0.9.2) 2026-03-05 15:23:06 -06:00
Timothy Jaeryang Baek
fb2870fab1 feat: startup package installation via env vars (0.9.1) 2026-03-05 15:08:37 -06:00
Timothy Jaeryang Baek
ef63677087 feat: port detection and reverse proxy (0.9.0)
Some checks failed
Build Docker Image / build (linux/amd64) (push) Has been cancelled
Build Docker Image / build (linux/arm64) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (push) Has been cancelled
- GET /ports discovers listening TCP ports scoped to descendant processes
- /proxy/{port}/{path} reverse-proxies HTTP to localhost:{port}
- Cross-platform: /proc/net/tcp (Linux), lsof (macOS), netstat (Windows)
- New utils/port.py module with detect_listening_ports and get_descendant_pids
- Zero new dependencies
2026-03-04 14:59:59 -06:00
Timothy Jaeryang Baek
87b0dcfba5 feat: configurable TERM env var for terminal color support
Some checks failed
Build Docker Image / build (linux/amd64) (push) Has been cancelled
Build Docker Image / build (linux/arm64) (push) Has been cancelled
Release / release (push) Has been cancelled
Build Docker Image / merge (push) Has been cancelled
Terminal sessions now set the TERM environment variable (default
xterm-256color) so programs emit ANSI color codes. Configurable via
OPEN_TERMINAL_TERM environment variable or 'term' in config.toml.
2026-03-02 19:17:16 -06:00
Timothy Jaeryang Baek
4d896916bd feat: configurable terminal feature with server-side discovery
- Add OPEN_TERMINAL_ENABLE_TERMINAL env var / enable_terminal config
  option to enable/disable the interactive terminal (default: true)
- Add GET /api/config endpoint for client-side feature discovery
- When disabled, all /api/terminals routes and WebSocket endpoint
  are not mounted
- Bump version to 0.8.1
2026-03-02 16:29:41 -06:00
Timothy Jaeryang Baek
035a02629e feat: add Windows PTY support via pywinpty (0.8.0)
Some checks are pending
Build Docker Image / build (linux/amd64) (push) Waiting to run
Build Docker Image / build (linux/arm64) (push) Waiting to run
Build Docker Image / merge (push) Blocked by required conditions
Release / release (push) Waiting to run
- Add WinPtyRunner class using winpty.PtyProcess for full PTY on Windows
- Refactor terminal session endpoints to support both Unix PTY and pywinpty
- Auto-install pywinpty on Windows via sys_platform marker
- Update create_runner factory: Unix PTY → WinPTY → pipe fallback
- Bump version to 0.8.0
2026-03-02 16:05:46 -06:00
Timothy Jaeryang Baek
fa224bcadd refac 2026-03-02 15:48:12 -06:00
Timothy Jaeryang Baek
840f538d79 fix: PTY device exhaustion and session limit (0.7.2)
- Close leaked PTY file descriptors when subprocess creation fails
- Return 503 with descriptive message when PTY devices are exhausted
- Add MAX_TERMINAL_SESSIONS (default 16, env OPEN_TERMINAL_MAX_SESSIONS)
- Prune dead sessions before checking the session limit
- Bump version to 0.7.2
2026-03-02 15:43:10 -06:00
Timothy Jaeryang Baek
8ea7499911 fix: use bash as default shell in Docker container
Set /bin/bash as the login shell for the container user and export
SHELL=/bin/bash so the terminal PTY handler picks it up. Previously
the user was created with /bin/sh (dash), which does not support
interactive job control, producing 'can't access tty; job control
turned off'.

Bump version to 0.7.1.
2026-03-02 15:20:07 -06:00
Timothy Jaeryang Baek
ee74581eeb feat: interactive terminal sessions API with first-message auth (0.7.0)
Add resource-oriented terminal sessions following the JupyterLab/Kubernetes
pattern with full session lifecycle management:

- POST /api/terminals — create a PTY session
- GET /api/terminals — list active sessions
- GET /api/terminals/{id} — session info
- DELETE /api/terminals/{id} — kill session
- WS /api/terminals/{id} — attach via WebSocket

Authentication uses first-message auth (Socket.IO pattern): client sends
{type: 'auth', token: '...'} as first WebSocket message. No credentials
in URLs. 10 second auth timeout.

Non-blocking PTY I/O using select() with timeouts and O_NONBLOCK.
Sessions auto-cleanup on WebSocket disconnect.
2026-03-02 14:41:24 -06:00
Timothy Jaeryang Baek
f94fa3cec6 feat: add TOML configuration file support
Load settings from /etc/open-terminal/config.toml (system) and
$XDG_CONFIG_HOME/open-terminal/config.toml (user). Supports host, port,
api_key, cors_allowed_origins, log_dir, and binary_mime_prefixes.

CLI flags and environment variables still take precedence. A --config
flag allows pointing to a custom config file.

Closes #11
2026-03-02 12:52:33 -06:00