git.lirion.de

Of git, get, and gud

aboutsummaryrefslogtreecommitdiffstats
path: root/bin/os-patching-adhoc
blob: 0b496be78dd28499333cb0a7975256a0c4a152db (plain)
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
#!/usr/bin/env bash
# shellcheck disable=SC1091

# Ad-hoc script for just patching the system.

# Environment variables:
# RKHUNTER - will be set to 0 if rkhunter exists and this variable is not set.
#            Setting it to 1 will skip using rkhunter.
# NEEDREST - will be set to 0 if needrestart exists and this variable is not set.
#            Setting it to 1 will skip using needrestart or any other helper
#            like dnf needs-restarting or zypper needs-rebooting / zypper ps.

# os_patching made by albatrossflavour et al., binary:
OSPBIN='/usr/local/bin/os_patching_fact_generation.sh'

. /etc/os-release || exit 1

# Debian act as if ID_LIKE wasn't necessary if ID == ID_LIKE. Great job, guys.
if [ "$ID" = "debian" ]; then
	ID_LIKE="debian"
fi


# 0. Internal helpers
hline() {
	[ -n "$COLUMNS" ] && MYCOLS="$COLUMNS"
	[ -z "$MYCOLS" ] && MYCOLS="$(/usr/bin/tput cols 2>/dev/null)"
	[ -z "$MYCOLS" ] && MYCOLS=16
	c=0
	printf '\033[1m'
	while [ "$c" -lt "$MYCOLS" ]; do
		printf '─'
		c="$((c+1))"
	done
	printf '\033[0m\n'
}

dline() {
	[ -n "$COLUMNS" ] && MYCOLS="$COLUMNS"
	[ -z "$MYCOLS" ] && MYCOLS="$(/usr/bin/tput cols 2>/dev/null)"
	[ -z "$MYCOLS" ] && MYCOLS=16
	c=0
	while [ "$c" -lt "$MYCOLS" ]; do
		printf '┄'
		c="$((c+1))"
	done
	printf '\n'
}

header() {
	if [ -n "$1" ]; then
		hline
		printf '    \033[3m\033[1m%b\033[0m\n' "$1"
		hline
	fi
}

footer() {
	if [ -n "$1" ]; then
		dline
		printf '\033[3m\033[1m%b\033[0m\n' "$1"
	fi
}


# 1. Find out about auxiliary helpers like rkhunter
declare NRSBIN RKHBIN
[ -z "$RKHUNTER" ] && RKHUNTER=2
[ -z "$NEEDREST" ] && NEEDREST=2
if [ "$NEEDREST" -gt 1 ] ; then
	for bin in /usr/sbin/needrestart /usr/bin/needrestart; do
		if [ -x "$bin" ]; then
			NRSBIN="$bin"
			NEEDREST=0
			break
		fi
	done
fi
if [ "$RKHUNTER" -gt 1 ] ; then
	for bin in /usr/bin/rkhunter /usr/sbin/rkhunter; do
		if [ -x "$bin" ]; then
			RKHBIN="$bin"
			RKHUNTER=0
			break
		fi
	done
fi


# 2. Patching.
case "$ID_LIKE" in
	"debian")
		APTBIN='/usr/bin/apt'
		APTOPTS=(
			'-o' 'Apt::Cmd::Disable-Script-Warning=true'
			'-o' 'Dpkg::Progress-Fancy=False'
			'-o' 'Apt::Color=False'
			'-o' 'Dpkg::Use-Pty=False'
			'-o' 'Quiet::NoUpdate=True'
			'-o' 'APT::Get::AutomaticRemove=False'
			'-o' 'APT::Get::AutomaticRemove::Kernels=False'
			'-o' 'APT::Get::Assume-Yes=True'
		)
		if [ "$RKHUNTER" -eq 0 ]; then
			header 'Starting rkhunter check'
			"$RKHBIN" -c --sk || exit 120
		fi
		# 2.1. Package list refresh
		header 'Starting package list update'
		"$APTBIN" "${APTOPTS[@]}" update || exit 110
		ULIST="$("$APTBIN" "${APTOPTS[@]}" -q list --upgradable | grep -iP '^[0-9a-z_:\-+\.]+/.+' | sed 's/^\([^/]\+\).*/\1/')"
		# Only one update will be one update with or without line-break, and NO update will be also with or without line-break.
		# Solution: Always add a line-break, and grep away empty lines.
		UPDATENUM="$(printf '%b\n' "$ULIST" | grep -vcP '^$')"
		printf '\033[3m\033[1m%b update(s) found.\033[0m\n' "$UPDATENUM"
		# 2.2. Package update.
		# 2.2.1. No updates found?
		if [ "$UPDATENUM" -lt 1 ]; then
			printf '\033[3m\033[1m\033[2mSkipping updates.\033[0m\n'
		else
		# 2.2.2. Updates found?
			header 'Starting package updates'
			"$APTBIN" "${APTOPTS[@]}" full-upgrade || exit 112
			header 'Starting package auto-removal'
			"$APTBIN" "${APTOPTS[@]}" --purge autoremove || exit 113
			# 2.2.3. Package file index update
			if [ -x /usr/bin/apt-file ]; then
				printf 'Starting apt-file update'
				/usr/bin/apt-file "${APTOPTS[@]}" update || true
			fi
			if [ "$RKHUNTER" -eq 0 ]; then
				header 'Starting rkhunter update'
				"$RKHBIN" --propupd || exit 121
			fi
			# 2.2.4. Requirement for reboot
			if [ "$NEEDREST" -eq 0 ]; then
				header 'Starting needrestart investigation'
				"$NRSBIN" -b
				# Outdated comment (kind of), see $NEEDREST at the top of the file --
				# If we don't have needrestart, this will fail - which is OK, without
				# a means of controlling whether reboot is necessary we will reboot in any case.
				if ! "$NRSBIN" -p; then
					footer 'Outdated libraries or kernel found, rebooting'
					/usr/bin/systemctl reboot || reboot
				else
					if [ "$UPDATENUM" -gt 0 ]; then
						if [ -x "$OSPBIN" ]; then
							header 'Starting os_patching_fact_generation.sh'
							ospstart="$(/usr/bin/date '+%s')"
							"$OSPBIN"
							ospend="$(/usr/bin/date '+%s')"
							footer "...done ($((ospend - ospstart)) seconds)."
						fi
					fi
				fi
			elif [ "$NEEDREST" -gt 1 ]; then
				footer 'No needrestart found, rebooting'
				/usr/bin/systemctl reboot || reboot
			fi
		fi
	;;
	"suse"*)
		# Caution:
		# 1. Broken package dependenciers will not be solved
		# 2. Orphaned packages will be kept in-place
		
		# global zypper options
		ZGOPTS=(
			'-q'
			# this option also works with non-patching commands:
			'--non-interactive-include-reboot-patches'
			# --refresh is only an option to addrepo. Adding --no-refresh to
			# a refresh action, however, will ignore that flag and toss a warning.
			# Warnings are not nice, but we'll ignore that over here...
			'--no-refresh'
		)
		# update/upgrade zypper options
		ZUOPTS=(
			'-y' '--auto-agree-with-licenses' '--no-allow-downgrade'
			'--allow-name-change' '--allow-arch-change' '--allow-vendor-change'
			'--solver-focus' 'Update'
		)
		header 'Refreshing zypper "services"'
		mystart="$(/usr/bin/date '+%s')"
		# for now, refresh-services doesn't even display a warning regarding --no-refresh,
		# but we'll keep it in case that changes. Dang, zypper is a mess at times.
		/usr/bin/zypper "${ZGOPTS[@]}" refresh-services | grep -v -- '--no-refresh' 
		[ "${PIPESTATUS[0]}" -eq 0 ] && printf 'OK.\n' || exit 110
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		header 'Refreshing repository cache'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/zypper "${ZGOPTS[@]}" refresh | grep -v -- '--no-refresh'
		[ "${PIPESTATUS[0]}" -eq 0 ] && printf 'OK.\n' || exit 111
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		# TODO: no amount of "-q" keeps zypper from delivering the verbose list of updates before
		# installing them. If only the zypper guys were modern in thinking script or automation approaches...
		header 'Running update'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/zypper "${ZGOPTS[@]}" --no-refresh up "${ZUOPTS[@]}" && printf 'OK.\n' || exit 112
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		header 'Running dist-upgrade'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/zypper "${ZGOPTS[@]}" --no-refresh dup "${ZUOPTS[@]}" && printf 'OK.\n' || exit 113
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		header 'Running "purge-kernels"'
		# yes, there is no general cleanup actions, there is only "-u" for "rm". But there _is_ purge-kernels.
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/zypper "${ZGOPTS[@]}" --no-refresh purge-kernels && printf 'OK.\n' || exit 114
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		# zypper: why deliver exit codes WHEN WE CAN TOSS EFFIN STRINGS AT THE CONSOLE ONLY
		# also... too many people rather tend to localise their systems....:
		export LANG=C # use "C" as safe haven, we DO NOT want this to fail
		# yes, in the very recent part they invented "needs-rebooting", but that only checks
		# core services and libraries... :( so we do both here. We want to be rather aggressive
		# on unattended updates in that we reboot if there are lingering "programs". Any of them.
		header 'Checking reboot requirement... '
		if ! /usr/bin/zypper -q needs-rebooting; then
			# TODO: zypper being locked by another "application" delivers RC7 - is this reserved
			#       exclusively for this case?
			footer 'Rebooting (zypper needs-rebooting)'
			/usr/bin/systemctl reboot
		elif [ "$(/usr/bin/zypper ps -sss 2>&1 | wc -l)" -gt 0 ]; then
			footer 'Rebooting (zypper ps)'
			/usr/bin/systemctl reboot
		else
			printf 'no reboot required.\n'
			if [ -x "$OSPBIN" ]; then
				header 'Starting os_patching_fact_generation.sh'
				ospstart="$(/usr/bin/date '+%s')"
				"$OSPBIN"
				ospend="$(/usr/bin/date '+%s')"
				footer "...done ($((ospend - ospstart)) seconds)."
			fi
		fi
	;;
	"rhel"*|"centos"*)
		# we do not use --skip-broken here - we keep our systems tidy, so any pollution may and should
		# cause an error :-)
		
		header 'Starting package list update'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/dnf -d1 makecache && printf 'OK.\n' || exit 110
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		header 'Starting package upgrade'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/dnf --comment='os_patching_adhoc' -d1 --obsoletes --best -y upgrade &&\
			printf 'OK.\n' || exit 111
		myend="$(/usr/bin/date '+%s')"
		footer "...done ($((myend - mystart)) seconds)."

		header 'Checking reboot requirement'
		if ! /usr/bin/dnf -d1 needs-restarting -r; then
			footer 'Outdated libraries or kernel found, rebooting.'
			/usr/bin/systemctl reboot
		else
			if [ -x "$OSPBIN" ]; then
				header 'Starting os_patching_fact_generation.sh'
				ospstart="$(/usr/bin/date '+%s')"
				"$OSPBIN"
				ospend="$(/usr/bin/date '+%s')"
				footer "...done ($((ospend - ospstart)) seconds)."
			fi
		fi
	;;
esac