aboutsummaryrefslogtreecommitdiff
path: root/src/slack-autoupdate
blob: 4784c23d8e90ba804e47e98b92b991189c049502 (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
#!/bin/bash
#
# Update your system automatically!
#
# Packages from different sources are downloaded, under sub-shells, into the
# PACKAGE_DIR where they can be installed on reboot.
#
# A failure encountered at any point should abort any pending package updates
# to install.
#
# To enable sbotools updates, make the /etc/sbotools/sbotools.conf file exist
# and configure PKG_DIR to point to the same path as STAGING_DIR.

# Set to "yes" to send notification to stdout, causing cron to send an email.
NOTIFY="${NOTIFY:-no}"

# Reboot after this period for install after a successful update.
#
# Set to empty to disable reboot or 'now' to reboot immediately.
REBOOT_TIME="${REBOOT_TIME:-""}"

# Packages are copied here on successful download.  All contents of this
# directory will be deleted.
PACKAGE_DIR="${PACKAGE_DIR:-/var/spool/slack-autoupdate}"

# Packages are temporarily stored here until success.  All contents of this
# directory are deleted.
STAGING_DIR="/var/cache/slack-autoupdate/staging"

# Information of interest to the admin on success.
UPDATE_INFO="$STAGING_DIR/info.txt"

# Information of interest to the admin on failure.
UPDATE_ERROR="$STAGING_DIR/error.txt"

# Avoid concurrently running with another instance.
if [ "$(ls /var/lock/autoupdate.* 2>/dev/null)" ]; then
  echo "Another instance of autoupdate is running.  If this is not correct, you can remove /var/lock/autoupdate.* files and run autoupdate again." \
    >>"$PACKAGE_DIR/$(basename "$UPDATE_ERROR")"
  if [ "$NOTIFY" = "yes" ]; then
    cat "$PACKAGE_DIR/$(basename "$UPDATE_ERROR")"
  fi

  exit 1
fi

touch /var/lock/autoupdate.$$
trap "rm -f /var/lock/autoupdate.$$" EXIT

# Keep it simple stupid, install pending updates first.
mkdir --parents "$PACKAGE_DIR"
if [ -n "$(find "$PACKAGE_DIR" -name "*.t*z")" ]; then
  exit 0
fi

# Make the set of package updates available atomically by storing them in a
# temporary location until completion. 
mkdir --parents "$STAGING_DIR"
find "$STAGING_DIR" -mindepth 1 | xargs rm -fr

# Following are bash functions to check one or more package sources for
# updates.
#
# The functions must use the naming format "[source_name]_source".  They
# take two arguments.
#
#  - First argument is the file path to save packages.
#  - Second argument is the file path to append update information.
#

slackpkg_source() {
  local staging_dir="$1"
  local update_info="$2"

  yes | slackpkg -batch=on -default_answer=yes update || exit "$?"

  local changelog_txt="$(mktemp /tmp/slack-autoupdate.XXXXXX)"
  local package_updates="$(mktemp /tmp/slack-autoupdate.XXXXXX)"
  trap "rm -f ${changelog_txt} ${package_updates}" EXIT

  local output="$(slackpkg -batch=on -default_answer=n upgrade-all)" || exit_code="$?"
  if [[ $exit_code -ne 0 ]] && [[ $exit_code -ne 20 ]] && [[ $exit_code -ne 100 ]]; then
    # Slackpkg has several safe exit codes.
    exit exit_code
  fi

  echo "$output" \
    | grep "\.t.z$" \
    | tee "$package_updates"
  if [ ! -s "$package_updates" ]; then
    # No updates
    exit 0
  fi

  #
  # This script needs to download updates from slackpkg into a particular
  # directory, which slackpkg does not support.  The following is a work
  # around.
  #

  local mirror=$(sed -n '
          # Remove leading and trailing blanks
          s/^[[:blank:]]*//
          s/[[:blank:]]*$//
          # Only one token is allowed per line
          /[[:blank:]]/d
          # A single solidus should end the URI
          s,[/]*$,/,
          # Print the lines beginning with one of the URI schemes we look for
          \@^file://@p
          \@^cdrom://@p
          \@^local://@p
          \@^https\{0,1\}://@p
          \@^ftps\{0,1\}://@p' /etc/slackpkg/mirrors)

  while read pkg; do
    local pkg_url="$(
      grep "${pkg}$" /var/lib/slackpkg/CHECKSUMS.md5 \
        | tr -s ' ' \
        | cut -f 2 -d ' '
      )" || exit "$?"

    wget \
      --quiet \
      --directory-prefix="$staging_dir" \
      "${mirror}${pkg_url}" \
      "${mirror}${pkg_url}.asc" \
      || exit "$?"
  done <"$package_updates"

  echo arg $1
  echo staging_dir $staging_dir
  gpg2 --verify-files "$staging_dir"/*.asc || exit "$?"

  (
    # Provide update information to the user.

    # Redirect this subshell's standard output to info file.
    exec 1>>"$update_info"

    local last_installed_package="$(
      grep \
        "$(ls /var/log/packages | sed "s/$/\\\|/g" | tr --delete '\n')v4EcFvjXKlWB" \
        /var/lib/slackpkg/ChangeLog.txt \
        | head -n 1
    )"

    while read pkg; do
      echo " - $pkg"
    done <"$package_updates"
    echo
    echo "### Recent Changelog Entries"
    echo ""
    grep --before-context 10000 "$last_installed_package" /var/lib/slackpkg/ChangeLog.txt
  )
}

sbotools_source() {
  local staging_dir="$1"
  local update_info="$2"

  if [ ! -f /etc/sbotools/sbotools.conf ] \
    || [ "$(sed -n 's/PKG_DIR=//p' /etc/sbotools/sbotools.conf)" != "$staging_dir" ]; then
    # Assume sbotools is disabled.
    exit 0
  fi

  source /etc/profile.d/*.sh

  local package_updates=$(mktemp /tmp/slack-autoupdate.XXXXXX)
  trap "rm -f ${package_updates}" EXIT

  (sbocheck | tee $package_updates) || exit 1

  # Avoid checking for 'no updates available'.  It will not
  # work if you host 'overrides' purposely different than what is
  # on SBO.
  if ! grep "needs updating" "$package_updates"; then
    exit 0
  fi

  sboupgrade \
    --all \
    --noinstall \
    --nointeractive \
    || exit "$?"

  (
    exec 1>>"$update_info"

    cat "$package_updates"
  )
}

update_sources=(slackpkg_source sbotools_source)
for update_source in ${update_sources[@]}; do
  source_name="$(echo "${update_source}" | sed "s/_source$//" | sed "s/_/ /")"

  if [ -f "$UPDATE_INFO" ]; then
    >>"$UPDATE_INFO" echo ""
  fi
  >>"$UPDATE_INFO" echo "## ${source_name} updates"
  >>"$UPDATE_INFO" echo ""

  if ! OUTPUT="$(
    exec 2>&1

    echo "Checking updates for ${source_name}..."

    $update_source "$STAGING_DIR" "$UPDATE_INFO"
  )"; then
    if [ -f "$UPDATE_ERROR" ]; then
      >>"$UPDATE_ERROR" echo ""
      >>"$UPDATE_ERROR" echo ""
    fi

    >>"$UPDATE_ERROR" echo -e "${source_name}:\n\n$OUTPUT"
  fi
done

if [ -z "$(find "$STAGING_DIR" -name "*.t*z" -or -name "info.txt" -or -name "error.txt")" ]; then
  # No updates
  exit 0
fi

if [ "$NOTIFY" = "yes" ]; then
  if [ -f "$UPDATE_ERROR" ]; then
    echo "Failures were encountered while trying to download updates"
    echo ""
    cat "$UPDATE_ERROR"
  elif [ -f "$UPDATE_INFO" ]; then
    echo "# Updates pending installation"
    echo ""
    cat "$UPDATE_INFO"
  fi
fi

if [ -f "$UPDATE_ERROR" ]; then
  mv "$UPDATE_ERROR" "$PACKAGE_DIR"
  if [ -f "$UPDATE_INFO" ]; then
    mv "$UPDATE_INFO" "$PACKAGE_DIR"
  fi

  exit 1
fi

# Now that everything is successful.
find "$PACKAGE_DIR" -mindepth 1 | xargs rm -fr
mv "$STAGING_DIR"/* "$PACKAGE_DIR/"

if [ -n "$REBOOT_TIME" ]; then
  nohup shutdown -r "$REBOOT_TIME" >/dev/null 2>&1 &
fi