Performing Unix overnight tasks while the computer's switched off!

OK. It’s a slightly misleading subject line. :rolleyes:

The Unix operating system that underlies Mac OS X performs certain routine maintenance tasks overnight, while the machine’s theoretically not being used for anything else. The problem with this is that environmentally conscious users will switch their machines off overnight to save the vast amounts of electricity that unattended computers reportedly consume.

There’s a nice little freeware application called MacJanitor that can run the tasks on demand. Its literature lists the relevant sudo calls for the benefit of those who prefer to use the Terminal. Since I use an AppleScript to shut down my computer “ which mutes the sound at the same time and avoids the irritating chime when the machine starts up again “ I’ve incorporated a ‘do shell script’ with the relevant sudo calls into that.

The script invokes the relevant daily, weekly, or monthly maintenance tasks if it’s used between 10:00 in the evening and 4:00 in the morning or if more than the prescribed interval has elapsed since it last performed a particular task. It stores its “times last done” in a data file, which it keeps in a folder called “Shut Down Script” in another folder called “AppleScripts” in the user’s “Application Support” folder, for want of anywhere better. (It creates the folders itself.) However, the sudo calls can only be run by the administrative user of the machine. That user’s password will be required and “ if thought wise “ can be added as a parameter to the shell script call at the end of the doMaintenance() handler.

(* Execute the Unix daily, weekly, and/or monthly maintenance tasks as appropriate *)
on doMaintenance()
	-- Check that this script's support folder exists in the system Application Support folder. Create it if not.
	set supportFolderPath to (path to application support from user domain as Unicode text) & "AppleScripts:Shut Down Script:"
	try
		supportFolderPath as alias
	on error number -43
		do shell script ("mkdir -p " & quoted form of POSIX path of supportFolderPath)
	end try
	-- Open the data file in the support folder. If there isn't one, create it.
	set fref to (open for access file (supportFolderPath & "LastMaintenanceTimes.dat") with write permission)
	try
		-- Read the times this script last initiated the maintenance tasks.
		set {lastDaily, lastWeekly, lastMonthly} to (read fref as list)
		if ((get {class of lastDaily, class of lastWeekly, class of lastMonthly}) is not {date, date, date}) then error
	on error
		-- If this can't be done, start with three deliberately early dates.
		set lastDaily to date "Wednesday 1 January 1000 00:00:00"
		set lastWeekly to lastDaily
		set lastMonthly to lastDaily
	end try
	-- Do each task if it's now before 04:00 on the relevant day or after 22:00 on the day
	-- before, or if more than the appointed interval has elapsed since the task was last done.
	-- A simpler check is to see if the time two hours from now is before 06:00 on the relevant day.
	try
		set twoHoursTime to (current date) + 2 * hours
		set nearMidnight to (twoHoursTime's time < 6 * hours)
		
		-- Build a shell script string, appending the daily, weekly, and/or monthly tasks as appropriate.
		set shellStr to "" as Unicode text
		
		if (nearMidnight) or (twoHoursTime - lastDaily > days) then
			set shellStr to addTask(shellStr, "daily")
			set lastDaily to twoHoursTime
		end if
		
		if ((nearMidnight) and (twoHoursTime's weekday is Sunday)) or (twoHoursTime - lastWeekly > weeks) then
			set shellStr to addTask(shellStr, "weekly")
			set lastWeekly to twoHoursTime
		end if
		
		if ((nearMidnight) and (twoHoursTime's day is 1)) or (twoHoursTime - lastMonthly > 31 * days) then
			set shellStr to addTask(shellStr, "monthly")
			set lastMonthly to twoHoursTime
		end if
		
		-- Save the possibly updated "last done" times to the support data file and close it.
		set eof fref to 0
		write {lastDaily, lastWeekly, lastMonthly} to fref
	on error msg
		display dialog msg
	end try
	close access fref
	
	-- If any tasks have been added to the shell script string, run it.
	if ((count shellStr) > 0) then do shell script shellStr with administrator privileges
end doMaintenance

(* Say what maintenance task is to be carried out and add it to the shell script string. *)
on addTask(shellStr, interval)
	if ((count shellStr) is 0) then
		say "Starting " & interval & " maintenance tasks."
	else
		say "and " & interval & " maintenance tasks."
	end if
	set shellStr to shellStr & "sudo sh /etc/" & interval & " ;  "
	
	return shellStr
end addTask

with timeout of (30 * minutes) seconds
	doMaintenance()
end timeout
set volume 0 -- Mute the startup chime.
tell application "System Events" to shut down

Hi,

I made a few changes that I thought I’d share. My problem was that the computer in question may be left on overnight, allowing the maintenance scripts to run according to the normal mechanism but it may be put to sleep, in which case the AppleScript would have to run them. So, with some help from this article, I adapted your script to get the modification dates of the log files generated by the maintenance scripts, rather than keeping its own record. Also, I didn’t need it to speak or mute the sound and wanted it to put the computer to sleep rather than shut down.

--Execute the daily/weekly/monthly maintenance scripts as appropriate

on doMaintenance()
	--Check when each script was last run, defaulting to an arbitrarily old date
	set dailyLog to "/var/log/daily.out"
	set weeklyLog to "/var/log/weekly.out"
	set monthlylog to "/var/log/monthly.out"
	
	set lastDaily to date "Friday, January 1, 1904 00:00:0"
	try
		set dailyLog to POSIX file dailyLog
		tell application "Finder" to set lastDaily to modification date of dailyLog
	end try
	
	set lastWeekly to date "Friday, January 1, 1904 00:00:0"
	try
		set weeklyLog to POSIX file weeklyLog
		tell application "Finder" to set lastWeekly to modification date of weeklyLog
	end try
	
	set lastMonthly to date "Friday, January 1, 1904 00:00:0"
	try
		set monthlylog to POSIX file monthlylog
		tell application "Finder" to set lastMonthly to modification date of monthlylog
	end try
	
	-- Do each task if it's now before 04:00 on the relevant day or after 22:00 on the day
	-- before, or if more than the appointed interval has elapsed since the task was last done.
	-- A simpler check is to see if the time two hours from now is before 06:00 on the relevant day.
	
	set twoHoursTime to (current date) + 2 * hours
	set nearMidnight to (twoHoursTime's time < 6 * hours)
	
	-- Build a shell script string, appending the daily, weekly, and/or monthly tasks as appropriate.
	set shellStr to "" as Unicode text
	
	if (nearMidnight) or (twoHoursTime - lastDaily > days) then
		--set shellStr to addTask(shellStr, "daily")
		set shellStr to shellStr & " daily"
	end if
	
	if ((nearMidnight) and (twoHoursTime's weekday is Sunday)) or (twoHoursTime - lastWeekly > weeks) then
		--set shellStr to addTask(shellStr, "weekly")
		set shellStr to shellStr & " weekly"
	end if
	
	if ((nearMidnight) and (twoHoursTime's day is 1)) or (twoHoursTime - lastMonthly > 31 * days) then
		--set shellStr to addTask(shellStr, "monthly")
		set shellStr to shellStr & " monthly"
	end if
	
	-- If any tasks have been added to the shell script string, run it.
	if ((count shellStr) > 0) then
		set shellStr to "periodic" & shellStr
		do shell script shellStr with administrator privileges
	end if
end doMaintenance


with timeout of (30 * minutes) seconds
	doMaintenance()
end timeout
--set volume 0 -- Mute the startup chime.
tell application "System Events" to sleep

Edit: Fixed that, thanks!

Hi. Thanks.

That’s a great idea. It’s much nicer not to have to repeat the tasks if the system’s already done them recently. I think I’ll adopt that approach myself. :slight_smile:

Hi there!

Newbie at scripting humbly asks for assistance on the above scripts :slight_smile:

Sounds perfect for me, I will have a program wake up the computer and perform a backup task during the night and then trigger a script to go to sleep. Simple enough, but your addition to that to let the maintenance scripts to be performed if needed would even be better indeed. I tried using the last posted script straight off but had to modify it since the compiler complained about incorrect date.

So, I changed the following lines…

set lastDaily to "Friday, January 1, 1904, 00:00:00"
set lastWeekly to "Friday, January 1, 1904 00:00:00"
set lastMonthly to "Friday, January 1, 1904 00:00:00"

This seemed to work indeed but after running a second time and checking the modification dates of the maintenance logs manually I noticed that the weekly script is triggered every time.

Any idea what went wrong?

Best regards,
Hans

Hi, Hans. And welcome! :slight_smile:

That’s right. AppleScript bases its interpretation of date/time strings on the user’s own Date and Time preferences. You’d need to translate those date strings into German (I presume) for the script to work on your machine. Another way to specify the date, which seems to work with anyone’s preferences, is:

set lastDaily to "1000-01-01" as «class isot» as date

Or:

set lastDaily to «data isot313030302D30312D3031» as date

I’m not totally sure why your weekly script executes again on a second run of the script. If the runs are not very far apart in time, it’s possible that the Finder isn’t updating its own information about the log files’ modification dates in time for the second run. It might not be a problem in normal use. My own adaptation of TheMouthOfSauron’s idea uses ‘info for’, rather than the Finder, to get the modification dates:

(* Execute the Unix daily, weekly, and/or monthly maintenance tasks as appropriate *)
on doMaintenance()
	-- Set three deliberately early default "last done" dates.
	set lastDaily to «data isot313030302D30312D3031» as date
	set lastWeekly to lastDaily
	set lastMonthly to lastDaily
	
	-- Replace as appropriate with the actual dates, gleaned from the modification dates of the ".out" files.
	try
		set lastDaily to modification date of (info for ("/var/log/daily.out" as POSIX file))
	end try
	try
		set lastWeekly to modification date of (info for ("/var/log/weekly.out" as POSIX file))
	end try
	try
		set lastMonthly to modification date of (info for ("/var/log/monthly.out" as POSIX file))
	end try
	
	-- Do each task if it's now before 04:00 on the relevant day or after 22:00 on the day
	-- before, or if more than the appointed interval has elapsed since the task was last done.
	-- A simpler check is to see if the time two hours from now is before 06:00 on the relevant day.
	set twoHoursTime to (current date) + 2 * hours
	set nearMidnight to (twoHoursTime's time < 6 * hours)
	
	-- Build a shell script string, appending the daily, weekly, and/or monthly tasks as appropriate.
	set shellStr to "periodic" as Unicode text
	if (nearMidnight) or (twoHoursTime - lastDaily > days) then set shellStr to shellStr & " daily"
	if ((nearMidnight) and (twoHoursTime's weekday is Sunday)) or (twoHoursTime - lastWeekly > weeks) then set shellStr to shellStr & " weekly"
	if ((nearMidnight) and (twoHoursTime's day is 1)) or (twoHoursTime - lastMonthly > 31 * days) then set shellStr to shellStr & " monthly"
	
	-- If any tasks have been added to the shell script string, run it.
	if ((count shellStr) > 8) then do shell script shellStr with administrator privileges
end doMaintenance

with timeout of (30 * minutes) seconds
	doMaintenance()
end timeout
set volume 0 -- Mute the startup chime.
tell application "System Events" to shut down

Thanks Nigel for your help and welcoming words :slight_smile:

Instead of changing the script to follow Date and Time pref settings (Swedish btw :slight_smile: )
I just tried out your version of the script, I only changed it to put the computer to sleep.

The script compiles and runs of course but now no maintenance kicked in,
then I remembered, stupid me, it shouldn’t since it is around 10am here now.

I’ll post back later to tell you how things went…

Cheers,
Hans

Tried the script out earlier during the night.

It starts up alright but

  • it will always ask for my password, is there any way around that?
  • it seems to perform the daily maintenance every time it is launched (checked both in terminal and in System Loggs),
    not a big deal but would be nicer if it didn’t do that every time

Have a good day or night, I will try sleeping again :stuck_out_tongue:

Cheers,
Hans

Hi, Hans.

You can automate the password by changing ”

if ((count shellStr) > 8) then do shell script shellStr with administrator privileges

” to ”

if ((count shellStr) > 8) then do shell script shellStr password "yourpassword" with administrator privileges

” where “yourpassword” is your password or some means of getting it. But obviously it’s a security risk.

The script was originally designed to shut down the computer, performing the maintenance tasks as appropriate. It does the “daily” tasks if it’s run between between 22:00 and 04:00 or more than 24 hours after the previous daily maintenance. If you run it several times between 22:00 and 04:00, it’ll do the daily stuff every time. Outside those hours, it’ll only do the daily stuff if more than 24 hours have elapsed since that was last done. Similarly with the other tasks if you run them several times near midnight on the appointed days. It’s possible to add more conditions to allow a few hours before they “re-arm”, but it’s overkill for the envisaged usage.

Hi Nigel!

Thank you very much for being patient and helping me out!
I am starting to get some hang of this at last :slight_smile:

I have now implemented this together with a backup approach,
each night a backup program will kick in and do it’s job and will
launch the script when it is done. Backup and maintenance all in one go :slight_smile:

Again, many thanks!

Cheers,
Hans