diff --git a/bin/rbtop b/bin/rbtop index 7186ada..0e6d6a7 100755 --- a/bin/rbtop +++ b/bin/rbtop @@ -1,3 +1,35 @@ -#!/bin/bash -# rbtop - Connect to apricot and run btop (transient install) -ssh -t apricot.lan "sudo dnf install -y --transient btop && btop" +#!/bin/sh +# rbtop — connect to apricot and run btop (transient dnf install on first run). +# +# Prefers mosh over ssh when both ends have it (better resilience for the +# interactive TUI under sleep/roam/network blips). Override with +# RBTOP_TRANSPORT=ssh to force ssh. + +set -eu + +HOST=${RBTOP_HOST:-apricot.lan} +INNER='sudo dnf install -y --transient btop && btop' + +# Reuse rclaude's auto-installer for shared deps (mosh, etc.). Idempotent — +# the marker in ~/.cache/rclaude/ skips re-running on every invocation. +if command -v rclaude >/dev/null 2>&1; then + rclaude install --on "$HOST" >&2 || true +fi + +case ${RBTOP_TRANSPORT:-auto} in + ssh) use=ssh ;; + mosh) use=mosh ;; + *) + use=ssh + if command -v mosh >/dev/null 2>&1 \ + && ssh -o BatchMode=yes -o ConnectTimeout=3 "$HOST" 'command -v mosh-server' >/dev/null 2>&1; then + use=mosh + fi + ;; +esac + +if [ "$use" = mosh ]; then + exec mosh "$HOST" -- sh -c "$INNER" +else + exec ssh -t -o ServerAliveInterval=30 -o ServerAliveCountMax=6 "$HOST" "$INNER" +fi diff --git a/bin/rclaude b/bin/rclaude index d9cdfa4..17e315d 100755 --- a/bin/rclaude +++ b/bin/rclaude @@ -350,14 +350,25 @@ detect_os_on() { } # Install on using its native package manager. Uses sudo for -# system pkgs on Linux; brew (no sudo) on macOS. +# system pkgs on Linux; brew (no sudo) on macOS. On bootc/Silverblue-style +# immutable systems (signaled by /run/ostree-booted), dnf is given +# `--transient` so the install lands in an overlay that survives until +# reboot — rclaude's per-host marker is invalidated below on reboot so it +# re-installs automatically. install_pkgs_on() { _h=$1; _os=$2; shift 2 _pkgs=$* [ -z "$_pkgs" ] && return 0 + _dnf_flags="-y" + if [ "$_os" = "rhel" ]; then + if _probe_on "$_h" 'test -e /run/ostree-booted && echo bootc; :' | grep -q bootc; then + _dnf_flags="-y --transient" + printf 'rclaude: %s is bootc/immutable — using --transient overlay\n' "$_h" >&2 + fi + fi case $_os in macos) _cmd="brew install $_pkgs" ;; - rhel) _cmd="sudo dnf install -y $_pkgs" ;; + rhel) _cmd="sudo dnf install $_dnf_flags $_pkgs" ;; debian) _cmd="sudo apt-get update && sudo apt-get install -y $_pkgs" ;; *) echo "rclaude: don't know how to install $_pkgs on $_h ($_os) — do it manually" >&2 @@ -397,17 +408,21 @@ setup_host() { echo "rclaude: couldn't detect OS on $_h; skipping setup" >&2 return 0 fi - # Probe system binaries. + # Probe system binaries. The trailing `; :` guarantees a 0 exit from the + # remote shell so `set -e` in our caller doesn't kill us when a missing + # binary's `command -v` returns 1 inside the loop. Newline-delim output; + # the awk comparison below ignores delimiter shape. _missing="" - _have=$(_probe_on "$_h" 'for c in tmux rsync mosh; do command -v "$c" >/dev/null 2>&1 && echo "$c"; done') + _have=$(_probe_on "$_h" 'for c in tmux rsync mosh; do command -v "$c" >/dev/null 2>&1 && echo "$c"; done; :') for c in tmux rsync mosh; do - case " $_have " in *" $c "*) ;; *) _missing="$_missing $c" ;; esac + printf '%s\n' "$_have" | grep -Fxq "$c" || _missing="$_missing $c" done if [ -n "$_missing" ]; then install_pkgs_on "$_h" "$_os" $_missing || true fi - # Python SDK for triage. Try to install per-user without sudo. - _has_sdk=$(_probe_on "$_h" 'for p in python3.13 python3.12 python3.11 python3; do b=$(command -v "$p" 2>/dev/null) || continue; "$b" -c "import claude_code_batch_sdk" 2>/dev/null && echo "$b" && break; done') + # Python SDK for triage. Try to install per-user without sudo. Same `; :` + # guard for the same reason. + _has_sdk=$(_probe_on "$_h" 'for p in python3.13 python3.12 python3.11 python3; do b=$(command -v "$p" 2>/dev/null) || continue; "$b" -c "import claude_code_batch_sdk" 2>/dev/null && echo "$b" && break; done; :') if [ -z "$_has_sdk" ]; then _pick=$(_probe_on "$_h" 'for p in python3.12 python3.11 python3; do command -v "$p" 2>/dev/null && break; done | head -1') if [ -n "$_pick" ]; then @@ -911,7 +926,7 @@ case ${1:-} in list) shift; cmd_list "$@"; exit ;; resume) shift; cmd_resume "$@"; exit ;; triage) shift; cmd_triage "$@"; exit ;; - setup) shift; cmd_setup "$@"; exit ;; + setup|install) shift; cmd_setup "$@"; exit ;; -v|--version) cmd_version; exit ;; -h|--help|help) cmd_help; exit ;; esac