mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
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:
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user