forked from rackerlabs/auter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauter
executable file
·457 lines (397 loc) · 13.7 KB
/
auter
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
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
#!/bin/bash
#
# auter is a yum-cron type package which implements automatic updates on an
# individual server with features such as predownloading packages and reboots.
#
#
# Copyright 2016 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use
# this file except in compliance with the License. You may obtain a copy of the
# License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
declare -r -x AUTERVERSION="1.0.0"
declare -r -x SCRIPTDIR="/etc/auter"
declare -r -x DATADIR="/var/lib/auter"
declare -r -x LOCKFILE="${DATADIR}/enabled"
declare -r -x PIDFILE="/var/run/auter/auter.pid"
# Set default options - these can be overridden in the config file or with a command line argument
declare -x -l AUTOREBOOT="no"
declare -x -i REBOOTCALL=0
declare -x -a PACKAGEMANAGEROPTIONS
declare -x -l PREDOWNLOADUPDATES="yes"
declare -x -l ONLYINSTALLFROMPREP="no"
declare -x CONFIGFILE="/etc/auter/auter.conf"
declare -x DOWNLOADDIR="/var/cache/auter"
declare -x -i MAXDELAY=3600
declare -x CONFIGSET="default"
declare -x ROTATE="5"
declare -x PREPREPSCRIPTDIR="${SCRIPTDIR}/pre-prep.d"
declare -x POSTPREPSCRIPTDIR="${SCRIPTDIR}/post-prep.d"
declare -x PREAPPLYSCRIPTDIR="${SCRIPTDIR}/pre-apply.d"
declare -x POSTAPPLYSCRIPTDIR="${SCRIPTDIR}/post-apply.d"
declare -x PREREBOOTSCRIPTDIR="${SCRIPTDIR}/pre-reboot.d"
declare -x POSTREBOOTSCRIPTDIR="${SCRIPTDIR}/post-reboot.d"
declare -x -i SKIPALLSCRIPTS=0
declare -x -a SKIPPHASESCRIPTS
declare -x -a SKIPSCRIPTNAMES
# Adding extra super-user PATHS if required
for p in /usr/local/sbin /usr/sbin /sbin; do
[[ ! $PATH =~ (^|:)$p(:|$) ]] && declare -x PATH="$p:$PATH"
done
function default_signal_handling() {
trap 'rm -f "$PIDFILE"' SIGINT SIGTERM
}
# The man page is generated in part from the print_help() text by running:
# help2man --include=auter.help2man --no-info ./auter > auter.man
function print_help() {
echo "Usage: auter [--enable|--disable|--status|--prep|--apply|--reboot|--postreboot] [OPTIONS]
Automatic Update Transaction Execution by Rackspace. A wrapper around cron and
yum/dnf/apt to manage system updates with the ability to configure automatic
reboots and custom scripts.
Actions:
-e, --enable Enable auter
-d, --disable Disable auter. Also deletes unused pidfile if it exists
-s, --status Show whether enabled or disabled
--prep Pre-download updates before applying
--apply Apply updates, and reboot if AUTOREBOOT=yes
--reboot Reboot system including pre/post reboot scripts
--postreboot Run post reboot script
Options:
--config=FILE Specify the full path to an auter config file. Defaults to
/etc/auter/auter.conf
--stdout Always log to STDOUT, regardless of not having a tty
--maxdelay Override MAXDELAY from the command line
--skip-all-scripts
Skip the executions of all custom scripts (Default in /etc/auter/*.d/)
--skip-scripts-by-phase=PHASE
Skip the execution of the custom scripts for the specified
phase. You can specify myltiple phases.
Valid Phases: pre-prep, post-prep, pre-apply, post-apply,
pre-reboot, post-reboot.
Example: --skip-scripts-by-phase=\"pre-prep,post-apply,pre-reboot\"
--skip-scripts-by-name=SCRIPTNAME
Skip specific scripts by name. You can specify myltiple phases.
Example: --skip-scripts-by-name=\"10-configsnap-pre, 20-startApp.sh\"
--no-wall If possible, suppress shutdown wall messages in the reboot phase
-h, --help Show this help text
-v, --version Show the version"
}
function logit() {
# If running on a tty, or the --stdout option is provided, print to screen
( tty -s || [[ $STDOUT ]] ) && echo "$1"
logger -p info -t auter "$1"
}
function read_config() {
if [[ -f "$CONFIGFILE" ]]; then
source "$CONFIGFILE"
elif [[ "$CUSTOMCONFIG" ]]; then
logit "ERROR: Custom config file $CONFIGFILE does not exist"
quit 5
else
logit "WARNING: Using default config values."
fi
IFS=' ' read -r -a PACKAGEMANAGEROPTIONS <<< "${PACKAGEMANAGEROPTIONS[*]}"
}
function rotate_file {
local output_file="$1"
#Rotate old file
for i in $(seq $((ROTATE-1)) -1 1); do
[[ -e "$output_file.$i" ]] && mv -f "$output_file.$i" "$output_file.$((i+1))"
done
# Move base files to basefile.1
[[ -e "$output_file" ]] && mv "$output_file" "$output_file.1"
readarray -t remove_files <<< "$(find "$DATADIR" -type f -name "$(basename "$output_file").*" | awk -F'.' -v s="$ROTATE" '($NF+1)>s')"
# Finally check for extra file from the rotation, and remove if it exists
[[ -n "${remove_files[*]}" ]] && rm -f "${remove_files[@]}"
}
function run_script {
local script="$1"
local phase="$2"
if [[ "$SKIPALLSCRIPTS" -eq 0 ]]; then
local skip=0
# Check if the phase scripts have been skipped with --skip-scripts-by-phase
if [[ -n "${SKIPPHASESCRIPTS[*]}" ]]; then
for _skipphasescript in "${SKIPPHASESCRIPTS[@]}"; do
[[ "${_skipphasescript,,}" == *"${phase,,}"* ]] && skip=1
done
fi
# Check if the script has been excluded with --skip-scripts-by-name
if [[ -n "${SKIPSCRIPTNAMES[*]}" ]]; then
for _skipscriptname in "${SKIPSCRIPTNAMES[@]}"; do
[[ ${script,,} == *"${_skipscriptname,,}"* ]] && skip=1
done
fi
if [[ "$skip" -eq 0 ]]; then
if [[ -x "$script" ]] && [[ -f "$script" ]]; then
logit "INFO: Running $phase script $script"
$script
local RC=$?
if [[ "$RC" -ne 0 ]]; then
logit "ERROR: $phase script $script exited with non-zero exit code $RC. Aborting auter run."
quit 8
fi
elif [[ -f "$script" ]]; then
logit "ERROR: $phase script $script exists but the execute bit is not set. Skipping."
fi
else
logit "INFO: Skipping script $script"
fi
else
logit "INFO: The --skip-all-scripts flag was used. NOT executing $script as part of the $phase phase"
fi
}
# Check whether yum, or dnf is available
function check_package_manager() {
for pkg_mgr in dnf yum apt-get; do
command -v "$pkg_mgr" && return 0
done
return 1
}
function reboot_server() {
for _script in "$PREREBOOTSCRIPTDIR"/*; do
run_script "$_script" "Pre-Reboot"
done
if [[ -d "${POSTREBOOTSCRIPTDIR}" ]]; then
logit "INFO: Creating post-reboot hook /etc/cron.d/auter-postreboot-${CONFIGSET}"
echo -e "@reboot root /usr/bin/auter --postreboot --config ${CONFIGFILE}" > "/etc/cron.d/auter-postreboot-${CONFIGSET}"
chown root:root "/etc/cron.d/auter-postreboot-${CONFIGSET}"
chmod 0644 "/etc/cron.d/auter-postreboot-${CONFIGSET}"
fi
logit "INFO: Rebooting server"
if [[ $NOWALLMSG -eq 1 ]] && shutdown --help | grep -q "no-wall"; then
/sbin/shutdown --no-wall -r +2 "auter: System reboot to apply updates" &>/dev/null &
else
/sbin/shutdown -r +2 "auter: System reboot to apply updates" &>/dev/null &
fi
}
function post_reboot() {
logit "INFO: Removed post-reboot hook: /etc/cron.d/auter-postreboot-$CONFIGSET"
rm -f "/etc/cron.d/auter-postreboot-$CONFIGSET"
tty -s || sleep 300
for _script in "$POSTREBOOTSCRIPTDIR"/*; do
run_script "$_script" "Post-Reboot"
done
}
function print_status() {
if [[ -f "$LOCKFILE" ]] && [[ -f "$PIDFILE" ]]; then
if currentpidstatus="$(kill -0 "$(cat $PIDFILE)" 2>&1 )"; then
echo "auter is currently enabled and running"
elif [[ "$currentpidstatus" == *"No such process"* ]]; then
echo "auter is currently enabled and pid file exists but process is dead"
elif [[ "$currentpidstatus" == *"Operation not permitted"* ]]; then
echo "auter is enabled but permission denied on $PIDFILE. Run 'auter --status' as root"
fi
elif [[ -f "$LOCKFILE" ]] && [[ ! -f "$PIDFILE" ]]; then
echo "auter is currently enabled and not running"
else
echo "auter is currently disabled"
fi
}
# Needed to cleanup our PID file. The only argument is the exit code to use.
function quit() {
[[ -f "$PIDFILE" ]] && rm -f "$PIDFILE"
exit "$1"
}
function log_last_run() {
logit "INFO: Auter successfully ran at $(date -Iseconds)"
}
#
# Main
#
# Make sure we trap signals and clean up the PID before exiting
default_signal_handling
ARGS="$*"
if ! OPTS=$(getopt -n "$0" -o 'edhvs' --long 'prep,apply,enable,disable,reboot,postreboot,version,help,stdout,no-wall,status,config:,skip-all-scripts,skip-scripts-by-phase:,skip-scripts-by-name:,maxdelay:' -- "$@"); then
echo "See '$0 --help' for valid options."
quit 1
fi
eval set -- "$OPTS"
unset OPTS
_required=false
while true ; do
case "$1" in
'-h'|'--help')
print_help
quit 0
;;
'-v'|'--version')
echo "auter ${AUTERVERSION}"
exit 0
;;
'--stdout')
STDOUT=1
shift
;;
'--no-wall')
NOWALLMSG=1
shift
;;
'--maxdelay')
__MAXDELAY="$2"
shift 2
;;
'--config')
CONFIGSET=""
CONFIGFILE="$2"
CUSTOMCONFIG=1
shift 2
;;
'--prep')
_required=true
PREP=1
shift
;;
'--apply')
_required=true
APPLY=1
shift
;;
'--reboot')
_required=true
REBOOTCALL=1
shift
;;
'--postreboot')
_required=true
POSTREBOOT=1
shift
;;
'-e'|'--enable')
_required=true
ENABLE=1
shift
;;
'-d'|'--disable')
_required=true
DISABLE=1
shift
;;
'--skip-all-scripts')
SKIPALLSCRIPTS=1
shift
;;
'--skip-scripts-by-phase')
IFS=',' read -r -a SKIPPHASESCRIPTS <<< "$2"
shift 2
;;
'--skip-scripts-by-name')
IFS=',' read -r -a SKIPSCRIPTNAMES <<< "$2"
shift 2
;;
'-s'|'--status')
print_status
quit 0
;;
'--')
shift
break
;;
*)
quit 1
;;
esac
done
if ! $_required; then
echo "Auter must be run with one of the following:
$0 [--enable|--disable|--status|--prep|--apply|--reboot|--postreboot]"
echo "See auter --help for details."
quit 1
fi
unset _required
declare -x PACKAGE_MANAGER
if ! PACKAGE_MANAGER=$(check_package_manager); then logit "ERROR: Cannot find yum, dnf or apt-get"; quit 7; fi
# Do this after option processing so --help and --status still work.
if [[ "$(whoami)" != "root" ]]; then
echo "Script must be run as root"
quit 5
fi
if [[ ! -d "$DATADIR" ]]; then
logit "FATAL ERROR: auter DATADIR $DATADIR does not exist."
quit 5
fi
if [[ "$ENABLE" ]] ; then
touch "$LOCKFILE"
echo "DO NOT DELETE THIS FILE. This file is automatically generated by auter. To disable auter, run auter --disable instead." > "$LOCKFILE"
logit "INFO: auter enabled"
quit 0
fi
if [[ "$DISABLE" ]] ; then
rm -f "$LOCKFILE"
if [[ -f "$PIDFILE" ]] && ! kill -0 "$(cat $PIDFILE)" &>/dev/null; then
rm -f "$PIDFILE"
logit "INFO: auter disabled and cleared pid file"
else
logit "INFO: auter disabled"
fi
quit 0
fi
if [[ ! -f "$LOCKFILE" ]]; then
logit "WARNING: auter disabled. Please run auter --enable to enable automatic updates."
quit 4
fi
# PID file checking to make sure multiple copies of auter don't run at once.
PIDDIR=$(dirname "$PIDFILE")
if [[ ! -d "$PIDDIR" ]]; then
install -m 755 -o root -g root -d "$PIDDIR"
fi
# Note: ALL script exits after this block must use the quit() function instead so the PIDfile is cleaned up.
if [[ -f "$PIDFILE" ]]; then
logit "ERROR: auter is already running or $PIDFILE exists."
quit 6
else
echo "$$" > "$PIDFILE"
fi
read_config
# CONFIGSET needs to be set if we're using a custom configuration file.
if [[ -z "$CONFIGSET" ]]; then
logit "ERROR: You must specify the CONFIGSET variable in custom config file $CONFIGFILE to avoid naming collisions"
quit 5
fi
if [[ "$ONLYINSTALLFROMPREP" == "yes" ]]; then
if [[ ! -d "$DOWNLOADDIR/$CONFIGSET" ]]; then
install -m 755 -o root -g root -d "$DOWNLOADDIR/$CONFIGSET"
elif [[ $(stat -c %G%U%a "$DOWNLOADDIR") != rootroot[0-9][0-9][0145] ]]; then
logit "ERROR: $DOWNLOADDIR/$CONFIGSET does not have the correct permissions."
quit 3
fi
fi
# Validate the SKIPPHASESCRIPTS values
if [[ -n "${SKIPPHASESCRIPTS[*]}" ]]; then
for _skipphasescript in "${SKIPPHASESCRIPTS[@]}"; do
if [[ "$_skipphasescript" =~ ^(pre|post)-(prep|apply|reboot)$ ]]; then
logit "INFO: The --skip-scripts-by-phase argument was used. Skipping $_skipphasescript scripts"
else
logit "ERROR: The --skip-scripts-by-phase argument was used with an invalid option: '$_skipphasescript'. Exiting"
quit 1
fi
done
fi
logit "INFO: Running with: $0 $ARGS"
# If --maxdelay is set on the command line, override the config file.
if [[ -n "$__MAXDELAY" ]]
then
MAXDELAY="$__MAXDELAY"
logit "INFO: Overriding MAXDELAY from command line: --maxdelay=$__MAXDELAY"
else
tty -s && MAXDELAY=1 && logit "INFO: Running in an interactive shell, disabling all random sleeps"
fi
[[ "$MAXDELAY" -lt 1 ]] && MAXDELAY=1
# There is an explicit quit here to avoid auter automatically running any
# other unexpected functions.
[[ "$POSTREBOOT" ]] && post_reboot && quit 0
# Source the module for the specific package manager.
. /usr/lib/auter/auter.module
# The following 3 functions are provided by the previously sourced /usr/lib/auter/auter.module
# Run the package manager specific check for locks
[[ "$PREP" ]] && prepare_updates
[[ "$APPLY" ]] && apply_updates
[[ $REBOOTCALL -eq 1 ]] && reboot_server
quit 0