git.lirion.de

Of git, get, and gud

aboutsummaryrefslogtreecommitdiffstats
path: root/bin/os-patching-adhoc
blob: ebe83184f6cc3a8d7574599897210d5d9fb57226 (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
#!/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

		header 'Refreshing zypper "services"'
		mystart="$(/usr/bin/date '+%s')"
		/usr/bin/zypper -q --non-interactive refresh-services && 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 -q --non-interactive refresh && 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 -q --no-refresh --non-interactive-include-reboot-patches \
			up -y --auto-agree-with-licenses --solver-focus 'Update' && 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 -q --no-refresh --non-interactive-include-reboot-patches \
			dup -y --allow-name-change --allow-arch-change --allow-vendor-change --no-allow-downgrade \
			--auto-agree-with-licenses --solver-focus 'Update' && printf 'OK.\n' || exit 113
		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