-
Notifications
You must be signed in to change notification settings - Fork 1
/
my-deploy-setup
executable file
·225 lines (185 loc) · 7 KB
/
my-deploy-setup
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
#! /usr/bin/env bash
readonly args=("$@")
# shellcheck source=../share/my/bash/helpers.bash
source "${XDG_DATA_HOME:-$HOME/.local/share}"/my/bash/helpers.bash
_my-script-prelude
# shellcheck disable=SC2154 # selfBase is assigned by _my-script-prelude
if (( ${#args[@]} != 1 )); then
std cat 1>&2 <<-EOF
Usage: $selfBase ssh://[USER@]HOSTNAME[:PORT]
or: $selfBase vagrant://[NAME|ID]
or: $selfBase dir:LOCALDIR
EOF
exit 1
fi
# Defaults
readonly defaultDotfilesRepo=$HOME/.dotfiles
# '$USER' is a placeholder that will be substituted with the target user's name.
readonly defaultDotfilesRefspecs="main:user/\$USER main"
# Functions
function process-vars
{
process-args
process-env-vars
readonly baseDirOfNeededByBootstrap="${MY_DATA_HOME:?}"/my
readonly dirsNeededByBootstrap=(sh bash deploy-setup)
readonly prefixNeededByBootstrap=.local/share/my
}
function process-args
{
targetUrl="${args[0]}"
reuseConnOpts=()
# shellcheck disable=SC2016
targetHomeDirExpr='"${HOME:?}"'
if [[ "$targetUrl" =~ ^dir:(.*)$ ]]; then
local targetLocalDir=${BASH_REMATCH[1]}
if [ "$targetLocalDir" ]; then
targetLocalDir=$(norm_abs_path "$targetLocalDir") || true
if ! [ -e "$targetLocalDir" ] && [ "$targetLocalDir" ]; then
std mkdir -p "$targetLocalDir" || return
fi
if [ -d "$targetLocalDir" ] \
&& [ -r "$targetLocalDir" ] && [ -w "$targetLocalDir" ] && [ -x "$targetLocalDir" ]
then
targetHomeDirExpr=$(quote "$targetLocalDir")
targetUrl=shell://localhost
else
error "Invalid directory: ${targetLocalDir@Q}!" 2
fi
else
error "Missing directory name in URL: ${targetUrl@Q}!" 3
fi
else
# Reusing the SSH connection, via multiplexing, can greatly speed-up the numerous `ssh`
# commands this script does, for some target hosts that are slow at establishing each SSH
# connection. Persistence of the master connection must be done, because each `ssh`
# command terminates sequentially, and so persisting the initial connection is needed to
# reuse it. (Note: The length of a control-socket's pathname must be minimized, due to
# the size limit of `sockaddr_un.sun_path`.)
reuseConnOpts=(
-o ControlMaster=auto
-o ControlPath="${MY_RUNTIME_DIR:?}"/my/deploy-setup/%C # (%C is constant-length.)
-o ControlPersist=1h # Just enough for subsequent commands of this script.
)
std mkdir -p "${MY_RUNTIME_DIR:?}"/my/deploy-setup
if [[ "$targetUrl" =~ ^vagrant:// ]]; then
reuseConnOpts=(-- "${reuseConnOpts[@]}")
fi
fi
readonly targetUrl targetHomeDirExpr reuseConnOpts
}
function process-env-vars
{
localDotfilesRepo=${MY_DEPLOY_SETUP_DOTFILES_FROM_REPO:-$defaultDotfilesRepo}
if ! [[ "$localDotfilesRepo" = */@(.git|.dotfiles) ]]; then
localDotfilesRepo+=/.git
fi
localDotfilesRepo=$(abs_path "$localDotfilesRepo") || return
[ -r "$localDotfilesRepo" ] || error "Invalid dotfiles repo: ${localDotfilesRepo@Q}!" 4
# Note: The HEAD of the repo, which usually is the branch for the user that ran this script,
# could be included, e.g.: 'HEAD:of-user-that-deployed'.
# The first refspec is considered the primary.
dotfilesRefspecs=${MY_DEPLOY_SETUP_DOTFILES_FROM_REFSPECS:-$defaultDotfilesRefspecs}
split-on-words "$dotfilesRefspecs" dotfilesRefspecs
extraConnMethodOpts=${MY_DEPLOY_SETUP_EXTRA_CONN_METHOD_OPTS-}
split-on-words "$extraConnMethodOpts" extraConnMethodOpts
readonly localDotfilesRepo dotfilesRefspecs extraConnMethodOpts
}
function rsh { rsh-cd . "$@" ;}
function rsh-cd {
(( $# == 2 )) || exit
local shOpts=('set -e;')
(( VERBOSE >= 5 )) && shOpts+=('set -x;')
(( VERBOSE >= 6 )) && shOpts+=('set -v;')
remote-sh-cd "$targetUrl" "$1" "${shOpts[*]} $2" \
"${reuseConnOpts[@]}" "${extraConnMethodOpts[@]}"
}
function rsh-in-bd { rsh-cd "$(quote "$targetBootstrapDir")" "$@" ;}
function check-can-command-target
{
# Basic sanity check before the looping in `make-bootstrap-dir`.
#
rsh 'true' || error "Cannot execute a simple shell command in the target host!" 5
}
function has-repo-already
{
if rsh-cd "$targetHomeDirExpr" '[ -e .dotfiles ] || [ -e .git ] || [ -e .git-hidden ]'
then
info "Already have a repository in $targetHomeDirExpr. Skipping."
return 0
else
return 1
fi
}
function make-bootstrap-dir
{
local attempts=5
while (( attempts-- >= 1 )); do
# (Use `mktemp` in the local host, because it might not be available in a remote host.)
targetBootstrapDir=$(gnu mktemp -u /tmp/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX)
if rsh "mkdir -m u=rwx,g=,o= $targetBootstrapDir"; then
break
else
unset targetBootstrapDir
fi
done
readonly targetBootstrapDir
[ "${targetBootstrapDir:-}" ] || fail "Failed to make bootstrap directory in target host!" 6
}
function copy-bootstrap-scripts
{
local - ; set -o pipefail
make-bootstrap-dir
gnu tar --create --directory="$baseDirOfNeededByBootstrap" --dereference \
--transform="s,^,$prefixNeededByBootstrap/," \
"${dirsNeededByBootstrap[@]}" \
| \
rsh-in-bd 'tar xf -' # (Any `tar` implementation, not only GNU's, should work this way.)
}
function copy-dotfiles-bundle
{
local - ; set -o pipefail
local -r bundleRefs=("${dotfilesRefspecs[@]%:*}")
# As bundle is created, immediately output it over the connection to the target host without
# having an intermediate local file.
#
git --git-dir="$localDotfilesRepo" bundle create - "${bundleRefs[@]}" \
| \
rsh-in-bd 'cat > ./dotfiles.git.bundle'
}
function copy-bootstrap-files
{
copy-bootstrap-scripts
copy-dotfiles-bundle
}
function run-bootstrap
{
local quotedDotfilesRefspecs=() rs
for rs in "${dotfilesRefspecs[@]}"; do
quotedDotfilesRefspecs+=("$(quote "$rs")")
done
rsh-in-bd "
HOME=$targetHomeDirExpr \\
VERBOSE=$(quote "$VERBOSE") \\
./.local/share/my/deploy-setup/bootstrap/start0.sh \\
$targetHomeDirExpr \\
./dotfiles.git.bundle \\
${quotedDotfilesRefspecs[*]}
"
}
function cleanup {
rsh "rm -r -f $(quote "$targetBootstrapDir")"
}
# Operations
process-vars
check-can-command-target
# Check if we should not do anything. Doing nothing avoids invoking start0.sh when this script is
# applied to the same home multiple times (e.g. via a `vagrant up` trigger).
#
if has-repo-already ; then
exit 0 # Considered a success.
else
copy-bootstrap-files
run-bootstrap || error "Bootstrap script failed!" 6
cleanup # Don't cleanup if anything above fails (`errexit` will exit before here, if so).
fi