A monorepo for remote-shell, a browser-based interactive shell whose defining feature is session persistence: refresh, close the tab, or switch devices and you resume the exact same shell — working directory, environment, running processes, and history all intact.
The server forks a PTY directly and keeps a per-session in-memory ring buffer; the frontend (xterm.js + WebGL, 50k-line scrollback) owns all scrolling, so it stays buttery-smooth and a reconnect repaints the screen from the buffer. No tmux.
Browser (xterm.js + WebGL) ──WebSocket──► Go gateway ──fork──► PTY ──► bash/zsh (or `ssh host`)
│
per-session ring buffer → reconnect repaints
| Path | Project | Description |
|---|---|---|
server/ |
server | Go gateway: PTY + ring-buffer session persistence, WebSocket wire protocol, auth. See server/README.md. |
web/ |
web | xterm.js + WebGL frontend (no build step; vendored addons). Served by the Go server. |
android/ |
android | Native Android client (Kotlin + Jetpack Compose, Termux terminal emulator) that speaks the same login API and WebSocket protocol. See android/README.md. |
deploy/ |
deploy | Docker Compose, Dockerfile, and the nginx TLS proxy. Driven by install.sh. |
Server + web (Docker) — one command, no clone needed:
curl -fsSL https://raw.githubusercontent.com/0xshawn/remote-shell/main/install.sh | bash
# open the printed https://<host>:8443 (self-signed cert → accept the warning)install.sh fetches the repo into ~/.remote-shell (override with
REMOTE_SHELL_DIR=) when run outside a checkout, then builds the image, creates
.env, auto-detects your host user, generates + persists the secrets, generates
a self-signed TLS cert, and authorizes the container's SSH key on the host —
then prints the login password. It is safe to re-run. From an existing clone,
run ./install.sh directly.
By default the web terminal logs into the host shell over SSH (not the
container). To pin credentials, use real TLS certs, or switch to a plain
container shell, see server/README.md.
Binary (no Docker) — download one self-contained binary and run it in the background; it serves its own HTTPS:
curl -fsSL https://raw.githubusercontent.com/0xshawn/remote-shell/main/install-binary.sh | bash
# open the printed https://<host>:8443 (self-signed cert → accept the warning)install-binary.sh downloads the prebuilt binary for your architecture, runs it
under systemd when available (else a nohup process that survives the shell
closing), and prints the generated password. The web terminal is the host user's
own shell — no SSH hop. Serve on a different port with PORT= (e.g.
PORT=9443 bash remote-shell-installer-linux-amd64.sh); pin a version with
REMOTE_SHELL_VERSION=. See server/README.md for changing
the port of an existing install, management, and uninstall.
Offline / blocked network (the server can't reach GitHub) — grab the
self-extracting installer remote-shell-installer-linux-amd64.sh (or -arm64)
from the releases page. It's
this script with the binary baked in, so one file is the whole package — copy it
over and run it; no network, no separate binary, no checkout:
bash remote-shell-installer-linux-amd64.shPrefer the raw binary? Download remote-shell-linux-amd64, then
./install-binary.sh ./remote-shell-linux-amd64 (or set REMOTE_SHELL_BIN=).
Run it directly instead (Go ≥ 1.26):
cd server && go build -o remote-shell . && cd ..
WEB_DIR=web ./server/remote-shell --no-authAndroid client:
cd android
./gradlew assembleDebugThen point the app at your server's URL and log in with the same credentials.
Each installer has an uninstall subcommand that tears down its own deployment.
Docker — removes the containers, the volume (secrets + host SSH key), the image, and the authorized host key:
~/.remote-shell/install.sh uninstall # from the checkout (default ~/.remote-shell)Binary — removes the systemd service (system or user), the binary, and
~/.remote-shell (password, token secret, self-signed cert):
remote-shell uninstall # the binary cleans up after itself (add -y to skip the prompt)
# or, if the binary is already gone: ./install-binary.sh uninstallBoth are best-effort and safe to re-run. For a system (root) install, run them
with sudo so root-owned units/binaries can be removed.
Manual teardown (binary), if you'd rather do it by hand
install-binary.sh installs a system service (as root), a user service
(non-root), or a bare nohup process. sudo systemctl only sees the system one
— "Unit could not be found" usually means a user service or nohup. Detect, then remove:
systemctl --user status remote-shell ; pgrep -af remote-shell # which one is it?
# stop the matching one:
sudo systemctl disable --now remote-shell && sudo rm -f /etc/systemd/system/remote-shell.service
systemctl --user disable --now remote-shell && rm -f ~/.config/systemd/user/remote-shell.service
kill "$(cat ~/.remote-shell/remote-shell.pid)" 2>/dev/null
# then:
rm -f /usr/local/bin/remote-shell ~/.local/bin/remote-shell
rm -rf ~/.remote-shell(If systemctl --user reports "Failed to connect to bus" over SSH, run
export XDG_RUNTIME_DIR=/run/user/$(id -u) first.)