Skip to content

moukrea/sshx-mobile

Repository files navigation

sshx-mobile

Reach your computer's shell from your phone — anywhere, with zero infrastructure.

Release License: MIT CI

sshx-mobile turns an sshx.io session into a real, phone-native terminal. A small Android app reshapes the session into a single-window tabbed terminal built for touch, and a host package keeps that session alive and reachable through reboots, network changes and process restarts — so your phone can always find your shell again.

No port forwarding. No VPN. No account. No server to run. The host pushes its current session address to a free, end-to-end-encrypted relay; the app reads it back and reconnects automatically.

Heads up: an sshx session URL grants full read/write access to your shell. Everything after # is the end-to-end decryption key and never reaches the sshx server. Treat it like a password. See Security.


Features

  • Touch-native terminal — one active terminal fills the screen; a top tab bar switches/creates/closes sessions; a bottom bar sends real Esc Tab Ctrl Alt - / and arrow keys. Keyboard-aware: the bars lift above the on-screen keyboard.
  • Swipe to scroll the scrollback; start typing and you snap straight back to the live prompt.
  • Lossless persistence — your work lives in a dedicated tmux server, so a dropped connection, a new session URL, or a reboot never loses a pane.
  • Auto-reconnect — the app follows the host to its new address with no manual re-pairing.
  • Self-healing sessions. If the relay drops the session (long idle, a network blip, waking from sleep), the host detects it and re-mints automatically, so you never land on a stale blank screen.
  • Host windows sync to tabs — open or close a window on the host and the app's tabs follow, live.
  • Built-in chat & presence — sshx's collaboration, reworked for mobile, with an unread badge.
  • QR pairing — scan once; the app remembers the resolver across session changes.
  • Tunable density — pick your column count (default 80) for bigger or smaller text.

How it works

   your phone                     a free relay                 your machine
┌───────────────┐   reads addr   ┌─────────────┐   pushes addr  ┌──────────────────────┐
│ sshx-mobile   │ ◀───────────── │  resolver   │ ◀───────────── │ sshx  ──▶  tmux (-L   │
│   (the app)   │                │ (MantleDB / │                │            sshx)      │
│               │ ──────────────▶│  self-host) │                │ persistent, isolated  │
└───────────────┘   sshx session └─────────────┘                └──────────────────────┘
        │                                                                    ▲
        └────────────── end-to-end encrypted sshx session ───────────────────┘

The host runs sshx against a persistent tmux server and publishes the current (rotating) session URL to a resolver — by default a random, private MantleDB namespace (no account, no email), or your own loopback/LAN service. The app polls the resolver, connects to the session, and re-syncs whenever the host's address changes.


Install

You need two things: the app on your phone, and the host tooling on the machine whose shell you want to reach.

Using Claude Code (or another coding agent)? It can do the whole host setup for you, interactively — package, sshx CLI, always-on services, resolver + claim, verification, and wiring claude into the app's tmux. See Assisted setup with Claude Code (or run the bundled /sshx-mobile-install skill from a clone). You still install the APK and scan one QR.

1. The app (Android)

Download the latest sshx-mobile-*.apk from the Releases page and install it (you may need to allow "install unknown apps" for your browser/file manager).

You only sideload once: the app updates itself. On launch it checks the Releases feed and, when a newer version exists, offers to download and install it in-app (no Play Store, no re-sideloading) — see Updating.

2. The host

tmux, python3 and qrencode install automatically as package dependencies. The only extra piece is the sshx CLI — it isn't in distro repos, so sshx-host-setup offers to install it for you (or run curl -sSf https://sshx.io/get | sh, which puts it in /usr/local/bin).

Debian / Ubuntu (apt)
curl -fsSL https://moukrea.github.io/apt-repo/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/moukrea.gpg
echo "deb [signed-by=/usr/share/keyrings/moukrea.gpg] https://moukrea.github.io/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/moukrea.list
sudo apt update && sudo apt install sshx-mobile-host
Fedora / RHEL (dnf)
sudo tee /etc/yum.repos.d/moukrea.repo > /dev/null <<'EOF'
[moukrea]
name=moukrea
baseurl=https://moukrea.github.io/rpm-repo/
enabled=1
gpgcheck=1
gpgkey=https://moukrea.github.io/rpm-repo/pubkey.gpg
EOF
sudo dnf install sshx-mobile-host
macOS / Linux (Homebrew)
brew install moukrea/tap/sshx-mobile-host
Arch Linux
# Grab the PKGBUILD from the matching release and build it:
curl -fsSLO https://github.com/moukrea/sshx-mobile/releases/latest/download/PKGBUILD
makepkg -si
From source
git clone https://github.com/moukrea/sshx-mobile.git
cd sshx-mobile
host/install.sh

3. Connect

On the host, run the one-shot setup (as your normal user, not root):

sshx-host-setup

It generates a private resolver, enables the per-user services, and prints a QR code. Open the app, tap Scan QR, point it at the code — done. Your shell is now in your pocket, and it will stay reachable across reboots and network changes.

To check status later: systemctl --user status sshx tmux-server sshx-tmux-bridge.

Updating

The app updates itself. On launch (from the home screen, so it never interrupts a live session) it checks the GitHub Releases feed; if a newer version is out it shows an "Update available" dialog. Tap Update and it downloads that release's APK and hands it to the system installer — you grant "install unknown apps" to the app once, the first time. Later re-prompts next launch; Skip silences that one version. (An update can only self-install if it is signed with the same key as the installed app, which holds for these releases; a key change would need a one-time manual reinstall.)

The host you upgrade the way you installed it, then re-run setup so the new version is the one actually running:

sudo apt update && sudo apt install --only-upgrade sshx-mobile-host   # or: sudo dnf upgrade sshx-mobile-host / brew upgrade
sshx-host-setup

sshx-host-setup restarts sshx and the window bridge (lossless: your tmux session is preserved, the phone re-resolves automatically) so the update takes effect right away.


Gesture / control Action
Tap a tab Switch terminal
Burger menu (top-right) Columns (text size), new terminal, connected people, chat
× on a tab Close that terminal
Bottom bar Esc Tab Ctrl Alt - / and arrow keys (real terminal input)
Set your name (main menu) Shown to others in the session, persisted across reconnects

Scrolling & selecting

Scrolling works on both surfaces, including inside full-screen TUIs such as Claude Code (whose output lives in the scrollback):

  • Scroll — on the phone, swipe up/down on the terminal; on a laptop/desktop tmux client (sshx-desk, or a claude session wired through the app's tmux) use the mouse wheel. To resume typing after scrolling up, scroll back to the bottom (or press q / Enter).

Selecting text on the desktop. The wheel and plain-drag native selection can't both be live at once — that's a tmux limitation, not a bug (tmux either owns the mouse for the wheel, or releases it for selection). The desktop defaults to wheel on, so:

  • Hold Shift while you drag to select — the terminal's own native selection (works on every terminal, copies via the terminal itself). This is the standard way to select over any mouse-aware program.
  • Or run sshx-mouse off to switch that session to plain-drag native selection for a while (the wheel then sends arrows until sshx-mouse on). sshx-mouse with no argument toggles.

The mouse setting is per surface: desktop sessions (sshx-desk, claude windows) keep mouse on for the wheel; phone tabs are mouse off, so a stray touch never drops the pane into tmux copy-mode. The phone keeps scrolling because its swipe is sent as keys, not mouse.

Selecting text on the phone is limited today: sshx renders the terminal with WebGL and keeps its terminal object private, and sshx itself has no clipboard support (sshx#77), so the app can't yet pull selected text into the Android clipboard. Tracked for a follow-up (a tmux-capture bridge). For now, copy from the desktop side.

Co-access from your desktop at the same time with sshx-desk (a normal tmux client sharing the windows). Route claude (or any command) into the shared session with the installed claude-tmux.sh helper so it shows up as a tab — the host installer wires this into ~/.bashrc automatically; for zsh add the delegating wrapper from docs/setup-with-claude-code.md.


Troubleshooting

App shows a blank/black screen with no terminals after scanning the QR. The sshx session the resolver points at has gone stale (sshx sessions expire after a long idle or a network drop, and the host process doesn't always notice). The host now detects this and heals itself: a liveness monitor re-mints the session automatically, usually within a minute, and the app re-resolves and reconnects with no re-scan needed. Just wait a moment. To force it immediately:

systemctl --user restart sshx.service

This self-heal is on by default; tune or disable it with SSHX_HEALTH_* (see host/README.md). Periodic rotation is now optional (it only limits how long any one URL is exposed):

systemctl --user enable --now sshx-rotate.timer    # default 1h; `systemctl --user edit sshx-rotate.timer` to change

Check the host services are up:

systemctl --user status tmux-server sshx sshx-tmux-bridge

New host windows don't appear as tabs / a closed tab lingers. The window list is published by sshx-tmux-bridge; make sure it's active. More operational recipes (reboot, no URL served, sizing) are in docs/runbook.md.


Security

  • The session URL's #fragment is the end-to-end encryption key. sshx uses it only in the browser; it never reaches the sshx server. Anyone with the full URL can use your terminal.
  • sshx-qr encodes the URL offline — it never sends it anywhere. Never paste an sshx URL into an online QR generator.
  • The resolver stores the full URL (incl. the key). The default MantleDB namespace is random and private (the namespace itself is a bearer secret). Claim it (sshx-resolver-setup --claim, no email) to attach a key: as of 2026-06 a claimed namespace returns HTTP 401 without the key on reads and writes (verified — see docs/setup-with-claude-code.md), so claiming is what makes a readable namespace safe. For guaranteed read-privacy with zero third-party trust, run the bundled self-hosted resolver over a network you control. Never expose a resolver to the public internet. (Note: MantleDB lowercases namespaces on claim — use the lowercase form.)
  • host/resolver.env (your live resolver config) is git-ignored and never published. Only resolver.env.example ships.

Build from source

App: cd android && ./gradlew assembleReleaseapp/build/outputs/apk/release/. Requires JDK 17 and the Android SDK. See android/BUILD.md.

Host packages: SSHX_VERSION=x.y.z nfpm pkg -p deb -f packaging/nfpm.yaml -t dist/ (and -p rpm). CI builds and publishes them on every x.y.z tag — see .github/workflows/.

Design & internals

The lossless remote-access design, decisions and runbook live in docs/: setup-host, setup-app, runbook, and the ADRs. Host tooling reference: host/README.md. App internals: android/README.md.

License

MIT © moukrea

About

Reach your computer's shell from your phone, anywhere, with zero infrastructure — a touch-native sshx terminal app + persistent host tooling

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors