-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstall.sh
executable file
·405 lines (329 loc) · 10.1 KB
/
install.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
397
398
399
400
401
402
403
404
405
#!/usr/bin/env bash
set -Eeuo pipefail
### IMPORTANT DEVELOPER NOTE ###
# You should ALWAYS test any changes you make to this script by running:
# ./install.sh -vv --fake-home "test"
# The error flags set above are very, very picky about bad coding practices, and
# you'd be amazed at what they preclude. Never assume your changes are safe
# before you test!
# Variable Definitions {{{
# The name of the current executable
declare THIS_EXEC="$(basename "${BASH_SOURCE[0]}")"
# Where dotfiles will be installed (useful for testing the installation on a
# fake home directory).
declare TARGET_HOME="${HOME}"
# This repository's root directory.
declare DOTFILES_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Dotfiles directories (will be set after we've established TARGET_HOME)
declare DOTFILES_HOME
declare DOTFILES_SHELL
declare COMMON_SHELL
declare COMMON_SOURCE
declare DOTFILES_LINK
declare DOTFILES_COPY
declare DOTFILES_ZDOTDIR
# Filesystem directories to create
# Main projects directory
declare WS="${HOME}/workspace"
# Practice projects directory
declare PRAC="${HOME}/practice"
# Directory for installed third-party applications
declare APPS="${HOME}/applications"
# Third party archives directory
declare ARCHIVES="${APPS}/archives"
# The file containing paths to use when dynamically constructing the
# PATH environment variable.
declare PATH_FILE="${HOME}/.path"
# Program flags
# Force all actions that would otherwise involve answering a prompt
declare FORCE_INSTALL=false
# Logging control variables
declare LOG_LEVEL=1
declare LOG_TO_FILE=""
# }}}
# OS Preparation {{{
# Perform other preparation steps depending on the current operating system.
# This function runs BEFORE the `setup()` function.
prepare_for_os() {
set_dotfiles_variables
source_common_defs
init_path_file
export_path
spath
}
init_path_file() {
local pathFileSrc="${DOTFILES_COPY}/dotfiles_to_copy/.path"
if [ ! -f "${PATH_FILE}" ]; then
log_info "Copying initial path file to: ${PATH_FILE}"
cp "${pathFileSrc}" "${PATH_FILE}"
fi
}
# }}}
# Setup/Cleanup {{{
# Set all dotfiles-related variables after all arguments have been parsed and
# key variables have been set.
set_dotfiles_variables() {
DOTFILES_HOME="${TARGET_HOME}/.dotfiles"
DOTFILES_SHELL="${DOTFILES_REPO}/shell"
COMMON_SHELL="${DOTFILES_SHELL}/common"
COMMON_SOURCE="${COMMON_SHELL}/source"
DOTFILES_LINK="${DOTFILES_REPO}/link"
DOTFILES_COPY="${DOTFILES_REPO}/copy"
DOTFILES_ZDOTDIR="${DOTFILES_SHELL}/zsh/zdotdir"
}
# Take care of backing up existing ~/.dotfiles directory
backup_existing_installation() {
local oldDotfilesDir
# Safe name for backup directory
oldDotfilesDir="$(mktemp -u "${DOTFILES_HOME}.bak.XXXXXXXXXX")"
if [ -d "${DOTFILES_HOME}" ]; then
log_info "Backing up existing dotfiles installation to ${oldDotfilesDir}"
mv "${DOTFILES_HOME}" "${oldDotfilesDir}"
fi
}
# Check for an existing dotfiles installation at $DOTFILES_HOME.
check_existing_installation() {
log_info "Checking for existing dotfiles installation"
test -h "${DOTFILES_HOME}" || test -d "${DOTFILES_HOME}"
}
# Figure out what to do if an existing dotfiles installation is found.
remove_existing_installation() {
local response=""
command cat <<EOF
An existing dotfiles installation was found at ${DOTFILES_HOME}.
It must be removed before this installation can progress.
EOF
while ! echo "${response}" | grep -q '^[YyNn]$'; do
echoe "Remove it and continue with the installation? [y/n]"
IFS="" read -r response
done
if echo "${response}" | grep -q '^[Nn]$'; then
echoe "Exiting dotfiles installation."
exit 1
elif [ -h "${DOTFILES_HOME}" ]; then
log_info "Removing old dotfiles symlink"
rm -f "${DOTFILES_HOME}"
else
log_info "Removing old dotfiles installation"
rm -rf "${DOTFILES_HOME}"
fi
}
# Performs initial setup.
setup() {
log_info "Setting up..."
if ! ${FORCE_INSTALL}; then
check_existing_installation && remove_existing_installation
fi
backup_existing_installation
ensure_dirs_present
}
# }}}
# Help {{{
_help() {
command cat <<EOF
${THIS_EXEC}
Install tjtrabue's dotfiles on the current system. For the most part, this
script just creates a bunch of symlinks, so it is highly non-destructive. As
opposed to overwriting the user's existing dotfiles, this script backs up all
of the existing files before creating any symlinks. Nothing should be lost in
the process. Check the 'backup' directory created by this script if you wish to
restore your old dotfiles.
USAGE:
${THIS_EXEC} [OPTIONS]
OPTIONS:
-h | --help
Print the help message (this message) and exit.
-v | --verbose
Increase the logging output level. This command may be specified multiple
times to further increase verbosity of output.
-f | --force
Force dotfiles to install, assuming "yes" for all prompts.
This option should be used with caution, as it may overwrite some of your
files, even though this script tries hard not to do that.
-k | --fake-home <target_directory>
Install dotfiles to a different directory. The default is to install
dotfiles to the user's \$HOME directory. This option changes the default
behavior, telling the install script to instead install everything to a
different directory. This only real use for this option is to test the
install script.
EXAMPLES:
Install dotfiles with INFO logging output:
./install.sh -vv
Force a new dotfiles installation, wihtout prompting for confirmation:
./install.sh --force
Test the install script by installing dotfiles to a fake home directory:
./install.sh -vv --fake-home ./test
EOF
}
# }}}
# Primary Functions {{{
# Link files/directories to the ~/.config directory.
link_config() {
local dotfilesConfig="${DOTFILES_LINK}/config"
local homeConfig="${TARGET_HOME}/.config"
local homeConfigBackup="${homeConfig}.bak"
log_info "Linking ${dotfilesConfig} to ${homeConfig}"
if [ -d "${homeConfig}" ]; then
log_info "Backing up files in ${homeConfig} to ${homeConfigBackup}"
mv -f "${homeConfig}" "${homeConfigBackup}"
fi
ln -sf "${dotfilesConfig}" "${homeConfig}"
if [ -d "${homeConfigBackup}" ]; then
log_info "Restoring files from ${homeConfigBackup} to ${homeConfig}"
rsync -ah "${homeConfigBackup}/" "${homeConfig}"
rm -rf "${homeConfigBackup}"
fi
log_info "Done."
}
# Link the LSP configuration dir to ~/.lsp
link_lsp_config() {
local lspConfigDir="${DOTFILES_LINK}/lsp"
local lspConfigTarget="${HOME}/.lsp"
if [ -d "${lspConfigTarget}" ]; then
rm -rf "${lspConfigTarget}"
fi
log_info "Linking ${lspConfigDir} to ${lspConfigTarget}"
ln -sf "${lspConfigDir}" "${lspConfigTarget}"
}
# Link the gpg.conf file to ~/.gnupg/gpg.conf
link_gpg_config() {
local gpgConfFile="${DOTFILES_LINK}/gnupg/gpg.conf"
local gnupgHome="${HOME}/.gnupg"
local gpgConfTarget="${gnupgHome}/gpg.conf"
if [ ! -f "${gpgConfFile}" ]; then
err "No GPG config file found at: ${BLUE}${gpgConfFile}${NC}"
return 1
fi
log_info "Linking ${BLUE}${gpgConfFile}${NC} to ${BLUE}${gpgConfTarget}${NC}"
mkdir -p "${gnupgHome}"
ln -sf "${gpgConfFile}" "${gpgConfTarget}"
}
# Link the repository itself, if necessary.
link_repo() {
log_info "Linking dotfiles repository to: ${DOTFILES_HOME}"
if [ "${DOTFILES_REPO}" != "${DOTFILES_HOME}" ]; then
ln -sf "${DOTFILES_REPO}" "${DOTFILES_HOME}"
fi
log_info "Done."
}
# Create dotfile symlinks from user's home dir to those managed by the dotfiles
# repository. This keeps the user's dotfiles in sync with the repository.
# Use the `dotsync` function to keep dotfiles up to date after the initial
# installation.
link_dotfiles() {
log_info "Linking dotfiles"
find "${DOTFILES_LINK}/home" -type f -exec ln -sfb -t "${TARGET_HOME}" '{}' \;
log_info "Done"
}
# Link the Zsh dotfiles directory to ~/.zsh
link_zdotdir() {
local targetZdotdir="${TARGET_HOME}/.zsh"
log_info "Linking Zsh directory ${DOTFILES_ZDOTDIR} to ${targetZdotdir}"
if [ -h "${targetZdotdir}" ]; then
rm -f "${targetZdotdir}"
elif [ -d "${targetZdotdir}" ]; then
rm -rf "${targetZdotdir}"
fi
ln -sf "${DOTFILES_ZDOTDIR}" "${targetZdotdir}"
}
# Copy one-time transfer files.
copy_dotfiles() {
local oneTimeTransfersDir="${DOTFILES_COPY}/dotfiles_to_copy"
log_info "Copying dotfiles from: ${BLUE}${oneTimeTransfersDir}${NC}"
find "${oneTimeTransfersDir}" -maxdepth 1 -mindepth 1 -type f \
-exec cp -f -t "${TARGET_HOME}/" '{}' \;
log_info "Copying complete"
}
# Create important directories.
ensure_dirs_present() {
log_info "Creating important directories"
local dirs=(
"${TARGET_HOME}"
"${WS}"
"${PRAC}"
"${APPS}"
"${ARCHIVES}"
)
local dir
for dir in "${dirs[@]}"; do
mkdir -p "${dir}" &>/dev/null
done
}
# Import common aliases and functions for use within this script.
source_common_defs() {
local f
for f in "${COMMON_SOURCE}"/{aliases,functions,other}/*; do
. "${f}"
done
}
# Initialize extra application configuration.
run_init_scripts() {
log_info "Running initialization scripts"
runinit emacs
}
# Main that calls all subroutines
main() {
setup
copy_dotfiles
link_repo
link_dotfiles
link_zdotdir
link_config
link_lsp_config
link_gpg_config
run_init_scripts
add_extra_os_vars
add_extra_paths_to_path_file
}
# }}}
# Need to prepare OS before CLI option parsing because we may not even have
# access to GNU getopt yet.
prepare_for_os
# Parse CLI Options {{{
declare args=$(getopt -o hvfk: \
--long help,verbose,force,fake-home: \
-n 'install.sh' -- "$@" \
)
eval set -- "$args"
# extract options and their arguments into variables.
while true; do
case "$1" in
-h | --help)
_help
shift
exit 0
;;
-v | --verbose)
((LOG_LEVEL += 1))
shift
;;
-f | --force)
FORCE_INSTALL=true
shift
;;
-k | --fake-home)
case "$2" in
"")
shift 2
;;
*)
TARGET_HOME="${2}"
shift 2
;;
esac
;;
--)
shift
break
;;
*)
err "Unknown option $1 to ${THIS_EXEC}"
exit 2
;;
esac
done
# }}}
# Main execution
main
# Modeline for this file (KEEP IT COMMENTED!)
# vim:foldenable:foldmethod=marker:foldlevel=0