Original tool

Is your host ready for a phone terminal?

Nine checks for the failures that only show up when you leave the desk. The snippet runs on your machine. No host data is sent anywhere. This page has no collector and no form post; the script's output never leaves your terminal.

host-check.sh local only
#!/usr/bin/env bash
# SSHHIP host-check - runs entirely on your machine.
# Nothing is uploaded. Review before running.
set -u

ok()   { printf 'OK   %s %s\n' "$1" "${2-}"; }
warn() { printf 'WARN %s %s\n' "$1" "${2-}"; }
fail() { printf 'FAIL %s %s\n' "$1" "${2-}"; }

echo "SSHHIP host-check"
echo "All evaluation is local. No data leaves this machine."
echo

# SSHD
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet sshd 2>/dev/null; then
  ok SSHD "sshd active (systemd)"
elif command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet ssh 2>/dev/null; then
  ok SSHD "ssh.service active (systemd)"
elif launchctl print system/com.openssh.sshd >/dev/null 2>&1; then
  ok SSHD "macOS Remote Login service present"
else
  warn SSHD "could not confirm sshd; check Remote Login / sshd manually"
fi

# SHELL
shell_name="$(basename "${SHELL:-/bin/sh}")"
ok SHELL "login shell=$shell_name"

# Set by multiplexer_path_check so later checks can tell "not installed" from
# "installed somewhere this probe did not look".
interactive_available=0

multiplexer_path_check() {
  local label="$1"
  local available=0
  local found=""
  local bin
  for bin in tmux herdr; do
    if command -v "$bin" >/dev/null 2>&1; then
      available=$((available + 1))
      found="$found $bin=$(command -v "$bin")"
    else
      found="$found $bin=MISSING"
    fi
  done
  interactive_available="$available"
  if [ "$available" -gt 0 ]; then
    ok "$label" "$found"
  else
    fail "$label" "$found"
  fi
}

# PATH-I interactive baseline (this shell)
multiplexer_path_check PATH-I

# PATH-N non-interactive ssh to self if possible.
# Not being able to ssh to yourself is a different result from ssh working and
# seeing nothing - only the second one is the PATH trap this check exists for.
if command -v ssh >/dev/null 2>&1; then
  remote="$(ssh -o BatchMode=yes -o ConnectTimeout=3 localhost 'command -v tmux; command -v herdr; true' 2>/dev/null)"
  ssh_status=$?
  remote="$(printf '%s' "$remote" | tr '\n' ' ')"
  if [ "$ssh_status" -ne 0 ]; then
    warn PATH-N "could not ssh to localhost in BatchMode, so this check could not run; try it from the machine you connect with"
  elif [ -n "$remote" ]; then
    ok PATH-N "localhost non-interactive sees:$remote"
  else
    fail PATH-N "ssh to localhost worked but saw no tmux/herdr - this is the PATH trap; see PATH-FB"
  fi
else
  warn PATH-N "ssh client missing; skipped"
fi

# PATH-FB common prefixes. Only interesting when discovery already failed:
# binaries installed outside these three directories are perfectly normal.
fb=""
for dir in /opt/homebrew/bin /usr/local/bin "$HOME/.local/bin"; do
  for bin in tmux herdr; do
    if [ -x "$dir/$bin" ]; then
      fb="$fb $dir/$bin"
    fi
  done
done
if [ -n "$fb" ]; then
  ok PATH-FB "$fb"
elif [ "$interactive_available" -gt 0 ]; then
  ok PATH-FB "none in the common prefixes, but PATH-I already found your binaries elsewhere - nothing to fix"
else
  warn PATH-FB "no tmux/herdr binaries in common prefix paths"
fi

# MUX versions
if command -v tmux >/dev/null 2>&1; then
  ok MUX "tmux $(tmux -V 2>/dev/null | tr '\n' ' ')"
elif [ -x /opt/homebrew/bin/tmux ]; then
  ok MUX "/opt/homebrew/bin/tmux $(/opt/homebrew/bin/tmux -V 2>/dev/null)"
else
  warn MUX "tmux not visible in this shell"
fi
if command -v herdr >/dev/null 2>&1; then
  ok MUX "herdr $(herdr --version 2>/dev/null | head -1 | tr '\n' ' ')"
elif [ -x /opt/homebrew/bin/herdr ]; then
  ok MUX "/opt/homebrew/bin/herdr present"
else
  warn MUX "herdr not visible in this shell"
fi

# KEEP
if [ -f /etc/ssh/sshd_config ] || ls /etc/ssh/sshd_config.d/*.conf >/dev/null 2>&1; then
  keep="$(grep -hE '^(ClientAliveInterval|ClientAliveCountMax)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr '\n' ' ')"
  if [ -n "$keep" ]; then
    ok KEEP "$keep"
  else
    warn KEEP "no ClientAlive* settings found; phone sleep may drop idle sessions"
  fi
else
  warn KEEP "sshd_config not readable"
fi

# AUTH methods offered on localhost if possible
if command -v ssh >/dev/null 2>&1; then
  auth="$(ssh -o BatchMode=yes -o PreferredAuthentications=none -o ConnectTimeout=3 localhost true 2>&1 | tr '\n' ' ' || true)"
  if printf '%s' "$auth" | grep -qi 'permission denied'; then
    ok AUTH "ssh negotiates (permission denied expected without creds)"
  else
    warn AUTH "could not probe auth methods cleanly"
  fi
else
  warn AUTH "ssh client missing; skipped"
fi

# SLEEP (macOS)
if command -v pmset >/dev/null 2>&1; then
  sleep_lines="$(pmset -g custom 2>/dev/null | grep -E 'sleep|disablesleep|tcpkeepalive' | tr '\n' '; ')"
  if [ -n "$sleep_lines" ]; then
    ok SLEEP "$sleep_lines"
  else
    warn SLEEP "pmset present but no sleep lines parsed"
  fi
else
  warn SLEEP "not macOS pmset; check host sleep policy manually"
fi

echo
echo "Done. Interpret codes on https://sshhip.com/tools/host-check/"

Review the script, then run it on the host: bash host-check.sh

What each code means

SSHD - Is sshd / Remote Login present?

Enable Remote Login on macOS or ensure sshd is installed and reachable on Linux.

SHELL - Which login shell will profile files come from?

zsh uses .zprofile/.zshrc; bash uses .bash_profile/.profile; csh/tcsh differ. PATH fixes must match the shell.

PATH-I - Can this interactive shell see tmux/herdr?

If this fails, install the multiplexer or fix your interactive PATH first.

PATH-N - Can non-interactive SSH see tmux/herdr?

The classic Apple Silicon Homebrew trap. FAIL means ssh connected and still saw nothing: login-shell probes plus /opt/homebrew/bin fallbacks are what fix that false not-installed result. WARN means the script could not ssh to localhost at all, so this check did not run - re-run it from the machine you actually connect with.

PATH-FB - Are binaries present in common prefix directories?

Only interesting when discovery already failed. If PATH-N fails but PATH-FB finds /opt/homebrew/bin/tmux, the binary exists and discovery is the bug. Binaries installed outside those three directories are normal and are not a problem on their own.

MUX - What versions are visible?

Very old tmux builds may lack format features; herdr is moving quickly - note the version you run.

KEEP - Will idle sessions survive phone sleep?

Consider ClientAliveInterval / ClientAliveCountMax on the server so half-open phone connections clear cleanly.

AUTH - Can SSH negotiate at all?

For Tailscale SSH keyless flows, the client must be willing to offer none/publickey before insisting on a password prompt.

SLEEP - Will the host itself sleep under an agent?

A perfect phone client cannot wake a Mac that powered down. Align pmset / power settings with the workload.

The two failure stories

PATH-N is why phone clients silently decide your multiplexer is missing on a healthy Homebrew Mac. SLEEP is why your agent "stopped" overnight when the client did nothing wrong. Both are host problems with host fixes.

What this does not check

  • Firewall / tailnet ACL policy beyond a best-effort localhost probe
  • Whether your agent binary itself is installed
  • Disk encryption, MDM, or corporate SSH middleboxes
  • SSHHIP app configuration on the phone

Next