2009-10-25

Losing ZFS

Luis Gerbarg has an interesting article with his thoughts about the reasons behind the absence of ZFS in 10.6 on his blog at http://devwhy.blogspot.com/2009/10/loss-of-zfs.html.

Here's hoping that he's right and Apple is already working on their own next generation filesystem - HFS+ is getting a bit long in the tooth, and it'd be nice to see an alternative in 10.7.

2009-10-12

Released the Luggage

I've been rewriting a tool I originally wrote when I was at Google that allows you to generate an OS X pkg file using a Makefile without using PackageMaker.app's gui at all. At Google we used the internal tool to create most of the packages we used to create disk images with InstaDMG or with puppet. After I left, I missed it, so I'm releasing my rewrite, the Luggage, with the Apache 2.0 license. The source is at http://github.com/unixorn/luggage

What the Luggage provides

Easy repackaging of GUI apps that don't come with Apple pkg installers, custom scripts and configuration files, payload-less packages that do settings changes on target machines in their postflight/postupdate scripts. These packages are easily used in InstaDMG to create master disk images for deployment, or system management systems like puppet to distribute new software to managed Macintoshes.

How it works

You create a Makefile describing where you want each file in the package to end up on the target machines. luggage.make includes rules that will generate a package root with appropriate permissions, copy the files into place, create a package version-stamped with the date, then wrap the package in a disk image file.

So how do I use it?

Requirements for all Makefiles

Every package makefile must include luggage.make, specify a title for the package, a base reversed domain for creating the package id, and the payload listing which files we want installed by the package. Include luggage.make before your variable declarations so your declarations override the internal ones in luggage.make. Please note that TITLE must not include spaces - it's going to be used as part of the package id. Also remember to include luggage.make before any of your definitions so your definitions override the defaults.

include /usr/local/share/luggage/luggage.make
 
TITLE=install_foo
REVERSE_DOMAIN=com.example.corp
PAYLOAD=

Creating a package for a GUI Application

For our first example, we're going to repackage a GUI application that didn't come with a PKG installer. First, tar up the application (making sure to use Apple's tar so resource forks don't get mangled) with /usr/bin/tar cvj Foo.app.tar.bz Foo.app, then update PAYLOAD with
PAYLOAD=unbz2-applications-foo.app

This tells luggage.make that we want Foo.app.tar.bz2 untarred into /Applications. If we want it installed in /Applications/Utilities instead, we'd useunbz2-utilities-foo.app instead, Now we can create a package with "make pkg", or have it automatically wrapped in a disk image with "make dmg". That's all it takes.

Packaging scripts and configuration files

Similarly, if you want to install bar as /usr/local/bin/bar, add pack-usr-local-bin-bar to PAYLOAD. 

Available PAYLOAD additions

 Rule Ownership Permissions Destination
 pack-etc-foo root:wheel 644 /etc/foo
 pack-usr-bin-foo root:wheel 755 /usr/bin/foo
 pack-usr-sbin-foo root:wheel 755 /usr/sbin/foo
 pack-usr-local-bin-foo root:wheel 755 /usr/local/bin/foo
 pack-usr-local-sbin-foo root:wheel 755 /usr/local/sbin/foo
 pack-hookscript-foo root:wheel 755 /etc/hooks/foo
 pack-Library-LaunchAgents-foo root:wheel 644 /Library/LaunchAgents/foo
 pack-Library-LaunchDaemons-foo root:wheel 644 /Library/LaunchDaemons/foo
 pack-Library-Preferences-foo root:admin 644 /Library/Preferences/foo
 pack-ppd-foo root:admin 644 /Library/Printers/PPDs/Contents/Resources/foo
 pack-user-template-plist-foo root:wheel 644 /System/Library/User\ Template/English.lproj/Library/Preferences/foo
 unbz2-applications-foo root:admin based on tarball contents /Applications/Foo
 ungz-applications-foo root:admin based on tarball contents /Applications/Foo
 unbz2-utilities-foo root:admin based on tarball contents /Applications/Utilities/Foo
 ungz-utilities-foo root:admin based on tarball contents /Applications/Utilities/Foo

Adding preflight/postflight/postinstall/postupdate scripts

Name the script preflight, postflight, postinstall, or postupdate, then add pack-script-XX to PAYLOAD and it will automatically be added to the final package.

Make targets

  • make dmg - create a package, then wrap it in a dmg. Result will be in the current directory.
  • make pkg - create a package and copy it into the current directory
  • make pkgls - create a package, then list the contents so you can confirm it's generating a package with a payload that matches what you're expecting.

Customizing your packages

How do I add a file to my package that is installed somewhere luggage.make doesn't cover?

luggage.make defines several convenience targets that create various directory paths within the package root. You can use them to create the parent directory for your target location, then create your target location. Here's an example that creates /etc/cups so you can install a custom cupsd.conf

l_cups: l_etc
    @sudo mkdir ${WORK_D}/etc/cups
    @sudo chown root:_lp ${WORK_D}/etc/cups
    @sudo chmod 755 ${WORK_D}/etc/cups

pack-cupsd.conf: l_cups
    @sudo ${CP} cupsd.conf ${WORK_D}/etc/cups
    @sudo chown root:_lp ${WORK_D}/etc/cups/cupsd.conf
    @sudo chmod 644 ${WORK_D}/etc/cups/cupsd.conf

Now all you need to do is add pack-cupsd.conf to your PAYLOAD variable, and make will create /etc/cups in your package root, change the permissions and ownership, then copy in your cupsd.conf.

I need to install foo with permissions different from the defaults luggage.make uses. How do I change the ownership/permissions for files installed using luggage.make?

You could make your changes using a postflight script, but for convenience, luggage.make runs make modify_packageroot after it has created the package root and copied the install files there, but before it invokes packagemaker's command line tool to create the package.

If you add that target to your package's Makefile, it will override the dummy one in luggage.make and let you change the permissions of files in your package root. ${WORK_D} contains the path to your package root. For example, if your package installed a modified cupsd.conf to /etc/cups, and you needed it to have a group of _lp, you would add the following to your package Makefile:
modify_packageroot:
    @sudo chgrp _lp ${WORK_D}/etc/cups/cupsd.conf

I don't want the package version to be based on the current date. How can I force it to something specific?

By default, luggage.make sets the version number to YYYYMMDD. If you prefer to set it to something specific, set PACKAGE_VERSION=something_numeric.

FAQ

My makefile looks ok, but I get an error about a missing separator

You've probably indented your rules with spaces rather than a tab. Make requires the indentation be with tabs.

2009-09-12

So this is what a 50mm f1.8 looks like inside...

I would have been quite happy without finding out exactly what my 50mm f1.8 looks like inside.


I was going through airport security in San Jose on my way to visit family in Chicago and TSA had to hand check my bag because I had forgotten there was a leatherman in it. Once they spotted my "contraband", they insisted on unpacking the bag instead of letting me do it. The next day, I took the lens cap off to shoot a picture of my niece, and the whole front assembly fell off into my hand.

I only started using a DSLR a few months ago, and on the recommendation of a coworker at Google, the 50mm f1.8 was the first lens I bought besides the kit lens that came with the XSi body.

I loved this lens - nice and sharp, much faster than the kit lens, nice color, nice and light. I ended up using it far more than my other two lenses.

Here's a shot I took of Xan McCurdy with it at the Cake concert at WWDC -

Not bad for a $90 lens. That said, the reason it's so nice and light is that the innards are mostly plastic, and the front assembly that came off is held on by four tiny (3mm or so) plastic fingers, two of which appear to have sheared off on mine.

Ended up deciding to replace it with the Canon EF 50mm f1.4,which I'm impatiently waiting to arrive.

More Cake shots:







2009-03-13

New disk image tests

I got permission to release the changes I've made at work to the run_image_tests.py disk image test framework that Nigel Kersten originally wrote (thanks Chris, for pushing them into svn for me) along with a bunch of example tests you can use. These are all the tests I use at work every time I make a new master disk image that are generic enough the be useful to others.

I don't bother to manually test any images that don't pass all of the unit tests first.

You can find them at the pymacadmin project site at Google Code.

All the unit tests descend from macdmgtest.DMGUnitTest. DMGUnitTest provides some handy utility functions that convert paths relative to / to the path relative to the mounted dmg you're testing.

dmgtestutilities.py provides some more convenience functions - there are functions to lint plists and load plists using Foundation so plistlib doesn't crap out when you try to load a binary plist.

Example tests:
applications_dir_test.py:
Ensures all applications in /Applications and /Applications/Utilities have sane ownership/permissions.

blacklist_test.py:
Confirms the absence of files in your blacklist. I mainly use this to confirm that various settings files that would contain information specific to the hardware model I ran InstaDMG on don't end up on my master images.

empty_directory_test.py:
Confirms that directories that you want to be empty on the generated image, actually are.

network_plist_test.py:
Confirms the absence of various network preference files. This prevents a problem we ran into when booting MacBook Airs from an image created on a machine that has built-in ethernet.

ouradmin_test.py:
Ensures that there's a local admin user on the image named ouradmin, and that it's a member of the admin, _lpadmin and _appserveradm groups. Also lints the appropriate plist files in /var/db/dslocal/nodes/Default to detect if your user creation package corrupted them.

skipsetup_test.py:
Makes sure the appropriate files have been touched on the image to prevent a newly imaged machine from playing the annoying welcome movie.

software_update_test.py:
If you're using a local software update server in your environment, you can use this to make sure the dmg has the correct settings to use it instead of the standard Apple servers.

zz_plint_test.py:
Lints every plist on the dmg and displays a list of any files that failed their lint check.

zz_suidguid_test.py:
Finds every suid/setguid file on the dmg and reports any that aren't in your whitelist. Handy for detecting crappy installers.

zz_world_writable_test.py:
Finds every file or directory that is world writable and reports any that aren't in your whitelist. Also useful for detecting poorly written installers.

I've also included the whitelists and blacklists I'm using for these tests for you to use as a base to customize your own, though I'd appreciate it if you'd email any additions you think are useful.

Creative Commons License

This work is licensed under a Creative Commons License.
Copyright 2007-2010, Joseph P. Block, Some Rights Reserved.

Creative Commons Logo