Auto-poweroff that server in your cellar
Our cellar houses an old grey box that acts as a home server for the family. It's quite useful in a number of ways, as a file server, web server, database server, and so on. It also, traditionally, had a habit of wasting power—it's so much nicer to just have the machine running when you use it. But, with the wonders of Wake on LAN, even the “I'm too lazy to run into the cellar” argument has lost any validity it might have had.
So much for turning the box on, how about turning it off? Figuring out when nobody is using the machine and then remembering to turn it off as well is hardly a task for a mere mortal. So I wrote a script that does it for me. is_anyone_here.py checks whether anyone is logged in, and looks for any evidence of recent usage. It was written on/for a Debian GNU/Linux (lenny) system with vsftpd and samba, and may require some modifications to work properly in your environment.
Have a look at the whole script after the break.
#!/usr/bin/env python # Python 2.4 or newer. Python 3 untested. """ usage: is_anyone_here.py [-v] [{mitutes}] -v print what the script is doing. {minutes} number of minutes in the past to check for human activity. An exit status of 0 means that nobody appears to be using the system. A status of 1 means that there are humans lurking somewhere. """ import sys import os import re from subprocess import Popen, PIPE from datetime import * _VERBOSE = False def _dbg(s): if _VERBOSE: print (s) def main(argv): MINUTES = 0 if len(argv) > 1 and argv[1] == '-v': globals()['_VERBOSE'] = True elif len(argv) > 1 and argv[1] == '-h': print __doc__ return True if len(argv) > 1 and argv[-1].isdigit(): MINUTES = int(argv[-1]) _dbg("Checking for humans within the past %d minutes." % MINUTES) if not check_who(): return False if not check_smbstatus(): return False if MINUTES > 0: if not check_auth_log(MINUTES): return False if not check_vsftpd_log(MINUTES): return False if not check_smb_log(MINUTES): return False return True def check_who(): who = Popen(['who'], stdout=PIPE) output = who.communicate()[0].strip() + '\nEOF' nusers = len(output.strip().split('\n'))-1 _dbg("%d users logged in." % nusers) if nusers > 0: return False else: return True def check_smbstatus(): who = Popen(['smbstatus', '-S'], stdout=PIPE) outlines = who.communicate()[0].strip().split('\n') in_content = False nusers = 0 for l in outlines: # crop header if l.startswith('------------------'): in_content = True elif in_content: nusers += 1 _dbg("%d users using samba." % nusers) if nusers > 0: return False else: return True def check_auth_log(m): f = file('/var/log/auth.log', 'r') now = datetime.now() for line in f: # ignore cron and at sessions: they don't qualify as human use. if not 'cron:session' in line and not 'atd:session' in line: lastline = line # insert the current year, since auth.log is sloppy like that. # this will cause problems at the beginning of the year, but I # can live with that. datestring = re.sub(r'^(\S+ +\d+ +\S{8}).*$', r'%d \1' % now.year, lastline).strip() _dbg("last auth.log entry at %s" % datestring) date = datetime.strptime(datestring, '%Y %b %d %H:%M:%S') if now - date > timedelta(minutes=m): return True else: return False def check_vsftpd_log(m): f = file('/var/log/vsftpd.log', 'r') now = datetime.now() for line in f: lastline = line datestring = re.match(r'^(\S+ +\S+ +\d+ +\S{8} +\d{4}).*$', lastline).group(1) _dbg("last vsftpd.log entry at %s" % datestring) date = datetime.strptime(datestring, '%a %b %d %H:%M:%S %Y') if now - date > timedelta(minutes=m): return True else: return False def check_smb_log(m): files = os.listdir('/var/log/samba') now = datetime.now() date = datetime(1900,1,1) # ignore log.smbd and log.nmbd - they contain crap like # "can't connect to CUPS." for f in (file('/var/log/samba/' + fn, 'r') for fn in files if fn.startswith('log.') and fn not in ('log.smbd', 'log.nmbd')): lastline = None for line in f: if line.startswith('['): lastline = line if lastline is not None: thisdate = datetime.strptime( re.match(r'^\[(.*?),.*$', lastline).group(1), '%Y/%m/%d %H:%M:%S') if thisdate > date: date = thisdate _dbg("found samba log entry from %s" % thisdate) if now - date > timedelta(minutes=m): return True else: return False if __name__ == '__main__': # dates in log files are C-locale. import locale locale.setlocale(locale.LC_ALL, 'C') if main(sys.argv): exit(0) else: _dbg("the system is in use by people.") exit(1)
UPDATE 2010-05-29: Tiny bug in the script removed. Might work now if it didn't before.
This just looks for evidence of humans. If you don't use vsftpd or samba, you'd have to remove those function calls in main. To shut down the system when nobody is using it, you could add something like this to your /etc/crontab:
*/15 * * * * root is_anyone_here.py 40 && poweroff
This would power down the machine after 40 minutes of boredom.
