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
Runtime requirements on the host: libgtk-3 and libwebkit2gtk-4.1. 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¶
Not yet: the Windows PTY backend is on the roadmap. See the FAQ.
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.