An open API service indexing awesome lists of open source software.

https://github.com/0xshawn/remote-shell

Browser-based shell with transparent tmux-backed session persistence — refresh, close the tab, or switch devices and resume the exact same shell (cwd, env, running processes).
https://github.com/0xshawn/remote-shell

docker node-pty nodejs persistent-sessions pty remote-shell shell terminal tmux ttyd web-shell web-terminal websocket xterm-js

Last synced: about 10 hours ago
JSON representation

Browser-based shell with transparent tmux-backed session persistence — refresh, close the tab, or switch devices and resume the exact same shell (cwd, env, running processes).

Awesome Lists containing this project

README

          

# remote-shell

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
```

## Projects

| Path | Project | Description |
|------|---------|-------------|
| [`server/`](server/) | **server** | Go gateway: PTY + ring-buffer session persistence, WebSocket wire protocol, auth. See [`server/README.md`](server/README.md). |
| [`web/`](web/) | **web** | xterm.js + WebGL frontend (no build step; vendored addons). Served by the Go server. |
| [`android/`](android/) | **android** | Native Android client (Kotlin + Jetpack Compose, Termux terminal emulator) that speaks the same login API and WebSocket protocol. See [`android/README.md`](android/README.md). |
| [`deploy/`](deploy/) | **deploy** | Docker Compose, Dockerfile, and the nginx TLS proxy. Driven by [`install.sh`](install.sh). |

## Quick start

**Server + web** (Docker) — one command, no clone needed:

```bash
curl -fsSL https://raw.githubusercontent.com/0xshawn/remote-shell/main/install.sh | bash
# open the printed https://: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`](server/README.md).

**Binary** (no Docker) — download one self-contained binary and run it in the
background; it serves its own HTTPS:

```bash
curl -fsSL https://raw.githubusercontent.com/0xshawn/remote-shell/main/install-binary.sh | bash
# open the printed https://: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`](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](https://github.com/0xshawn/remote-shell/releases). 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
bash remote-shell-installer-linux-amd64.sh
```

Prefer 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):

```bash
cd server && go build -o remote-shell . && cd ..
WEB_DIR=web ./server/remote-shell --no-auth
```

**Android client:**

```bash
cd android
./gradlew assembleDebug
```

Then point the app at your server's URL and log in with the same credentials.

## Uninstall

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:

```bash
~/.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):

```bash
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 uninstall
```

Both 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:

```bash
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.)