-
Notifications
You must be signed in to change notification settings - Fork 0
/
install_glit.sh
executable file
·396 lines (318 loc) · 11.9 KB
/
install_glit.sh
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#!/usr/bin/env bash
# ------------------------------------------------------------------------------------------
#
# NOTE: UNLIKE the main `glit` scripts, the install script prefers brevity over readability.
# This is because the install script is intended to be a standalone script that can be
# downloaded and run with as few dependencies as possible, meaning we are unable to
# modularise it as we have done so with the main `glit` utility.
#
# As such, brevity is preferred to reduce the overall size of the script and allow
# developers to quickly understand the general structure and logic of the script, at
# an acceptable cost to the readability of deeper implementation details.
#
# For example, this means that shorter conditional statements like these:
# [[ "$INSTALL_MODE" == "remote" ]] && TEMP_DIR=$(mktemp -d) || :
#
# Are often preferred over these:
# if [[ "$INSTALL_MODE" == "remote" ]]; then
# TEMP_DIR=$(mktemp -d)
# fi
#
# ------------------------------------------------------------------------------------------
# --- Set Script Options ---
# NOTE: Due to exit on error, append '|| :' to add a no-op fallback to commands that might fail where we should continue.
set -o errexit
set -o nounset
# --- Declare Variables ---
# Set global variables
RED="\e[31m"
YELLOW="\e[33m"
GREEN="\e[32m"
BLUE="\e[34m"
RESET="\e[0m"
INSTALL_MODE="remote"
LOCAL_PATH="$(dirname "$(pwd)")"
VERSION="latest"
GH_API_ENDPOINT="https://api.github.com/repos/justJackjon/glit"
GLIT_REPO_URL="https://github.com/justJackjon/glit"
GLIT_DIR="/opt/glit"
SYMLINK_PATH="/usr/local/bin/glit"
PREFIX_COMMAND=""
IS_ROOT=false
UNATTENDED=false
IS_INPUT_INTERACTIVE=false
IS_OUTPUT_INTERACTIVE=false
PLATFORM="$(uname)"
MINIMUM_BASH_VERSION=4.2
INSTALLED_BASH_VERSION="${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"
BASH_VERSION_ISSUE=false
MISSING_REQUIRED_PKGS=()
MISSING_RECOMMENDED_PKGS=()
declare -A DEPENDENCIES=(
["curl"]="required"
["git"]="required"
["rsync"]="required"
["uname"]="required"
["realpath"]="required"
["bc"]="required"
["tput"]="recommended"
)
declare -A DEP_TO_PKG_MAP=(
["curl"]="curl"
["git"]="git"
["rsync"]="rsync"
["uname"]="coreutils"
["realpath"]="coreutils"
["tput"]="ncurses"
["bash"]="bash"
["bc"]="bc"
)
# Set conditional values
(( $(id -u) == 0 )) && IS_ROOT=true || :
[[ -t 0 ]] && IS_INPUT_INTERACTIVE=true || :
[[ -t 1 ]] && IS_OUTPUT_INTERACTIVE=true || :
[[ ! $IS_ROOT ]] && PREFIX_COMMAND="sudo " || :
[[ "$PLATFORM" == "Linux" && -f "/etc/debian_version" ]] && DEP_TO_PKG_MAP["tput"]="ncurses-bin" || :
[[ "$INSTALL_MODE" == "remote" ]] && TEMP_DIR=$(mktemp -d) || :
cleanup() {
echo -e "\nInstallation aborted. Cleaning up temporary files...\n"
rm -rf "$TEMP_DIR"
exit 1
}
# NOTE: Only trap on ERR, INT and TERM signals. TEMP_DIR is explicitly removed on successful EXIT.
trap cleanup ERR INT TERM
# Check for missing dependencies
for DEPENDENCY in "${!DEPENDENCIES[@]}"; do
command -v "$DEPENDENCY" &> /dev/null && continue
if [[ "${DEPENDENCIES[$DEPENDENCY]}" == "required" ]]; then
MISSING_REQUIRED_PKGS+=("${DEP_TO_PKG_MAP["$DEPENDENCY"]}")
else
MISSING_RECOMMENDED_PKGS+=("${DEP_TO_PKG_MAP["$DEPENDENCY"]}")
fi
done
# Quick sanity check on the following variables as we rm -rf them later.
# NOTE: This is just for belt and braces. The user is unable to set these variables.
if [[ "$GLIT_DIR" == "/" ]] || [[ "$TEMP_DIR" == "/" ]]; then
echo -e "\nInvalid value for GLIT_DIR or TEMP_DIR. Aborting."
exit 1
fi
# --- Argument Parsing ---
while (( "$#" )); do
case "$1" in
local)
INSTALL_MODE="local"
shift
;;
remote)
INSTALL_MODE="remote"
shift
;;
-v|--version)
VERSION="$2"
shift 2
;;
-u|--unattended)
UNATTENDED=true
shift
;;
*)
LOCAL_PATH="$1"
shift
;;
esac
done
# --- Fn Declarations ---
print() {
local message_type="$1"
local message="$2"
local color=""
local prefix=""
local suffix=""
case "$message_type" in
error) color="$RED"; prefix="Error: ";;
warning) color="$YELLOW"; prefix="Warning: ";;
success) color="$GREEN"; suffix=" ✔";;
info) color="$BLUE";;
esac
if $IS_OUTPUT_INTERACTIVE; then
echo -e "${color}\n${prefix}${message}${suffix}$RESET"
else
echo -e "\n${prefix}${message}${suffix}"
fi
}
print_dependencies() {
local message_type=$1; shift
local message=$1; shift
local dependencies=("$@")
print "$message_type" "$message\n"
for dependency in "${dependencies[@]}"; do
echo " - $dependency"
done
}
create_os_advice() {
local os_message="$1"; shift
local command_prefix="$1"; shift
local command_name=i"$1"; shift
local install_command="$1"; shift
local extra_command="$1"; shift
local lines_of_additional_info=("$@")
local deps_space=""
(( ${#MISSING_REQUIRED_PKGS[@]} > 0 && ${#MISSING_RECOMMENDED_PKGS[@]} > 0 )) && deps_space=" " || :
print info "It seems you are using $os_message. You may be able to install missing dependencies using $command_name:\n"
[[ ! -z "$extra_command" ]] && echo -e " \`${command_prefix}${extra_command}\`" || :
echo -e " \`${command_prefix}${install_command} ${MISSING_REQUIRED_PKGS[*]-}${deps_space}${MISSING_RECOMMENDED_PKGS[*]-}\`\n"
for line in "${lines_of_additional_info[@]}"; do
[[ -n "$line" ]] && echo -e "$line" || :
done
[[ -n "${lines_of_additional_info[*]}" ]] && echo || :
}
ask_should_force_install() {
echo
read -p "Do you want to install \`glit\` anyway? [y/N]: " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo -e "\nInstalling \`glit\`..."
else
echo -e "\nInstallation aborted."
exit 0
fi
}
ask_should_reinstall() {
print info "\`glit\` is already installed."
if $UNATTENDED || ! $IS_INPUT_INTERACTIVE; then
print info "Running in non-interactive mode. Assuming 'yes' for reinstall."
return 0
fi
echo
read -p "Do you want to reinstall it? [y/N]: " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
return 0
else
return 1
fi
}
is_bash_version_issue() {
local installed=()
local required=()
IFS='.' read -ra installed <<< "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"
IFS='.' read -ra required <<< "$MINIMUM_BASH_VERSION"
for i in "${!installed[@]}"; do
if (( installed[i] < required[i] )); then
return 0
elif (( installed[i] > required[i] )); then
return 1
fi
done
return 1
}
# --- Dependency Checks ---
if is_bash_version_issue; then
BASH_VERSION_ISSUE=true
MISSING_REQUIRED_PKGS+=("${DEP_TO_PKG_MAP["bash"]}")
fi
if
$BASH_VERSION_ISSUE || \
(( ${#MISSING_REQUIRED_PKGS[@]} > 0 )) || \
(( ${#MISSING_RECOMMENDED_PKGS[@]} > 0 ))
then
SHOULD_ABORT=false
echo -e "\n!!!--------------------------------------------"
if $BASH_VERSION_ISSUE; then
print error "Bash version $INSTALLED_BASH_VERSION.x is installed."
echo
echo "The minimum version requred by \`glit\` is $MINIMUM_BASH_VERSION.x."
echo "Please upgrade Bash and try again."
SHOULD_ABORT=true
fi
if (( ${#MISSING_REQUIRED_PKGS[@]} > 0 )); then
print_dependencies "error" "Missing or unsupported dependencies:" "${MISSING_REQUIRED_PKGS[@]}"
SHOULD_ABORT=true
fi
if (( ${#MISSING_RECOMMENDED_PKGS[@]} > 0 )); then
print_dependencies "warning" "\`glit\` will work best if you install the following recommended dependencies:" "${MISSING_RECOMMENDED_PKGS[@]}"
fi
echo -e "\n--------------------------------------------!!!"
case $PLATFORM in
Darwin)
create_os_advice "macOS" "$PREFIX_COMMAND" "Homebrew" "brew install" "" \
"If you don't have Homebrew yet, visit https://brew.sh to get started." \
"In a typical brew installation, you must ensure \`/usr/local/bin/\` is before \`/bin/\` in your \$PATH."
;;
Linux)
if [[ -f /etc/debian_version ]]; then
create_os_advice "a Debian-based system" "$PREFIX_COMMAND" "apt" "apt install" "apt update" ""
elif [[ -f /etc/redhat-release ]]; then
create_os_advice "a RedHat-based system" "$PREFIX_COMMAND" "yum or dnf" "yum install" "" ""
elif [[ -f /etc/alpine-release ]]; then
create_os_advice "Alpine Linux" "$PREFIX_COMMAND" "apk" "apk add" "" ""
fi
;;
*)
echo "Please install these dependencies using your system's package manager."
;;
esac
! $IS_ROOT && echo -e "INFO: Only use \`sudo\` when necessary and when you understand the risks.\n" || :
echo -e "!!!--------------------------------------------"
if $SHOULD_ABORT; then
print error "Installation aborted due to unmet prerequisites."
exit 1
fi
! $UNATTENDED && $IS_INPUT_INTERACTIVE && ask_should_force_install || :
fi
# --- Installation ---
# Check if the user has write permissions for the install directories
if [[ ! -w "/opt" || ! -w "/usr/local/bin" ]]; then
print error "You do not have write permissions for /opt or /usr/local/bin."
print info "You can try running this script with sudo, but you should be certain that you can trust the content that you are installing."
exit 1
fi
# Check if the `glit` dir and symlink already exist
if [[ -d "$GLIT_DIR" || -L "$SYMLINK_PATH" ]]; then
if ask_should_reinstall; then
rm -rf "$GLIT_DIR"
rm -f "$SYMLINK_PATH"
else
echo -e "\nInstallation aborted."
exit 0
fi
fi
# Determine the installation mode and act accordingly
if [[ "$INSTALL_MODE" == "local" ]]; then
if [[ ! -d "$LOCAL_PATH/glit" ]]; then
print error "The \`glit\` directory was not found at the specified or default path ($LOCAL_PATH/glit). Please provide a valid path or check your current directory."
exit 1
fi
cp -r "$LOCAL_PATH/glit" "$GLIT_DIR"
else
# NOTE: Using grep and sed to parse JSON so we don't add a dependency on `jq`
if [[ "$VERSION" == "latest" ]]; then
RELEASE_URL=$(curl -s "$GH_API_ENDPOINT/releases/latest" | grep tarball_url | sed 's/.*: "\(.*\)",/\1/')
else
RELEASE_URL=$(curl -s "$GH_API_ENDPOINT/releases/tags/$VERSION" | grep tarball_url | sed 's/.*: "\(.*\)",/\1/')
fi
# Check if RELEASE_URL is empty, which might be due to an invalid or non-existent tag
if [[ -z "$RELEASE_URL" ]]; then
print error "Failed to retrieve the release URL for version '$VERSION'. Please ensure the provided version exists."
exit 1
fi
print info "Downloading \`glit\` from $RELEASE_URL...\n"
curl -L "$RELEASE_URL" -o "$TEMP_DIR/glit.tar.gz"
tar -xzf "$TEMP_DIR/glit.tar.gz" -C "$TEMP_DIR"
mv $TEMP_DIR/justJackjon-glit-* "$GLIT_DIR"
# NOTE: The cleanup trap will remove temp files on ERR, INT and TERM signals, but not on [successful] EXIT.
rm -rf "$TEMP_DIR"
print success "Temporary files have been cleaned up"
fi
# Create a symlink to the main `glit` script
ln -s "$GLIT_DIR/main.sh" "$SYMLINK_PATH"
# Set permissions to make `glit` accessible to normal users
chmod -R 755 "$GLIT_DIR"
chmod 755 "$SYMLINK_PATH"
# Print success messages
print success "\`glit\` has been successfully installed to $GLIT_DIR"
print success "A symlink for \`glit\` has been created in $SYMLINK_PATH"
# Verify if the symlink directory is in the PATH and provide usage instructions
if echo "$PATH" | tr ':' '\n' | grep -qx "$(dirname "$SYMLINK_PATH")"; then
glit --help
else
print info "Since $(dirname "$SYMLINK_PATH") is not in your PATH, you might not be able to run \`glit\` directly. Please add $(dirname "$SYMLINK_PATH") to your PATH and then try running 'glit --help'."
fi