-
Notifications
You must be signed in to change notification settings - Fork 2
/
RunScript
executable file
·284 lines (235 loc) · 9.93 KB
/
RunScript
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
#!/usr/bin/env bash
# test: RunScript -v -nh --elevate -- touch /mnt/c/hi.txt \; sleep 2 \; echo \"all done\"
# no dependencies: do not source other scripts here as we do not assume other scripts are in the path
# run in Homebrew's bash if needed
[[ "$BASH" == "/bin/bash" && "$(uname)" == "Darwin" ]] && { /opt/homebrew/bin/bash /usr/local/data/bin/RunScript "$@"; return; }
usage()
{
ScriptUsage "$1" "\
Usage: RunScript [OPTION]... SCRIPT
Run a shell script, executable, or a series of commands.
-s, --ssh HOST run the script on the host using SSH
-x --x-server force initialization of the X server"
exit $1
}
usageVerbose()
{
ScriptUsageEcho "
Environment options:
-c, --credential configure a credential manager
-cm, --manager=MANAGER use a specific credential manager
-H, --hashi configure Hashi tools
-sa, --ssh-agent configure the SSH agent
Other options:
-E, --elevate in Windows run the script with an elevated (Administrator) token
-e, --exec exec the script so it retains this process ID
--functions use functions in the script
-hs, --hashi-service return errors for a HashiCorp service, where 0=success 1=warning 2=error
-ie, --ignore-errors ignore non-fatal errors
-nh, --no-hide do not hide the elevated script window
-np, --no-pause never pause
-nq, --no-quote do not hide quote script arguments
-m, --multiple allow multiple commands, i.e. \"sudo RunScript --multiple 'ls; pause'\"
Useful in cases where bash -c cannot be used
-p, --pause pause after the command is run, useful for transitory windows
-pe, --pause-error pause after the command is run with an error, useful for transitory windows
-s, --sensitive do not log script which contains sensitive information
Notes:
- finds function.sh even if it is not in the PATH. This is useful for initial startup, non-login shells,
or running as root.
- simplifies pausing the script. This is useful when running elevated in Windows,
i.e. elevate RunScript --pause-error service start WpnUserService_5f5dbe
- all diagnostic (verbose) output is sent to the standard error so RunScript can be called from
a subshell, i.e. dir=\"\$(RunScript --elevate --verbose -- unc mount "//pi1/root")\""
}
init() { defaultCommand="run"; }
#
# run command
#
runArgStart()
{
unset -v credential credentialManager elevate exec hashi hashiService ignoreErrors multiple noPause noQuote pause pauseError sensitive ssh sshAgent xServer
credentialManagerArg=() sshArgs=()
unset -v eval
credentialConfArgs=() script=() windowStyle="--window-style hidden" functions="# no functions loaded"
}
runArgs() { (( $# == 0 )) && return; script=( "$@" ); (( shift+=$# )); return 0; }
runArgEnd() { [[ $script || $otherArgs ]] && return; MissingOperand "script"; }
runOpt()
{
case "$1" in
--credential|-c) credential="--credential";;
--credential-manager|--credential-manager=*|-cm|-cm=*) ScriptOptGet "credentialManager" "credential-manager" "$@"; credentialManagerArgs=(--credential-manager "$credentialManager") credentialConfArgs=(--manager "$credentialManager");;
--elevate|-E) elevate="--elevate";;
--exec|-e) exec="--exec";;
--functions) functions=". function.sh || return";;
--hashi|-H) hashi="--hashi";;
--hashi-service|-hs) hashiService="--hashi-service";;
--ignore-errors|-ie) ignoreErrors="--ignore-errors";;
--multiple|-m) multiple="--multiple" eval="eval";;
--no-hide|-nh) windowStyle="";;
--no-pause|-np) noPause="--no-pause";;
--no-quote|-nq) noQuote="--no-quote";;
--pause|-p) pause="--pause";;
--pause-error|-pe) pauseError="--pause-error";;
--sensitive|-s) sensitive="--sensitive";;
--ssh-agent|-sa) sshAgent="--ssh-agent";;
--ssh|-s) ScriptOptGet "ssh" "$@"; sshArgs=(--ssh "$ssh");;
--x-server|-x) xServer="--x-server"; InitializeXServer || return;;
*) return 1;;
esac
}
#
# run command
#
runCommand()
{
set -- "${script[@]}" "${otherArgs[@]}"
# elevate
[[ $elevate ]] && ! IsElevated && { runElevated "$@"; return; }
# configure credential manager
[[ $credential ]] && { RunLog CredentialConf --unlock "${credentialManagerArg[@]}" "${globalArgs[@]}" || return; }
# configure Hashi tools
[[ $hashi ]] && { RunLog HashiConf "${globalArgs[@]}" || return; }
# configure the SSH agent
[[ $sshAgent ]] && { RunLog SshAgentConf "${globalArgs[@]}" || return; }
# run the script
if [[ $exec ]]; then
exec -- "$@"
elif [[ $ssh ]]; then
runSsh "$ssh" "$@"
else
[[ $verbose || ! $quiet ]] && [[ ! $sensitive ]] && LogScript 2 "$@"
$eval "$@"
fi
local result="$?"
log1 "the script returned $result"
# pause
if [[ "$result" != "0" && $pauseError ]]; then
EchoErr "The script returned error $result."
pause
elif [[ $pause ]]; then
pause
fi
# return errors for a HashiCorp service where 0=success 1=warning 2=error
[[ $hashiService ]] && (( result > 0 )) && (( result+=1 ))
# return
[[ $ignoreErrors ]] && return 0 || return $result
}
# runSsh - run command over ssh
runSsh()
{
local host="$1"; shift
local script="$(cat <<-EOF
[[ -f "$functionFile" ]] && { . "$functionFile" || exit; }
$@
EOF
)"
if IsLocalHost "$host"; then
[[ $verbose ]] && { ScriptErr "running the following script locally:"; echo "$script" | AddTab >&2; }
bash -c "$script"
else
# DISPLAY="" - disable warning "Warning: No xauth data; using fake authentication data for X11 forwarding."
# -X - enable X11 forwardining for credential store prompt
# -t - allocate a pseudo-terminal to allow the sudo password to be entered on the terminal if needed
local args=(-q -X -t)
[[ $verbose ]] && { ScriptErr "running the following script on '$host':"; echo "$script" | AddTab >&2; }
DISPLAY="" ssh "${args[@]}" "$host" "$script"
fi
}
# runElevated - run command elevated
runElevated()
{
! CanElevate && { ScriptErrQuiet "unable to elevate"; return 1; }
local dir="$(mktemp -d -t 'RunScript.XXXXXXXXXX')" || return
# script: the script file
# scriptLog: a file which contains the standard output and error of the script
# scriptResult: a file which contains the return code (result) of the script
# scriptStart: a file which is written when the script starts
local scriptFile="$dir/script.sh" scriptLog="$dir/log.txt" scriptResult="$dir/result.txt" scriptStart="$dir/start.txt"
# create the script log file now so inotifywait does not return when it is created, it should wait for the scriptResult
touch "$scriptLog" || return
# create scriptStart now, we will wait for it to be removed
touch "$scriptStart" || return
# create the script and run it elevated
# - use the same CREDENTIAL_MANAGER as we have now
# - ScriptReturn sets the script variable to an array of quoted arguments so spaces, single, and double quotes are preserved
# - example: RunScript --elevate powershell 'Get-VM | where {\$_.Name -eq \"oversoul-wvm1\"}'
cat > "$scriptFile" <<-EOF
#!/usr/bin/env bash
rm "$scriptStart" || return
export CREDENTIAL_MANAGER="$CREDENTIAL_MANAGER"
$functions
EOF
if [[ $noQuote ]]; then
cat >> "$scriptFile" <<-EOF
$@ |& tee "$scriptLog"
EOF
else
local script=("$@")
cat >> "$scriptFile" <<-EOF
$(ScriptReturn script) # for reference
$(ArrayShow script) |& tee "$scriptLog"
EOF
fi
cat >> "$scriptFile" <<-EOF
echo \${PIPESTATUS[0]} > "$scriptResult"
EOF
# logging
[[ $sensitive ]] && log2 "script: hidden" || LogFile2 "$scriptFile"
[[ $verbose || $pause || $pauseError ]] && unset windowStyle
# ensure the user with an elevated token can run the script
chmod ugo+x "$scriptFile" || return
# execute the script
log1 "running script ('$scriptFile')..."
local wait; ! InPath "hstart64.exe" && wait="--wait"
if ! start --elevate $wait $windowStyle $verbose RunScript "$scriptFile" "${globalArgs[@]}" $credential "${credentialManagerArg[@]}" $exec $hashi $hashiService $ignoreErrors $noPause $pause $pauseError $sensitive $sshAgent "${sshArgs[@]}" $xServer && [[ ! -f "$scriptResult" ]]; then
ScriptErr "unable to run the script"; ScriptTryVerbose; pause
fi
# wait for removal of the script start file
if [[ -f "$scriptStart" ]]; then
logp1 "$(ScriptPrefix)waiting for script start ('$scriptStart' removal)..."
inotifywait --event delete --quiet --quiet --timeout 2 "$scriptStart"
[[ -f "$scriptStart" ]] && { ScriptErr "the script did not start in a timely fashion"; runElevatedCleanup; return 1; }
logp1 "started\n"
fi
# wait for creation of the script result file
if [[ ! -f "$scriptResult" ]]; then
logp1 "$(ScriptPrefix)waiting for script result ('$scriptResult')..."
inotifywait --event create --quiet --quiet "$dir/" # two quiets for very silent (no output)
logp1 "found\n"
fi
# return the script log to standard output
if [[ -f "$scriptLog" ]]; then
if (( $(GetFileSize "$scriptLog") > 0 )); then
LogFile2 "$scriptLog"; cat "$scriptLog"
else
log2 "script output: none"
fi
fi
# get the script result (return code) if it was created (if the script was killed, cancelled, or interrupted it will not have a result)
[[ -f "$scriptResult" ]] && scriptResult="$(cat "$scriptResult")"
log1 "script result=${RESET}${scriptResult}"
runElevatedCleanup; return "$scriptResult"
}
runElevatedCleanup() { rm -fr "$dir"; }
#
# helper
#
# functions which may be used before we load function.sh
EchoErr() { echo "$@" >&2; }
pause() { [[ $noPause ]] && { [[ $verbose ]] && EchoErr "pause skipped"; return; }; local response m="${@:-Press any key when ready...}"; ReadChars "" "" "$m"; }
# loadFunctions - load functions from function.sh
# - look in the path, the current directory, the script directory, and /usr/local/bin
# - sets functionFile to the function file
# - sets scriptDir to the script directory
loadFunctions()
{
# initialize
scriptDir="${BASH_SOURCE[0]%/*}"
functionFile="$scriptDir/function.sh"
# source function file - set quiet to supress PlatformConf warning
quiet="--quiet" . "$functionFile" "" || { EchoErr "RunScript: unable to run the '$file' script"; [[ $pause ]] && pause; return 1; }
return 0
}
loadFunctions && . "$scriptDir/script.sh" && ScriptRun "$@"