[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

apt cron script



attached is the apt script that gets placed into /etc/cron.daily, with
what i think are improvements.

i am not on the mailing list, so please cc me in any discussions.

thanks.

-- 
Hiren Patel
hiren@obsidian.co.za


If you pick up a starving dog and make him prosperous, he will not bite you.
This is the principal difference between a dog and a man.
--Mark Twain

-- 
This message has been scanned for viruses and
dangerous content by MailScanner, and is
believed to be clean.

#!/bin/bash
#

#
# This file understands the following apt configuration variables:
#
#  APT::Periodic::Update-Package-Lists "1";
#  - Do "apt-get update" automatically every n-days (0=disable)
#    
#  APT::Periodic::Download-Upgradeable-Packages "1";
#  - Do "apt-get upgrade --download-only" every n-days (0=disable)
# 
#  APT::Periodic::AutocleanInterval" "1";
#  - Do "apt-get autoclean" every n-days (0=disable)
#
#  APT::Archives::MaxAge",
#  - Set maximum allowed age of a cache package file. If a cache 
#    package file is older it is deleted (0=disable)
#
#  APT::Archives::MaxSize",
#  - Set maximum size of the cache in MB (0=disable). If the cache
#    is bigger, cached package files are deleted until the size
#    requirement is met (the biggest packages will be deleted 
#    first).
#
#  APT::Archives::MinAge"
#  - Set minimum age of a package file. If a file is younger it
#    will not be deleted (0=disable). Usefull to prevent races 
#    and to keep backups of the packages for emergency.
# 

check_stamp()
{

    if [[ "$#" -ne 2 ]]; then
	echo "check_stamp: arguments to function does not equal 2, returning 1." 1>&2
	return 1
    fi


    local file="$1"
    local file_mod_time=""
    local now=""

    declare -i now_seconds=""
    declare -i stamp=0
    declare -i interval="$2"
    declare -i delta=0
    declare -i stamp_error=0

    if [ "$interval" -eq 0 ]; then
        return 1
    fi

    if ! [ -f "$file" ]; then
        return 0
    fi

    if file_mod_time=$(date -r $file --iso-8601 2>/dev/null); then
	if [[ "$file_mod_time" =~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ]]; then
		if stamp=$(date --date=${file_mod_time} +%s 2>/dev/null); then
			if [[ "$stamp" -gt 0 ]]; then
				if now=$(date --iso-8601 2>/dev/null); then
					if [[ "$now" =~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ]]; then
						if now_seconds=$(date --date=${now} +%s 2>/dev/null); then
							if [[ "$now_seconds" -gt 0 ]]; then
								delta=$((${now_seconds}-${stamp}))
									if ! [[ "$delta" -ge 0 ]]; then
										stamp_error=1
									fi	
							else
								stamp_error=1
							fi
						else
							stamp_error=1
						fi
					else
						stamp_error=1
					fi
				else
					stamp_error=1
				fi
			else
				stamp_error=1
			fi
		else
			stamp_error=1
		fi
	else
		stamp_error=1
	fi
    else
	stamp_error=1
    fi


    if [[ "$stamp_error" -eq 1 ]]; then
	echo "timestamp calculation failed somewhere, file is ${file}, returning 1." 1>&2
	return 1
    fi				
				

    # interval is in days,
    interval=$((${interval}*60*60*24))

    if ! [[ "$interval" -gt 0 ]]; then
	echo "check_stamp: variable 'interval' is not > 0, returning 1" 1>&2
	return 1
    fi


    if [ "$delta" -ge "$interval" ]; then
        return 0
    else
	return 1
    fi

}

update_stamp()
{


    if [[ "$#" -ne 1 ]]; then
	echo "update_stamp: number of arguments to functions is not exactly 1, returning 1." 1>&2
	return 1

    fi

    local file="$1"


    if ! [ -f "$file" ]; then
        return 1
    fi

    if touch $file; then
	return 0
    else
	return 1
    fi

}



# we check here if autoclean was enough sizewise
check_size_constraints()
{

    set --

    # min-age in days
    declare -i MaxAge=0
    declare -i MinAge=2
    declare -i MaxSize=0
    local Dir="/"
    local CacheDir="var/cache/apt"
    local CacheArchive="archives/"
    local to_run=""
    local Cache=""
    local tempfile=""
    local tempdir=""
    local filename=""



    if ! to_run=$(apt-config shell MaxAge APT::Archives::MaxAge MinAge APT::Archives::MinAge MaxSize APT::Archives::MaxSize Dir Dir CacheDir Dir::Cache CacheArchive Dir::Cache::archives); then
        echo "check_size_constraints: could not retrieve apt variables with apt-config, returning 1." 1>&2
        return 1
    fi

    if ! eval "$to_run"; then
       	echo "check_size_constraints: could not initialize apt variables, returning  1." 1>&2
       	return 1
    fi


    # sanity check
    if [ -z "$CacheDir" -o -z "$CacheArchive" -o -z "$Dir" ]; then
	echo "check_size_constraints: empty Dir::Cache or Dir::Cache::archives or Dir in apt config, returning 1." 1>&2
	return 1
    fi

    if ! [[ "$MaxAge" -ge 0 && "$MinAge" -ge 0 && "$MaxSize" -ge 0 ]]; then
	echo "check_size_constraints: variable MaxAge or MinAge or MaxSize is not >= 0, returning 1." 1>&2
	return 1
    fi

    if ! [[ "$CacheDir" =~ '^[a-zA-Z0-9/_-]+$' && "$Dir" =~ '^[a-zA-Z0-9/_-]+$' && "$CacheArchive" =~ '^[a-zA-Z0-9/_-]+$' ]]; then
	echo "check_size_constraints: variables CacheDir or Dir or CacheArchive failed syntax check, returning 1." 1>&2
	return 1
    fi


    

    Cache="${Dir%/}/${CacheDir%/}/${CacheArchive%/}"


    if ! [[ -d "$Cache" && -r "$Cache" && -w "$Cache" ]]; then
	echo "check_size_constraints: directory '$Cache' does not exist or is not readable or writable, returning 1." 1>&2
	return 1
    fi



    # check age
    if [[ "$MaxAge" -gt 0 ]]; then

	TMPDIR=""

	if [[ -d "/var/tmp" && -r "/var/tmp" && -w "/var/tmp" ]]; then
		tmpdir="/var/tmp"
	else
		tmpdir="/tmp"
	fi

	
	trap "rm -f $tempfile; exit 1" 1 2 3 15 
	if tempfile=$(mktemp -p $tmpdir aptcronscript.XXXXXX); then

		#the reason why MaxAge is reduced by 1:
		#in find(1)
		#When find figures out how many 24-hour periods ago the file was last accessed,
		#any fractional part is ignored, so to match -atime +1, a file has to have been modified at least two days ago.	
		#same seems to apply for ctime and mtime

    		if ! [[ "$MaxAge" -eq 0 && "$MinAge" -eq 0 ]]; then
	 		find $Cache -name "*.deb"  \( -mtime +$(($MaxAge - 1)) -and -ctime +$(($MaxAge -1 )) \) -and -not \( -mtime -${MinAge} -or -ctime -${MinAge} \) > $tempfile
    		elif ! [ "$MaxAge" -eq 0 ]; then
			find $Cache -name "*.deb"  -ctime +$(($MaxAge - 1)) -and -mtime +$(($MaxAge - 1)) > $tempfile
    		fi


		if [[ -s "$tempfile" ]]; then
			{
				while read filename; do
					if ! rm -f $filename; then
						echo "check_size_constraints: failed to remove file '$filename'" 1>&2
					fi
				done;
			} < ${tempfile}
		fi

		rm -f $tempfile
		trap - 1 2 3 15


	else
		trap - 1 2 3 15
		echo "check_size_constraints: creating temp file returned non-zero status, returning 1." 1>&2
		return 1
	fi

    fi



    
    # check size
    if ! [ "$MaxSize" -eq 0 ]; then

	# MaxSize is in MB
	MaxSize=$(($MaxSize*1024))

	if ! [[ "$MaxSize" -gt 0 ]]; then
		echo "check_size_constraints: varilable MaxSize not greater then zero after conversion to MB, returning 1." 1>&2
		return 1
	fi


	local date=""
	local now=""

	#get current time
	if date=$(date --iso-8601); then
		if ! now=$(date --date=$(date --iso-8601) +%s); then
			echo "check_size_constraints: failed to accuire current time, returning 1." 1>&2
			return 1
		fi
	else
		echo "check_size_constraints: failed to accuire current date, returning 1." 1>&2
		return 1
	fi



	MinAge=$(($MinAge*24*60*60))

	if ! [[ "$MinAge" -ge 0 ]]; then
		echo "check_size_constraints: variable MinAge not greater or equal to zero after conversion to seconds, returning 1." 1>&2
		return 1
	fi





	trap "rm -f $tempfile; exit 1" 1 2 3 15 
	if tempfile=$(mktemp -p $tmpdir aptcronscript.XXXXXX); then
		if ! find $Cache -name '*.deb' > $tempfile; then
		
			rm -f $tempfile
			trap - 1 2 3 15

			echo "check_size_constraints: check size: find for .deb files returned non-zero, returning 1." 1>&2
			return 1
		fi

		if [[ -s "$tempfile" ]]; then

			{

				declare -i size=0
				declare -i mtime=0
				declare -i delta=0

				while read filename; do
					if ! size=$(stat -c '%s' $filename); then
						echo "check_size_constraints: check size: could not stat file $filename" 1>&2
						continue
					fi
					
					if ! mtime=$(stat -c '%Y' $filename); then
						echo "check_size_constraints: check size: could not get mtime of $filename" 1>&2
						continue
					fi

					if ! [[ "$zize" -gt 0 && "$now" -gt 0 && "$mtime" -gt 0 ]]; then
						echo "check_size_constraints: check size: variable size or now or mtime not greater then zero." 1>&2
						continue
					fi

					delta=$(($now - $mtime))

					if ! [[ "$delta" -gt 0 ]]; then
						echo "check_size_constraints: check size: time - mtime for $filename not greater then zero." 1>&2
						continue
					fi

					if [[ "$delta" -lt "$MinAge" ]]; then
						continue
					fi

					if [[ "$size" -lt "$MaxSize" ]]; then
						continue
					fi

					if ! rm -f $filename; then
						echo "check_size_constraints: check size: could not delete file $filename" 1>&2
						continue
					fi
					
				done;
			} < ${tempfile}

		fi


		rm -f $tempfile
		trap - 1 2 3 15
	else
		trap - 1 2 3 15
		echo "check_size_constraints: check size:  creating temp file returned non-zero status, returning 1." 1>&2
		return 1
	fi
    fi

}


declare -i UpdateInterval=0
declare -i DownloadUpgradeableInterval=0
declare -i AutocleanInterval=0

to_run=""

if ! to_run=$(apt-config shell UpdateInterval APT::Periodic::Update-Package-Lists DownloadUpgradeableInterval APT::Periodic::Download-Upgradeable-Packages AutocleanInterval APT::Periodic::AutocleanInterval 2>/dev/null); then
	echo "could not retrieve apt variables with apt-config, exiting." 1>&2
	exit 1
fi

if ! eval "$to_run"; then
	echo "could not initialize apt variables, exiting." 1>&2
	exit 1
fi

if ! [[ "$UpdateInterval" -ge 0 && "$DownloadUpgradeableInterval" -ge 0 && "$AutocleanInterval" -ge 0 ]]; then
	echo "variable UpdateInterval or DownloadUpgradeableInterval is not >= 0, exiting." 1>&2
	exit 1
fi


if [[ "$UpdateInterval" -eq 0 && "$DownloadUpgradeableInterval" -gt 0 ]]; then
	echo "note : package index file syncronization has been disabled, yet upgradeable package downloads have been enabled." 1>&2
fi


if [[ "$DownloadUpgradeableInterval" -lt "$UpdateInterval" ]]; then
	echo "note : the package index file synchronization interval is longer then the upgradeable package download interval." 1>&2
fi



declare -i AutocleanInterval=${AutocleanInterval:-DownloadUpgradeableInterval}

if ! [[ "$AutocleanInterval" -ge 0 ]]; then
	echo "variable AutocleanInterval is not >= 0, exiting." 1>&2
	exit 1
fi




# laptop check, on_ac_power returns:
#       0 (true)    System is on mains power
#       1 (false)   System is not on mains power
#       255 (false) Power status could not be determined
# Desktop systems always return 255 it seems
if which on_ac_power >/dev/null; then
    on_ac_power
    if [ "$?" -eq 1 ]; then
	echo "on_ac_power determined system to not be powered by mains, exiting." 1>&2
	exit 0
    fi
fi


# since we're working with timestamps in the directory, non-root users should not be able to change timestamps of files
# within the directory, if it looks like users can do this, the script exits.
# note: this only accomodates for the standard unix permissions model, ACL's could still allow a non-root user to change timestamps
# of files in the directory.

if ! [[ "$(stat -c %u /var/lib/apt/periodic 2>/dev/null)" == "0" && "$(stat -c %a /var/lib/apt/periodic 2>/dev/null)" == "755" ]]; then
        echo "directory /var/lib/apt/periodic isnt owned by root or doesnt have permissions 755, exiting." 1>&2
	exit 1
fi



UPDATE_STAMP="/var/lib/apt/periodic/update-stamp"
if check_stamp $UPDATE_STAMP $UpdateInterval; then
    echo "--- running apt-get update ---" 1>&2

    #not pipeing stderr to /dev/null since we want to see the errors if the command fails
    #since this script is run by cron, errors of this command will be in the mail cron sends, which is usefull

    apt-get -qq update 
    if [[ "$?" -eq 0 ]]; then
	if which dbus-send >/dev/null; then
	    dbus-send --system / app.apt.dbus.updated boolean:true
	fi

        if ! update_stamp $UPDATE_STAMP; then
		echo "updating timestamp of file '$UPDATE_STAMP' failed." 1>&2
	fi
    else
	echo "" 1>&2
	echo "'apt-get update' returned non-zero status, exiting." 1>&2
	exit 1
    fi
fi




DOWNLOAD_UPGRADEABLE_STAMP="/var/lib/apt/periodic/download-upgradeable-stamp"
if check_stamp $DOWNLOAD_UPGRADEABLE_STAMP $DownloadUpgradeableInterval; then
    echo "--- running apt-get -d dist-upgrade ---" 1>&2

    #not pipeing stderr to /dev/null since we want to see the errors if the command fails
    #since this script is run by cron, errors of this command will be in the mail cron sends, which is usefull

    apt-get -qq -d dist-upgrade
    if [[ "$?" -eq 0 ]]; then
    	if ! update_stamp $DOWNLOAD_UPGRADEABLE_STAMP; then
		echo "updating timestamp of file '$DOWNLOAD_UPGRADEABLE_STAMP' failed." 1>&2
	fi
    else
	echo "" 1>&2
	echo "'apt-get -d dist-upgrade' returned non-zero status, exiting." 1>&2
	exit 1
    fi
fi




AUTOCLEAN_STAMP="/var/lib/apt/periodic/autoclean-stamp"
if check_stamp $AUTOCLEAN_STAMP $AutocleanInterval; then
    echo "--- running apt-get autoclean ---" 1>&2

    #not pipeing stderr to /dev/null since we want to see the errors if the command fails
    #since this script is run by cron, errors of this command will be in the mail cron sends, which is usefull

    apt-get -qq autoclean
    if [[ "$?" -eq 0 ]]; then
    	if ! update_stamp $AUTOCLEAN_STAMP; then
		echo "updateing timestamp of file '$AUTOCLEAN_STAMP' failed." 1>&2
	fi
    else
	echo "" 1>&2
	echo "'apt-get autoclean' returned non-zero status." 1>&2
    fi
fi



# check cache size 
if ! check_size_constraints; then
	echo "function check_size_constraints returned with a non-zero status!." 1>&2
fi





Reply to: