ssh-integration: improve host caching, new method for "full" integration

Need a sanity check on this new approach for "full" to help determine if
it's worth additional iteration/refinement.

It solves the double auth issue, successfully propagates env vars, and
avoids output noise for connections that happen after terminfo is
installed. The only issue I don't have time to fix tonight is the fact
that it drops the MOTD for cached (re)connections.
This commit is contained in:
Jason Rayne
2025-06-17 21:33:29 -07:00
parent 69f9976394
commit f206e76841

View File

@ -99,91 +99,90 @@ fi
# SSH # SSH
if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then
# Cache file for tracking hosts with terminfo installed # Cache configuration
_ghostty_cache_file="${GHOSTTY_RESOURCES_DIR:-$HOME/.config/ghostty}/terminfo_hosts" _ghostty_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/ghostty"
_ghostty_cache_file="$_ghostty_cache_dir/terminfo_hosts"
# Extract target host from SSH arguments # Create cache directory with proper permissions
[[ ! -d "$_ghostty_cache_dir" ]] && mkdir -p "$_ghostty_cache_dir" && chmod 700 "$_ghostty_cache_dir"
# Extract SSH target from arguments
_ghostty_get_ssh_target() { _ghostty_get_ssh_target() {
local target="" local target=""
local skip_next=false local skip_next=false
local args=("$@")
for arg in "$@"; do for ((i=0; i<${#args[@]}; i++)); do
if [[ "$skip_next" == "true" ]]; then local arg="${args[i]}"
skip_next=false
continue
fi
# Skip flags that take arguments # Skip if we're processing a flag's argument
[[ "$skip_next" == "true" ]] && { skip_next=false; continue; }
# Handle flags that take arguments
if [[ "$arg" =~ ^-[bcDEeFIiJLlmOopQRSWw]$ ]]; then if [[ "$arg" =~ ^-[bcDEeFIiJLlmOopQRSWw]$ ]]; then
skip_next=true skip_next=true
continue continue
fi fi
# Skip other flags # Handle combined short flags with values (e.g., -p22)
if [[ "$arg" =~ ^- ]]; then [[ "$arg" =~ ^-[bcDEeFIiJLlmOopQRSWw].+ ]] && continue
continue
fi
# This should be the target # Skip other flags
[[ "$arg" =~ ^- ]] && continue
# This should be our target
target="$arg" target="$arg"
break break
done done
echo "$target" # Handle user@host format
echo "${target##*@}"
} }
# Check if host has terminfo installed # Check if host has terminfo cached
_ghostty_host_has_terminfo() { _ghostty_host_has_terminfo() {
local target="$1" local host="$1"
[[ -f "$_ghostty_cache_file" ]] && grep -qFx "$target" "$_ghostty_cache_file" 2>/dev/null [[ -f "$_ghostty_cache_file" ]] && grep -qFx "$host" "$_ghostty_cache_file" 2>/dev/null
} }
# Add host to terminfo cache # Add host to cache atomically
_ghostty_cache_host() { _ghostty_cache_host() {
local target="$1" local host="$1"
local cache_dir local temp_file
cache_dir="$(dirname "$_ghostty_cache_file")" temp_file="$_ghostty_cache_file.$$"
# Create cache directory if needed # Merge existing cache with new host
[[ ! -d "$cache_dir" ]] && mkdir -p "$cache_dir"
# Atomic write to cache file
{ {
if [[ -f "$_ghostty_cache_file" ]]; then [[ -f "$_ghostty_cache_file" ]] && cat "$_ghostty_cache_file"
cat "$_ghostty_cache_file" echo "$host"
fi } | sort -u > "$temp_file"
echo "$target"
} | sort -u > "$_ghostty_cache_file.tmp" && mv "$_ghostty_cache_file.tmp" "$_ghostty_cache_file"
# Secure permissions # Atomic replace with proper permissions
chmod 600 "$_ghostty_cache_file" 2>/dev/null mv -f "$temp_file" "$_ghostty_cache_file" && chmod 600 "$_ghostty_cache_file"
} }
# Wrap `ssh` command to provide Ghostty SSH integration. # Remove host from cache (for maintenance)
# _ghostty_uncache_host() {
# This approach supports wrapping an `ssh` alias, but the alias definition local host="$1"
# must come _after_ this function is defined. Otherwise, the alias expansion [[ -f "$_ghostty_cache_file" ]] || return 0
# will take precedence over this function, and it won't be wrapped.
function ssh { local temp_file="$_ghostty_cache_file.$$"
grep -vFx "$host" "$_ghostty_cache_file" > "$temp_file" 2>/dev/null || true
mv -f "$temp_file" "$_ghostty_cache_file"
}
# Main SSH wrapper
ssh() {
case "$GHOSTTY_SSH_INTEGRATION" in case "$GHOSTTY_SSH_INTEGRATION" in
"term-only") term-only) _ghostty_ssh_term_only "$@" ;;
_ghostty_ssh_term-only "$@" basic) _ghostty_ssh_basic "$@" ;;
;; full) _ghostty_ssh_full "$@" ;;
"basic") *) _ghostty_ssh_basic "$@" ;; # Default to basic
_ghostty_ssh_basic "$@"
;;
"full")
_ghostty_ssh_full "$@"
;;
*)
# Unknown level, fall back to basic
_ghostty_ssh_basic "$@"
;;
esac esac
} }
# Level: term-only - Just fix TERM compatibility # Level: term-only - Just fix TERM compatibility
_ghostty_ssh_term-only() { _ghostty_ssh_term_only() {
if [[ "$TERM" == "xterm-ghostty" ]]; then if [[ "$TERM" == "xterm-ghostty" ]]; then
TERM=xterm-256color builtin command ssh "$@" TERM=xterm-256color builtin command ssh "$@"
else else
@ -191,65 +190,158 @@ if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then
fi fi
} }
# Level: basic - TERM fix + environment variable propagation # Level: basic - TERM fix + environment propagation
_ghostty_ssh_basic() { _ghostty_ssh_basic() {
local env_vars=() local term_value
term_value=$([[ "$TERM" == "xterm-ghostty" ]] && echo "xterm-256color" || echo "$TERM")
# Fix TERM compatibility builtin command ssh "$@" "
if [[ "$TERM" == "xterm-ghostty" ]]; then # Set environment for this session
env_vars+=("TERM=xterm-256color") export GHOSTTY_SHELL_FEATURES='$GHOSTTY_SHELL_FEATURES'
fi export TERM='$term_value'
# Propagate Ghostty shell integration environment variables # Start interactive shell
[[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") exec \$SHELL -l
"
# Execute with environment variables if any were set
if [[ ${#env_vars[@]} -gt 0 ]]; then
env "${env_vars[@]}" ssh "$@"
else
builtin command ssh "$@"
fi
} }
# Level: full - All features # Level: full - Complete integration with terminfo
_ghostty_ssh_full() { _ghostty_ssh_full() {
local target local target
target="$(_ghostty_get_ssh_target "$@")" target="$(_ghostty_get_ssh_target "$@")"
# Check if we already know this host has terminfo # Quick path for cached hosts
if [[ -n "$target" ]] && _ghostty_host_has_terminfo "$target"; then if [[ -n "$target" ]] && _ghostty_host_has_terminfo "$target"; then
# Direct connection with xterm-ghostty # Direct connection with full ghostty support
local env_vars=("TERM=xterm-ghostty") builtin command ssh -t "$@" "
[[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") export GHOSTTY_SHELL_FEATURES='$GHOSTTY_SHELL_FEATURES'
env "${env_vars[@]}" ssh "$@" export TERM='xterm-ghostty'
return 0 exec \$SHELL -l
"
return $?
fi fi
# Full integration: Install terminfo if needed # Check if we can export terminfo
if builtin command -v infocmp >/dev/null 2>&1; then if ! builtin command -v infocmp >/dev/null 2>&1; then
# Install terminfo only if needed echo "Warning: infocmp not found locally. Using basic integration." >&2
if infocmp -x xterm-ghostty 2>/dev/null | builtin command ssh "$@" ' _ghostty_ssh_basic "$@"
if ! infocmp xterm-ghostty >/dev/null 2>&1; then return $?
echo "Installing Ghostty terminfo..." >&2 fi
tic -x - 2>/dev/null
fi
'; then
echo "Connecting with full Ghostty support..." >&2
# Cache this host for future connections # Generate terminfo data
[[ -n "$target" ]] && _ghostty_cache_host "$target" local terminfo_data
terminfo_data="$(infocmp -x xterm-ghostty 2>/dev/null)" || {
echo "Warning: xterm-ghostty terminfo not found locally. Using basic integration." >&2
_ghostty_ssh_basic "$@"
return $?
}
# Connect with xterm-ghostty since terminfo is available echo "Setting up Ghostty terminal support on remote host..." >&2
local env_vars=("TERM=xterm-ghostty")
[[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") # Create control socket path
env "${env_vars[@]}" ssh "$@" local control_path="/tmp/ghostty-ssh-${USER}-$"
builtin return 0 trap "rm -f '$control_path'" EXIT
# Start control master and check/install terminfo
local setup_script='
if ! infocmp xterm-ghostty >/dev/null 2>&1; then
if command -v tic >/dev/null 2>&1; then
mkdir -p "$HOME/.terminfo" 2>/dev/null
echo "NEEDS_INSTALL"
else
echo "NO_TIC"
fi
else
echo "ALREADY_INSTALLED"
fi fi
echo "Terminfo installation failed. Using basic integration." >&2 '
# First connection: Start control master and check status
local install_status
install_status=$(builtin command ssh -o ControlMaster=yes \
-o ControlPath="$control_path" \
-o ControlPersist=30s \
"$@" "$setup_script")
case "$install_status" in
"NEEDS_INSTALL")
echo "Installing xterm-ghostty terminfo..." >&2
# Send terminfo through existing control connection
if echo "$terminfo_data" | builtin command ssh -o ControlPath="$control_path" "$@" \
'tic -x - 2>/dev/null && echo "SUCCESS"' | grep -q "SUCCESS"; then
echo "Terminfo installed successfully." >&2
[[ -n "$target" ]] && _ghostty_cache_host "$target"
else
echo "Warning: Failed to install terminfo. Using basic integration." >&2
ssh -O exit -o ControlPath="$control_path" "$@" 2>/dev/null || true
_ghostty_ssh_basic "$@"
return $?
fi
;;
"ALREADY_INSTALLED")
[[ -n "$target" ]] && _ghostty_cache_host "$target"
;;
"NO_TIC")
echo "Warning: tic not found on remote host. Using basic integration." >&2
ssh -O exit -o ControlPath="$control_path" "$@" 2>/dev/null || true
_ghostty_ssh_basic "$@"
return $?
;;
esac
# Now use the existing control connection for interactive session
echo "Connecting with full Ghostty support..." >&2
# Pass environment through and start login shell to show MOTD
builtin command ssh -t -o ControlPath="$control_path" "$@" "
# Set up Ghostty environment
export GHOSTTY_SHELL_FEATURES='$GHOSTTY_SHELL_FEATURES'
export TERM='xterm-ghostty'
# Display MOTD if this is a fresh connection
if [[ '$install_status' == 'NEEDS_INSTALL' ]]; then
# Try to display MOTD manually
if [[ -f /etc/motd ]]; then
cat /etc/motd 2>/dev/null || true
fi
# Run update-motd if available (Ubuntu/Debian)
if [[ -d /etc/update-motd.d ]]; then
run-parts /etc/update-motd.d 2>/dev/null || true
fi
fi
# Force a login shell
exec \$SHELL -l
"
local exit_code=$?
# Clean up control socket
ssh -O exit -o ControlPath="$control_path" "$@" 2>/dev/null || true
return $exit_code
}
# Utility function to clear cache for a specific host
ghostty_ssh_reset() {
local host="${1:-}"
if [[ -z "$host" ]]; then
echo "Usage: ghostty_ssh_reset <hostname>" >&2
return 1
fi fi
# Fallback to basic integration _ghostty_uncache_host "$host"
_ghostty_ssh_basic "$@" echo "Cleared Ghostty terminfo cache for: $host"
}
# Utility function to list cached hosts
ghostty_ssh_list_cached() {
if [[ -f "$_ghostty_cache_file" ]]; then
echo "Hosts with cached Ghostty terminfo:"
cat "$_ghostty_cache_file"
else
echo "No hosts cached yet."
fi
} }
fi fi