| 1 | #!/bin/sh |
| 2 | #set -e |
| 3 | # |
| 4 | # This file understands the following apt configuration variables: |
| 5 | # Values here are the default. |
| 6 | # Create /etc/apt/apt.conf.d/10periodic file to set your preference. |
| 7 | # |
| 8 | # Dir "/"; |
| 9 | # - RootDir for all configuration files |
| 10 | # |
| 11 | # Dir::Cache "var/cache/apt/"; |
| 12 | # - Set apt package cache directory |
| 13 | # |
| 14 | # Dir::Cache::Archives "archives/"; |
| 15 | # - Set package archive directory |
| 16 | # |
| 17 | # APT::Periodic::Enable "1"; |
| 18 | # - Enable the update/upgrade script (0=disable) |
| 19 | # |
| 20 | # APT::Periodic::BackupArchiveInterval "0"; |
| 21 | # - Backup after n-days if archive contents changed.(0=disable) |
| 22 | # |
| 23 | # APT::Periodic::BackupLevel "3"; |
| 24 | # - Backup level.(0=disable), 1 is invalid. |
| 25 | # |
| 26 | # Dir::Cache::Backup "backup/"; |
| 27 | # - Set periodic package backup directory |
| 28 | # |
| 29 | # APT::Archives::MaxAge "0"; (old, deprecated) |
| 30 | # APT::Periodic::MaxAge "0"; (new) |
| 31 | # - Set maximum allowed age of a cache package file. If a cache |
| 32 | # package file is older it is deleted (0=disable) |
| 33 | # |
| 34 | # APT::Archives::MinAge "2"; (old, deprecated) |
| 35 | # APT::Periodic::MinAge "2"; (new) |
| 36 | # - Set minimum age of a package file. If a file is younger it |
| 37 | # will not be deleted (0=disable). Useful to prevent races |
| 38 | # and to keep backups of the packages for emergency. |
| 39 | # |
| 40 | # APT::Archives::MaxSize "0"; (old, deprecated) |
| 41 | # APT::Periodic::MaxSize "0"; (new) |
| 42 | # - Set maximum size of the cache in MB (0=disable). If the cache |
| 43 | # is bigger, cached package files are deleted until the size |
| 44 | # requirement is met (the oldest packages will be deleted |
| 45 | # first). |
| 46 | # |
| 47 | # APT::Periodic::Update-Package-Lists "0"; |
| 48 | # - Do "apt-get update" automatically every n-days (0=disable) |
| 49 | # |
| 50 | # APT::Periodic::Download-Upgradeable-Packages "0"; |
| 51 | # - Do "apt-get upgrade --download-only" every n-days (0=disable) |
| 52 | # |
| 53 | # APT::Periodic::Download-Upgradeable-Packages-Debdelta "1"; |
| 54 | # - Use debdelta-upgrade to download updates if available (0=disable) |
| 55 | # |
| 56 | # APT::Periodic::Unattended-Upgrade "0"; |
| 57 | # - Run the "unattended-upgrade" security upgrade script |
| 58 | # every n-days (0=disabled) |
| 59 | # Requires the package "unattended-upgrades" and will write |
| 60 | # a log in /var/log/unattended-upgrades |
| 61 | # |
| 62 | # APT::Periodic::AutocleanInterval "0"; |
| 63 | # - Do "apt-get autoclean" every n-days (0=disable) |
| 64 | # |
| 65 | # APT::Periodic::CleanInterval "0"; |
| 66 | # - Do "apt-get clean" every n-days (0=disable) |
| 67 | # |
| 68 | # APT::Periodic::Verbose "0"; |
| 69 | # - Send report mail to root |
| 70 | # 0: no report (or null string) |
| 71 | # 1: progress report (actually any string) |
| 72 | # 2: + command outputs (remove -qq, remove 2>/dev/null, add -d) |
| 73 | # 3: + trace on |
| 74 | # |
| 75 | |
| 76 | check_stamp() |
| 77 | { |
| 78 | stamp="$1" |
| 79 | interval="$2" |
| 80 | |
| 81 | if [ $interval -eq 0 ]; then |
| 82 | debug_echo "check_stamp: interval=0" |
| 83 | # treat as no time has passed |
| 84 | return 1 |
| 85 | fi |
| 86 | |
| 87 | if [ ! -f $stamp ]; then |
| 88 | debug_echo "check_stamp: missing time stamp file: $stamp." |
| 89 | # treat as enough time has passed |
| 90 | return 0 |
| 91 | fi |
| 92 | |
| 93 | # compare midnight today to midnight the day the stamp was updated |
| 94 | stamp_file="$stamp" |
| 95 | stamp=$(date --date=$(date -r $stamp_file --iso-8601) +%s 2>/dev/null) |
| 96 | if [ "$?" != "0" ]; then |
| 97 | # Due to some timezones returning 'invalid date' for midnight on |
| 98 | # certain dates (e.g. America/Sao_Paulo), if date returns with error |
| 99 | # remove the stamp file and return 0. See coreutils bug: |
| 100 | # http://lists.gnu.org/archive/html/bug-coreutils/2007-09/msg00176.html |
| 101 | rm -f "$stamp_file" |
| 102 | return 0 |
| 103 | fi |
| 104 | |
| 105 | now=$(date --date=$(date --iso-8601) +%s 2>/dev/null) |
| 106 | if [ "$?" != "0" ]; then |
| 107 | # As above, due to some timezones returning 'invalid date' for midnight |
| 108 | # on certain dates (e.g. America/Sao_Paulo), if date returns with error |
| 109 | # return 0. |
| 110 | return 0 |
| 111 | fi |
| 112 | |
| 113 | delta=$(($now-$stamp)) |
| 114 | |
| 115 | # interval is in days, convert to sec. |
| 116 | interval=$(($interval*60*60*24)) |
| 117 | debug_echo "check_stamp: interval=$interval, now=$now, stamp=$stamp, delta=$delta (sec)" |
| 118 | |
| 119 | # remove timestamps a day (or more) in the future and force re-check |
| 120 | if [ $stamp -gt $(($now+86400)) ]; then |
| 121 | echo "WARNING: file $stamp_file has a timestamp in the future: $stamp" |
| 122 | rm -f "$stamp_file" |
| 123 | return 0 |
| 124 | fi |
| 125 | |
| 126 | if [ $delta -ge $interval ]; then |
| 127 | return 0 |
| 128 | fi |
| 129 | |
| 130 | return 1 |
| 131 | } |
| 132 | |
| 133 | update_stamp() |
| 134 | { |
| 135 | stamp="$1" |
| 136 | touch $stamp |
| 137 | } |
| 138 | |
| 139 | # we check here if autoclean was enough sizewise |
| 140 | check_size_constraints() |
| 141 | { |
| 142 | MaxAge=0 |
| 143 | eval $(apt-config shell MaxAge APT::Archives::MaxAge) |
| 144 | eval $(apt-config shell MaxAge APT::Periodic::MaxAge) |
| 145 | |
| 146 | MinAge=2 |
| 147 | eval $(apt-config shell MinAge APT::Archives::MinAge) |
| 148 | eval $(apt-config shell MinAge APT::Periodic::MinAge) |
| 149 | |
| 150 | MaxSize=0 |
| 151 | eval $(apt-config shell MaxSize APT::Archives::MaxSize) |
| 152 | eval $(apt-config shell MaxSize APT::Periodic::MaxSize) |
| 153 | |
| 154 | Cache="/var/cache/apt/archives/" |
| 155 | eval $(apt-config shell Cache Dir::Cache::archives/d) |
| 156 | |
| 157 | # sanity check |
| 158 | if [ -z "$Cache" ]; then |
| 159 | echo "empty Dir::Cache::archives, exiting" |
| 160 | exit |
| 161 | fi |
| 162 | |
| 163 | # check age |
| 164 | if [ ! $MaxAge -eq 0 ] && [ ! $MinAge -eq 0 ]; then |
| 165 | debug_echo "aged: ctime <$MaxAge and mtime <$MaxAge and ctime>$MinAge and mtime>$MinAge" |
| 166 | find $Cache -name "*.deb" \( -mtime +$MaxAge -and -ctime +$MaxAge \) -and -not \( -mtime -$MinAge -or -ctime -$MinAge \) -print0 | xargs -r -0 rm -f |
| 167 | elif [ ! $MaxAge -eq 0 ]; then |
| 168 | debug_echo "aged: ctime <$MaxAge and mtime <$MaxAge only" |
| 169 | find $Cache -name "*.deb" -ctime +$MaxAge -and -mtime +$MaxAge -print0 | xargs -r -0 rm -f |
| 170 | else |
| 171 | debug_echo "skip aging since MaxAge is 0" |
| 172 | fi |
| 173 | |
| 174 | # check size |
| 175 | if [ ! $MaxSize -eq 0 ]; then |
| 176 | # maxSize is in MB |
| 177 | MaxSize=$(($MaxSize*1024)) |
| 178 | |
| 179 | #get current time |
| 180 | now=$(date --date=$(date --iso-8601) +%s) |
| 181 | MinAge=$(($MinAge*24*60*60)) |
| 182 | |
| 183 | # reverse-sort by mtime |
| 184 | for file in $(ls -rt $Cache/*.deb 2>/dev/null); do |
| 185 | du=$(du -s $Cache) |
| 186 | size=${du%%/*} |
| 187 | # check if the cache is small enough |
| 188 | if [ $size -lt $MaxSize ]; then |
| 189 | debug_echo "end remove by archive size: size=$size < $MaxSize" |
| 190 | break |
| 191 | fi |
| 192 | |
| 193 | # check for MinAge of the file |
| 194 | if [ $MinAge -ne 0 ]; then |
| 195 | # check both ctime and mtime |
| 196 | mtime=$(stat -c %Y $file) |
| 197 | ctime=$(stat -c %Z $file) |
| 198 | if [ $mtime -gt $ctime ]; then |
| 199 | delta=$(($now-$mtime)) |
| 200 | else |
| 201 | delta=$(($now-$ctime)) |
| 202 | fi |
| 203 | if [ $delta -le $MinAge ]; then |
| 204 | debug_echo "skip remove by archive size: $file, delta=$delta < $MinAge" |
| 205 | break |
| 206 | else |
| 207 | # delete oldest file |
| 208 | debug_echo "remove by archive size: $file, delta=$delta >= $MinAge (sec), size=$size >= $MaxSize" |
| 209 | rm -f $file |
| 210 | fi |
| 211 | fi |
| 212 | done |
| 213 | fi |
| 214 | } |
| 215 | |
| 216 | # deal with the Apt::Periodic::BackupArchiveInterval |
| 217 | do_cache_backup() |
| 218 | { |
| 219 | BackupArchiveInterval="$1" |
| 220 | if [ $BackupArchiveInterval -eq 0 ]; then |
| 221 | return |
| 222 | fi |
| 223 | |
| 224 | # Set default values and normalize |
| 225 | CacheDir="/var/cache/apt" |
| 226 | eval $(apt-config shell CacheDir Dir::Cache/d) |
| 227 | CacheDir=${CacheDir%/} |
| 228 | if [ -z "$CacheDir" ]; then |
| 229 | debug_echo "practically empty Dir::Cache, exiting" |
| 230 | return 0 |
| 231 | fi |
| 232 | |
| 233 | Cache="${CacheDir}/archives/" |
| 234 | eval $(apt-config shell Cache Dir::Cache::Archives/d) |
| 235 | if [ -z "$Cache" ]; then |
| 236 | debug_echo "practically empty Dir::Cache::archives, exiting" |
| 237 | return 0 |
| 238 | fi |
| 239 | |
| 240 | BackupLevel=3 |
| 241 | eval $(apt-config shell BackupLevel APT::Periodic::BackupLevel) |
| 242 | if [ $BackupLevel -le 1 ]; then |
| 243 | BackupLevel=2 ; |
| 244 | fi |
| 245 | |
| 246 | Back="${CacheDir}/backup/" |
| 247 | eval $(apt-config shell Back Dir::Cache::Backup/d) |
| 248 | if [ -z "$Back" ]; then |
| 249 | echo "practically empty Dir::Cache::Backup, exiting" 1>&2 |
| 250 | return |
| 251 | fi |
| 252 | |
| 253 | CacheArchive="$(basename "${Cache}")" |
| 254 | test -n "${CacheArchive}" || CacheArchive="archives" |
| 255 | BackX="${Back}${CacheArchive}/" |
| 256 | for x in $(seq 0 1 $((${BackupLevel}-1))); do |
| 257 | eval "Back${x}=${Back}${x}/" |
| 258 | done |
| 259 | |
| 260 | # backup after n-days if archive contents changed. |
| 261 | # (This uses hardlink to save disk space) |
| 262 | BACKUP_ARCHIVE_STAMP=/var/lib/apt/periodic/backup-archive-stamp |
| 263 | if check_stamp $BACKUP_ARCHIVE_STAMP $BackupArchiveInterval; then |
| 264 | if [ $({(cd $Cache 2>/dev/null; find . -name "*.deb"); (cd $Back0 2>/dev/null;find . -name "*.deb") ;}| sort|uniq -u|wc -l) -ne 0 ]; then |
| 265 | mkdir -p $Back |
| 266 | rm -rf $Back$((${BackupLevel}-1)) |
| 267 | for y in $(seq $((${BackupLevel}-1)) -1 1); do |
| 268 | eval BackY=${Back}$y |
| 269 | eval BackZ=${Back}$(($y-1)) |
| 270 | if [ -e $BackZ ]; then |
| 271 | mv -f $BackZ $BackY ; |
| 272 | fi |
| 273 | done |
| 274 | cp -la $Cache $Back ; mv -f $BackX $Back0 |
| 275 | update_stamp $BACKUP_ARCHIVE_STAMP |
| 276 | debug_echo "backup with hardlinks. (success)" |
| 277 | else |
| 278 | debug_echo "skip backup since same content." |
| 279 | fi |
| 280 | else |
| 281 | debug_echo "skip backup since too new." |
| 282 | fi |
| 283 | } |
| 284 | |
| 285 | debug_echo() |
| 286 | { |
| 287 | # Display message if $VERBOSE >= 1 |
| 288 | if [ "$VERBOSE" -ge 1 ]; then |
| 289 | echo $1 1>&2 |
| 290 | fi |
| 291 | } |
| 292 | |
| 293 | # ------------------------ main ---------------------------- |
| 294 | |
| 295 | if test -r /var/lib/apt/extended_states; then |
| 296 | # Backup the 7 last versions of APT's extended_states file |
| 297 | # shameless copy from dpkg cron |
| 298 | if cd /var/backups ; then |
| 299 | if ! cmp -s apt.extended_states.0 /var/lib/apt/extended_states; then |
| 300 | cp -p /var/lib/apt/extended_states apt.extended_states |
| 301 | savelog -c 7 apt.extended_states >/dev/null |
| 302 | fi |
| 303 | fi |
| 304 | fi |
| 305 | |
| 306 | # check apt-config existence |
| 307 | if ! which apt-config >/dev/null 2>&1; then |
| 308 | exit 0 |
| 309 | fi |
| 310 | |
| 311 | # check if the user really wants to do something |
| 312 | AutoAptEnable=1 # default is yes |
| 313 | eval $(apt-config shell AutoAptEnable APT::Periodic::Enable) |
| 314 | |
| 315 | if [ $AutoAptEnable -eq 0 ]; then |
| 316 | exit 0 |
| 317 | fi |
| 318 | |
| 319 | # Set VERBOSE mode from apt-config (or inherit from environment) |
| 320 | VERBOSE=0 |
| 321 | eval $(apt-config shell VERBOSE APT::Periodic::Verbose) |
| 322 | debug_echo "verbose level $VERBOSE" |
| 323 | if [ "$VERBOSE" -le 2 ]; then |
| 324 | # quiet for 0,1,2 |
| 325 | XSTDOUT=">/dev/null" |
| 326 | XSTDERR="2>/dev/null" |
| 327 | XAPTOPT="-qq" |
| 328 | XUUPOPT="" |
| 329 | else |
| 330 | XSTDOUT="" |
| 331 | XSTDERR="" |
| 332 | XAPTOPT="" |
| 333 | XUUPOPT="-d" |
| 334 | fi |
| 335 | if [ "$VERBOSE" -ge 3 ]; then |
| 336 | # trace output |
| 337 | set -x |
| 338 | fi |
| 339 | |
| 340 | # check if we can lock the cache and if the cache is clean |
| 341 | if which apt-get >/dev/null 2>&1 && ! eval apt-get check $XAPTOPT $XSTDERR ; then |
| 342 | debug_echo "error encountered in cron job with \"apt-get check\"." |
| 343 | exit 0 |
| 344 | fi |
| 345 | |
| 346 | # Global current time in seconds since 1970-01-01 00:00:00 UTC |
| 347 | now=$(date +%s) |
| 348 | |
| 349 | # Support old Archive for compatibility. |
| 350 | # Document only Periodic for all controlling parameters of this script. |
| 351 | |
| 352 | UpdateInterval=0 |
| 353 | eval $(apt-config shell UpdateInterval APT::Periodic::Update-Package-Lists) |
| 354 | |
| 355 | DownloadUpgradeableInterval=0 |
| 356 | eval $(apt-config shell DownloadUpgradeableInterval APT::Periodic::Download-Upgradeable-Packages) |
| 357 | |
| 358 | UnattendedUpgradeInterval=0 |
| 359 | eval $(apt-config shell UnattendedUpgradeInterval APT::Periodic::Unattended-Upgrade) |
| 360 | |
| 361 | AutocleanInterval=0 |
| 362 | eval $(apt-config shell AutocleanInterval APT::Periodic::AutocleanInterval) |
| 363 | |
| 364 | CleanInterval=0 |
| 365 | eval $(apt-config shell CleanInterval APT::Periodic::CleanInterval) |
| 366 | |
| 367 | BackupArchiveInterval=0 |
| 368 | eval $(apt-config shell BackupArchiveInterval APT::Periodic::BackupArchiveInterval) |
| 369 | |
| 370 | Debdelta=1 |
| 371 | eval $(apt-config shell Debdelta APT::Periodic::Download-Upgradeable-Packages-Debdelta) |
| 372 | |
| 373 | # check if we actually have to do anything that requires locking the cache |
| 374 | if [ $UpdateInterval -eq 0 ] && |
| 375 | [ $DownloadUpgradeableInterval -eq 0 ] && |
| 376 | [ $UnattendedUpgradeInterval -eq 0 ] && |
| 377 | [ $BackupArchiveInterval -eq 0 ] && |
| 378 | [ $AutocleanInterval -eq 0 ] && |
| 379 | [ $CleanInterval -eq 0 ]; then |
| 380 | |
| 381 | # check cache size |
| 382 | check_size_constraints |
| 383 | |
| 384 | exit 0 |
| 385 | fi |
| 386 | |
| 387 | # deal with BackupArchiveInterval |
| 388 | do_cache_backup $BackupArchiveInterval |
| 389 | |
| 390 | # include default system language so that "apt-get update" will |
| 391 | # fetch the right translated package descriptions |
| 392 | if [ -r /etc/default/locale ]; then |
| 393 | . /etc/default/locale |
| 394 | export LANG LANGUAGE LC_MESSAGES LC_ALL |
| 395 | fi |
| 396 | |
| 397 | # update package lists |
| 398 | UPDATED=0 |
| 399 | UPDATE_STAMP=/var/lib/apt/periodic/update-stamp |
| 400 | if check_stamp $UPDATE_STAMP $UpdateInterval; then |
| 401 | if eval apt-get $XAPTOPT -y update $XSTDERR; then |
| 402 | debug_echo "download updated metadata (success)." |
| 403 | if which dbus-send >/dev/null 2>&1 && pidof dbus-daemon >/dev/null 2>&1; then |
| 404 | if dbus-send --system / app.apt.dbus.updated boolean:true ; then |
| 405 | debug_echo "send dbus signal (success)" |
| 406 | else |
| 407 | debug_echo "send dbus signal (error)" |
| 408 | fi |
| 409 | else |
| 410 | debug_echo "dbus signal not send (command not available)" |
| 411 | fi |
| 412 | update_stamp $UPDATE_STAMP |
| 413 | UPDATED=1 |
| 414 | else |
| 415 | debug_echo "download updated metadata (error)" |
| 416 | fi |
| 417 | else |
| 418 | debug_echo "download updated metadata (not run)." |
| 419 | fi |
| 420 | |
| 421 | # download all upgradeable packages (if it is requested) |
| 422 | DOWNLOAD_UPGRADEABLE_STAMP=/var/lib/apt/periodic/download-upgradeable-stamp |
| 423 | if [ $UPDATED -eq 1 ] && check_stamp $DOWNLOAD_UPGRADEABLE_STAMP $DownloadUpgradeableInterval; then |
| 424 | if [ $Debdelta -eq 1 ]; then |
| 425 | debdelta-upgrade >/dev/null 2>&1 || true |
| 426 | fi |
| 427 | if eval apt-get $XAPTOPT -y -d dist-upgrade $XSTDERR; then |
| 428 | update_stamp $DOWNLOAD_UPGRADEABLE_STAMP |
| 429 | debug_echo "download upgradable (success)" |
| 430 | else |
| 431 | debug_echo "download upgradable (error)" |
| 432 | fi |
| 433 | else |
| 434 | debug_echo "download upgradable (not run)" |
| 435 | fi |
| 436 | |
| 437 | # auto upgrade all upgradeable packages |
| 438 | UPGRADE_STAMP=/var/lib/apt/periodic/upgrade-stamp |
| 439 | if which unattended-upgrade >/dev/null 2>&1 && check_stamp $UPGRADE_STAMP $UnattendedUpgradeInterval; then |
| 440 | if unattended-upgrade $XUUPOPT; then |
| 441 | update_stamp $UPGRADE_STAMP |
| 442 | debug_echo "unattended-upgrade (success)" |
| 443 | else |
| 444 | debug_echo "unattended-upgrade (error)" |
| 445 | fi |
| 446 | else |
| 447 | debug_echo "unattended-upgrade (not run)" |
| 448 | fi |
| 449 | |
| 450 | # clean package archive |
| 451 | CLEAN_STAMP=/var/lib/apt/periodic/clean-stamp |
| 452 | if check_stamp $CLEAN_STAMP $CleanInterval; then |
| 453 | if eval apt-get $XAPTOPT -y clean $XSTDERR; then |
| 454 | debug_echo "clean (success)." |
| 455 | update_stamp $CLEAN_STAMP |
| 456 | else |
| 457 | debug_echo "clean (error)" |
| 458 | fi |
| 459 | else |
| 460 | debug_echo "clean (not run)" |
| 461 | fi |
| 462 | |
| 463 | # autoclean package archive |
| 464 | AUTOCLEAN_STAMP=/var/lib/apt/periodic/autoclean-stamp |
| 465 | if check_stamp $AUTOCLEAN_STAMP $AutocleanInterval; then |
| 466 | if eval apt-get $XAPTOPT -y autoclean $XSTDERR; then |
| 467 | debug_echo "autoclean (success)." |
| 468 | update_stamp $AUTOCLEAN_STAMP |
| 469 | else |
| 470 | debug_echo "autoclean (error)" |
| 471 | fi |
| 472 | else |
| 473 | debug_echo "autoclean (not run)" |
| 474 | fi |
| 475 | |
| 476 | # check cache size |
| 477 | check_size_constraints |
| 478 | |
| 479 | # |
| 480 | # vim: set sts=4 ai : |
| 481 | # |
| 482 | |