A downstream wrapper image around ghcr.io/open-webui/open-terminal that turns the upstream "remote terminal API" into a batteries-included DevOps / SRE / writing workbench for AI agents.
The upstream image gives an agent a sandboxed shell with a REST API. This image keeps everything upstream provides and layers on the tools, configuration, and side-services we actually use day to day — so a fresh container is immediately useful for managing Kubernetes clusters, opening GitHub PRs, running infrastructure-as-code, and acting as a writable backend for the fiction-writing pipeline.
Baked into the image (single consolidated apt layer plus a few binary installs):
- Kubernetes:
kubectl(v1.34 channel),helm,argocd - GitHub / git:
ghCLI, plus the upstreamgit - Infrastructure:
terraform,ansible,act(run GitHub Actions locally) - Data / files:
yq,jq,pandoc,sqlite3,redis-tools,postgresql-client - Productivity:
ripgrep,fd-find,bat,tmux,tree,htop,httpie - Archives / transfer:
pigz,unar,rsync,zip,unzip,diffutils - Crypto:
gnupg2
A final apt-get upgrade is run on top of the upstream base so security patches travel with each rebuild.
/etc/skel/.kube/config ships a context (in-cluster / user open-terminal) that points at kubernetes.default.svc and reads the pod's serviceaccount token from /var/run/secrets/kubernetes.io/serviceaccount/. When the container runs in a Kubernetes pod with a serviceaccount mounted, kubectl works with no extra setup. New users provisioned by the upstream multi-user mode inherit this via skel.
entrypoint.sh extends upstream behaviour with:
- Docker-secrets style env vars — any
<VAR>_FILEis resolved into<VAR>(matching the official PostgreSQL image convention). Currently applied toOPEN_TERMINAL_API_KEY, so you can mount the API key as a file instead of passing it on the command line. - Home-directory ownership repair —
chowns/home/userback touserwhen a bind-mounted volume comes in owned by someone else. - Dotfile seeding for empty bind mounts — copies
/etc/skel/.bashrc,.profile, and.kube/into a freshly mounted home so the shell is usable immediately. Docker doesn't populate bind-mounts from the image, this fills that gap. - Shell helpers appended to the seeded
.bashrc:GIT_PAGER=cat,GIT_CONFIG_GLOBAL=/dev/null,LESS=-RXFfor non-interactive friendly outputGH_TOKENexported into the shell soghis authenticated out of the boxverify_pr <branch>— list open PRs for a branchverify_push <branch>— confirm a branch reached the remotesetup_git_auth <token>— rewrite theoriginURL with anx-access-tokencredential
- Docker socket group fixup — when
/var/run/docker.sockis mounted, the entrypoint discovers the socket's GID, creates a matching group if needed, addsuserto it, and re-execs throughsgso the new group membership is live without a re-login. - Runtime package install hooks — preserves and respects the upstream
OPEN_TERMINAL_PACKAGES(apt) andOPEN_TERMINAL_PIP_PACKAGES(pip), and addsOPEN_TERMINAL_NPM_PACKAGES(npm). Multi-user mode installs pip/npm globally viasudoso every provisioned user shares them. - Network egress firewall — passes through the upstream
OPEN_TERMINAL_ALLOWED_DOMAINSmechanism (DNS whitelist via dnsmasq + iptables +ipset, thenCAP_NET_ADMINis dropped viacapsh). Behaviour:- unset → full egress
- empty string → block all outbound
- comma list → only those domains (and subdomains) resolve
- Bible bridge launch — starts
helpers/bible_bridge.pyin the background on${BRIDGE_PORT:-8765}(see below). It is started before the egress firewall dropsCAP_NET_ADMINso it can bind its port.
On startup, everything in /app/helpers/ is copied into the user's home so it is reachable from interactive shells and from the agent.
-
helpers/create-pr.sh — opinionated five-step PR workflow (
branch → add → commit → push → gh pr create) with colorized progress and an open-PR verification step at the end. Usage:~/create-pr.sh <branch> <commit-msg> <pr-title> <pr-body>
-
helpers/bible_bridge.py — small
http.server-based HTTP bridge that exposes one or more git-cloned "story bible" projects underBIBLE_ROOT(default/home/u3aa02715/fiction). It exists so the Fiction Writing Filter running in a different (pipelines) pod can read and write bible files across namespace boundaries without sharing a volume. Endpoints:Method Path Purpose GET/healthbridge status + list of detected project repos GET/versionbridge version (currently 1.1)GET/projectslist project names under BIBLE_ROOTGET/bible?project=…&task_type=…read all bible/*.md(andfragments/*.mdforFRAGMENT/BIBLE_UPDATEtasks)POST/bible/writewrite/append a file, then git add/commit/pushPOST/bible/pullgit pull --ff-onlyon a projectPOST/bible/syncgit add -A+ commit + push everything dirtyPOST/bible/prbranch + commit + push + gh pr createPath traversal into other directories is blocked via
realpathcontainment underBIBLE_ROOT. OptionalBRIDGE_TOKENenablesAuthorization: Bearer …checks. Configurable withBIBLE_ROOT,BRIDGE_PORT,BRIDGE_TOKEN,GIT_REMOTE,GIT_BRANCH.The bridge is dormant if
BIBLE_ROOTis empty — it just lists no projects — so the image is harmless to run for non-fiction workloads.
docker build -t open-terminal-docker .
docker run -d --name open-terminal \
-p 8000:8000 \
-v open-terminal:/home/user \
-e OPEN_TERMINAL_API_KEY=your-secret-key \
open-terminal-dockerThe wrapper inherits the upstream CMD ["run"] and ENTRYPOINT chain (now tini → entrypoint.sh → open-terminal), so all upstream CLI flags and environment variables continue to work — see the upstream README for the full configuration surface (config files, multi-user mode, MCP server, etc.).
| Variable | Effect |
|---|---|
OPEN_TERMINAL_API_KEY_FILE |
Read the API key from a file (Docker/Kubernetes secret friendly) |
OPEN_TERMINAL_NPM_PACKAGES |
Space-separated npm packages to install at startup |
GH_TOKEN |
Exported into the shell so gh is authenticated |
BIBLE_ROOT |
Parent directory of bible-bridge git repos (default /home/u3aa02715/fiction) |
BRIDGE_PORT |
Port the bible bridge listens on (default 8765) |
BRIDGE_TOKEN |
Optional bearer token for the bible bridge |
GIT_REMOTE / GIT_BRANCH |
Defaults used by bible-bridge git operations (origin / main) |
Plus everything upstream exposes: OPEN_TERMINAL_PACKAGES, OPEN_TERMINAL_PIP_PACKAGES, OPEN_TERMINAL_MULTI_USER, OPEN_TERMINAL_ALLOWED_DOMAINS, etc.
Dockerfile # builds open_terminal from dvystrcil/open-terminal-app-fork + tooling
entrypoint.sh # secrets resolution, dotfile seeding, helpers, egress, bridge
helpers/
bible_bridge.py # multi-project Story Bible HTTP bridge
create-pr.sh # five-step PR workflow
CONTAINER_TEST_PLAN.md
This repo has no vendored copy of
open_terminal's Python source. An earlier attempt at that (checked-in, tested, and documented in this CHANGELOG as if deployed) was never actually built into the image -- the Dockerfile just pulled a pre-built upstream image the whole time. See homelab#822.The fix: this repo now genuinely builds
open_terminalfrom source, via a real fork,dvystrcil/open-terminal-app-fork-- Dockerfile stage 1 doesgit clone+pip install .against it, mirroring upstream's own build. The fork carries a handful of fixes submitted upstream (open-webui/open-terminal#148, #149, #150, #151) plus one homelab-specific patch (GH_TOKEN refresh, not upstream-appropriate). This is meant to be temporary: once those PRs merge into a real upstream release, switch stage 1 back to a plainFROM .../open-terminal:latestand the fork goes away.If you need to change
open_terminal's own behavior (not just this wrapper's tooling), make the change in the fork, not here.
MIT — see LICENSE.