<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ptone &#187; Technology</title>
	<atom:link href="http://ptone.com/dablog/category/tech/feed/" rel="self" type="application/rss+xml" />
	<link>http://ptone.com/dablog</link>
	<description>Hodgepodge of thoughts, technical notes, and random observations</description>
	<lastBuildDate>Sat, 04 Jun 2011 14:42:35 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.5</generator>
		<item>
		<title>Setting up pinax on Dreamhost Private Server</title>
		<link>http://ptone.com/dablog/2009/10/setting-up-pinax-on-dreamhost-private-server/</link>
		<comments>http://ptone.com/dablog/2009/10/setting-up-pinax-on-dreamhost-private-server/#comments</comments>
		<pubDate>Thu, 29 Oct 2009 22:29:07 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[django pinax]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/10/setting-up-pinax-on-dreamhost-private-server/</guid>
		<description><![CDATA[using apache, mysql, mod_wsgi, and virtualenv Dreamhost provides free hosting to nonprofits, and for now they seem to have thrown in a virtual private server. This is hard not to take advantage of, and I&#8217;m hoping that the VPS is better performing than the shared hosting &#8211; but my concern is that they all share [...]]]></description>
			<content:encoded><![CDATA[<p>using apache, mysql, mod_wsgi, and virtualenv</p>

<p>Dreamhost provides free hosting to nonprofits, and for now they seem to have thrown in a virtual private server.  This is hard not to take advantage of, and I&#8217;m hoping that the VPS is better performing than the shared hosting &#8211; but my concern is that they all share the same mysql servers.  So here is how I got pinax up and running in its comfy and modern Python web stack.</p>

<p><span id="more-127"></span>
At first I tried to go the route of custom install of python2.6 &#8211; but got bogged down trying to get either passenger<em>wsgi or mod</em>wsgi to work properly &#8211; they both seemed to have fatal dead ends in the end.</p>

<p>The first thing you need to do is associate a domain with your PS and set it up as a directory of a user.</p>

<p>so that you have a user and directory like /home/django_user/example.com/</p>

<p>In the Dreamhost panel private server section &#8220;Web Server Configuration&#8221; uncheck &#8220;DreamHost Managed:&#8221;.  This will prevent any accidental changes you make in the panel from undoing the customizations you need to make to the apache config.</p>

<p>Next you need to create an admin user in the Dreamhost control panel for your PS, lets say django<em>admin, this probably should be a different user than is set up for the domain.  This user will just be configuring system level software, namely set the python version to use, install the mysql bindings, PIL, and mod</em>wsgi packages.</p>

<p>login as the admin user and then:</p>

<pre><code>mkdir src

# This is to set python 2.5 as the default - if you don't care you can skip this step - but I'm assuming this is done
cd /usr/bin/
sudo rm python
sudo ln -s python2.5 python


cd ~/src
# note that this is specific for 2.5 - use the right egg for your python version
wget http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11-py2.5.egg#md5=64c94f3bf7a72a13ec83e0b24f2749b2
sudo sh setuptools-0.6c11-py2.5.egg
</code></pre>

<p>You next need to download the MySQL-python tarball from http://sourceforge.net/projects/mysql-python/ as there is no easy way to fetch it from the command line.  FTP it to your src/ directory of the admin user then:</p>

<pre><code>tar xvzf MySQL-python-1.2.3c1.tar.gz
cd MySQL-python-1.2.3c1
sudo python setup.py install

cd ..
wget http://effbot.org/media/downloads/Imaging-1.1.6.tar.gz
tar xvzf Imaging-1.1.6.tar.gz
cd Imaging-1.1.6
python setup.py build --force
sudo python setup.py install
python selftest.py
</code></pre>

<p>You should see: 57 tests passed. Next:</p>

<pre><code>sudo easy_install pip
sudo pip install virtualenv
# virtualenvwrapper is primarily useful if you are going to do 
# dev work or host many projects - I don't assume you fetch this
sudo pip install virtualenvwrapper

cd ~/src
wget http://modwsgi.googlecode.com/files/mod_wsgi-3.0c5.tar.gz
tar xvzf mod_wsgi-3.0c5.tar.gz 

./configure --with-apxs=/usr/local/dh/apache2/template/sbin/apxs --with-python=/usr/bin/python
make
sudo make install

# note that your private server ID replaces the 99999, 
# sub your favorite editor of choice (vi,emacs etc)

sudo pico /usr/local/dh/apache2/apache2-ps99999/etc/httpd.conf
</code></pre>

<p>find the section on modules and add: </p>

<pre><code>LoadModule wsgi_module /dh/apache2/template/lib/modules/mod_wsgi.so
</code></pre>

<p>Now your base python environment is configured, don&#8217;t log out of this user &#8211; as we will need to make more changes to the httpd.conf file later.  Instead open up another terminal session and login as your django_user</p>

<p>The first thing we need to do is create some folders we will use later</p>

<pre><code>mkdir src envs projects wsgi-scripts
</code></pre>

<p>Then we will grab pinax, install it, and activate the virtualenv</p>

<pre><code>cd ~/src
wget http://downloads.pinaxproject.com/Pinax-0.7.1-bundle.tar.gz
tar xvzf Pinax-0.7.1-bundle.tar.gz
python Pinax-0.7.1-bundle/scripts/pinax-boot.py ~/envs/pinax_test/
cd ~
source envs/pinax_test/bin/activate

#optional - update django to current release version
pip install -U django
</code></pre>

<p>Now we will create a basic pinax project</p>

<pre><code>pinax-admin clone_project basic_project projects/basictest
cd projects/basictest/
</code></pre>

<p>Before you continue, go back to the Dreamhost panel and create a mysql database for your project &#8211; note the name and password, and the host name.</p>

<p>Edit the settings.py in your basictest project to use these database settings</p>

<p>Now lets see if we can get pinax up and running:</p>

<pre><code>chmod +x manage.py 
./manage.py syncdb
./manage.py runserver 0.0.0.0:8000
</code></pre>

<p>You should now be able to go to http://example.com:8000 and see the pinax welcome screen, ctrl-C to stop the dev server.</p>

<p>To get pinax working with mod_wsgi you need two parts, a wsgi script, and changes to the apache configuration</p>

<p>create a file called pinax_basictest.wsgi in your wsgi-scripts folder.  Its contents should look like (cobbled from wsgi docs and pinax deployment sample wsgi file):</p>

<pre><code>import os
import sys
import site
sys.stdout = sys.stderr

# not needed in all environments - but is in others
os.environ['PYTHON_EGG_CACHE'] = '/tmp'

# this forces the virtualenv site-packages to be higher priority than system site-packages
site.addsitedir('/home/django_user/envs/pinax_test/lib/python2.5/site-packages')

sys.path.insert(0,'/home/django_user/projects/')

from django.conf import settings
os.environ['DJANGO_SETTINGS_MODULE'] = 'basictest.settings'

sys.path.insert(0, os.path.join(settings.PINAX_ROOT, "apps"))
sys.path.insert(0, os.path.join(settings.PROJECT_ROOT, "apps"))


import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()


# uncomment below to do a sanity check on the wsgi setup

# def test_wsgi(environ, start_response):
#     status = '200 OK'
#     output = 'Hello World! wsgi py \n' + sys.version + '\n' + '\n'.join(sys.path)
# 
#     response_headers = [('Content-type', 'text/plain'),
#                         ('Content-Length', str(len(output)))]
#     start_response(status, response_headers)
# 
#     return [output]
# 
# application = test_wsgi
</code></pre>

<p>Next you need to switch back to your admin user terminal (you didn&#8217;t close it right?) and edit your httpd.conf file again</p>

<pre><code>sudo pico /usr/local/dh/apache2/apache2-ps99999/etc/httpd.conf
</code></pre>

<p>go to the very end of the file, and before the last closing /virtualhost tag (so inside the virtualhost) add the following</p>

<pre><code># wsgi
&lt;Directory /home/django_user/wsgi-scripts&gt;
Order allow,deny
Allow from all
&lt;/Directory&gt;


# absolute path
WSGIScriptAlias / /home/django_user/wsgi-scripts/pinax_basictest.wsgi 

&lt;Directory "/home/django_user/projects/basictest/media"&gt;
    Allow from all
    Order allow,deny
&lt;/Directory&gt;


&lt;Directory "/home/django_user/envs/pinax_test/lib/python2.5/site-packages/django/contrib/admin/media"&gt;
    Allow from all
    Order allow,deny
&lt;/Directory&gt;

Alias "/media/" "/home/django_user/projects/basictest/media/"

# note some use hyphen - others _
Alias "/admin_media/" "/home/django_user/envs/pinax_test/lib/python2.5/site-packages/django/contrib/admin/media/"
</code></pre>

<p>Finally, still while in the admin user terminal  &#8211; restart apache (remember to change for your private server id):</p>

<pre><code>sudo /etc/init.d/httpd2 restart apache2-ps999999
</code></pre>

<p>Now you should be able to go to http://example.com and see the pinax welcome screen</p>

<p>If you get a 500 error:</p>

<ul>
<li>look in your apache error log which will be at /home/django_user/logs/example.com/http/error.log</li>
<li>uncomment the sanity check in the wsgi script to see if you can get basic hello world working, along with some info about what python environment is being used by wsgi</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/10/setting-up-pinax-on-dreamhost-private-server/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Restricting login to account based on IP address</title>
		<link>http://ptone.com/dablog/2009/10/restricting-login-to-account-based-on-ip-address/</link>
		<comments>http://ptone.com/dablog/2009/10/restricting-login-to-account-based-on-ip-address/#comments</comments>
		<pubDate>Wed, 28 Oct 2009 16:31:38 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[osx]]></category>
		<category><![CDATA[sys-admin]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/10/restricting-login-to-account-based-on-ip-address/</guid>
		<description><![CDATA[At work we needed to have a standard local account that would work off campus, but not on campus. Here was my solution. First I check for the user and create it if it doesn&#8217;t exist #!bash user_exists=`dscl . -read /Users/remote GeneratedUID &#124; grep -c GeneratedUID` if [ $user_exists -ne 1 ]; then echo "creating [...]]]></description>
			<content:encoded><![CDATA[<p>At work we needed to have a standard local account that would work off campus, but not on campus. Here was my solution.</p>

<p><span id="more-97"></span>
First I check for the user and create it if it doesn&#8217;t exist</p>

<pre><code>#!bash
user_exists=`dscl . -read /Users/remote GeneratedUID | grep -c GeneratedUID`
if [ $user_exists -ne 1 ]; then
    echo "creating remote user"
    sudo dscl . -create /Users/remote
    dscl . -create /Users/remote UserShell /bin/bash
    sudo dscl . -create /Users/remote RealName "remote"
    dscl . -create /Users/remote UniqueID 509
    dscl . -create /Users/remote PrimaryGroupID 1000
    dscl . -create /Users/remote NFSHomeDirectory /Local/Users/remote
    dscl . -passwd /Users/remote remote
fi
</code></pre>

<p>because these are fully managed machines, I know what UIDs are available.  (For a method that checks for available UID I&#8217;ve posted a script from Andrew Mortensen below)</p>

<p>The next part of the script will check if the user is logged in as &#8220;remote&#8221; and on campus using a regular expression (our two subnets are 10.5.5.X and 10.6.6.X).  You could also check a router, DHCP server, or internal DNS as other approaches.  If they are on campus I use a display utility called BigHonkingText to throw up a message and then kill the loginwindow.</p>

<pre><code>#!bash
user="$1"

if [ "$user" == "remote" ]; then
    IP=`ifconfig | grep "inet " | grep -v 127.0.0.1 | awk 'NR&gt;1{exit};{ print $2 }'`
    echo $IP
    if [[ "$IP" =~ 10.[5,6].[5,6].[0-9]* ]]; then
        echo "on campus"
    /usr/local/bin/BigHonkingText "account not allowed on campus"
    killall loginwindow
        # reboot
    fi
fi
</code></pre>

<p>Here is the script from Andrew Mortensen:</p>

<pre><code>#!bash
#! /bin/sh

# create a template user

export PATH=/bin:/usr/bin:/sbin:/usr/sbin

# arbitrary uid
N_UID=501

# arbitrary gid
N_GID=501

# user name
N_USERNAME="$1"

# home
N_HOME="/var/${N_USERNAME}"

# system default user home template
SYSHOMETEMPLATE="/System/Library/User Template/English.lproj"

# make sure the uid and gid are available
while [ true ]; do
    user="`dscl . -search /users UniqueID ${N_UID} 2&gt;/dev/null`"

    if [ -z "${user}" ]; then
    break
    fi

    N_UID=$((${N_UID} + 1));
done

while [ true ]; do
    group=`dscl . -search /groups PrimaryGroupID ${N_GID} 2&gt;/dev/null`

    if [ -z "${group}" ]; then
    break
    fi

    N_GID=$((${N_GID} + 1));
done

# create user
dscl . &lt;&lt;EOF
create "/users/${N_USERNAME}"
create "/users/${N_USERNAME}" AppleMetaNodeLocation /Local/Default
create "/users/${N_USERNAME}" GeneratedUID `uuidgen`
create "/users/${N_USERNAME}" UniqueID ${N_UID}
create "/users/${N_USERNAME}" PrimaryGroupID ${N_GID}
create "/users/${N_USERNAME}" Password "*"
create "/users/${N_USERNAME}" RecordName ${N_USERNAME}
create "/users/${N_USERNAME}" RecordType dsRecTypeNative:users
create "/users/${N_USERNAME}" NFSHomeDirectory ${N_HOME}
create "/users/${N_USERNAME}" RealName "Template User"
create "/users/${N_USERNAME}" UserShell /bin/bash
EOF
if [ $? -ne 0 ]; then
    logger -is Creation of ${N_USERNAME} failed.

    # destroy account
    dscl . -delete "/users/${N_USERNAME}" 2&gt;/dev/null
    exit 2
fi

# create group
dscl . &lt;&lt;EOF
create "/groups/${N_USERNAME}"
create "/groups/${N_USERNAME}" AppleMetaNodeLocation /Local/Default
create "/groups/${N_USERNAME}" GeneratedUID `uuidgen`
create "/groups/${N_USERNAME}" PrimaryGroupID ${N_GID}
create "/groups/${N_USERNAME}" RecordName ${N_USERNAME}
create "/groups/${N_USERNAME}" RecordType dsRecTypeNative:groups
create "/groups/${N_USERNAME}" Password "*"
create "/groups/${N_USERNAME}" GroupMembership ${N_USERNAME}
EOF
if [ $? -ne 0 ]; then
    logger -is Creation of ${N_USERNAME} failed.

    # destroy account
    dscl . -delete "/users/${N_USERNAME}" 2&gt;/dev/null
    dscl . -delete "/groups/${N_USERNAME}" 2&gt;/dev/null
    exit 2
fi

# make home directory
mkdir -m 0700 -p ${N_HOME}
ditto --rsrc "${SYSHOMETEMPLATE}" "${N_HOME}"

if [ $? -ne 0 ]; then
    logger -is Creation of ${N_USERNAME} failed.

    # destroy account
    dscl . -delete "/users/${N_USERNAME}" 2&gt;/dev/null
    dscl . -delete "/groups/${N_USERNAME}" 2&gt;/dev/null
    exit 2
fi

chown -R ${N_USERNAME}:${N_USERNAME} ${N_HOME}

logger -i Creation of template user ${N_USERNAME} succeeded.

exit 0
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/10/restricting-login-to-account-based-on-ip-address/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Launching OE-Cake</title>
		<link>http://ptone.com/dablog/2009/09/launching-oe-cake/</link>
		<comments>http://ptone.com/dablog/2009/09/launching-oe-cake/#comments</comments>
		<pubDate>Wed, 09 Sep 2009 17:24:25 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[osx]]></category>
		<category><![CDATA[sysadmin]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/09/launching-oe-cake/</guid>
		<description><![CDATA[OE-Cake is still expired for OS X &#8211; lets work around that So OE-Cake now has a beta for windows &#8211; but not OS X. We still have the binary from their old version that expired some time ago &#8211; we were launching it with ARD with the below trick, but now I&#8217;ve moved it [...]]]></description>
			<content:encoded><![CDATA[<p>OE-Cake is still expired for OS X &#8211; lets work around that</p>

<p><span id="more-86"></span></p>

<p>So OE-Cake now has a beta for windows &#8211; but not OS X.</p>

<p>We still have the binary from their old version that expired some time ago &#8211; we were launching it with ARD with the below trick, but now I&#8217;ve moved it into a read-only applescript application that the students can launch themselves:</p>

<pre><code>try
    do shell script "sudo date 1103100008" user name "admin" password "*****" with administrator privileges
end try

do shell script "open /Applications/OE-CAKE\\!.app"
delay 10
do shell script "launchctl stop org.ntp.ntpd" user name "admin" password "*****" with administrator privileges
do shell script "launchctl start org.ntp.ntpd" user name "admin" password "*****" with administrator privileges
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/09/launching-oe-cake/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Making iPhone iTunes remote more useful</title>
		<link>http://ptone.com/dablog/2009/09/making-iphone-itunes-remote-more-useful/</link>
		<comments>http://ptone.com/dablog/2009/09/making-iphone-itunes-remote-more-useful/#comments</comments>
		<pubDate>Wed, 09 Sep 2009 00:10:17 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[iphone]]></category>
		<category><![CDATA[itunes]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/09/making-iphone-itunes-remote-more-useful/</guid>
		<description><![CDATA[The iTunes remote is pretty slick &#8211; if only it could turn on the stereo I have a mac mini set up as a HTPC. The music end of this was a bit neglected as it was originally set up to use a custom onscreen menu control to switch between several different iTunes libraries and [...]]]></description>
			<content:encoded><![CDATA[<p>The iTunes remote is pretty slick &#8211; if only it could turn on the stereo</p>

<p><span id="more-85"></span></p>

<p>I have a mac mini set up as a HTPC.  The music end of this was a bit neglected as it was originally set up to use a custom onscreen menu control to switch between several different iTunes libraries and then have basic control within them.</p>

<p>Now that we have iPhones in the house &#8211; I consolidated the decade + worth of iTunes libraries into one.</p>

<p>I wanted a way to control the speakers based on iTunes state.</p>

<p>So this little python script runs every 10 seconds, here is a quick outline of what it does.</p>

<p>If iTunes is playing, but the speakers are off &#8211; then adjust some volumes on the speakers, turn them on, then rewind the track.</p>

<p>If iTunes is paused for more than 2 minutes, and the speakers are on &#8211; and they were turned on by this autodetect system, then turn the speakers off.</p>

<p>The turning on and off is done by my original HTPC applescript which controls an <a href="http://www.irtrans.de/en/shop/usb.php">irTrans</a> USB IR transceiver through the iRed software.</p>

<p>Now we just fire up the remote app on the phone, and within a few seconds the speakers are on and the music is playing.</p>

<pre><code>#!/usr/bin/env python
# encoding: utf-8
"""

Created by Preston Holmes on 2009-09-05.
preston@ptone.com
Copyright (c) 2009

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import sys
import os
from appscript import *
from subprocess import Popen, call, STDOUT, PIPE
import plistlib
import time

prefs = '/Library/Preferences/com.ptone.tuneswatcher.plist'
vol_target = '/Users/Preston/Documents/HTPC/targetvolume'

def sh(cmd):
    return Popen(cmd,shell=True,stdout=PIPE,stderr=PIPE).communicate()[0]

def itunes_running_():
    for p in psi.process.ProcessTable().items():
        try:
            if "Applications/iTunes" in p.command:
                return True
        except:
            pass
    return False

def init_prefs ():
    d = {'state':'unknown','last_state_change':time.time(),'auto_on':False}
    plistlib.writePlist (d,prefs)

def itunes_running():
    pcount = sh('ps -xa | grep -c iTunes').strip()
    return int(pcount) &gt; 3

def getSettings():
    settings_file = '/Users/preston/Documents/HTPC/settings'
    f = open(settings_file,'r')
    flines = f.readlines()
    f.close()
    settingsDict = {}
    for aLine in flines:
        aLine = aLine.rstrip('\n')
        #print aLine
        if aLine != '':
            apair = aLine.split(' ')
            settingsDict[apair[0]] = apair[1]
    return settingsDict

def read_prefs ():
    if not os.path.exists (prefs):
        init_prefs()
    d = plistlib.readPlist (prefs)
    htpc_settings = getSettings()
    d.update(htpc_settings)
    return d

def save_prefs(d):
    plistlib.writePlist (d,prefs)

def main():
    d = read_prefs()
    # print d
    is_running = itunes_running()
    if is_running:
        tunes = app("iTunes")
        # print "tunes.running"
        playing = (tunes.player_state() == k.playing)
        # print tunes.player_state() 
        # print playing
        if playing:  
            # print "tunes.playing"
            if d['speakers'] == 'off':
                # turn on speakers
                tunes.pause()
                sh ("""osascript -e 'tell application "HTPC" to power_speakers()'""")
                if int(d['volume']) &lt; 40:
                    open(vol_target,'w').write("50")
                    sh ("""osascript -e 'tell application "HTPC" to setVolume()'""")
                tunes.sound_volume.set(60)
                tunes.player_position.set(0)
                tunes.play()
                d['auto_on'] = True
                save_prefs(d)
                # start streamer here?
            if d['state'] != 'playing':
                d['state'] = 'playing'
                d['last_state_change'] = time.time()
                save_prefs(d)


        else: # iTunes not playing
            if d['state'] == 'playing':
                d['state'] = 'stopped'
                d['last_state_change'] = time.time()
                save_prefs(d)
            # check if last change was 5 minutes ago and turn off speakers
            if d['auto_on'] and ((time.time() - d['last_state_change']) &gt; 10):
                if d['speakers'] == 'on':
                    # turn speakers off only if turned on automatically
                    sh ("""osascript -e 'tell application "HTPC" to power_speakers()'""")
                    sh ("""osascript -e 'tell application "HTPC" to calibrateVolume()'""")
                d['auto_on'] = False
                save_prefs(d)
        # print (tunes.player_state() == k.stopped)


if __name__ == '__main__':
    main()
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/09/making-iphone-itunes-remote-more-useful/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Making Google Earth work for us</title>
		<link>http://ptone.com/dablog/2009/09/making-google-earth-work-for-us/</link>
		<comments>http://ptone.com/dablog/2009/09/making-google-earth-work-for-us/#comments</comments>
		<pubDate>Wed, 09 Sep 2009 00:02:07 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[osx]]></category>
		<category><![CDATA[sysadmin]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/09/making-google-earth-work-for-us/</guid>
		<description><![CDATA[In our school environment, we had a couple issues with Google Earth&#8217;s default behaviors &#8211; here are my workarounds. First off we have a student web proxy that works based on a whitelist concept. So we have to allow any web traffic explicitly. With the help of little snitch and one incomplete help page from [...]]]></description>
			<content:encoded><![CDATA[<p>In our school environment, we had a couple issues with Google Earth&#8217;s default behaviors &#8211; here are my workarounds.</p>

<p><span id="more-84"></span></p>

<p>First off we have a student web proxy that works based on a whitelist concept.  So we have to allow any web traffic explicitly.  With the help of little snitch and one incomplete help page from google, here is what I&#8217;ve added to our global whitelist and seems to be allowing all the features:</p>

<ul>
<li>kh.google.com</li>
<li>www.keyhole.com</li>
<li>mw2.google.com</li>
<li>earth.google.com</li>
<li>auth.keyhole.com</li>
<li>maps.google.com</li>
<li>khmdb.google.com</li>
</ul>

<p>It also want access to www.google.com &#8211; but we don&#8217;t allow students to full google access &#8211; but Google Earth still seems to run fine.</p>

<p>The next annoyance was Google&#8217;s softwareupdate which wants to run for all users and update Google Earth on launch.</p>

<p>Adding the following to our loginhook fixed that:</p>

<pre><code>mkdir -P $nethomedir/Library/Google/
touch $nethomedir/Library/Google/GoogleSoftwareUpdate
chown root $nethomedir/Library/Google/GoogleSoftwareUpdate
chmod 644 $nethomedir/Library/Google/GoogleSoftwareUpdate
</code></pre>

<p>the $nethomedir var is fetched from dscl earlier in the script</p>

<p>Students need to have stored the proxy password in their keychain &#8211; but most have already done that when visiting a web page earlier.  They then just need to allow Google Earth to access their keychain.</p>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/09/making-google-earth-work-for-us/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>simple weak password generator</title>
		<link>http://ptone.com/dablog/2009/09/simple-weak-password-generator/</link>
		<comments>http://ptone.com/dablog/2009/09/simple-weak-password-generator/#comments</comments>
		<pubDate>Thu, 03 Sep 2009 21:04:39 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[sysadmin]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/09/simple-weak-password-generator/</guid>
		<description><![CDATA[There are times I need to create some easy to remember weak initial passwords for students &#8211; here is a quick script Note that these are not strong passwords as they are the definition of &#8220;dictionary&#8221; passwords&#8230; import sys import os import random dfile = '/usr/share/dict/web2' def main(): for x in range(130): winner = False [...]]]></description>
			<content:encoded><![CDATA[<p>There are times I need to create some easy to remember weak initial passwords for students &#8211; here is a quick script</p>

<p><span id="more-83"></span></p>

<p>Note that these are not strong passwords as they are the definition of &#8220;dictionary&#8221; passwords&#8230;</p>

<pre><code>import sys
import os
import random

dfile = '/usr/share/dict/web2'

def main():
    for x in range(130):
        winner = False
        MAX = 234936
        suffix = random.randint (10,100)
        while not winner:
            choice = random.randint(1,MAX)
            f = open(dfile)
            for i,l in enumerate(f):
                l = l.strip()
                length = len(l)
                if i &lt; choice:
                    continue
                if length &gt; 3 and length &lt; 7:
                    winner = '%s%d' % (l,suffix)
                    break
        f.close()
        print winner.lower()


if __name__ == '__main__':
    main()
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/09/simple-weak-password-generator/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Managing mailman on the command line</title>
		<link>http://ptone.com/dablog/2009/08/managing-mailman-on-the-command-line/</link>
		<comments>http://ptone.com/dablog/2009/08/managing-mailman-on-the-command-line/#comments</comments>
		<pubDate>Mon, 24 Aug 2009 17:14:00 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[sysadmin]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/08/managing-mailman-on-the-command-line/</guid>
		<description><![CDATA[Even though we moved our email from OS X server to google apps for education &#8211; we still manage our lists with a local mailman instance (still on 10.4). However the web based interface for adding new members can be a bit of a pain as it never seems to remember the right authorization when [...]]]></description>
			<content:encoded><![CDATA[<p>Even though we moved our email from OS X server to google apps for education &#8211; we still manage our lists with a local mailman instance (still on 10.4).  However the web based interface for adding new members can be a bit of a pain as it never seems to remember the right authorization when switching between lists &#8211; luckily mailman has some great command line tools.</p>

<p><span id="more-82"></span></p>

<p>These are documented here:</p>

<p><a href="http://www.list.org/site.html">http://www.list.org/site.html</a></p>

<p>They are located in /usr/share/mailman/bin/</p>

<p>so you may want to add that to your path in .bash_profile</p>

<p>most have decent help summary with &#8211;help arg at command line</p>

<p>while the remove_members command accepts addresses on the command line itself:</p>

<pre><code>remove_members [options] [listname] [addr1 ...]

remove_members staff frank@foo.com
</code></pre>

<p>The add_members command specifically wants file input.  Sometimes I just want to add 2-3 so I use this trick:</p>

<pre><code>add_members -r - staff &lt;&lt;-EOF
&gt;sue@foo.com
&gt;bob@bar.com
&gt; &lt;cntl-d&gt;
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/08/managing-mailman-on-the-command-line/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Editing long commands</title>
		<link>http://ptone.com/dablog/2009/08/editing-long-commands/</link>
		<comments>http://ptone.com/dablog/2009/08/editing-long-commands/#comments</comments>
		<pubDate>Tue, 18 Aug 2009 21:39:52 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[osx]]></category>
		<category><![CDATA[tip]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/08/editing-long-commands/</guid>
		<description><![CDATA[Sometimes you get in a situation where you are editing a long command on the command line and you&#8217;d kill to be able to use your mouse to select a word or option in the middle. This tip makes it a pleasure First for me their was the discovery of cntl-a which jumps one back [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes you get in a situation where you are editing a long command on the command line and you&#8217;d kill to be able to use your mouse to select a word or option in the middle.  This tip makes it a pleasure</p>

<p><span id="more-81"></span></p>

<p>First for me their was the discovery of cntl-a which jumps one back to the beginning of a line, but just as often I wanted to delete a long path as an opt to a long command.</p>

<p>The first thing is to set your default Editor in your environment variables.  I use TextMate &#8211; but you could use textwrangler, VI, Emacs.</p>

<p>add a line like this to your ~/.bash_profile</p>

<p>export EDITOR=&#8221;mate -w&#8221;</p>

<p>then close your terminal session or &#8220;source ~/.bash_profile&#8221;</p>

<p>Now when you are in the middle of typing a long command, or after hitting the up arrow, press cntl-x and hold it, then hit &#8220;e&#8221;</p>

<p>boom &#8211; your current command opens up in your editor, you can use all the features of that editor, and when you save and close that file &#8211; the command will be executed back in your shell.</p>

<p>Since I&#8217;ve integrated this tip into my workflow &#8211; I find I use it all the time.</p>

<p>Only downside is GUI editors won&#8217;t work for SSH since you are in the remote hosts env &#8211; there is probably a tricky way to reverse-ssh the editor command back to you, but I haven&#8217;t explored that.</p>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/08/editing-long-commands/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Pre-fetch Apple downloads as dmg files</title>
		<link>http://ptone.com/dablog/2009/07/pre-fetch-apple-downloads-as-dmg-files/</link>
		<comments>http://ptone.com/dablog/2009/07/pre-fetch-apple-downloads-as-dmg-files/#comments</comments>
		<pubDate>Thu, 16 Jul 2009 16:43:38 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/07/pre-fetch-apple-downloads-as-dmg-files/</guid>
		<description><![CDATA[In my quest to automate the workflow of managing Macs at work, I wanted a way to download disk image files from apple ahead of time &#8211; once downloaded the next will be to integrate with my watched install project from the previous post, and then auto lcreate the loadsets. I can then do all [...]]]></description>
			<content:encoded><![CDATA[<p>In my quest to automate the workflow of managing Macs at work, I wanted a way to download disk image files from apple ahead of time &#8211; once downloaded the next will be to integrate with my watched install project from the previous post, and then auto lcreate the loadsets.  I can then do all my management on the radmind server (picking and choosing loadsets ready to go).  </p>

<p>The script monitors http://images.apple.com/downloads/macosx/apple/recent.rss and if it has been updated since last check (stores the last check in a plist file) it will check the feed links for any dmg it can find and download it to /downloaded_dmgs/ (which is easy enough to change in the script source)</p>

<p>The script source is below the fold &#8211; or download <a href="/downloads/get_apple_updates.py.zip">here</a></p>

<p><span id="more-78"></span></p>

<div class="highlight" ><pre><span style="color: #60a0b0; font-style: italic">#!/usr/bin/env python</span>
<span style="color: #60a0b0; font-style: italic"># encoding: utf-8</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>
<span style="color: #4070a0; font-style: italic">This script attempts to download any dmg files it can find in Apple&#39;s RSS feed of updates</span>
<span style="color: #4070a0; font-style: italic"></span>
<span style="color: #4070a0; font-style: italic">requires BeautifulSoup and feedparser modules</span>
<span style="color: #4070a0; font-style: italic"></span>
<span style="color: #4070a0; font-style: italic">Created by Preston Holmes on 2009-07-15.</span>
<span style="color: #4070a0; font-style: italic">preston@ptone.com</span>
<span style="color: #4070a0; font-style: italic">Copyright (c) 2009</span>
<span style="color: #4070a0; font-style: italic"></span>
<span style="color: #4070a0; font-style: italic">Permission is hereby granted, free of charge, to any person obtaining</span>
<span style="color: #4070a0; font-style: italic">a copy of this software and associated documentation files (the</span>
<span style="color: #4070a0; font-style: italic">&quot;Software&quot;), to deal in the Software without restriction, including</span>
<span style="color: #4070a0; font-style: italic">without limitation the rights to use, copy, modify, merge, publish,</span>
<span style="color: #4070a0; font-style: italic">distribute, sublicense, and/or sell copies of the Software, and to</span>
<span style="color: #4070a0; font-style: italic">permit persons to whom the Software is furnished to do so, subject to</span>
<span style="color: #4070a0; font-style: italic">the following conditions:</span>
<span style="color: #4070a0; font-style: italic"></span>
<span style="color: #4070a0; font-style: italic">The above copyright notice and this permission notice shall be included</span>
<span style="color: #4070a0; font-style: italic">in all copies or substantial portions of the Software.</span>
<span style="color: #4070a0; font-style: italic"></span>
<span style="color: #4070a0; font-style: italic">THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,</span>
<span style="color: #4070a0; font-style: italic">EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF</span>
<span style="color: #4070a0; font-style: italic">MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.</span>
<span style="color: #4070a0; font-style: italic">IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY</span>
<span style="color: #4070a0; font-style: italic">CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,</span>
<span style="color: #4070a0; font-style: italic">TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE</span>
<span style="color: #4070a0; font-style: italic">SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>

<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">sys</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">os</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">feedparser</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">urllib</span><span style="color: #666666">,</span> <span style="color: #0e84b5; font-weight: bold">urllib2</span>
<span style="color: #007020; font-weight: bold">from</span> <span style="color: #0e84b5; font-weight: bold">BeautifulSoup</span> <span style="color: #007020; font-weight: bold">import</span> BeautifulSoup
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">plistlib</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">re</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">pdb</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">time</span>

feed_url <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;http://images.apple.com/downloads/macosx/apple/recent.rss&#39;</span>
dmg_location <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;/downloaded_dmgs/&#39;</span>
prefs_file <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;/Library/Preferences/com.ptone.updatefetcher.plist&#39;</span>

last_done <span style="color: #666666">=</span> <span style="color: #40a070">0</span>
ticker <span style="color: #666666">=</span> <span style="color: #40a070">0</span>
current_download <span style="color: #666666">=</span> {}
last_msg <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;&#39;</span>
debug <span style="color: #666666">=</span> <span style="color: #007020">False</span>

<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">download_status</span>(block_ct,block_sz,total):
    <span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;The hook will be passed three arguments; a count of blocks transferred so far, a block size in bytes, and the total size of the file.&quot;&quot;&quot;</span>
    <span style="color: #007020; font-weight: bold">global</span> last_done, current_download,ticker,last_msg
    now <span style="color: #666666">=</span> time<span style="color: #666666">.</span>time()
    done <span style="color: #666666">=</span> ((block_ct <span style="color: #666666">*</span> block_sz)<span style="color: #666666">/</span><span style="color: #007020">float</span>(total)) <span style="color: #666666">*</span> <span style="color: #40a070">100</span>
    <span style="color: #007020; font-weight: bold">if</span> done <span style="color: #666666">&gt;</span> last_done <span style="color: #666666">+</span> <span style="color: #40a070">5</span> <span style="color: #007020; font-weight: bold">and</span> now<span style="color: #666666">-</span>ticker <span style="color: #666666">&gt;</span> <span style="color: #40a070">1</span>:
        msg <span style="color: #666666">=</span> <span style="color: #4070a0">&quot;downloading </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">, </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">mb </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0"> </span><span style="color: #70a0d0; font-style: italic">%%</span><span style="color: #4070a0"> done&quot;</span> <span style="color: #666666">%</span> (current_download[<span style="color: #4070a0">&#39;entry&#39;</span>],total<span style="color: #666666">/</span>(<span style="color: #40a070">1024</span><span style="color: #666666">*</span><span style="color: #40a070">1024</span>),<span style="color: #007020">int</span>(done)) 
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(<span style="color: #4070a0">&#39;&#39;</span><span style="color: #666666">.</span>ljust(<span style="color: #007020">len</span>(last_msg),<span style="color: #4070a0">&#39;</span><span style="color: #4070a0; font-weight: bold">\b</span><span style="color: #4070a0">&#39;</span>))
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(msg)
        last_msg <span style="color: #666666">=</span> msg
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>flush()
        last_done <span style="color: #666666">=</span> done
        ticker <span style="color: #666666">=</span> now

<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">get_dmg_from_iframepage</span>(url):
    <span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;Sleuths out the dmg url from pages that have an iFrame download form like iTunes and Safari&quot;&quot;&quot;</span>
    <span style="color: #60a0b0; font-style: italic"># pdb.set_trace()</span>
    <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> url<span style="color: #666666">.</span>endswith(<span style="color: #4070a0">&#39;/&#39;</span>):
        <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">False</span>
    soup <span style="color: #666666">=</span> BeautifulSoup(urllib2<span style="color: #666666">.</span>urlopen(url)<span style="color: #666666">.</span>read())
    iframe_url <span style="color: #666666">=</span> soup<span style="color: #666666">.</span>find(<span style="color: #4070a0">&#39;iframe&#39;</span>,src<span style="color: #666666">=</span>re<span style="color: #666666">.</span>compile(<span style="color: #4070a0">&#39;.*swdlp.apple.com.*&#39;</span>))
    <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> iframe_url: 
        <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">False</span>
    <span style="color: #007020; font-weight: bold">else</span>:
        iframe_url <span style="color: #666666">=</span> iframe_url[<span style="color: #4070a0">&#39;src&#39;</span>]
    <span style="color: #60a0b0; font-style: italic"># user agent is needed to get the mac version of safari</span>
    user_agent <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/4.0.1 Safari/530.18&#39;</span>
    headers <span style="color: #666666">=</span> { <span style="color: #4070a0">&#39;User-Agent&#39;</span> : user_agent }
    req <span style="color: #666666">=</span> urllib2<span style="color: #666666">.</span>Request(iframe_url, headers<span style="color: #666666">=</span>headers)
    page_data <span style="color: #666666">=</span> urllib2<span style="color: #666666">.</span>urlopen(req)<span style="color: #666666">.</span>read()
    soup <span style="color: #666666">=</span> BeautifulSoup(page_data)
    dmgs <span style="color: #666666">=</span> soup<span style="color: #666666">.</span>findAll(<span style="color: #4070a0">&#39;input&#39;</span>,attrs <span style="color: #666666">=</span> {<span style="color: #4070a0">&#39;type&#39;</span>:<span style="color: #4070a0">&#39;hidden&#39;</span>,<span style="color: #4070a0">&#39;name&#39;</span>:<span style="color: #4070a0">&#39;downloadURL&#39;</span>,<span style="color: #4070a0">&#39;value&#39;</span>:re<span style="color: #666666">.</span>compile(<span style="color: #4070a0">&#39;.*dmg$&#39;</span>)})
    <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020">len</span>(dmgs) <span style="color: #666666">&gt;</span> <span style="color: #40a070">0</span>:
        <span style="color: #007020; font-weight: bold">return</span> [i[<span style="color: #4070a0">&#39;value&#39;</span>] <span style="color: #007020; font-weight: bold">for</span> i <span style="color: #007020; font-weight: bold">in</span> dmgs]
    <span style="color: #007020; font-weight: bold">else</span>:
        <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">False</span>

<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">clear_status</span>():
    <span style="color: #007020; font-weight: bold">global</span> current_download,last_done,last_msg
    <span style="color: #007020; font-weight: bold">if</span> last_done <span style="color: #666666">&gt;</span> <span style="color: #40a070">0</span>:
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(<span style="color: #4070a0">&#39;&#39;</span><span style="color: #666666">.</span>ljust(<span style="color: #007020">len</span>(last_msg),<span style="color: #4070a0">&#39;</span><span style="color: #4070a0; font-weight: bold">\b</span><span style="color: #4070a0">&#39;</span>))
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(<span style="color: #4070a0">&#39;&#39;</span><span style="color: #666666">.</span>ljust(<span style="color: #007020">len</span>(last_msg),<span style="color: #4070a0">&#39; &#39;</span>))
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(<span style="color: #4070a0">&#39;</span><span style="color: #4070a0; font-weight: bold">\r</span><span style="color: #4070a0">&#39;</span>)
        sys<span style="color: #666666">.</span>stdout<span style="color: #666666">.</span>flush()
    last_done <span style="color: #666666">=</span> <span style="color: #40a070">0</span>
    last_msg <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;&#39;</span>
    
<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">get_dmg</span>(dmg_url):
    <span style="color: #007020; font-weight: bold">global</span> current_download,last_done,last_msg
    <span style="color: #60a0b0; font-style: italic"># do fancy clearing of % done</span>
    clear_status()
    fname <span style="color: #666666">=</span> dmg_url<span style="color: #666666">.</span>split(<span style="color: #4070a0">&#39;/&#39;</span>)[<span style="color: #666666">-</span><span style="color: #40a070">1</span>]
    fpath <span style="color: #666666">=</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(dmg_location,fname)
    <span style="color: #007020; font-weight: bold">if</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(fpath): 
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;</span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0"> already downloaded&quot;</span> <span style="color: #666666">%</span> fname
    <span style="color: #007020; font-weight: bold">else</span>:
        <span style="color: #60a0b0; font-style: italic"># save the dmg</span>
        current_download[<span style="color: #4070a0">&#39;fname&#39;</span>] <span style="color: #666666">=</span> fname
        urllib<span style="color: #666666">.</span>urlretrieve (dmg_url,fpath,download_status)
        clear_status()
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&#39;downloaded: </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&#39;</span> <span style="color: #666666">%</span> current_download[<span style="color: #4070a0">&#39;entry&#39;</span>]

<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">main</span>():
    <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;============ Script Run </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0"> ============&quot;</span> <span style="color: #666666">%</span> time<span style="color: #666666">.</span>asctime()
    prefs <span style="color: #666666">=</span> {}
    <span style="color: #007020; font-weight: bold">global</span> current_download, last_done, last_msg

    <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;Parsing Feed&quot;</span>
    feed <span style="color: #666666">=</span> feedparser<span style="color: #666666">.</span>parse(feed_url)
    feed_time <span style="color: #666666">=</span> time<span style="color: #666666">.</span>mktime (feed<span style="color: #666666">.</span>feed<span style="color: #666666">.</span>updated_parsed)
    <span style="color: #007020; font-weight: bold">if</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(prefs_file):
        prefs <span style="color: #666666">=</span> plistlib<span style="color: #666666">.</span>readPlist(prefs_file)
    <span style="color: #007020; font-weight: bold">else</span>:
        prefs <span style="color: #666666">=</span> {<span style="color: #4070a0">&#39;last_check&#39;</span>:feed_time <span style="color: #666666">-</span> <span style="color: #40a070">1000</span>}
    
    <span style="color: #007020; font-weight: bold">if</span> debug: prefs <span style="color: #666666">=</span> {<span style="color: #4070a0">&#39;last_check&#39;</span>:feed_time <span style="color: #666666">-</span> <span style="color: #40a070">1000</span>}
    
    now <span style="color: #666666">=</span> time<span style="color: #666666">.</span>mktime(time<span style="color: #666666">.</span>gmtime())
    <span style="color: #60a0b0; font-style: italic"># print feed_time</span>
    <span style="color: #60a0b0; font-style: italic"># print now</span>
    <span style="color: #007020; font-weight: bold">if</span> feed_time <span style="color: #666666">&lt;=</span> prefs[<span style="color: #4070a0">&#39;last_check&#39;</span>]:
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;Feed not updated since last check&quot;</span>
        prefs[<span style="color: #4070a0">&#39;last_check&#39;</span>] <span style="color: #666666">=</span> now
        plistlib<span style="color: #666666">.</span>writePlist(prefs, prefs_file)
        sys<span style="color: #666666">.</span>exit()
    <span style="color: #007020; font-weight: bold">else</span>:
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;feed updated </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&quot;</span> <span style="color: #666666">%</span> feed<span style="color: #666666">.</span>feed<span style="color: #666666">.</span>updated
    <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(dmg_location): os<span style="color: #666666">.</span>makedirs(dmg_location)
    <span style="color: #60a0b0; font-style: italic"># loop over the entries</span>
    <span style="color: #007020; font-weight: bold">for</span> entry <span style="color: #007020; font-weight: bold">in</span> feed<span style="color: #666666">.</span>entries:
        current_download <span style="color: #666666">=</span> {<span style="color: #4070a0">&#39;entry&#39;</span>:entry<span style="color: #666666">.</span>title}
        dmg_found <span style="color: #666666">=</span> <span style="color: #007020">False</span>
        url <span style="color: #666666">=</span> entry<span style="color: #666666">.</span>link
        <span style="color: #60a0b0; font-style: italic"># load link with urllib2 into beautiful soup</span>
        soup <span style="color: #666666">=</span> BeautifulSoup(urllib2<span style="color: #666666">.</span>urlopen(url)<span style="color: #666666">.</span>read())
        <span style="color: #60a0b0; font-style: italic"># find sidecar link</span>
        downloads <span style="color: #666666">=</span> soup<span style="color: #666666">.</span>findAll(<span style="color: #4070a0">&#39;a&#39;</span>,href<span style="color: #666666">=</span>re<span style="color: #666666">.</span>compile(<span style="color: #4070a0">&#39;http://wsidecar.*&#39;</span>))
        <span style="color: #007020; font-weight: bold">if</span> downloads:
            download_url <span style="color: #666666">=</span> downloads[<span style="color: #40a070">0</span>][<span style="color: #4070a0">&#39;href&#39;</span>]
            <span style="color: #60a0b0; font-style: italic"># determine if link is dmg                </span>
            <span style="color: #007020; font-weight: bold">if</span> download_url<span style="color: #666666">.</span>endswith(<span style="color: #4070a0">&#39;.dmg&#39;</span>):
                dmg_found <span style="color: #666666">=</span> <span style="color: #007020">True</span>
                <span style="color: #60a0b0; font-style: italic"># make it a list for compatibility with pages that return multiple dmgs</span>
                dmg_urls <span style="color: #666666">=</span> [download_url]
            <span style="color: #007020; font-weight: bold">else</span>:
                dmg_found <span style="color: #666666">=</span> dmg_urls <span style="color: #666666">=</span> get_dmg_from_iframepage(download_url)            
            <span style="color: #007020; font-weight: bold">if</span> dmg_found:
                <span style="color: #007020; font-weight: bold">for</span> a_dmg <span style="color: #007020; font-weight: bold">in</span> dmg_urls:
                    get_dmg(a_dmg)
            <span style="color: #007020; font-weight: bold">else</span>:
                <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&quot;no dmg found in </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&quot;</span> <span style="color: #666666">%</span> entry<span style="color: #666666">.</span>title
                
    prefs[<span style="color: #4070a0">&#39;last_check&#39;</span>] <span style="color: #666666">=</span> now
    plistlib<span style="color: #666666">.</span>writePlist(prefs, prefs_file)
    
<span style="color: #007020; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #4070a0">&#39;__main__&#39;</span>:
    main()
</pre></div>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/07/pre-fetch-apple-downloads-as-dmg-files/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Saving time (and wrists) scripting Illustrator</title>
		<link>http://ptone.com/dablog/2009/06/saving-time-and-wrists-scripting-illustrator/</link>
		<comments>http://ptone.com/dablog/2009/06/saving-time-and-wrists-scripting-illustrator/#comments</comments>
		<pubDate>Thu, 04 Jun 2009 19:04:11 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/06/saving-time-and-wrists-scripting-illustrator/</guid>
		<description><![CDATA[Sometimes doing tedious layout work in Illustrator is both time consuming and hard on the wrists. In this post I show a quick example of how one can script Illustrator very effectively I work at a school where I&#8217;m involved in putting together our yearbook. For several years now we have done a collage of [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes doing tedious layout work in Illustrator is both time consuming and hard on the wrists.  In this post I show a quick example of how one can script Illustrator very effectively</p>

<p><span id="more-75"></span></p>

<p>I work at a school where I&#8217;m involved in putting together our yearbook. For several years now we have done a collage of self portraits.</p>

<p>These can be a chore to layout in Illustrator.  You want to randomize the grades, scale everything to be in nice rows and line everything up.</p>

<p>Here is what I started with:</p>

<p><img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-46.png" alt="Picture 46" /></p>

<p>Each grade is a different layer (and highlight color)</p>

<p>Its hard to know what size to standardize on to flow these out best onto the cover layout (its a front and back deal)
Also I don&#8217;t want all of one grade next to another, so they need some shuffling.</p>

<p>So I whipped up this python script to do the grunt work for me.</p>

<p><em>quick digression on scripting language</em>
Note I could have done this in Applescript &#8211; and for most of you that would be easier to use. I think that for quick scripts where the bulk of the script is generating apple events &#8211; Applescript is generally easier.  But as soon as you involve much general programming, Applescript is sorely lacking.  It has an awkward and verbose syntax, and has a very limited standard library &#8211; so you often have to write all your own utilities.  <a href="http://appscript.sourceforge.net/py-appscript/">appscript</a> is a Python module that provides access to Apples OSA system and allows you to do anything you can do in Applescript.  The syntax takes some getting used to, but if you need to do other things in your script &#8211; its a win.  For example the &#8220;Shuffle&#8221; bit in the code below was a single line of python, but would have required a PITA sub-routine in Applescript.</p>

<div class="highlight" ><pre><span style="color: #60a0b0; font-style: italic">#!/usr/bin/env python</span>
<span style="color: #60a0b0; font-style: italic"># encoding: utf-8</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>
<span style="color: #4070a0; font-style: italic">Created by Preston Holmes on 2009-06-04.</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>

<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">sys</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">os</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">appscript</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">random</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">time</span>
<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">main</span>():
    il <span style="color: #666666">=</span> appscript<span style="color: #666666">.</span>app(<span style="color: #4070a0">&quot;Adobe Illustrator&quot;</span>)
    page_items <span style="color: #666666">=</span> il<span style="color: #666666">.</span>current_document<span style="color: #666666">.</span>page_items()
    random_index <span style="color: #666666">=</span> <span style="color: #007020">range</span>(<span style="color: #007020">len</span>(page_items))
    random<span style="color: #666666">.</span>shuffle(random_index)
    left_edge <span style="color: #666666">=</span> <span style="color: #40a070">15</span>
    curr_x <span style="color: #666666">=</span> left_edge
    curr_y <span style="color: #666666">=</span> <span style="color: #40a070">775</span>
    x_max <span style="color: #666666">=</span> <span style="color: #40a070">600</span>
    row_height <span style="color: #666666">=</span> <span style="color: #40a070">80</span>
    <span style="color: #007020; font-weight: bold">print</span> <span style="color: #007020">len</span>(random_index)
    <span style="color: #007020; font-weight: bold">for</span> i <span style="color: #007020; font-weight: bold">in</span> random_index:
        item <span style="color: #666666">=</span> page_items[i]
        <span style="color: #007020; font-weight: bold">if</span> item<span style="color: #666666">.</span>locked() <span style="color: #007020; font-weight: bold">or</span> item<span style="color: #666666">.</span>layer<span style="color: #666666">.</span>locked():
            <span style="color: #007020; font-weight: bold">continue</span>
        h <span style="color: #666666">=</span> item<span style="color: #666666">.</span>height()
        w <span style="color: #666666">=</span> item<span style="color: #666666">.</span>width()
        <span style="color: #007020; font-weight: bold">if</span> h <span style="color: #666666">==</span> <span style="color: #40a070">0</span> <span style="color: #007020; font-weight: bold">or</span> w <span style="color: #666666">==</span><span style="color: #40a070">0</span>:
            <span style="color: #007020; font-weight: bold">continue</span>
        s <span style="color: #666666">=</span> <span style="color: #007020">int</span> ((row_height<span style="color: #666666">/</span>h) <span style="color: #666666">*</span> <span style="color: #40a070">100</span>)
        <span style="color: #60a0b0; font-style: italic"># s2 = row_height/h</span>
        <span style="color: #007020; font-weight: bold">if</span> (curr_x <span style="color: #666666">+</span> w) <span style="color: #666666">&gt;</span> x_max:
            curr_x <span style="color: #666666">=</span> left_edge
            curr_y <span style="color: #666666">=</span> curr_y <span style="color: #666666">-</span> row_height
        sm <span style="color: #666666">=</span> il<span style="color: #666666">.</span>get_scale_matrix(horizontal_scale<span style="color: #666666">=</span>s,vertical_scale<span style="color: #666666">=</span>s)
        <span style="color: #60a0b0; font-style: italic"># Having some real problems with scale transforms screwing up stroke width - but I&#39;m just working with placed items</span>
        il<span style="color: #666666">.</span>transform(item,using<span style="color: #666666">=</span>sm,line_scale<span style="color: #666666">=</span><span style="color: #40a070">100</span>,transforming_stroke_patterns<span style="color: #666666">=</span><span style="color: #007020">False</span>)
        <span style="color: #60a0b0; font-style: italic"># item.stroked_width.set(1)</span>
        <span style="color: #60a0b0; font-style: italic"># alternate scale approach (also seems to affect stroke width...)</span>
        <span style="color: #60a0b0; font-style: italic"># item.width.set(w*s2)</span>
        <span style="color: #60a0b0; font-style: italic"># item.height.set(h*s2)</span>
        left,top,right,bottom <span style="color: #666666">=</span> item<span style="color: #666666">.</span>geometric_bounds()
        dx <span style="color: #666666">=</span> curr_x <span style="color: #666666">-</span> left
        dy <span style="color: #666666">=</span> curr_y <span style="color: #666666">-</span> top
        m <span style="color: #666666">=</span> il<span style="color: #666666">.</span>get_translation_matrix(delta_x<span style="color: #666666">=</span>dx,delta_y<span style="color: #666666">=</span>dy)
        <span style="color: #60a0b0; font-style: italic"># can&#39;t use concatenated transform as scale will change the amount to move...</span>
        <span style="color: #60a0b0; font-style: italic"># full_m = il.concatenate_scale_matrix(m,horizontal_scale=s,vertical_scale=s)</span>
        il<span style="color: #666666">.</span>transform(item,using<span style="color: #666666">=</span>m)
        curr_x <span style="color: #666666">=</span> curr_x <span style="color: #666666">+</span> (right <span style="color: #666666">-</span> left)

    

<span style="color: #007020; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #4070a0">&#39;__main__&#39;</span>:
    main()
</pre></div>

<p>So what does this get me?</p>

<p><img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-47.png" alt="Picture 47" /></p>

<p>I made this to generate one page width &#8211; and its easy to split up across pages:
<img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-48.png" alt="Picture 48" /></p>

<p>And as you can see, it also shuffled the source layers:
<img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-49.png" alt="Picture 49" /></p>

<p>Now if I want to change how it flows &#8211; or add or subtract images I can just change the single row_height parameter of the script and get variations like this:
<img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-50.png" alt="Picture 50" /></p>

<p><img src="http://ptone.com/dablog/wp-content/uploads/2009/06/picture-51.png" alt="Picture 51" /></p>

<p>If you realize an image needs 90 degree rotation, it no longer requires having to entirely re-layout the whole project</p>

<p>Of course there is still some massaging needed by hand &#8211; but it is a fraction of the effort.</p>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/06/saving-time-and-wrists-scripting-illustrator/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Two quick collage assistants</title>
		<link>http://ptone.com/dablog/2009/06/two-quick-collage-assistants/</link>
		<comments>http://ptone.com/dablog/2009/06/two-quick-collage-assistants/#comments</comments>
		<pubDate>Tue, 02 Jun 2009 22:39:22 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Applescript]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/06/two-quick-montage-assistants/</guid>
		<description><![CDATA[Despite vowing to stay away from Applescript as far as I can &#8211; sometimes its the fastest way to save a little time At work I&#8217;m involved in laying out our yearbook. Honestly a lot of these pages are just quick photo montages. I&#8217;ve made these in both Illustrator and Pages and each have some [...]]]></description>
			<content:encoded><![CDATA[<p>Despite vowing to stay away from Applescript as far as I can &#8211; sometimes its the fastest way to save a little time</p>

<p><span id="more-68"></span></p>

<p>At work I&#8217;m involved in laying out our yearbook.  Honestly a lot of these pages are just quick photo montages.  I&#8217;ve made these in both Illustrator and Pages and each have some advantages &#8211; I use mostly Illustrator but its harder to have others help in that app.</p>

<p>I wanted to whip up some scripts that could take some of the scale and random rotation repetition out of the process, which these do &#8211; the pages version also adds drop shadow &#8211; both could be improved to do some repositioning&#8230;</p>

<p>The Pages version:</p>

<pre><code>tell application "Pages"

set docName to name of document 1
tell document 1
--mark images:

set imagelist to get every image -- of body text

set an_image to item 1 of imagelist
repeat with an_image in imagelist
set w to width of an_image
set h to height of an_image
if w &amp;gt; h then
set width of an_image to 3
else
set height of an_image to 3
end if
set x to random number from -15 to 15
set rotation of an_image to x
set shadow of an_image to true
set shadow angle of an_image to 58.0
set shadow color of an_image to {0, 0, 0}
set shadow offset of an_image to 7
set shadow opacity of an_image to 75
set shadow blur of an_image to 7

--get properties of an_image
end repeat

end tell
end tell
</code></pre>

<p>The Illustrator version:</p>

<pre><code>tell application "Adobe Illustrator"
set s to 2
set maxdim to 250

set page_items to page items of current document

repeat with an_item in page_items
set w to width of an_item
set h to height of an_item
if w &amp;gt; h then
set s to maxdim / w
else
set s to maxdim / h
end if
set width of an_item to w * s
set height of an_item to h * s
set x to random number from -15 to 15
set baseMatrix to get rotation matrix angle x
transform an_item using baseMatrix
end repeat

end tell
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/06/two-quick-collage-assistants/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Managing Macs with centralized login scripts</title>
		<link>http://ptone.com/dablog/2009/03/managing-macs-with-centralized-login-scripts/</link>
		<comments>http://ptone.com/dablog/2009/03/managing-macs-with-centralized-login-scripts/#comments</comments>
		<pubDate>Thu, 19 Mar 2009 17:58:16 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/03/managing-macs-with-centralized-login-scripts/</guid>
		<description><![CDATA[Managing a large number of Macs at an institution often requires a hodgepodge of tricks and tools. While Apple packages some useful stuff os OS X server, many of the most useful things are hidden, created by the community, or rely on OS X&#8217;s UNIX underpinnings. One of the tricks that I came up with [...]]]></description>
			<content:encoded><![CDATA[<p>Managing a large number of Macs at an institution often requires a hodgepodge of tricks and tools.  While Apple packages some useful stuff os OS X server, many of the most useful things are hidden, created by the community, or rely on OS X&#8217;s UNIX underpinnings.  One of the tricks that I came up with a couple years back that I have found invaluable, is a system where by I can create and manage a set of scripts on a server, that get run on each client at login.  Read on for details and some snippets.</p>

<p><span id="more-58"></span></p>

<p>First some prerequisites and expectations.  You will need a server on your network that supports sharing volumes to OS X, and a way to configure your clients to automount such a sharepoint. Both of these are easily achieved if you have an OS X server that your clients are binding to &#8211; but there are other ways.  Also I assume that you have some familiarity with what shell scripts are, and file permissions, if not more of a command on how to write them.</p>

<p>At its most basic level, a system of login scripts relies on a single hook provided by the system.  In the plist at:</p>

<pre><code>/var/root/Library/Preferences/com.apple.loginwindow.plist

&lt;key&gt;LoginHook&lt;/key&gt;
&lt;string&gt;/path/to/script&lt;/string&gt;
</code></pre>

<p>This just is the path to an executable script that will be run with root privileges at user login.  The script is passed a single argument, which is the name of the logging in user.</p>

<p>In this script you can do any number of things you can do in a script &#8211; it runs with root privileges (I&#8217;ll outline some of those things later on).</p>

<p>Now if you start doing a lot of things in this script, or you want to do some things on some machines but not others, managing the contents of this script can get tricky or tedious. The solution here is the solution for many similar situations.  Modularize it into a set of separate scripts.  This has the advantage of atomizing different purposes into their own files that can be moved and reused, or even downloaded from other sources and added into your process.  It also allows you to use scripts written in multiple languages.  You may have some things that are best done in Bash, others that are better done with Python or Perl.</p>

<p>The basic way to modularize this is to have a folder of scripts, and then your login script pointed to by the plist key acts as a wrapper that might look like this:</p>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">#! /bin/sh<br />
</span><br />
<span style="color:#881350;">export</span> PATH=/bin:/usr/bin:/usr/<span style="color:#881350;">local</span>/bin:/sbin:/usr/sbin:/usr/<span style="color:#881350;">local</span>/sbin<br />
<br />
DIR=<span style="color:#760f15;">&quot;/etc/loginscripts&quot;</span><br />
<br />
<span style="color:#881350;">if</span> [ -d <span style="color:#c4620a;">${DIR}</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span> script <span style="color:#881350;">in</span> <span style="color:#c4620a;">${DIR}</span>/*; <span style="color:#881350;">do</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;"># if the file exists and is executable<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span> [ -s <span style="color:#c4620a;">${script}</span> -a -x <span style="color:#c4620a;">${script}</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;"># run the item<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#c4620a;">${script}</span> $*<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit_value=$?<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;"># bail if any sub script returns abnormally <br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span> [ <span style="color:#c4620a;">${exit_value}</span> -ne <span style="color:#0000ff;">0</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger -s -t Loginscript -p user.info <span style="color:#c4620a;">${script}</span> failed! <span style="color:#0000ff;">1</span>&gt;&amp;<span style="color:#0000ff;">2</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#440088;">exit</span> <span style="color:#c4620a;">$exit_value</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">fi</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">fi</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">done</span><br />
<span style="color:#881350;">fi</span><br />
<br />
<span style="color:#880088;">echo</span> Loginscript <span style="color:#440088;">complete</span>.<br />
<span style="color:#440088;">exit</span> <span style="color:#0000ff;">0</span><br />
</div>

<p>This basically loops over the directory specified (in this case /etc/loginscripts/) and runs each script in turn &#8211; passing along whatever original arguments were sent to the primary loginscript.</p>

<p>This system has been used by several management packages, including both NetRestore, and Radmind/iHook.</p>

<blockquote>
  <p>This is as good a time as any for a quick digression on <a href="http://rsug.itd.umich.edu/software/ihook/">iHook</a>.  iHook is a fantastic tool that allows you to provide graphical feedback of scripts that are running.  If your login scripts are doing things that might take some time &#8211; there is no built in way to give the user feedback.  iHook provides a UI window that scripts can write their output to (echo) or using iHook&#8217;s special commands, can update a progress bar or put up images.</p>
  
  <p>I&#8217;m not going into the details here on iHook, but I use a modified version of the multiple scripts system that is distributed with the <a href="http://rsug.itd.umich.edu/software/radmind/download.html">Radmind Assistant</a></p>
</blockquote>

<p>The new (though I&#8217;m not claiming I&#8217;m the first/only one to think of or implement this) idea I want to introduce in this post is to take the modular approach another step and use a network folder to hold the scripts to be run at login.</p>

<p>That is, the login script first fetches all the scripts to run from a server, then runs them all locally.</p>

<p>The advantages of this are huge &#8211; mainly you no longer have to foresee at imaging time all the possible things you may want to do at login with a script, in order to have those actions written into scripts that are distributed as part of your image.  The advantage over via ARD for taking such late breaking actions is that there is no issue if you have many laptops or machines that are often offline on your network, the commands are run at next login.</p>

<p>For me one of the best examples is printer creation.  In the Tiger days I dutifully tried to deploy MCX printing &#8211; but had any number of problems with it, but primarily:</p>

<ul>
<li>Printers that I removed from the list, were not removed from the clients</li>
<li>Many printer specific features could not be managed via MCX</li>
</ul>

<p>Now I create printers with a script at login time &#8211; if we add or remove a printer from that list, I can make that change to a single copy of the script on the server, and all the machines who participate in this system will see the changes at login.</p>

<p>Another example is that midway through the school year, the administration asked that cameras on the Macs be disabled for students because they were posting to YouTube from campus.  I was able to add in a script that did this to the folder on the server and poof, it was implemented instantly on all the machines at next login.</p>

<p>I&#8217;m using a combination of locally and remotely modular loginscripts that look something like this:</p>

<ul>
<li>login.hook (starts iHook with the contents of the wrapper script as above )

<ul>
<li>locally defined login scripts are each run from /etc/hooks</li>
<li>one of these scipts then fetches additional scripts from the server.

<ul>
<li>each local copy of the remotely defined scripts is in turn run </li>
</ul></li>
</ul></li>
</ul>

<p>The script that fetches and runs the remote scripts looks like this:</p>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">if</span> [ -d /Network/Library/management08/ ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">echo</span> Running Centralized Management<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">mkdir</span> /tmp/manage<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">cp</span> /Network/Library/management08/scripts/* /tmp/manage<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">chmod</span> <span style="color:#0000ff;">755</span> /tmp/manage/*<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span> x <span style="color:#881350;">in</span> /tmp/manage/*<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">do</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#c4620a;">$x</span> $*<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">done</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">rm</span> -f /tmp/manage/*<br />
<span style="color:#881350;">fi</span><br />
<span style="color:#440088;">exit</span> <span style="color:#0000ff;">0</span></div>

<blockquote>
  <p>I&#8217;m using OS X&#8217;s server ability to define an automount at /Network/Library as this is in the search path for several other apps/technology that look in various Libraries. Obviously you want to make sure that such a location is read-only.</p>
</blockquote>

<p>OK, so what are some of the useful things you can do with login scripts (local or remote)?  Here is a collection of some snippets (see also Mike Bombich&#8217;s <a href="http://www.bombich.com/mactips/scripts.html">collection</a> for more ideas).  Remember these are snippets and should not just be pasted en-mass into one of your login scripts.  Let me know of your own tricks in the comments! (and yes &#8211; I know many of these can also be done via MCX &#8211; I do use MCX, but sometimes this seems more straightforward to me, and generally works more predictably)</p>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;"># set a user&#8217;s preference (in this case disabling perian update notices)<br />
</span><span style="color:#880088;">sudo</span> -u <span style="color:#c4620a;">$1</span> defaults <span style="color:#880088;">write</span> org.perian.Perian NextRunDate -<span style="color:#880088;">date</span> <span style="color:#760f15;">&#8217;4000-12-31 16:00:00 -0800&#8242;</span></div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><br />
<span style="color:#236e25;"># install that cool little tool you didn&#8217;t know about when making your image<br />
# http://duti.sourceforge.net/<br />
</span><span style="color:#881350;">if</span> [ ! -e /usr/<span style="color:#881350;">local</span>/bin/duti ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;ditto /Network/Library/management08/files/duti/usr/<span style="color:#881350;">local</span>/ /usr/<span style="color:#881350;">local</span>/<br />
<span style="color:#881350;">fi</span><br />
<br />
<span style="color:#236e25;"># then use that cool little tool to set quicktime player (not itunes) as the default player for .wav files <br />
# that are generated by that new phone system <img src='http://ptone.com/dablog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> <br />
</span><span style="color:#880088;">echo</span> <span style="color:#760f15;">&#8216;com.apple.quicktimeplayer com.microsoft.waveform-audio all&#8217;</span> | <span style="color:#880088;">sudo</span> -u <span style="color:#c4620a;">$1</span> /usr/<span style="color:#881350;">local</span>/bin/duti</div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;"># This grabs the user&#8217;s home directory path<br />
</span>user=<span style="color:#760f15;">&quot;</span><span style="color:#c4620a;">$1</span><span style="color:#760f15;">&quot;</span><br />
input=<span style="color:#660088;">`dscl localhost read Search/Users/$user NFSHomeDirectory`</span><br />
nethomedir=<span style="color:#c4620a;">${input:18}</span><br />
<br />
<br />
<span style="color:#236e25;"># so you can then do things like disable the &quot;Sharing&quot; section of the sidebar<br />
</span><br />
<span style="color:#880088;">sudo</span> -u <span style="color:#c4620a;">$1</span> /usr/libexec/PlistBuddy -c <span style="color:#760f15;">&quot;Set :networkbrowser:CustomListProperties:com.apple.NetworkBrowser.bonjourEnabled bool True&quot;</span> <span style="color:#c4620a;">$nethomedir</span>/Library/Preferences/com.apple.sidebarlists.plist<br />
<span style="color:#880088;">sudo</span> -u <span style="color:#c4620a;">$1</span> /usr/libexec/PlistBuddy -c <span style="color:#760f15;">&quot;Set :networkbrowser:CustomListProperties:com.apple.NetworkBrowser.backToMyMacEnabled bool False&quot;</span> <span style="color:#c4620a;">$nethomedir</span>/Library/Preferences/com.apple.sidebarlists.plist<br />
<span style="color:#880088;">sudo</span> -u <span style="color:#c4620a;">$1</span> /usr/libexec/PlistBuddy -c <span style="color:#760f15;">&quot;Set :networkbrowser:CustomListProperties:com.apple.NetworkBrowser.connectedEnabled bool False&quot;</span> <span style="color:#c4620a;">$nethomedir</span>/Library/Preferences/com.apple.sidebarlists.plist<br />
</div>

<p><div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><br />
<span style="color:#236e25;"># delete all printers and settings:<br />
</span>&nbsp;<span style="color:#236e25;"># Clean up from previous script if needed<br />
</span><span style="color:#880088;">rm</span> -f /tmp/print<em><br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
<br />
<span style="color:#236e25;"># Set user variable<br />
</span>user=<span style="color:#760f15;">&quot;</span><span style="color:#c4620a;">$1</span><span style="color:#760f15;">&quot;</span><br />
<span style="color:#236e25;"># This grabs the user&#8217;s home directory server<br />
</span>input=<span style="color:#660088;"><code>dscl localhost read Search/Users/$user NFSHomeDirectory | head -1</code></span><br />
nethomedir=<span style="color:#c4620a;">${input:18}</span><br />
<br />
<span style="color:#236e25;">### Delete Current Printers<br />
<span style="color:#236e25;"># From the 10.4 PrintingReset.sh script inside print utility</span><br />
</span><span style="color:#880088;">killall</span> <span style="color:#760f15;">&quot;PrinterProxy&quot;</span> <span style="color:#760f15;">&quot;Printer Setup Utility&quot;</span> <span style="color:#760f15;">&quot;cupsd&quot;</span> <span style="color:#0000ff;">2</span>&gt;/dev/null<br />
<br />
<span style="color:#236e25;"># Give them all a chance to die<br />
</span><span style="color:#880088;">sleep</span> <span style="color:#0000ff;">1</span><br />
<br />
<span style="color:#880088;">rm</span> -rf <span style="color:#c4620a;">$nethomedir</span>/Library/Printers/</em>.app<br />
<span style="color:#880088;">rm</span> -rf <span style="color:#c4620a;">$nethomedir</span>/Library/Preferences/com.apple.print.<em><br />
<span style="color:#880088;">rm</span> -rf <span style="color:#c4620a;">$nethomedir</span>/Library/Preferences/ByHost/com.apple.print.</em><br />
<span style="color:#880088;">rm</span> -rf /etc/cups/printers.conf* <br />
<span style="color:#880088;">rm</span> -rf /etc/cups/classes.conf* <br />
<span style="color:#880088;">rm</span> -rf /etc/cups/ppds.dat <br />
<span style="color:#880088;">rm</span> -rf /etc/cups/ppds/* <span style="color:#0000ff;">2</span>&gt;/dev/null<br />
<br />
launchctl start org.cups.cupsd<br />
</div></p>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;"># set up printers using lpadmin:<br />
#HP socket example<br />
</span>lpadmin -p <span style="color:#760f15;">&quot;science_laser&quot;</span> -L science -D <span style="color:#760f15;">&quot;Science B&amp;W (D)&quot;</span> -E -v socket://<span style="color:#0000ff;">10.5</span>.<span style="color:#0000ff;">5.36</span>/?bidi -P <span style="color:#760f15;">&quot;/Library/Printers/PPDs/Contents/Resources/HP LaserJet 2200.gz&quot;</span> -o Duplex=DuplexNoTumble<br />
<br />
<span style="color:#236e25;"># Using airport/bonjour connected printer:<br />
</span>lpadmin -p <span style="color:#760f15;">&quot;laser_90299&quot;</span> -L <span style="color:#0000ff;">102</span> -D <span style="color:#760f15;">&quot;Preschool Laser&quot;</span> -E -v mdns://<span style="color:#0000ff;">90299.</span>_pdl-datastream._tcp.<span style="color:#881350;">local</span>./?bidi -P <span style="color:#760f15;">&quot;/Library/Printers/PPDs/Contents/Resources/HP LaserJet 1200.gz&quot;</span> <br />
<br />
<span style="color:#236e25;"># Using LPD<br />
</span>lpadmin -p <span style="color:#760f15;">&quot;tower_copier&quot;</span> -L Tower -D <span style="color:#760f15;">&quot;Tower Copier&quot;</span> -E -v lpd://<span style="color:#0000ff;">10.5</span>.<span style="color:#0000ff;">5.46</span>/ -P <span style="color:#760f15;">&quot;/Library/Printers/PPDs/Contents/Resources/en.lproj/Kyocera KM-C4035E.PPD&quot;</span><br />
</div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><br />
<span style="color:#236e25;"># run some stuff only every 5 days &#8211; not every login:<br />
</span>tfile=/Library/Management/.timestamp<br />
<span style="color:#881350;">if</span> [ ! -e <span style="color:#c4620a;">$tfile</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">touch</span> -t <span style="color:#0000ff;">200811010000</span> <span style="color:#c4620a;">$tfile</span><br />
<span style="color:#881350;">fi</span><br />
r=<span style="color:#660088;">`find $tfile -mtime +5`</span><br />
<span style="color:#881350;">if</span> [ ! -z <span style="color:#c4620a;">$r</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">#do periodic stuff here<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">touch</span> <span style="color:#c4620a;">$tfile</span><br />
<span style="color:#881350;">fi</span><br />
</div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;"># fix the poor network performance on the netgear WAPs on the third floor<br />
</span>MODEL=<span style="color:#660088;">`system_profiler SPHardwareDataType | grep &#8216;Model Name&#8217; | awk &#8216;{print $3}&#8217;`</span><br />
<span style="color:#880088;">echo</span> <span style="color:#c4620a;">$MODEL</span><br />
<br />
<span style="color:#881350;">if</span> [ <span style="color:#c4620a;">$MODEL</span> == <span style="color:#760f15;">&quot;MacBook&quot;</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;sysctl -<span style="color:#880088;">w</span> net.inet.tcp.delayed_ack=<span style="color:#0000ff;">0</span><br />
<span style="color:#881350;">fi</span></div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">#disable iSight if not staff member<br />
</span>ISSTAFF=<span style="color:#660088;">`dseditgroup -n /Search -m $1 -o checkmember srstaff | awk &#8216;{print $1}&#8217;`</span><br />
<br />
<span style="color:#881350;">if</span> [ <span style="color:#760f15;">&quot;</span><span style="color:#c4620a;">$ISSTAFF</span><span style="color:#760f15;">&quot;</span> == <span style="color:#760f15;">&quot;yes&quot;</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">chmod</span> <span style="color:#0000ff;">755</span> /System/Library/QuickTime/QuickTimeUSBVDCDigitizer.component/Contents/MacOS/QuickTimeUSBVDCDigitizer /System/Library/PrivateFrameworks/CoreMediaIOServicesPrivate.framework/Versions/A/Resources/VDC.plugin/Contents/MacOS/VDC<br />
<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">echo</span> <span style="color:#760f15;">&#8216;Camera Disabled&#8217;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">chmod</span> <span style="color:#0000ff;">700</span> /System/Library/QuickTime/QuickTimeUSBVDCDigitizer.component/Contents/MacOS/QuickTimeUSBVDCDigitizer /System/Library/PrivateFrameworks/CoreMediaIOServicesPrivate.framework/Versions/A/Resources/VDC.plugin/Contents/MacOS/VDC<br />
<span style="color:#881350;">fi</span><br />
</div>

<div style="text-align:left;margin-bottom:15px;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;"># run a full os update:<br />
</span>osversionlong=<span style="color:#660088;">`sw_vers -productVersion`</span><br />
osvers=<span style="color:#c4620a;">${osversionlong:5:1}</span><br />
<span style="color:#881350;">if</span> [ <span style="color:#c4620a;">$osvers</span> -eq <span style="color:#0000ff;">4</span> ]<br />
<span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MSG=<span style="color:#760f15;">&#8216;Updating to 10.5.6 this takes about 5 minutes&#8217;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">echo</span> <span style="color:#c4620a;">$MSG</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">#install updates<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hdiutil attach /Network/Library/management08/files/installers/MacOSXUpdCombo10.<span style="color:#0000ff;">5.6</span>.dmg<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#880088;">echo</span> <span style="color:#c4620a;">$MSG</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;installer -package <span style="color:#760f15;">&#8216;/Volumes/Mac OS X Update Combined/MacOSXUpdCombo10.5.6.pkg&#8217;</span> -target /Volumes/Internal<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shutdown -r now<br />
<span style="color:#881350;">fi</span><br />
</div>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/03/managing-macs-with-centralized-login-scripts/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>setting up denyhosts to block ssh attacks on Leopard</title>
		<link>http://ptone.com/dablog/2009/03/setting-up-denyhosts-to-block-ssh-attacks-on-leopard/</link>
		<comments>http://ptone.com/dablog/2009/03/setting-up-denyhosts-to-block-ssh-attacks-on-leopard/#comments</comments>
		<pubDate>Wed, 11 Mar 2009 21:08:00 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/03/setting-up-denyhosts-to-block-ssh-attacks-on-leopard/</guid>
		<description><![CDATA[Deny hosts is a clever python script that will monitor your ssh log file for repeated failed login attempts, and then add the offending hosts to a system blacklist. While you can disable ssh entirely, or move it to a different port, there are reason you may want to keep it available and on a [...]]]></description>
			<content:encoded><![CDATA[<p>Deny hosts is a clever python script that will monitor your ssh log file for repeated failed login attempts, and then add the offending hosts to a system blacklist.  While you can disable ssh entirely, or move it to a different port, there are reason you may want to keep it available and on a standard port and this tool will help keep the bad guys out.</p>

<ul>
<li>download and unpack <a href="http://denyhosts.sourceforge.net/">tarball</a></li>
<li>su as root</li>
<li><p>cd to the unpacked distribution folder and enter the following in terminal:</p>

<pre><code>python setup.py install
touch /etc/hosts.deny
cp /usr/share/denyhosts/denyhosts.cfg-dist /usr/share/denyhosts/denyhosts.cfg
cp /usr/share/denyhosts/daemon-control-dist /usr/share/denyhosts/daemon-control
chmod 700 /usr/share/denyhosts/daemon-control
</code></pre></li>
</ul>

<p>read on for configuration</p>

<p><span id="more-57"></span></p>

<p><strong>Configure Script</strong></p>

<p>These are the <strong>MINIMAL</strong> settings for OS X.  There are lots of other good options in there including some settings about thresholds and purging, and emailing an admin when a attacker is added to the list.  There is also a feature that lets you share your list of attackers with the community, as well as download the community generated blacklist.  Using your favorite text editor or the built in pico change the following settings:</p>

<pre><code>pico /usr/share/denyhosts/denyhosts.cfg
</code></pre>

<p>look for and change the following:</p>

<pre><code>    SECURE_LOG = /var/log/secure.log
    LOCK_FILE = /tmp/denyhosts.lock
</code></pre>

<p>(strictly speaking  &#8211; you don&#8217;t need to do this next part as we will be starting the script with launchd)</p>

<pre><code>pico /usr/share/denyhosts/daemon-control
</code></pre>

<p>look for and change the following:</p>

<pre><code>    DENYHOSTS_BIN   = "/usr/local/bin/denyhosts.py"
    DENYHOSTS_LOCK  = "/tmp/denyhosts.lock"
</code></pre>

<p>to test out the daemon you can execute the following:</p>

<pre><code>    /usr/bin/env python /usr/local/bin/denyhosts.py --daemon --config=/usr/share/denyhosts/denyhosts.cfg
</code></pre>

<p><strong>To launch the monitoring script automatically</strong></p>

<p>create and then edit a launchd plist</p>

<pre><code>    pico /Library/LaunchDaemons/denyhosts.daemon.plist
</code></pre>

<p>and then paste the following:</p>

<pre><code>    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    &lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/
    PropertyList-1.0.dtd"&gt;
    &lt;plist version="1.0"&gt;
    &lt;dict&gt;
            &lt;key&gt;Label&lt;/key&gt;
            &lt;string&gt;denyhosts.daemon&lt;/string&gt;
            &lt;key&gt;ProgramArguments&lt;/key&gt;
            &lt;array&gt;
                    &lt;string&gt;/usr/bin/env&lt;/string&gt;
                    &lt;string&gt;python&lt;/string&gt;
                    &lt;string&gt;/usr/local/bin/denyhosts.py&lt;/string&gt;
                    &lt;string&gt;--daemon&lt;/string&gt;
                    &lt;string&gt;--config=/usr/share/denyhosts/denyhosts.cfg&lt;/string&gt;
            &lt;/array&gt;
            &lt;key&gt;RunAtLoad&lt;/key&gt;
            &lt;false/&gt;
    &lt;/dict&gt;
    &lt;/plist&gt;
</code></pre>

<p>then to load the plist without logging out or restarting:</p>

<pre><code>    launchctl load /Library/LaunchDaemons/denyhosts.daemon.plist
</code></pre>

<p>try it out &#8211; try logging in as a fake user/bad pw and you should see your IP listed in &#8220;/etc/hosts.deny&#8221;</p>

<p>simply remove a host from that file if you want to gain access again (or see the configuration notes about purging)</p>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/03/setting-up-denyhosts-to-block-ssh-attacks-on-leopard/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Voice to OmniFocus, revisited</title>
		<link>http://ptone.com/dablog/2009/03/voice-to-omnifocus-revisited/</link>
		<comments>http://ptone.com/dablog/2009/03/voice-to-omnifocus-revisited/#comments</comments>
		<pubDate>Mon, 09 Mar 2009 17:29:44 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/03/voice-to-omnifocus-revisited/</guid>
		<description><![CDATA[9/2/2009 Updated to work with now longer Twitter ID numbers back when Jott was free I was one of several people who were using it to get quick voice notes into OmniFocus (OF). A couple of limitations of the Jott solution was that: It sometimes had trouble understanding the recipient of &#8220;who I wanted to [...]]]></description>
			<content:encoded><![CDATA[<p><strong>9/2/2009 Updated to work with now longer Twitter ID numbers</strong></p>

<p>back when Jott was free I was one of several people who were using it to get quick voice notes into <a href="http://www.omnigroup.com/applications/omnifocus/">OmniFocus</a> (OF).  A couple of limitations of the Jott solution was that:</p>

<ul>
<li>It sometimes had trouble understanding the recipient of &#8220;who I wanted to Jott&#8221; </li>
<li>You never got a chance to proofread or edit the transcription before it got sent</li>
</ul>

<p>There are just times when I think of something on the go, and firing up the iPhone OF app and tapping it in won&#8217;t work, so when a <a href="http://www.reolo.com">friend</a> told me about <a href="http://www.vlingo.com">vlingo</a> for iPhone and twitter, I thought &#8211; hmm I&#8217;ve been playing a bit with the twitter API and that might be a good route for me to get stuff from voice into OF.</p>

<p>So here are the steps (<strong>updated with more detail</strong>):</p>

<p><span id="more-55"></span></p>

<ul>
<li>Set up a new twitter account &#8211; make it &#8220;private&#8221; (assuming you don&#8217;t want to world to see your tasks)</li>
<li>Set up vlingo to post status updates to this twitter account (obvious caveat, you won&#8217;t be able to use vlingo for your regular twitter account&#8230;)</li>
<li>download the <a href="http://www.ptone.com/downloads/oftwitter.txt">python script</a> (Click on the link, then do a save as, from here we will assume its on your Desktop, but it is quite likely you may want to keep it somewhere else) </li>
<li>Rename it oftwitter.py (mind os x&#8217;s habit of hiding extensions&#8230;)</li>
<li>open the script in any text editor and set the username and password settings to those of your new account</li>
<li><p>open terminal and type the following lines (you will need your admin password)</p>

<pre><code>chmod +x ~/Desktop/oftwitter.py
sudo easy_install appscript
sudo easy_install twitter
</code></pre></li>
</ul>

<p>you will get a warning about a C extension not being compiled, but you can ignore that.</p>

<ul>
<li><p>Now assuming you have already logged some tweets to the twitter account, try it out by typing the following into terminal:</p>

<pre><code>~/Desktop/oftwitter.py
</code></pre></li>
</ul>

<p>You should see your tweets as items in your OmniFocus inbox</p>

<p>The script creates a preference file stored at ~/Library/Preferences/com.ptone.oftwitter.plist that stores the most recent tweet and uses that to query twitter more effectively.  </p>

<p>to set up automated retrieval of new tweets install <a href="http://tuppis.com/lingon/">Lingon</a> and configure a new launch agent to look something like this:</p>

<p><img src="http://ptone.com/dablog/wp-content/uploads/2009/03/oftwitterlingon.png" alt="Oftwitterlingon" /></p>

<p>Now when you are out and about you can fire up vlingo on the iPhone and say: &#8220;twitter, order new engine for rocket ship&#8221;, you get the chance to fix any quick errors, then update your status.  I&#8217;m finding it even easier to just hit the &#8220;social&#8221; button in vlingo, then you don&#8217;t have to say &#8220;twitter&#8221; &#8211; just your task.</p>

<p>When you return to your Mac &#8211; it should shortly appear in your OF inbox.  This script assumes you keep OmniFocus open</p>

<p>A couple other goodies:</p>

<ul>
<li>If you say &#8220;important&#8221; or &#8220;flag&#8221;, that word will be stripped, and the entry will be flagged in OF</li>
<li>you can send a @reply message to the twitter account and replies are checked too (with the leading @user stripped from the start)

<ul>
<li>This is handy if you want to give someone a way to submit tasks to your inbox, they can just send a tweet to your new account</li>
</ul></li>
</ul>

<p>To remove all traces:</p>

<ul>
<li>delete the oftwitter.py script file</li>
<li>delete ~/Library/Preferences/com.ptone.oftwitter.plist</li>
<li>delete /Library/Python/2.5/site-packages/appscript*</li>
<li>delete /Library/Python/2.5/site-packages/simplejson</li>
<li>delete /Library/Python/2.5/site-packages/twitter</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/03/voice-to-omnifocus-revisited/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Time Machine: poor man&#8217;s version control</title>
		<link>http://ptone.com/dablog/2009/02/time-machine-poor-mans-version-control/</link>
		<comments>http://ptone.com/dablog/2009/02/time-machine-poor-mans-version-control/#comments</comments>
		<pubDate>Tue, 24 Feb 2009 01:09:52 +0000</pubDate>
		<dc:creator>ptone</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://ptone.com/dablog/2009/02/time-machine-poor-mans-version-control/</guid>
		<description><![CDATA[There are a has been a number of version control systems en vogue over time, CVS, SVN, Git etc. I try to keep up with them, use them where possible, but don&#8217;t put EVERYTHING I do in version control. Since I am on a Mac running Leopard, I do have, and use Time Machine and [...]]]></description>
			<content:encoded><![CDATA[<p>There are a has been a number of version control systems en vogue over time, CVS, SVN, Git etc.</p>

<p>I try to keep up with them, use them where possible, but don&#8217;t put EVERYTHING I do in version control.  Since I am on a Mac running Leopard, I do have, and use <a href="http://arstechnica.com/apple/reviews/2007/10/mac-os-x-10-5.ars/14">Time Machine</a> and so wanted to see if it would be pretty easy to use that to do some quick diffs between some source files.</p>

<p>The result is a quick and dirty python script.  In the unlikely event that I ever have time, this would be a cool pyObjC project, a file browser panel, date versions picker, and a webkit view (with some better css).</p>

<p>The source to the script is below the fold &#8211; it will look for the first attached volume that has time machine backups for the current machine.  It will not work with network based time machine backups that are on disk images. This script will run on a stock Leopard install without any extra python modules needed.</p>

<p><span id="more-54"></span></p>

<div class="highlight" ><pre><span style="color: #60a0b0; font-style: italic">#!/usr/bin/env python</span>
<span style="color: #60a0b0; font-style: italic"># encoding: utf-8</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>
<span style="color: #4070a0; font-style: italic">Created by Preston Holmes on 2009-02-23.</span>
<span style="color: #4070a0; font-style: italic">Copyright (c) 2009 __MyCompanyName__. All rights reserved.</span>
<span style="color: #4070a0; font-style: italic">&quot;&quot;&quot;</span>

<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">sys</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">os</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">getopt</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">difflib</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">time</span>
<span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">pdb</span>
<span style="color: #007020; font-weight: bold">from</span> <span style="color: #0e84b5; font-weight: bold">subprocess</span> <span style="color: #007020; font-weight: bold">import</span> Popen, PIPE

<span style="color: #60a0b0; font-style: italic"># if you set time_machine_path, it should be to the full path of this machines backup drive:</span>
<span style="color: #60a0b0; font-style: italic"># ie &#39;/Volumes/TM_Drive/Backups.backupsdb/Joes-Mac/&#39;</span>
<span style="color: #60a0b0; font-style: italic"># if not set explicitly - the script will use the first TM drive it finds, </span>
<span style="color: #60a0b0; font-style: italic"># and the first host folder it finds - which should work for most cases</span>
<span style="color: #60a0b0; font-style: italic"># Will Not work with network or disk image based time machine backups</span>

time_machine_path <span style="color: #666666">=</span> <span style="color: #007020">None</span>

cmd <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;tell application &quot;Finder&quot; to name of (path to startup disk)&#39;</span>
boot_volume <span style="color: #666666">=</span> Popen(<span style="color: #4070a0">&#39;osascript -e </span><span style="color: #4070a0; font-weight: bold">\&#39;</span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0; font-weight: bold">\&#39;</span><span style="color: #4070a0">&#39;</span> <span style="color: #666666">%</span> cmd,shell<span style="color: #666666">=</span><span style="color: #007020">True</span>,stdout<span style="color: #666666">=</span>PIPE,stderr<span style="color: #666666">=</span>PIPE)<span style="color: #666666">.</span>communicate()[<span style="color: #40a070">0</span>][<span style="color: #40a070">0</span>:<span style="color: #666666">-</span><span style="color: #40a070">1</span>]



help_message <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;&#39;&#39;</span>
<span style="color: #4070a0">Call this script with the path to one or more text based files as arguments</span>
<span style="color: #4070a0">&#39;&#39;&#39;</span>
header <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;&#39;&#39;</span>
<span style="color: #4070a0"></span>
<span style="color: #4070a0">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;</span>
<span style="color: #4070a0">          &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;</span>
<span style="color: #4070a0"></span>
<span style="color: #4070a0">&lt;html&gt;</span>
<span style="color: #4070a0"></span>
<span style="color: #4070a0">&lt;head&gt;</span>
<span style="color: #4070a0">    &lt;meta http-equiv=&quot;Content-Type&quot;</span>
<span style="color: #4070a0">          content=&quot;text/html; charset=ISO-8859-1&quot; /&gt;</span>
<span style="color: #4070a0">    &lt;title&gt;&lt;/title&gt;</span>
<span style="color: #4070a0">    &lt;style type=&quot;text/css&quot;&gt;</span>
<span style="color: #4070a0">        table.diff {font-family:Courier; border:medium;}</span>
<span style="color: #4070a0">        .diff_header {background-color:#e0e0e0}</span>
<span style="color: #4070a0">        td.diff_header {text-align:right}</span>
<span style="color: #4070a0">        .diff_next {background-color:#c0c0c0}</span>
<span style="color: #4070a0">        .diff_add {background-color:#aaffaa}</span>
<span style="color: #4070a0">        .diff_chg {background-color:#ffff77}</span>
<span style="color: #4070a0">        .diff_sub {background-color:#ffaaaa}</span>
<span style="color: #4070a0">    &lt;/style&gt;</span>
<span style="color: #4070a0">&lt;/head&gt;</span>
<span style="color: #4070a0"></span>
<span style="color: #4070a0">&lt;body&gt;</span>
<span style="color: #4070a0">&#39;&#39;&#39;</span>
footer <span style="color: #666666">=</span> <span style="color: #4070a0">&#39;&#39;&#39;</span>
<span style="color: #4070a0">    &lt;table class=&quot;diff&quot; summary=&quot;Legends&quot;&gt;</span>
<span style="color: #4070a0">        &lt;tr&gt; &lt;th colspan=&quot;2&quot;&gt; Legends &lt;/th&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">        &lt;tr&gt; &lt;td&gt; &lt;table border=&quot;&quot; summary=&quot;Colors&quot;&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;th&gt; Colors &lt;/th&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td class=&quot;diff_add&quot;&gt;&amp;nbsp;Added&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td class=&quot;diff_chg&quot;&gt;Changed&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td class=&quot;diff_sub&quot;&gt;Deleted&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                  &lt;/table&gt;&lt;/td&gt;</span>
<span style="color: #4070a0">             &lt;td&gt; &lt;table border=&quot;&quot; summary=&quot;Links&quot;&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;th colspan=&quot;2&quot;&gt; Links &lt;/th&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td&gt;(f)irst change&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td&gt;(n)ext change&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                      &lt;tr&gt;&lt;td&gt;(t)op&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">                  &lt;/table&gt;&lt;/td&gt; &lt;/tr&gt;</span>
<span style="color: #4070a0">    &lt;/table&gt;</span>
<span style="color: #4070a0">&lt;/body&gt;</span>
<span style="color: #4070a0"></span>
<span style="color: #4070a0">&lt;/html&gt;</span>
<span style="color: #4070a0">&#39;&#39;&#39;</span>
<span style="color: #007020; font-weight: bold">class</span> <span style="color: #0e84b5; font-weight: bold">Usage</span>(<span style="color: #007020">Exception</span>):
    <span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">__init__</span>(<span style="color: #007020">self</span>, msg):
        <span style="color: #007020">self</span><span style="color: #666666">.</span>msg <span style="color: #666666">=</span> msg

<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">find_versions</span>(path):
    <span style="color: #60a0b0; font-style: italic">#pdb.set_trace()</span>
    <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&#39;looking for versions of </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&#39;</span> <span style="color: #666666">%</span> path
    <span style="color: #60a0b0; font-style: italic">#print time.strftime(&quot;%m/%d/%Y %I:%M:%S %p&quot;,time.localtime(os.path.getmtime(fname)))</span>
    backups <span style="color: #666666">=</span> os<span style="color: #666666">.</span>listdir(time_machine_path)
    backups<span style="color: #666666">.</span>sort()
    pathlist <span style="color: #666666">=</span> [os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(time_machine_path,b,boot_volume,path[<span style="color: #40a070">1</span>:]) <span style="color: #007020; font-weight: bold">for</span> b <span style="color: #007020; font-weight: bold">in</span> backups]
    pathlist<span style="color: #666666">.</span>append(path)
    versions <span style="color: #666666">=</span> []
    mod_times <span style="color: #666666">=</span> []
    <span style="color: #007020; font-weight: bold">for</span> f <span style="color: #007020; font-weight: bold">in</span> pathlist:
        <span style="color: #007020; font-weight: bold">if</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(f):
            mod_time <span style="color: #666666">=</span> time<span style="color: #666666">.</span>localtime(os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>getmtime(f))
            <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> mod_time <span style="color: #007020; font-weight: bold">in</span> mod_times:
                mod_times<span style="color: #666666">.</span>append(mod_time)
                versions<span style="color: #666666">.</span>append({mod_time:f})
    <span style="color: #007020; font-weight: bold">return</span> versions
    
<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">getTMLocation</span>():
    <span style="color: #007020; font-weight: bold">global</span> time_machine_path
    <span style="color: #007020; font-weight: bold">if</span> time_machine_path <span style="color: #007020; font-weight: bold">and</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(time_machine_path):
        <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">True</span>
    volumes <span style="color: #666666">=</span> os<span style="color: #666666">.</span>listdir(<span style="color: #4070a0">&#39;/Volumes&#39;</span>)
    <span style="color: #60a0b0; font-style: italic">#hostname = os.uname()[1].split(&#39;.&#39;)[0]</span>
    <span style="color: #60a0b0; font-style: italic">#cmd = &#39;scutil --get ComputerName&#39;</span>
    <span style="color: #60a0b0; font-style: italic">#machine_name = Popen(&#39;osascript -e \&#39;%s\&#39;&#39; % cmd,shell=True,stdout=PIPE,stderr=PIPE).communicate()[0][0:-1]</span>
    <span style="color: #007020; font-weight: bold">for</span> v <span style="color: #007020; font-weight: bold">in</span> volumes:
        <span style="color: #007020; font-weight: bold">if</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>exists(os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(<span style="color: #4070a0">&#39;/Volumes&#39;</span>,v,<span style="color: #4070a0">&#39;Backups.backupdb&#39;</span>)):
            backupsdb <span style="color: #666666">=</span> (os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(<span style="color: #4070a0">&#39;/Volumes&#39;</span>,v,<span style="color: #4070a0">&#39;Backups.backupdb&#39;</span>))
            time_machine_path <span style="color: #666666">=</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(backupsdb,os<span style="color: #666666">.</span>listdir(backupsdb)[<span style="color: #40a070">0</span>])
            <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">True</span>
            <span style="color: #60a0b0; font-style: italic"># candidate_path = os.path.join(&#39;/Volumes&#39;,v,&#39;Backups.backupdb&#39;,machine_name)</span>
            <span style="color: #60a0b0; font-style: italic"># if os.path.exists(candidate_path):</span>
            <span style="color: #60a0b0; font-style: italic">#     time_machine_path = candidate_path</span>
            <span style="color: #60a0b0; font-style: italic">#     return True</span>
    <span style="color: #007020; font-weight: bold">return</span> <span style="color: #007020">False</span>
    
<span style="color: #007020; font-weight: bold">def</span> <span style="color: #06287e">main</span>(argv<span style="color: #666666">=</span><span style="color: #007020">None</span>):
    
    <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> getTMLocation():
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&#39;No Time Macine Backup Found&#39;</span>
        sys<span style="color: #666666">.</span>exit()
    <span style="color: #007020; font-weight: bold">print</span> <span style="color: #4070a0">&#39;time machine path: &#39;</span> <span style="color: #666666">+</span> time_machine_path
    <span style="color: #007020; font-weight: bold">if</span> argv <span style="color: #007020; font-weight: bold">is</span> <span style="color: #007020">None</span>:
        argv <span style="color: #666666">=</span> sys<span style="color: #666666">.</span>argv
    <span style="color: #007020; font-weight: bold">try</span>:
        <span style="color: #007020; font-weight: bold">try</span>:
            opts, args <span style="color: #666666">=</span> getopt<span style="color: #666666">.</span>getopt(argv[<span style="color: #40a070">1</span>:], <span style="color: #4070a0">&quot;ho:v&quot;</span>, [<span style="color: #4070a0">&quot;help&quot;</span>, <span style="color: #4070a0">&quot;output=&quot;</span>])
        <span style="color: #007020; font-weight: bold">except</span> getopt<span style="color: #666666">.</span>error, msg:
            <span style="color: #007020; font-weight: bold">raise</span> Usage(msg)
    
        <span style="color: #60a0b0; font-style: italic"># option processing</span>
        <span style="color: #007020; font-weight: bold">for</span> option, value <span style="color: #007020; font-weight: bold">in</span> opts:
            <span style="color: #007020; font-weight: bold">if</span> option <span style="color: #666666">==</span> <span style="color: #4070a0">&quot;-v&quot;</span>:
                verbose <span style="color: #666666">=</span> <span style="color: #007020">True</span>
            <span style="color: #007020; font-weight: bold">if</span> option <span style="color: #007020; font-weight: bold">in</span> (<span style="color: #4070a0">&quot;-h&quot;</span>, <span style="color: #4070a0">&quot;â€“help&quot;</span>):
                <span style="color: #007020; font-weight: bold">raise</span> Usage(help_message)
            <span style="color: #007020; font-weight: bold">if</span> option <span style="color: #007020; font-weight: bold">in</span> (<span style="color: #4070a0">&quot;-o&quot;</span>, <span style="color: #4070a0">&quot;â€“output&quot;</span>):
                output <span style="color: #666666">=</span> value
        <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020; font-weight: bold">not</span> args:
            <span style="color: #007020; font-weight: bold">raise</span> Usage(help_message)
        differ <span style="color: #666666">=</span> difflib<span style="color: #666666">.</span>HtmlDiff(tabsize<span style="color: #666666">=</span><span style="color: #40a070">4</span>)
        html <span style="color: #666666">=</span> header
        <span style="color: #007020; font-weight: bold">for</span> path <span style="color: #007020; font-weight: bold">in</span> args:
            path <span style="color: #666666">=</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>join(os<span style="color: #666666">.</span>getcwd(),path)
            html <span style="color: #666666">+=</span> <span style="color: #4070a0">&#39;&lt;h1&gt;Changes for </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&lt;/h1&#39;</span> <span style="color: #666666">%</span> os<span style="color: #666666">.</span>path<span style="color: #666666">.</span>basename(path)
            versions <span style="color: #666666">=</span> find_versions(path)
            <span style="color: #007020; font-weight: bold">if</span> <span style="color: #007020">len</span>(versions) <span style="color: #666666">&lt;</span> <span style="color: #40a070">2</span>:
                html <span style="color: #666666">+=</span> <span style="color: #4070a0">&#39;&lt;h2&gt;Less than 2 Versions found on Time Machine Backup&lt;/h2&gt;&#39;</span>
            <span style="color: #007020; font-weight: bold">else</span>:
                html <span style="color: #666666">+=</span> <span style="color: #4070a0">&#39;&lt;h2&gt;</span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0"> versions found&lt;/h2&gt;&#39;</span> <span style="color: #666666">%</span> <span style="color: #007020">len</span>(versions)
            <span style="color: #007020; font-weight: bold">for</span> i <span style="color: #007020; font-weight: bold">in</span> <span style="color: #007020">range</span>(<span style="color: #40a070">0</span>,<span style="color: #007020">len</span>(versions)):
                <span style="color: #007020; font-weight: bold">if</span> i: <span style="color: #60a0b0; font-style: italic">#skip the first</span>
                    d1 <span style="color: #666666">=</span> time<span style="color: #666666">.</span>strftime(<span style="color: #4070a0">&quot;%m/</span><span style="color: #70a0d0; font-style: italic">%d</span><span style="color: #4070a0">/%Y %I:%M:%S %p&quot;</span>,versions[i<span style="color: #666666">-</span><span style="color: #40a070">1</span>]<span style="color: #666666">.</span>keys()[<span style="color: #40a070">0</span>])
                    d2 <span style="color: #666666">=</span> time<span style="color: #666666">.</span>strftime(<span style="color: #4070a0">&quot;%m/</span><span style="color: #70a0d0; font-style: italic">%d</span><span style="color: #4070a0">/%Y %I:%M:%S %p&quot;</span>,versions[i]<span style="color: #666666">.</span>keys()[<span style="color: #40a070">0</span>])
                    html <span style="color: #666666">=</span> html <span style="color: #666666">+</span> <span style="color: #4070a0">&#39;&lt;h2&gt;changes from </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0"> to </span><span style="color: #70a0d0; font-style: italic">%s</span><span style="color: #4070a0">&lt;/h2&gt;&#39;</span> <span style="color: #666666">%</span> (d1,d2)
                    l1 <span style="color: #666666">=</span> <span style="color: #007020">open</span>(versions[i<span style="color: #666666">-</span><span style="color: #40a070">1</span>]<span style="color: #666666">.</span>values()[<span style="color: #40a070">0</span>])<span style="color: #666666">.</span>readlines()
                    l2 <span style="color: #666666">=</span> <span style="color: #007020">open</span>(versions[i]<span style="color: #666666">.</span>values()[<span style="color: #40a070">0</span>])<span style="color: #666666">.</span>readlines()
                    table <span style="color: #666666">=</span> differ<span style="color: #666666">.</span>make_table(l1,l2,context<span style="color: #666666">=</span><span style="color: #007020">True</span>,numlines<span style="color: #666666">=</span><span style="color: #40a070">3</span>)
                    html <span style="color: #666666">+=</span> table
        html <span style="color: #666666">+=</span> footer
        o <span style="color: #666666">=</span> <span style="color: #007020">open</span>(<span style="color: #4070a0">&#39;/tmp/diff.html&#39;</span>,<span style="color: #4070a0">&#39;w&#39;</span>)
        o<span style="color: #666666">.</span>write(html)
        o<span style="color: #666666">.</span>close()
        <span style="color: #007020; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">webbrowser</span>
        webbrowser<span style="color: #666666">.</span>open(<span style="color: #4070a0">&#39;/tmp/diff.html&#39;</span>)
    <span style="color: #007020; font-weight: bold">except</span> Usage, err:
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #666666">&gt;&gt;</span> sys<span style="color: #666666">.</span>stderr, sys<span style="color: #666666">.</span>argv[<span style="color: #40a070">0</span>]<span style="color: #666666">.</span>split(<span style="color: #4070a0">&quot;/&quot;</span>)[<span style="color: #666666">-</span><span style="color: #40a070">1</span>] <span style="color: #666666">+</span> <span style="color: #4070a0">&quot;: &quot;</span> <span style="color: #666666">+</span> <span style="color: #007020">str</span>(err<span style="color: #666666">.</span>msg)
        <span style="color: #007020; font-weight: bold">print</span> <span style="color: #666666">&gt;&gt;</span> sys<span style="color: #666666">.</span>stderr, <span style="color: #4070a0">&quot;</span><span style="color: #4070a0; font-weight: bold">\t</span><span style="color: #4070a0"> for help use â€“help&quot;</span>
        <span style="color: #007020; font-weight: bold">return</span> <span style="color: #40a070">2</span>


<span style="color: #007020; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #4070a0">&quot;__main__&quot;</span>:
    sys<span style="color: #666666">.</span>exit(main())
</pre></div>
]]></content:encoded>
			<wfw:commentRss>http://ptone.com/dablog/2009/02/time-machine-poor-mans-version-control/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

