Skip to content

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)

make build-macos   # builds the frontend, then dist/terminux.app
open dist/terminux.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 universal2 binary 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

  1. Identify the TCC-responsible parent of the shells. Under pywebview it's typically python3.12 from your .venv/bin/, or uv if you launched via uv run terminux, or dist/terminux.app/Contents/MacOS/terminux if you ran the packaged bundle. From a working terminal (e.g. iTerm2):
    ps -o pid,ppid,command -p $(pgrep -f terminux)
    
  2. 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).
  3. Add the executable identified in step 1.
  4. 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.