Repository navigation
Expand file tree
/
Copy pathfix-ghostty-on-remote.sh
More file actions
executable file
·158 lines (126 loc) · 4.97 KB
/
Copy pathfix-ghostty-on-remote.sh
File metadata and controls
executable file
·158 lines (126 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env bash
#
# fix-ghostty-on-remote.sh - install a local terminfo entry on a remote host.
#
# Strategy:
# 1. Pipe `infocmp -x <term>` straight into `tic -x -` over ssh.
# 2. If the entry is not queryable afterwards, fall back to copying the
# description file over with scp and compiling it into ~/.terminfo.
#
# Exit codes:
# 0 the remote host knows the terminal (installed now or already)
# 1 ssh, scp or tic failed
# 2 usage error
set -uo pipefail
usage() {
cat <<HELP
----------------------------------------
Dimitry4Now :: fix-ghostty-on-remote.sh
----------------------------------------
USAGE
fix-ghostty-on-remote.sh [OPTIONS] user@host [term-name]
OPTIONS
-h, --help show this help
Teaches a remote host about your local terminal so that \`clear\`, editors and
TUIs stop failing with "unknown terminal type". term-name defaults to \$TERM
(currently ${TERM:-unset}). The remote host needs tic, from ncurses-bin.
HELP
}
die() {
printf '%s: %s\n' "${0##*/}" "$1" >&2
exit "${2:-2}"
}
# ---------------------------------------------------------------------------
# OPTIONS
# ---------------------------------------------------------------------------
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) die "unknown option: $1 (try --help)" ;;
*) break ;;
esac
shift
done
[ $# -gt 0 ] || die "missing user@host (try --help)"
[ $# -le 2 ] || die "unexpected extra argument: $3"
TARGET=$1
TERM_NAME=${2:-${TERM:-}}
[ -n "$TERM_NAME" ] || die 'no terminal name given and $TERM is unset'
for tool in infocmp ssh scp; do
command -v "$tool" >/dev/null 2>&1 || die "$tool not found" 1
done
log() { printf '==> %s\n' "$*"; }
warn() { printf '%s: warning: %s\n' "${0##*/}" "$*" >&2; }
# Make sure the entry exists locally before bothering the remote host.
infocmp -x "$TERM_NAME" >/dev/null 2>&1 || die "no local terminfo entry for $TERM_NAME" 1
# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------
# Reuse one ssh connection for every step below. The socket has to live under a
# short path: the sockaddr limit for a unix socket is 104 bytes, and macOS puts
# $TMPDIR somewhere far longer than that.
CTL="/tmp/.fgh-$$"
SSH_OPTS=(-o ControlMaster=auto -o "ControlPath=$CTL" -o ControlPersist=60s)
cleanup() {
ssh "${SSH_OPTS[@]}" -O exit "$TARGET" >/dev/null 2>&1 || true
}
trap cleanup EXIT
# ssh exits 255 when it could not run the remote command at all (unreachable
# host, refused key, wrong password). That is fatal — retrying the next method
# only produces the same prompt again.
die_if_ssh_failed() {
[ "$1" -ne 255 ] || die "cannot run commands on $TARGET over ssh" 1
}
remote_has_entry() {
local rc=0
ssh "${SSH_OPTS[@]}" "$TARGET" -- "infocmp -1 '$TERM_NAME'" >/dev/null 2>&1 || rc=$?
die_if_ssh_failed "$rc"
return "$rc"
}
# Authenticate once up front, so a bad target fails here rather than midway
# through, and so the later steps reuse this connection without re-prompting.
log "connecting to $TARGET"
CONNECT_RC=0
ssh "${SSH_OPTS[@]}" "$TARGET" -- true || CONNECT_RC=$?
[ "$CONNECT_RC" -eq 0 ] || die "cannot open an ssh session to $TARGET" 1
# Both methods need ncurses-bin on the far side.
ssh "${SSH_OPTS[@]}" "$TARGET" -- 'command -v tic >/dev/null 2>&1' \
|| die "$TARGET has no tic; install ncurses-bin there first" 1
if remote_has_entry; then
log "$TARGET already knows $TERM_NAME — nothing to do"
exit 0
fi
# --- attempt 1: pipe the description directly into tic ------------------------
log "attempt 1: piping infocmp output into tic on $TARGET"
TIC_RC=0
infocmp -x "$TERM_NAME" | ssh "${SSH_OPTS[@]}" "$TARGET" -- tic -x - || TIC_RC=${PIPESTATUS[1]}
die_if_ssh_failed "$TIC_RC"
if [ "$TIC_RC" -ne 0 ]; then
warn 'tic reported a problem (may only be a warning); checking the result'
fi
if remote_has_entry; then
log "success: $TERM_NAME installed on $TARGET via pipe"
exit 0
fi
# --- attempt 2: scp the description, compile into ~/.terminfo ----------------
log "attempt 2: copying description file and compiling into ~/.terminfo"
LOCAL_TI=$(mktemp "${TMPDIR:-/tmp}/${TERM_NAME}.XXXXXX.ti")
REMOTE_TI="/tmp/${TERM_NAME}.$$.ti"
trap 'rm -f "$LOCAL_TI"; cleanup' EXIT
infocmp -x "$TERM_NAME" > "$LOCAL_TI"
STEP_RC=0
ssh "${SSH_OPTS[@]}" "$TARGET" -- 'mkdir -p "$HOME/.terminfo"' || STEP_RC=$?
die_if_ssh_failed "$STEP_RC"
scp -o "ControlPath=$CTL" -q "$LOCAL_TI" "$TARGET:$REMOTE_TI" \
|| die "could not copy the description file to $TARGET" 1
STEP_RC=0
ssh "${SSH_OPTS[@]}" "$TARGET" -- \
"TERMINFO=\$HOME/.terminfo tic -x '$REMOTE_TI'; rm -f '$REMOTE_TI'" || STEP_RC=$?
die_if_ssh_failed "$STEP_RC"
if remote_has_entry; then
log "success: $TERM_NAME installed on $TARGET in ~/.terminfo"
exit 0
fi
die "both methods failed for $TERM_NAME on $TARGET
workaround: run remote sessions with TERM=xterm-256color" 1