Skip to content

Schedule a script to check remote disk usage and send an alert email using Mac OS X

mail

 

 

Goal: I need to schedule a bash script that runs each day on my mac and checks the available disk space on a remote computer. In case of low space it has to automatically send a usage report via email to alert the users.

 

 

 

This task involves:

  1. writing the script that
    • checks the remote machine for disk space [using df]
    • creates the disk usage report for each user [using du]
    • sends an alert email in case of low space to everyone [using mail]
  2. setting up the script to run automatically each day on OS X [using launchd]

I assume you have an administrator account in both machines (to be able to sudo commands) and you are able to login into the remote machine (golan) from the local one (macbob).

 

Solution:

First of all, you will need to set up your ssh private/public keys so that you won’t be prompted for a password each time you try to login to the remote machine. If you do not know how to do it,  it is very simple and involves using ssh-keygen. There are many how-to you can find on the internet, for example this one.

The script will then use the ssh ability to remotely execute commands as in

ssh bob@golan command

First, it will check the disk usage with df, then, if below the given threshold, it will execute a one-liner script on the remote machine to create the user usage report using the du command. Finally, it will nicely format and redirect the outputs to a text file and email it to a list of users using the mail command.

Everything is straightforward aside from a small tricky detail.  You will need to use your administrator super powers to create the usage report using something like sudo du to enter the users homes! The problem is that sudo is usually set up to prompt for your login password so that your script has to pass it somehow. Of course you do not want to “echo-pipe” the password written in clear in your script. The solution is either you change the sudo settings editing /etc/sudoers to avoid the password request, or you devise a way to obfuscate and pass your password to sudo on the remote machine.

I adopted the latter method even though it is not particularly safe but at least the password does not appear verbatim in any script. To do this you can use the -base64 option of openssl to encode/decode your password as in

bob@macbob:~ >echo "mypass" | openssl enc -base64
bXlwYXNzCg==
bob@macbob:~ >echo "bXlwYXNzCg==" | openssl enc -base64 -d
mypass

Finally, the mail command is simply great and does just what you would expect :)

Enough said, here is the local script (on macbob computer):

#!/bin/bash
# macBob:/var/root/mycronjobs/check-disk.sh

email="/tmp/email-disk.txt"
lim=100000000

# check if free space is below threshold
full=`ssh bob@golan "df | tail -n 1 "|awk -v l=$lim '{if ($4<l) print 1;else print 0}'`

if [ $full -eq 1 ]; then
   # disk space is low, prepare an email
   echo "This is an automatically generated email. Please do not reply." > $email
   echo "golan free space below $lim KB." >> $email
   echo >> $email
   echo "CURRENT USAGE PER USER:" >> $email
   echo >> $email
   
   # retrieve the disk usage per user, passing an encrypted password
   ssh bob@golan "echo bXlwYXNzd29yZAo= | /home/bob/script/disk-space.sh" >> $email

   # send the email to the users
   cat $email | mail -s "Disk usage limit exceeded" bob@mail.com alice@mail.com fred@mail.com

   echo "golan disk space exceeded, email sent."
   rm $email
else
   echo "golan is ok, nothing to be done."
fi

and here is the remote one-liner script that creates the user usage report and print it to stdout:

# golan:/home/bob/script/disk-space.sh
# expects an encrypted password from stdin, decrypt it and pipes it to sudo
# in order to be able to do a du (disk-usage) command on all users directories
openssl enc -base64 -d|sudo -S du /workspace/ --max-depth=1 -h|sort -n -g -k 1 -r

As an example, this is how the email would look like:

To: <bob@mail.com>, <alice@mail.com>, <fred@mail.com>
Subject: Disk usage limit exceeded
Date: Sun,  5 Feb 2012 07:03:05
From: "System Administrator" <root@bobmac.local>

This is an automatically generated email, do not reply.
golan free space below 100000000 KB.

CURRENT USAGE PER USER:

531G	/workspace/fred
512G	/workspace/alice
415G	/workspace/bob
286G	/workspace/john
176G	/workspace/boss
5.0G	/workspace/mark

Now we have to schedule check-disk.sh script to run each morning at, say, 7 a.m.. To do so you will need a launch manager, called a property list file (plist), located in a specific directory so that launchd daemons can take care of it. You can find the syntax reference of such files in the Mac OS X launchd plist documentation here.

Here is the com.bob.golan-check.plist file I created:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.bob.golan-check</string>

  <key>ProgramArguments</key>
  <array>
    <string>/var/root/mycronjobs/check-disk.sh</string>
  </array>

  <key>Nice</key>
  <integer>1</integer>

  <key>AbandonProcessGroup</key>
  <true/>

  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>7</integer>
    <key>Minute</key>
    <integer>0</integer>
  </dict>

  <key>RunAtLoad</key>
  <false/>

  <key>StandardErrorPath</key>
  <string>/tmp/golan-diskusage.err</string>

  <key>StandardOutPath</key>
  <string>/tmp/golan-diskusage.out</string>

</dict>
</plist>

You can put your plist file in any of the following directories:

/Library/LaunchDaemons/
/Library/LaunchAgents/
~/Library/LaunchAgents/

The first is for scripts that have to be executed even when no users are logged in, the second for users logged in (but run by root) and for the last one the script is executed only when the user is logged in.

I am using the first one, so the script will be run by root and for this reason I put it in a directory accessible only by root (I do not want a script run by root to be readable/writeable by any user).

Once you have your plist file you need to make launchd daemon aware of it. This is done using launchctl as follows:

load the manager:

bob@bobmac:~ >sudo launchctl load /Library/LaunchDaemons/com.bob.golan-diskusage.plist

check if it is loaded:

bob@bobmac:~ >sudo launchctl list |grep bob
-	0	com.bob.golan-diskusage

execute the script right away (to give it a try and test if everything is working):

bob@bobmac:~ >sudo launchctl start com.bob.golan-diskusage

if you need to change the plist file, unload it first:

bob@bobmac:~ >sudo launchctl unload /Library/LaunchDaemons/com.bob.golan-diskusage.plist

In these examples, I use sudo since the script is executed by root. If you are using the ~/Library/LaunchAgents/ directory and your script is executed by the user you do not need to.

That’s all, I hope it is clear otherwise play around or drop a line ;)

L
 

One final important note: if you want your scheduled script to be able to send emails you have to set the AbandonProcessGroup key to true in your plist file. This allows any spawned process by your script to finish before being killed by launchd. This is necessary since the mail command takes some time to execute and launchd would kill it otherwise (as reported here).

Further reading: I found this blog very useful and clear to describe how scheduled jobs work on Mac OS X. Give it a try if interested: Mac crontab

char a[n]; // my discovery of Variable Length Array!

VLA - Socorro - New Mexico - USA

Today, more than 10 years after its introduction, a shocking surprise: I discovered that a once forbidden practice in the ANSI C (C90) programming language had become normal practice: the use of Variable Length Arrays!

In other words, when I started learning and playing with C, several years ago, one could only define a fixed length array as in:

char name[100];

or if one needed a variable length array, the use of a pointer and the dynamic allocation of the memory was the only choice as in:

char *name;
/* some code... */
name = malloc(100*sizeof(char));
/* some other code... */
free(name);

The discovery of the existence of variable length array (or VLA, not to be confused with those in New Mexico which are the very large ones, beautifully captured in the picture above) undermined my certainties, but surely simplified array handling, especially in the case of multidimensional arrays where the allocation could easily become cumbersome. For instance, in the new ANSI C (C99) one can now thoughtlessly write:

void function(n)
{
    char name[n];
    /* do the crazy stuff... */
}

pretty shocking, isn’t it ?! :)

Writing this post, I also learned that actually the current standard ANSI C is not C99 anymore but C11 which has just been approved by ISO recently (8th december 2011)… who knows what it brings!

Overall, the lesson I took home is that even programming language standards change over time. Another tangible effect of time passing… It is hard, but I will get over it!

L

PS: to know more details, here is a nice post: The New C:Why Variable Length Arrays?

PPS: the way I made the discovery, is thanks to a student who innocently and naturally used the VLA in her code without knowing it. Then when she asked me some help for debugging, I promptly and firmly told her that char x[n]; she was trying to use was certainly wrong… turned out, it wasn’t. :-O

PPS: I’m sorry for the misleading picture, but I challenge you finding a beautiful and more appropriate one for this fascinating topic.

Pale blue dot of emotions

From time to time, I browse, driven by curiosity, through questions and answers on Quora. Today I wondered which was the most viewed question and it resulted to be:

What is the most epic photo ever taken ?

The most voted answer is the Pale Blue Dot. A tiny dot in the middle of nowhere… I found it so beautiful, I couldn’t retain from sharing:

Pale Blue Dot

It is Earth from 6 billion kilometers away, as seen from the edge of our solar system, taken by the spacecraft Voyager 1 in 1990, 13 years after NASA launched it. To give an idea of the distance, it takes light (thus any type of communication or data) about 5 hours and a half to reach the spacecraft, while our moon is reached in just 1 second from an hypothetical Earth light beam.

The brilliant idea to point the camera backwards and take the picture from the edge of humanity is from Carl Sagan, who wrote these inspiring words:

From this distant vantage point, the Earth might not seem of particular interest. But for us, it’s different. Look again at that dot. That’s here, that’s home, that’s us. On it everyone you love, everyone you know, everyone you ever heard of, every human being who ever was, lived out their lives. The aggregate of our joy and suffering, thousands of confident religions, ideologies, and economic doctrines, every hunter and forage, every hero and coward, every creator and destroyer of civilization, every king and peasant, every young couple in love, every mother and father, hopeful child, inventor and explorer, every teacher of morals, every corrupt politician, every “superstar,” every “supreme leader,” every saint and sinner in the history of our species lived there – on a mote of dust suspended in a sunbeam.

The Earth is a very small stage in a vast cosmic arena. Think of the rivers of blood spilled by all those generals and emperors so that, in glory and triumph, they could become the momentary masters of a fraction of a dot. Think of the endless cruelties visited by the inhabitants of one corner of this pixel on the scarcely distinguishable inhabitants of some other corner, how frequent their misunderstandings, how eager they are to kill one another, how fervent their hatreds.

Our posturing, our imagined self-importance, the delusion that we have some privileged position in the Universe, are challenged by this point of pale light. Our planet is a lonely speck in the great enveloping cosmic dark. In our obscurity, in all this vastness, there is no hint that help will come from elsewhere to save us from ourselves.

The Earth is the only world known so far to harbor life. There is nowhere else, at least in the near future, to which our species could migrate. Visit, yes. Settle, not yet. Like it or not, for the moment the Earth is where we make our stand.

It has been said that astronomy is a humbling and character-building experience. There is perhaps no better demonstration of the folly of human conceits than this distant image of our tiny world. To me, it underscores our responsibility to deal more kindly with one another, and to preserve and cherish the pale blue dot, the only home we’ve ever known.

L

Android fails app installation: Couldn’t install on USB storage or SD card

Since a couple of weeks ago every time I try to update or install an app from the android market to the SD card on my HTC Desire (with Android 2.2) I get the following annoying message :

“Couldn’t install on USB storage or SD card”

Googling around I found (here) that this is probably due to a previously  failed installation that leaves a file hanging around instead of properly removing it. The solution is to remove by hand the file smdl2tmp1.asec in the hidden folder:

/sdcard/.android_secure

In order to do that, connect your phone to your pc/mac, select “Mount as disk drive” on the phone and once the volume appears in the finder (I describe the mac procedure, in the case of windows or linux, mutatis mutandis, it should be equivalent), select “Go to folder” from the finder menu and type:

/Volumes/NO NAME/.android_secure

then delete the smdl2tmp1.asec file!

This is the easiest way to get to the file since the folder is hidden and you will not be able to get to it simply navigating in the usual way through the folders in the finder.

NB: in my case the SD name is “NO NAME”, yours could be different of course!

L

Follow

Get every new post delivered to your Inbox.