updater.sh 7.62 KB
Newer Older
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
1
2
#!/bin/sh

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
# Copyright (c) 2016, CZ.NIC, z.s.p.o. (http://www.nic.cz/)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#    * Neither the name of the CZ.NIC nor the
#      names of its contributors may be used to endorse or promote products
#      derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL CZ.NIC BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

28
29
. /lib/functions.sh

Michal Vaner's avatar
Michal Vaner committed
30
31
32
33
34
35
36
37
38
39
40
timeout() {
	# Let a command run for up to $1 seconds. If it doesn't finishes by then, kill it.
	# The timeout starts it in background. Also, a watcher process is started that'd kill
	# it after the timeout and waits for it to finish. If the program finishes, it kills
	# the watcher.
	TIME="$1"
	PROG="$2"
	shift 2
	"$PROG" "$@" >"$TMP_DIR"/t-output &
	export CPID="$!"
	(
Karel Koci's avatar
Karel Koci committed
41
42
43
		# Note that Busybox doesn't have sleep as build-in, but as external
		# program. So backgrounded wait can stay running even after script exits
		# if not killed.
44
		sleep "$TIME"
Michal Vaner's avatar
Michal Vaner committed
45
46
		echo "Killing $PROG/$CPID after $TIME seconds (stuck?)" | logger -t updater -p daemon.error
		kill "$CPID"
47
		sleep 5
Michal Vaner's avatar
Michal Vaner committed
48
49
		kill -9 "$CPID"
		# Wait to be killed by the parrent
50
		sleep 60
Michal Vaner's avatar
Michal Vaner committed
51
52
53
54
	) &
	WATCHER="$!"
	wait "$CPID"
	RESULT="$?"
Karel Koci's avatar
Karel Koci committed
55
56
	CPID=
	kill "$WATCHER"
Michal Vaner's avatar
Michal Vaner committed
57
	wait "$WATCHER"
Karel Koci's avatar
Karel Koci committed
58
	WATCHER=
Michal Vaner's avatar
Michal Vaner committed
59
60
	return "$RESULT"
}
61

62
63
64
65
66
67
68
69
70
config_load updater
config_get_bool DISABLED override disable 0

if [ "$DISABLED" = "1" ] ; then
	echo "Updater disabled" | logger -t daemon.warning
	echo "Updater disabled" >&2
	exit 0
fi

71
72
73
74
75
76
NET_WAIT=10
while [ $NET_WAIT -gt 0 ] && ! ping -c 1 -w 1 api.turris.cz >/dev/null 2>&1; do
	NET_WAIT=$(($NET_WAIT - 1))
	# Note: no sleep here because we wait in ping
done

77
get-api-crl || {
Martin Petráček's avatar
Martin Petráček committed
78
	timeout 120 create_notification -s error "Updater selhal: Chybí CRL, pravděpodobně je problém v připojení k internetu." "Updater failed: Missing CRL, possibly broken Internet connection." || echo "Create notification failed" | logger -t updater -p daemon.error; 
79
80
81
82
83
84
85
86
87
88
89
90
91
	exit 1
}

STATE_DIR=/tmp/update-state
LOCK_DIR="$STATE_DIR/lock"
LOG_FILE="$STATE_DIR/log2"
PID_FILE="$STATE_DIR/pid"
APPROVAL_ASK_FILE=/usr/share/updater/need_approval
APPROVAL_GRANTED_FILE=/usr/share/updater/approvals
EXIT_CODE=1
BACKGROUND=false
BACKGROUNDED=false
TMP_DIR="/tmp/$$.tmp"
92
PKGUPDATE_ARGS=""
93

94
95
while [ "$1" ] ; do
	case "$1" in
96
97
		-h|--help)
			echo "Usage: updater.sh [OPTION]..."
Karel Koci's avatar
Karel Koci committed
98
			echo "-e (ERROR|WARNING|INFO|DBG|TRACE)	Message level printed on stderr. In default set to INFO."
99
100
			exit 0
			;;
101
102
103
104
105
106
		-b)
			BACKGROUND=true
			;;
		-r)
			BACKGROUNDED=true
			;;
107
108
109
		-w|-n)
			echo "Argument $1 ignored as a compatibility measure for old updater."
			;;
110
		*)
111
			PKGUPDATE_ARGS="$PKGUPDATE_ARGS $1"
112
113
114
115
116
117
118
119
120
121
			;;
	esac
	shift
done

if ! $BACKGROUNDED ; then
	# Prepare a state directory and lock
	mkdir -p /tmp/update-state
	if ! mkdir "$LOCK_DIR" ; then
		echo "Already running" >&2
122
		echo "Already running" | logger -t updater -p daemon.warning
123
124
125
		EXIT_CODE=0
		exit
	fi
126
127
	cat /dev/null >"$LOG_FILE"
	echo startup >"$STATE_DIR/state"
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
128
	rm -f "$STATE_DIR/last_error" "$STATE_DIR/log2"
129
	echo $$>"$PID_FILE"
130
131
132
133
134
fi
if $BACKGROUND ; then
	"$0" -r >/dev/null 2>&1 &
	# Make sure the PID is of the process actually doing the work
	echo $!>"$PID_FILE"
135
136
	exit
fi
Michal Vaner's avatar
Michal Vaner committed
137
mkdir -p "$TMP_DIR"
Karel Koci's avatar
Karel Koci committed
138
139
140
141
142
143
144
145
146
147
148
149
150
151

WATCHER=
CPID=
trap_handler() {
	rm -rf "$LOCK_DIR" "$PID_FILE" "$TMP_DIR"
	[ -n "$WATCHER" ] && kill -9 $WATCHER 2>/dev/null
	[ -n "$CPID" ] && if kill $CPID 2>/dev/null; then
	(
		sleep 5
		kill -9 $CPID 2>/dev/null
	) & fi
	exit $EXIT_CODE
}
trap trap_handler EXIT INT QUIT TERM ABRT
152

153
# Check if we need an approval and if so, if we get it.
154
APPROVALS=
155
156
config_get_bool NEED_APPROVAL approvals need 0
if [ "$NEED_APPROVAL" = "1" ] ; then
157
158
159
	# Get a treshold time when we grant approval automatically. In case we don't, we set the time to
	# 1, which is long long time ago in the glorious times when automatic updaters were not
	# needed.
160
161
	config_get AUTO_GRANT_TIME approvals auto_grant_seconds
	AUTO_GRANT_TRESHOLD=$(expr $(date -u +%s) - $AUTO_GRANT_TIME 2>/dev/null || echo 1)
Karel Koci's avatar
Karel Koci committed
162
163
164
	APPROVALS="--ask-approval=$APPROVAL_ASK_FILE"
	APPROVED_HASH="$(tail -1 "$APPROVAL_GRANTED_FILE" | awk "\$2 == 'granted' || ( \$2 == 'asked' && \$3 <= '$AUTO_GRANT_TRESHOLD' ) {print \$1}")"
	[ -n "$APPROVED_HASH" ] && APPROVALS="$APPROVALS --approve=$APPROVED_HASH"
165
fi
166
# Run the actual updater
167
timeout 3000 pkgupdate $PKGUPDATE_ARGS --batch --state-log --task-log=/usr/share/updater/updater-log $APPROVALS
168
169
EXIT_CODE="$?"

170
171
172
if [ -f "$APPROVAL_ASK_FILE" ] ; then
	read HASH <"$APPROVAL_ASK_FILE"
	if ! grep -q "^$HASH" "$APPROVAL_GRANTED_FILE" ; then
Karel Koci's avatar
Karel Koci committed
173
		echo "$HASH asked $(date -u +%s)" >"$APPROVAL_GRANTED_FILE"
174
		config_get_bool NOTIFY_APPROVAL approvals notify 1
175
		echo "Asking for authorisation $HASH" | logger -t updater -p daemon.info
176
		if [ "$NOTIFY_APPROVAL" = "1" ] ; then
177
178
179
			timeout 120 create_notification -s update "Updater žádá o autorizaci akcí. Autorizaci můžete přidělit v administračním rozhraní Foris." "The updater requests an autorisation of its planned actions. You can grant it in the Foris administrative interface." || echo "Create notification failed" | logger -t updater -p daemon.error
		fi
	fi
180
181
182
183
184
185
186
else
	if [ "$EXIT_CODE" = 0 ] ; then
		# When we run successfully and didn't need any further approval, we
		# used up all the current approvals by that (if we ever want to
		# do the same thing again, we need to ask again). So delete all
		# the granted and asked lines ‒ asked might have reached approval
		# by being there long enough. Keep any other (like denied) permanently.
187
		sed -i -e '/asked/d;/granted/d' "$APPROVAL_GRANTED_FILE" 2>/dev/null
188
	fi
189
fi
190
191
# Evaluate what has run
STATE=$(cat "$STATE_DIR"/state)
Karel Koci's avatar
Karel Koci committed
192
if [ "$STATE" != "error" ] && ([ "$EXIT_CODE" != "0" ] || [ "$STATE" != "done" ]); then
193
	echo lost >"$STATE_DIR"/state
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
194
fi
195

196
if [ -s "$STATE_DIR"/log2 ] && grep -q '^[IR]' "$STATE_DIR/log2" ; then
Michal Vaner's avatar
Michal Vaner committed
197
	timeout 120 create_notification -s update "$(sed -ne 's/^I \(.*\) \(.*\)/ • Nainstalovaná verze \2 balíku \1/p;s/^R \(.*\)/ • Odstraněn balík \1/p' "$LOG_FILE")" "$(sed -ne 's/^I \(.*\) \(.*\)/ • Installed version \2 of package \1/p;s/^R \(.*\)/ • Removed package \1/p' "$LOG_FILE")" || echo "Create notification failed" | logger -t updater -p daemon.error
198
199
200
201
202
203
204
fi
if [ "$EXIT_CODE" != 0 ] || [ "$STATE" != "done" ] ; then
	if [ -s "$STATE_DIR/last_error" ] ; then
		ERROR=$(cat "$STATE_DIR/last_error")
	else
		ERROR="Unknown error"
	fi
Michal Vaner's avatar
Michal Vaner committed
205
	timeout 120 create_notification -s error "Updater selhal: $ERROR" "Updater failed: $ERROR" || echo "Create notification failed" | logger -t updater -p daemon.error
206
fi
Michal Vaner's avatar
Michal Vaner committed
207
timeout 120 notifier || echo "Notifier failed" | logger -t updater -p daemon.error
208

209
# Let the trap clean up here