Packaging & distribution¶
terminux bundles into a self-contained desktop app that needs neither Python
nor Node to run. Bundles are built with PyInstaller; dist/ and build/ are
gitignored build artifacts.
Linux (via Docker)¶
PyInstaller can't cross-compile, so the Linux bundle is built in a container (Ubuntu 24.04 + GTK3/WebKit2GTK + Python 3.12):
make build-linux # -> dist/linux/terminux/terminux (PyInstaller onedir)
make docker-run # run the same image in web mode on :8000
The host needs libgtk-3 and libwebkit2gtk-4.1 at runtime. Tested on
Ubuntu / Debian / Fedora / Arch derivatives.
Web mode (container-native)¶
make docker-run serves the UI headlessly. Open the
http://127.0.0.1:8000/?t=<token> URL from the container log in a browser.
The session token is the only auth
When bound beyond loopback (--host 0.0.0.0), the per-session token is the
only authentication. Keep the URL private.
Desktop GUI in a container¶
The desktop GUI needs a display. Either run the bundled binary natively on a Linux desktop, or pass X11 through:
docker run --rm -e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix terminux:bundle \
/app/dist/terminux/terminux
The bundle's architecture matches the Docker host. For x86_64 from an arm64
host (or vice versa), build with docker build --platform linux/amd64 …. The
Linux bundle is unsigned.
macOS (.app)¶
The bundle embeds the Python backend, the built web UI, and pywebview's WKWebView backend.
Architecture & signing
- Built for the host architecture (arm64 on Apple Silicon). A
universal2binary needs a universal Python and is not configured. - The app is ad-hoc signed only. On another Mac, Gatekeeper blocks it until right-click → Open, or until it is signed with a Developer ID and notarized (out of scope for the prototype).
Windows¶
There is no standalone Windows bundle yet. Install terminux from PyPI instead (uvx terminux or uv tool install terminux, with Python 3.12 or later); the FAQ covers the Windows specifics.
Troubleshooting on macOS¶
"Operation not permitted" in your shell¶
If shells inside terminux start showing errors like:
/bin/bash: /opt/homebrew/bin/brew: Operation not permitted
sh: /opt/homebrew/opt/nvm/nvm.sh: Operation not permitted
dyld: Library not loaded: /opt/homebrew/opt/pcre2/lib/libpcre2-8.0.dylib
Reason: tried: '…' (file system sandbox blocked open())
…this is macOS TCC (the "Files and Folders" / "Full Disk Access" privacy
controls) blocking the spawned shell from reading files on a path the
parent process isn't allowed to touch: most commonly a removable volume
(an external SSD where Homebrew, nvm, or similar has been relocated).
The phrase file system sandbox blocked open() is the giveaway that
macOS's sandboxd did the blocking; no chmod or ACL produces that message.
Why it can appear mid-session¶
TCC decisions are made at access time, not when the process starts. The
first shells often don't touch the protected location: your prompt, cd,
and basic builtins stay on the boot drive. The block kicks in only when
something later re-sources .profile / .zshrc or a new tab spawns a fresh
shell that dlopen()s a library from the restricted path.
Fix¶
- Identify the TCC-responsible parent of the shells. Under pywebview
it's typically
python3.12from your.venv/bin/, oruvif you launched viauv run terminux, ordist/terminux.app/Contents/MacOS/terminuxif you ran the packaged bundle. From a working terminal (e.g. iTerm2): - Open System Settings → Privacy & Security, then either:
- Files and Folders → Removable Volumes: grant access to the specific external drive (narrower scope), or
- Full Disk Access: add the executable (broader; needed if you rely on protected boot-drive locations too).
- Add the executable identified in step 1.
- Restart terminux. macOS only re-evaluates TCC scope at process start.
If you're running from the .app bundle, grant FDA to the bundle itself;
skip the embedded Python.
Quick check¶
You can confirm the diagnosis without changing settings by spawning a
working terminal (iTerm2 / Terminal.app with FDA) and seeing whether
brew --version succeeds there but fails in terminux. The shell and PATH
are the same; only the TCC scope differs.