openwebui-open-terminal/open_terminal/env.py
Timothy Jaeryang Baek 7738e7032a
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
fix: enforce env/_FILE mutual exclusivity for empty values
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

154 lines
4.3 KiB
Python

import os
from open_terminal import config
def _resolve_file_env(var: str, default: str = "") -> str:
"""Resolve an environment variable with Docker-secrets ``_FILE`` support.
If ``<var>_FILE`` is set, its value is treated as a path whose contents
supply the variable's value (trailing whitespace is stripped). Setting
*both* ``<var>`` and ``<var>_FILE`` is an error.
This follows the convention established by the official PostgreSQL Docker
image (see https://hub.docker.com/_/postgres#docker-secrets).
"""
value = os.environ.get(var)
file_path = os.environ.get(f"{var}_FILE")
if value is not None and file_path is not None:
raise ValueError(
f"Both {var} and {var}_FILE are set, but they are mutually exclusive."
)
if file_path:
with open(file_path) as f:
return f.read().strip()
return value if value is not None else default
API_KEY = _resolve_file_env("OPEN_TERMINAL_API_KEY", config.get("api_key", ""))
CORS_ALLOWED_ORIGINS = os.environ.get(
"OPEN_TERMINAL_CORS_ALLOWED_ORIGINS",
config.get("cors_allowed_origins", "*"),
)
LOG_DIR = os.environ.get(
"OPEN_TERMINAL_LOG_DIR",
config.get(
"log_dir",
os.path.join(
os.environ.get(
"XDG_STATE_HOME",
os.path.join(os.path.expanduser("~"), ".local", "state"),
),
"open-terminal",
"logs",
),
),
)
# Comma-separated mime type prefixes for binary files that read_file will return
# as raw binary responses (e.g. "image,audio" or "image/png,image/jpeg").
BINARY_FILE_MIME_PREFIXES = [
p.strip()
for p in os.environ.get(
"OPEN_TERMINAL_BINARY_MIME_PREFIXES",
config.get("binary_mime_prefixes", "image"),
).split(",")
if p.strip()
]
MAX_TERMINAL_SESSIONS = int(
os.environ.get(
"OPEN_TERMINAL_MAX_SESSIONS",
config.get("max_terminal_sessions", "16"),
)
)
ENABLE_TERMINAL = os.environ.get(
"OPEN_TERMINAL_ENABLE_TERMINAL",
str(config.get("enable_terminal", True)),
).lower() not in ("false", "0", "no")
TERMINAL_TERM = os.environ.get(
"OPEN_TERMINAL_TERM",
config.get("term", "xterm-256color"),
)
EXECUTE_TIMEOUT: float | None = None
_execute_timeout = os.environ.get(
"OPEN_TERMINAL_EXECUTE_TIMEOUT",
config.get("execute_timeout"),
)
if _execute_timeout is not None:
EXECUTE_TIMEOUT = float(_execute_timeout)
EXECUTE_DESCRIPTION = os.environ.get(
"OPEN_TERMINAL_EXECUTE_DESCRIPTION",
config.get("execute_description", ""),
)
# Maximum size (in bytes) for per-process JSONL log files.
# Once exceeded, logging stops for that process (the process keeps running).
MAX_PROCESS_LOG_SIZE = int(
os.environ.get(
"OPEN_TERMINAL_MAX_LOG_SIZE",
config.get("max_log_size", 50_000_000), # 50 MB
)
)
# How long (in seconds) to keep finished-process log files on disk.
# After this period, _cleanup_expired() will delete the log file.
PROCESS_LOG_RETENTION: float = float(
os.environ.get(
"OPEN_TERMINAL_LOG_RETENTION",
config.get("log_retention", 604_800), # 7 days
)
)
# Minimum interval (in seconds) between log flushes during command execution.
# 0 (default) = flush after every chunk (current behaviour).
# Setting this to e.g. 1.0 reduces I/O pressure on high-output commands.
LOG_FLUSH_INTERVAL: float = float(
os.environ.get(
"OPEN_TERMINAL_LOG_FLUSH_INTERVAL",
config.get("log_flush_interval", 0),
)
)
# Maximum unflushed buffer (in bytes) before a flush is forced.
# Only relevant when LOG_FLUSH_INTERVAL > 0. 0 = no buffer limit.
LOG_FLUSH_BUFFER: int = int(
os.environ.get(
"OPEN_TERMINAL_LOG_FLUSH_BUFFER",
config.get("log_flush_buffer", 0),
)
)
ENABLE_NOTEBOOKS = os.environ.get(
"OPEN_TERMINAL_ENABLE_NOTEBOOKS",
str(config.get("enable_notebooks", True)),
).lower() not in ("false", "0", "no")
MULTI_USER = os.environ.get(
"OPEN_TERMINAL_MULTI_USER",
str(config.get("multi_user", False)),
).lower() not in ("false", "0", "no", "")
USER_PREFIX = os.environ.get(
"OPEN_TERMINAL_USER_PREFIX",
config.get("user_prefix", ""),
)
UVICORN_LOOP = os.environ.get(
"OPEN_TERMINAL_UVICORN_LOOP",
config.get("uvicorn_loop", "auto"),
)
OPEN_TERMINAL_INFO = os.environ.get(
"OPEN_TERMINAL_INFO",
config.get("info", ""),
)