Sign inSign up

benakaben24/web-terminal

By benakaben24

Updated about 9 hours ago

Mobile-first web terminal - a lightweight bastion with zsh and a full DevOps toolchain

Image
Integration & delivery
Developer tools
0

938

benakaben24/web-terminal repository overview

web-terminal

A lightweight bastion you drive from your phone. A root shell in the browser with zsh + oh-my-zsh, a hotkey bar built for touch, IME-safe input, and a full DevOps toolchain preinstalled.

source license

Run it

docker run -d --name web-terminal \
  -p 127.0.0.1:7681:7681 \
  -e WT_PASSWORD='choose-something-strong' \
  -e WT_TZ=Asia/Ho_Chi_Minh \
  -v wt-home:/root \
  benakaben24/web-terminal

Then open http://127.0.0.1:7681. The port is bound to loopback on purpose — put a VPN interface or a reverse proxy in front before reaching it from anywhere else. $HOME lives in the wt-home volume, so shell history and dotfiles survive a rebuild.

Tags

TagWhat it is
latestnewest build of master, manifest list covering both architectures
amd64-latest / arm64-latestpin a single architecture
1.2.3, 1.2published from a v* git tag
sha-<short>one exact commit

docker pull benakaben24/web-terminal resolves the right architecture by itself, on an Ubuntu server and on an Apple Silicon Mac alike. Nothing 32-bit is published.

Why it exists

Administering a server from a phone is normally miserable: no Ctrl, no Esc and no arrow keys; scrolling back does nothing or walks your command history instead; and IME keyboards mangle what you type, because they commit whole words rather than keystrokes. This image owns the whole keyboard path — node-pty + ws on the server, xterm.js driven directly in the browser — instead of wrapping a terminal emulator and inheriting its limits.

  • A usable keyboard. Five groups of on-screen keys with sticky modifiers that also apply to the next key typed on the OS keyboard. Every label and byte sequence is editable in the app.
  • Scrolling that works. A drag is routed to whatever the running program can accept: the scrollback, real wheel notches for vim/less/htop, or page keys. Optional handling for shells running inside tmux.
  • IME input. A dedicated input field, so Telex and other composition-based keyboards commit correctly instead of dropping tone marks.
  • Sessions that outlive the browser. The server owns the session list, so a reconnect from any device finds every shell still running, scrollback replayed.
  • Copy that behaves. Long-press to select; a copy button appears rather than the selection silently taking over your clipboard.

As a bastion

Put it on the one host allowed to reach your private network and the DevOps toolchain runs there, not on the phone. No credentials, no kubeconfig and no private keys reach the device, and there is no client to install beyond a browser — optionally saved to the home screen as a PWA.

Pair it with WireGuard or Tailscale. The terminal then listens on the VPN interface only, so the login page is not reachable from the internet at all, and authentication becomes two independent layers: the VPN's keys, then WT_PASSWORD.

What is inside

Terraform, Terramate, tflint, terraform-docs, kubectl, Helm, k9s, kubectx/kubens, aws-cli v2 with the Session Manager plugin, Go, Python, uv, Neovim, plus ripgrep, fd, bat, fzf, zoxide, yq, jq, nmap, tcpdump, tmux and the Docker CLI. Every tool is installed from its vendor's own release endpoint at a pinned version.

zsh with oh-my-zsh, autosuggestions, syntax highlighting and completions. powerlevel10k is installed and a Nerd Font is bundled as a webfont, so a powerline theme renders on a phone with no fonts installed.

The Docker CLI ships without an engine. Mount /var/run/docker.sock to drive the host's daemon — and read the warning below before you do.

Configuration

VariableDefaultMeaning
WT_PASSWORD(empty)Login password. Empty disables authentication entirely.
WT_USERNAMEadminLogin name.
WT_TITLEWeb TerminalTab title and login heading.
WT_PORT7681Listen port.
WT_HOST0.0.0.0Listen address.
WT_BASE_PATH(empty)Serve under a sub-path, e.g. /terminal.
WT_SHELL/bin/zshShell to spawn.
WT_SHELL_ARGS-lShell arguments; a login shell, so ~/.zshrc is read.
WT_HOME/rootWorking directory for new sessions.
WT_ZSH_THEMErobbyrusselloh-my-zsh theme; powerlevel10k/powerlevel10k is bundled.
WT_MAX_SESSIONS12Maximum concurrent shells.
WT_SCROLLBACK_BYTES262144Replay buffer kept per session.
WT_READONLYfalseReject all input; view only.
WT_ALLOW_EXECtrueAllow creating new sessions.
WT_TOKEN_TTL_MS604800000Login token lifetime, 7 days.
WT_DEAD_SESSION_GRACE_MS300000How long an exited session stays listed.
WT_PING_INTERVAL_MS25000WebSocket keepalive interval.
WT_TRUST_PROXYtrueHonour X-Forwarded-* from a reverse proxy.
WT_TZ(unset)Timezone inside the container, e.g. Asia/Ho_Chi_Minh.

Before you expose it

This is an unrestricted root shell. The supported deployment is behind a VPN. If you put it on a public address anyway:

  1. Set a strong WT_PASSWORD. An empty one hands a shell to anyone who reaches the port.
  2. Serve it over HTTPS. On plain HTTP the password and session token travel in the clear, and browsers refuse clipboard reads outside a secure context.
  3. Restrict source addresses with a firewall, a security group, or nginx.
  4. Only mount /var/run/docker.sock if you need it. That socket is root on the host and it escapes the container.

Every push is scanned with Trivy, Hadolint, Gitleaks and npm audit. See SECURITY.md, which also explains why CVEs reported inside third-party Go binaries are not fixable from this image.

Tag summary

Content type

Image

Digest

sha256:7aabc27ba

Size

605.6 MB

Last updated

about 9 hours ago

docker pull benakaben24/web-terminal