-
Notifications
You must be signed in to change notification settings - Fork 19
/
tht
executable file
·239 lines (208 loc) · 7.73 KB
/
tht
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/bin/bash
if [ -n "$DEBUG" ]; then set -x; set -e; fi
IMAGE=${IMAGE:-ethack/tht:latest}
# switch to ghcr.io if dockerhub fails due to rate limits
#IMAGE=ghcr.io/ethack/tht:latest
PERSISTENT=/usr/local/share/zsh/
# if the current user doesn't have docker permissions run with sudo
DOCKER=docker
if [ ! -w "/var/run/docker.sock" ]; then
DOCKER="sudo --preserve-env docker"
fi
usage() {
cat <<EOF
Usage: tht [<command>] [<flags>] [-- [<args>]]
Run tht without arguments for normal use.
Commands:
install <user@remotesystem>
Installs THT on a remote system over SSH
(requires docker on remote system)
update Download the latest THT image and updates this script
version Print the THT version
Flags:
--experimental Enable experimental features
--dev Start THT in development mode
Arguments:
-- Any arguments after the -- are run inside THT in _interactive_ mode.
To execute a script with tht you may either:
- Pass the commands in through stdin (e.g. echo whoami | tht)
- Give the path to a script file as an argument (e.g. tht script.sh)
EOF
}
exists () {
command -v $1 >/dev/null 2>&1
}
# sets up to execute a script within tht
prepare_script_execution() {
SCRIPT_EXECUTION=true
local script_file=$(realpath "$1")
# mount the temp file in the container and execute it
DOCKER_ARGS+=(--mount "source=${script_file},destination=/run.sh,type=bind")
# check if script has a hash-bang, but ignore tht as a shell
if head -n 1 "$script_file" | grep -q '^#!' \
&& ! head -n 1 "$script_file" | grep -q '[/#]tht$'; then
# execute it directly with the defined interpreter
# NOTE: this will fail if tmpfs is mounted as noexec
#chmod +x $script_file
if [ ! -x "$script_file" ]; then
>&2 echo "WARNING: $1 is not executable"
else
CMD+=(/run.sh)
fi
else
# otherwise run using zsh
CMD+=(zsh /run.sh)
fi
# pass the rest as arguments to the script
shift
CMD+=("$@")
}
DOCKER_ARGS=(
container run
--rm
--hostname "$(hostname)"
--init
--pid host
--cap-add SYS_NICE
--env "TERM=${TERM}"
--mount source=/etc/localtime,destination=/etc/localtime,type=bind
--mount source=/,destination=/host,type=bind
# resolve the current path in case it's in a symlinked directory
--workdir "/host/$(realpath "${PWD}")"
)
CMD=()
SCRIPT_EXECUTION=false
SCRIPT_ARGS=()
while [ $# -gt 0 ]; do
case $1 in
help|-h|--help)
usage
exit 0
;;
install)
shift
echo "Installing THT script"
cat $(realpath "$0") | ssh "$@" "cat >tht; chmod +x tht; sudo mv tht /usr/local/bin/tht"
echo "Uploading THT container image"
if exists pv && exists jq; then
# display nice progress bar
docker image save "$IMAGE" | pv --size $(docker image inspect "$IMAGE" | jq '.[].Size') | ssh -C "$@" docker image load
else
docker image save "$IMAGE" | ssh -C "$@" docker image load
fi
echo "Done"
exit
;;
update|pull)
# exit if any of the update commands fail
set -e
echo "Downloading latest THT image..."
$DOCKER pull $IMAGE
SUDO=
if [ ! -w "$0" ]; then
SUDO="sudo"
fi
# hidden feature to disable clobbering this script for development
if [[ $1 != pull ]]; then
echo "Self-updating THT script..."
$SUDO curl -s https://raw.githubusercontent.com/ethack/tht/main/tht -o "$0"
fi
# remove all dangling tht images
if [[ $($DOCKER image ls --no-trunc --quiet $IMAGE --filter "dangling=true" | wc -l) -gt 0 ]]; then
echo "Removing old THT images..."
$DOCKER image rm --force $($DOCKER image ls --no-trunc --quiet $IMAGE --filter "dangling=true")
fi
exit 0
;;
version)
CMD=(zsh -c "cat /etc/tht-release")
;;
--dev)
# change to directory this script is in, following any symlinks
pushd $(dirname $(readlink -f "${BASH_SOURCE[0]}")) >/dev/null
# loop through dynamically to prevent having to update on every new addition
while IFS= read -r -d $'\0' PROG; do
# mount bin scripts for development
DOCKER_ARGS+=(--mount "source=${PWD}/${PROG},destination=/usr/local/bin/${PROG#"bin"},type=bind")
done < <(find bin -type f -print0 2>/dev/null)
while IFS= read -r -d $'\0' CONF; do
# mount zsh configs for development
DOCKER_ARGS+=(--mount "source=${PWD}/${CONF},destination=/root/${CONF#"zsh"},type=bind")
done < <(find zsh -type f -print0 2>/dev/null)
while IFS= read -r -d $'\0' CHEAT; do
# mount navi cheats development
DOCKER_ARGS+=(--mount "source=${PWD}/${CHEAT},destination=/root/.local/share/navi/cheats/${CHEAT#"cheatsheets"},type=bind")
done < <(find cheatsheets -type f -print0 2>/dev/null)
DOCKER_ARGS+=(--mount "source=${PWD}/test,destination=/usr/local/test,type=bind")
popd >/dev/null
# also enable experimental mode
DOCKER_ARGS+=(--env "EXPERIMENTAL=true")
;;
--experimental)
DOCKER_ARGS+=(--env "EXPERIMENTAL=true")
;;
--)
shift
if $SCRIPT_EXECUTION; then
# anything after -- is passed as an argument to the run script
CMD+=("$@")
else
# anything after -- is treated as a command to run in interactive mode
CMD+=(zsh -c "$*")
fi
break
;;
# undocumented compatibility with bash -c to work with ansible.builtin.shell
-c)
# TODO: give a tht prefix to mktemp
# create temporary file to store commands/script
SCRIPT_FILE=$(mktemp -u)
trap "rm -f '$SCRIPT_FILE'" EXIT
echo "$2" > "$SCRIPT_FILE"
shift
chmod +x "$SCRIPT_FILE"
prepare_script_execution "$SCRIPT_FILE" "${@:2}"
;;
*)
# if there's not already a script slated for execution
# and the argument is a valid file
if ! $SCRIPT_EXECUTION && [ -f "$1" ]; then
prepare_script_execution "$1" "${@:2}"
fi
;;
esac
shift
done
# -t returns true if file descriptor (stdin) is a tty,
# which means it is connected to the terminal;
# the opposite is if it is redirected or piped
# if there's not already a script slated for execution
# and stdin is being piped, execute stdin within tht
if ! $SCRIPT_EXECUTION && [ ! -t 0 ]; then
# TODO: give a tht prefix to mktemp
# create temporary file to store commands/script
SCRIPT_FILE=$(mktemp -u)
trap "rm -f '$SCRIPT_FILE'" EXIT
cat > "$SCRIPT_FILE"
chmod +x "$SCRIPT_FILE"
prepare_script_execution "$SCRIPT_FILE"
fi
# mount docker inside if we can find it, and it's not the snap version
if [[ -e $(command -v docker) ]] \
&& ! snap list docker >/dev/null 2>&1 \
&& [[ -e /var/run/docker.sock ]]; then
# mount the docker binary inside as well so we don't have to bundle it in THT
DOCKER_ARGS+=(
--mount "source=$(command -v docker),destination=/usr/local/bin/docker,type=bind"
--mount "source=/var/run/docker.sock,destination=/var/run/docker.sock,type=bind"
)
fi
# create and use a persistent volume
$DOCKER volume create tht_zsh-cache >/dev/null && \
DOCKER_ARGS+=(--mount "source=tht_zsh-cache,destination=${PERSISTENT},type=volume")
# connect a tty when not running a background command/script
if ! $SCRIPT_EXECUTION; then
DOCKER_ARGS+=(--interactive --tty)
fi
$DOCKER "${DOCKER_ARGS[@]}" "$IMAGE" "${CMD[@]}"
# TODO: create a service option for long running processes (e.g. vector), that have healthchecks and restart policy