Check out my new Google profile.
Posted late Thursday night, February 26th, 2010
I got quite a shock last week, when I got my bi-annual royalty statement.

Hyperion publishes six titles in my Jack Daniels series. They gave me my ebook figures.

Authors are usually quite secretive about their sales and their royalties.

Me? I'm spilling the beans. Here are my ebook Kindle numbers from Jan 1 to June 31, 2009.

Whiskey Sour priced at $3.96: 550 sales, $341 earned.

Bloody Mary priced at $7.99: 180 sales, $381 earned.

Rusty Nail priced at $7.99: 153 sales, $341 earned.

Dirty Martini priced at $6.39: 202 sales, $604 earned.

Fuzzy Navel
priced at $7.59: 152 sales, $341 earned.

That's 1237 ebooks sold in six months. Total money in JA's pocket: $2008.

Why do these numbers vary so much?

I get 25% of the amount received by the publisher. Depending on the deal my publisher makes with Amazon, that can be anywhere from 62 cents to $3 per ebook sold.

We can draw a simple conclusion looking at these sales: a $4 ebook sells 3 times as many copies as an $8 ebook.

Now lets compare these to my self-published Kindle sales. I'll use my four novels for comparison. This is also for a six month period.

The List priced at $1.99: 5142 sales, $3600 earned.

Origin priced at $1.99: 2619 sales, $1833 earned.

Disturb priced at $1.99: 1139 sales, $797 earned.

Shot of Tequila at $1.99: 900 sales, $630 earned.

That's 9800 ebooks sold in six months. Total money in JA's pocket: $6860.

I get 35% of the price I set on Kindle, or 70 cents per ebook download.

We can draw some simple conclusions looking at these numbers.

Ebooks priced at $4 sell an average of 1100 ebooks per year.

Ebooks priced at $8 sell an average of 342 ebooks per year.

Ebooks priced at $2 sell an average of 4900 ebooks per year.

It doesn't take a math whiz to see that the biggest profit is with low priced ebooks.

Now let's play the imagination game.

My five Hyperion ebooks (the sixth one came out in July so no royalties yet) each earn an average of $803 per year on Kindle.

My four self-pubbed Kindle novels each earn an average of $3430 per year.

If I had the rights to all six of my Hyperion books, and sold them on Kindle for $1.99, I'd be making $20,580 per year off of them, total, rather than $4818 a year off of them, total.

So, in other words, because Hyperion has my ebook rights, I'm losing $15,762 per year.

Now Hyperion also has my print rights, and my Jack Daniels books are still selling in print. But they aren't selling enough to make up the $15,762. Especially since all of them aren't regularly being stocked on bookstore shelves.

According to my math, I'd be making more money if my books were out of print, and I had my rights back.

Of course, there are a lot of different factors at play here. Certain titles are more popular than others. Print sales may fuel ebook sales. Ebooks sales may wane (though mine haven't yet.) Branding and name recognition and past customers and fans all come into play, making this damn confusing and far from conclusive.

That said, do I really want to keep signing deals with print publishers?

$3430 per ebook per year isn't really a big number. I've certainly never been paid so small an advance for a novel.

And yet, I'm 100% sure ebook sales are going to go up. I've signed deals with Smashwords to sell ebooks through Barnes and Noble, Apple to sell ebooks as iTunes apps for the Iphone and iPod Touch, and Sony to sell ebooks on their reader. Kindle was just released in 100 more countries. I predict more ebook sales in the near future.

Let's say by the end of 2010 I can make $5000 per year per ebook title by self publishing. I can easily write four books per year.

Again, $20,000 per year isn't enough to live on. But things begin to accumulate.

$20k per year for 4 new books, plus $20k per year for the books I'm already selling, is $40k per year.

But I'm selling more than novels on Kindle. I also have 6 collaborations and short story collections. This year I'm also going to put The Newbie's Guide to Publishing ebook on Kindle.

So now we're looking at 14 ebooks, each making $5k per year. That's $70,000 a year.

And as more people buy ereaders and ebooks, that number can go up. Plus, I publish on my schedule, I keep the profits, and best of all, the rights are 100% mine. So if I want to do a limited print edition, I can. If I want to sell the mass market paperback rights, I can.

Ebook rights began as gravy. I can picture a day when the print rights are the gravy, and authors make their living with ebooks.

Yes, it's still far off. And yes, print publishing is in no danger of going away anytime soon.

But I don't think I'll ever take a print contract for less than $30,000 per book, because I'm confident I could make more money on it over the course of six years than I could with a publisher over six years.

Isn't that bizarre?

For the bestselling author, this is all still very trivial. These numbers are chump change compared to the advances they get.

But for the midlist author, I'm beginning to think it's possible to make a living without print contracts.

I've struggled mightily to break into print. And I've made a nice chunk of change on my print novels.

Now I'm hoping those novels go out of print, so I can get my rights back.

I never would have guessed my mindset would change so dramatically in so short a time.

DISCLAIMER: YOUR MILEAGE MAY VARY

If you're a new author, reading this and thinking about the fame and fortune you'll make on ebooks, I urge you to try the traditional route first. Find an agent. Land a deal with a big NY house. Ebooks aren't there yet.

I'd hate to think some writer gave up on their print aspirations because of something I've said on my blog. I suggest you keep up the agent search, and hold out for that major deal. While I have no doubt others will be able to sell as many ebooks as I have, and probably many more, I still haven't made anywhere near the money I've made by being in print. Plus, everyone's situation is unique, and no writer should compare themselves to any other writer.

Most of all, don't change the future of your career based on one man's ideas. Learn as much as you can about all of your options, do research, get other opinions.
Posted at lunch time on Wednesday, October 14th, 2009
Posted late Wednesday evening, July 8th, 2009
Posted in the wee hours of Thursday night, June 5th, 2009
Posted late Monday afternoon, October 13th, 2008
Posted at lunch time on Sunday, September 28th, 2008
Posted at lunch time on Wednesday, September 17th, 2008

Mac OS X makes extensive use of a variety of event notification mechanisms, as described here. One such mechanism is documented in the notify(3) man pages, and it is these types of libnotify notifications that are the focus of this post. Notifications can be posted and observed in a global namespace across all processes (with optional access control), and in general allow a process to respond to events that occur outside the purview of the process.

One simple example would be the shutdown or restart event. When the user clicks Restart, loginwindow posts a notification called com.apple.loginwindow.restartinitiated. Various other processes who have registered to receive this notification will now know that a restart is coming (soon!). A lot of stuff begins to happen to prepare the machine for a reboot. Getting that event notification from userspace out in a timely fashion to all the various custodians who need to do work is key to the whole operation.

I see two primary use cases for such a notification system: 1) You are authoring software that creates events that others (perhaps even other parts of your own software, running in a different context) need to respond to, or 2) you wish to respond to a notification for a certain event that you know is being posted by someone else.

The first case is succinctly demonstrated by the example in the man page for notifyutil. To expand on it just a bit, and more clearly demonstrate the common case of there being some indeterminate time delta between registering for a notification and receiving it, consider this example:

Open a Terminal window. Execute:

date; notifyutil -t -1 some.pants ; say 'zomg'

Open another Terminal window, but keep the first one in view. In your mind, count to ‘d’, and then execute:

notifyutil -p some.pants

In the first window, you see that a notification for some.pants was received roughly ‘d’ seconds after the registration occured, and if your audio volume is up, you hear that Mac OS X does not know how to pronounce ‘zomg’.

The second case of responding to notifications generated by others is less obvious, since in normal operation, all of these notifications are invisible to the user. You have to know the name of a notification to register for it. The fun begins when you remember to ask the question “how do I find out what notifications are being posted?”. The answer might be obvious to anyone who read the man page! notifyd is our friend, buddy. As all this is open source, we could confirm our suspicions that notifyd probably logs to ASL, and sure enough, in notify_proc.c, we see :

log_message(ASL_LEVEL_DEBUG, "__notify_server_post %s", name);

Ok! So all we need to do is ask ASL to show us debug messages for notifyd.

sudo syslog -c notifyd -d

At this point, the messages are theoretically being sent. However, the default data store (i.e. that which you can examine with the Console app) still isn’t listening, so we also need:

sudo syslog -c syslogd -d

Next you should make a new log database query using Console:

Now you can just sit back and watch the notifications roll in! For example, if I choose Restart from the Apple menu:

__notify_server_post com.apple.loginwindow.likelyShutdown
__notify_server_post com.apple.loginwindow.restartinitiated

and then a few seconds later, after I click Cancel:

__notify_server_post com.apple.loginwindow.logoutcancelled

At this point, you should be armed with enough information to find out what events are flying around, and then attach an action of your choosing to events of interest. Have fun!

Update: you can disable the debug logging as follows:

sudo syslog -c syslogd -n
sudo syslog -c notifyd off
Posted mid-morning Monday, September 8th, 2008

Check your battery’s health with System Profiler
Mac Tip #354, 03 September 2008

Black MacBook · Black MacBook A new Mac laptop, such as a MacBook Pro or a MacBook, can run for maybe 3 or 4 hours on battery. After a while though that fully charged battery will last for only 2 hours, then 1, until eventually it just dies.

That’s the way of all rechargeable batteries — they have a certain number of ‘cycles’ in them. As the months go by the battery’s capacity gradually declines.

Back in Battery check, Mac Tip #260/13-Sept-2006, I explained how to use the button on the battery itself to check the charge. System Profiler gives you more detailed information: the number of charging cycles, the make of the battery, the capacity and so on.

Open up Applications > Utilities > System Profiler. A window appears showing information about your Mac.

Click on Power, under Hardware in the list of Contents on the left of the System Profiler window. Battery and other Power Settings information is displayed in the right-hand pane.

The Model Information section shows the battery’s serial number, manufacturer and other data about its manufacture.

The Charge Information and Health Information sections are the ones to monitor.

A charge cycle is when the battery is run flat and then recharged to full. If you run the battery partly down and recharge it, that counts as part of one cycle.

The battery’s capacity to store a charge is measure in Milliamps Per Hour (mAh).

Brand new Apple battery - information · Possibly faulty 3rd party battery - information Click the screenshots for larger versions.

In the first screenshot I can see that my brand new battery has been discharged and recharged 3 times (Cycle count: 3). It’s fully charged and has 5417 mAh capacity remaining. Its Full charge capacity is 5454 mAh. The Condition of the battery is listed as: Good, under Health Information.

The 3rd party battery (ie, not from Apple) in the second screenshot has been discharged and recharged 49 times (Cycle count: 49). It’s fully charged but has only 1673 mAh capacity remaining. Its Full charge capacity is only 1680 mAh. The Condition of the battery is listed as: Check battery, under Health Information.

This battery seems to have a problem. It hasn’t been used very much but its capacity is very low. I suspect a problem with it and have contacted the supplier for support.

If you use a Mac laptop, take a look at System Profiler to give it a health check.

Update: I’m using Mac OS X 10.5.4, Leopard. Older versions of the Operating System may have less Power information, or maybe none at all.

Please bookmark this Tip to help others find it

Add to Bloglines Add to Del.icio.us Add to digg Add to Facebook Add to FURL
Add to Google Bookmarks Add to Ma.gnolia Add to Netscape Add to Newsvine Add to reddit Add to Slashdot Add to Stumble Upon Add to Squidoo Add to Technorati Add to Yahoo My Web
Hide Sites
Posted terribly early Wednesday morning, September 3rd, 2008

In this article, we’ll take a look at using Leopard’s launchd and rsync to develop a automated data backup methodology. In addition to rsync and launchd, we will also integrate Bernhard Baehr’s SleepWatcher utility.

In our environment, we use NetApp filers to host our network shares and are using CIFS network shares. However, with NetApp and Mac OS X, those file shares could be NFS, just as well. This will also work with your AFP shares.


Components

  • launchd
    launchd is Apple’s (not-so-new) system startup program. launchd is more powerful than older programs such as init and cron, since it can not only start and stop programs but can also be triggered by other events, such as availability of network shares etc, which we will use in this setup.
  • rsync
    Most people reading this article will already know what rsync is. rsync is a program that behaves in much the same way that rcp does, but has many more options and uses the rsync remote-update protocol to greatly speed up file transfers when the destination file is being updated. In this setup, rsync does the heavy lifting of actually performing the backups.
  • SleepWatcher
    is a niftly command line tool (daemon) developed by Bernhard Baehr for Mac OS X that monitors sleep, wakeup and idleness of a Mac. It can be used to execute a Unix command when the Mac or the display of the Mac goes to sleep mode or wakes up or after a given time without user interaction. We will be using SleepWatcher to do just that – to automatically mount and unmount network shares.
  1. SleepWatcher
    As mentioned above, SleepWatcher monitors the sleep and wake states of a Mac (among other things). We’ll be using both of the SleepWatcher’s components, the SleepWatcher command line tool and the SleepWatcher StartupItem. As part of the tool’s functionality, SleepWatcher employs two hidden files, .wakeup and .sleep in the user’s home directory to execute user specific commands when a Mac wakes up from sleep or goes to sleep, as the case may be.

    First, we need a wakeup script to automatically mount our network shares when the laptop/Mac wakes up from sleep.

    /Users/username/.wakeup

    #!/bin/bash
    
    filer="bitbucket.example.com";
    test "$SHELLOPTS" && shopt -s xpg_echo
    
    function checkWorkNetwork() {
      # sleep for a bit, wait for network to show up
      sleep 30
      count=$(/sbin/ping -c 3 -o $filer | grep "received" | awk -F',' '{ print $2 }' | awk '{ print $1 }')
      if [ $count -eq 0 ]; then
        # 100% failed - try one more time after sleeping for 30 secs
        sleep 30
        count=$(/sbin/ping -c 3 -o $filer | grep "received" | awk -F',' '{ print $2 }' | awk '{ print $1 }')
        if [ $count -eq 0 ]; then
          /usr/bin/logger -t SleepWatcher:wakeup "filer not reachable...Exiting".
          exit;
        fi
      else
        /usr/bin/logger -t SleepWatcher:wakeup "filer is reachable...Continuing".
      fi
    }
    
    function doMounts {
      vol=$1;
      mnt_cnt=$(/sbin/mount | grep -ic "/Volumes/$vol")
      if [ $mnt_cnt -le 0 ]; then
        /usr/bin/osascript <<EOT
        tell application "Finder" to mount volume "cifs://$filer/$vol"
    EOT
      fi
    }
    
    checkWorkNetwork
    doMounts Backups
    
    exit 0
    

    In the above wakeup script, first we check to see if we are on our work network using the function
    checkWorkNetwork . and mount the network resources. This function can easily be modified to adapt to a home network. At home, if we are on a wireless network, a function like the following may be used.

    function checknMountHomeNetwork {
     # sleep for a bit, wait for network to show up
     sleep 60
     homelan=$(/usr/sbin/system_profiler SPAirPortDataType | awk -F': ' '/Current Wireless Network/{print $2}')
    
     if [ "$homelan" == "JewelThief-Fake" ]; then
            /usr/bin/osascript <<EOT
            tell application "Finder" to mount volume "cifs://192.168.1.3/Documents"
    EOT
            /usr/bin/logger -t SleepWatcher:wakeup "Mounted Home Volumes...Exiting"
            exit 0
     else
            /usr/bin/logger -t SleepWatcher:wakeup "Cannot ascertain home network...Exiting"
     fi
    }
    

    Next, we need a sleep script to trigger the stoppage of any running rsync backups and unmount the network shares.

    The sleep script needs to execute and finish within 15 seconds. After this timeout, the system forces the sleep mode and your script may not finish properly.

    /Users/username/.sleep

    #!/bin/bash
    
    test "$SHELLOPTS" && shopt -s xpg_echo
    
    function doUmounts {
            /usr/bin/logger -t SleepWatcher:sleep "Going to sleep...unmounting network volumes"
            /usr/bin/osascript -e 'tell application "Finder" to eject (every disk whose local volume is false)'
    }
    
    function kill_rsync {
            /usr/bin/logger -t SleepWatcher:sleep "Stopping rsync"
            pid=`ps auxw | grep rsync | grep -v grep | awk '{print $2}'`
            while [ ! -z "$pid" ]; do
                    echo -n "."
                    for pid in $pid
                    do
                            kill -9 $pid
                    done
                    sleep 3
                    pid=`ps auxw | grep rsync | grep -v grep | awk '{print $2}'`
            done
    }
    
    kill_rsync
    doUmounts
    
    exit 0
    

    In the above sleep script, we first kill off any running rsync jobs using the kill_rsync function and unmount the network shares using the doUmounts function.

  2. Now that we have the wakeup and sleep scripts, our network shares will be automatically mounted and unmounted. We will now need a way to detect these network shares transitions and periodically launch our backup scripts. For this, we need a rsync backup script and a launchd hook to manage the automatic launch of this script.

    Let’s first take a look at the rsync script itself. In our example, the script is installed in /usr/local/.

  3. /usr/local/laptop_rsync.sh
    #!/bin/sh
    
    PROG=$0
    SRC="$HOME/Documents/"
    DST="/Volumes/Backups/$USER/"
    CMD="/usr/bin/rsync -q  --numeric-ids -E  -axzS --exclude-from=$HOME/.rsync_excludes.txt $SRC $DST"
    
    if [ ! -r $SRC ]; then
     /usr/bin/logger -t $PROG "Source $SRC not readable - Cannot start the sync process"
     exit;
    fi
    
    if [ ! -w $DST ]; then
     /usr/bin/logger -t $PROG "Destination $DST not writeable - Cannot start the sync process"
     exit;
    fi
    
    /usr/bin/logger -t $PROG "Start rsync"
    $CMD
    /usr/bin/logger -t $PROG "End rsync"
    
    exit 0
    

    In this script, we are using rsync to synchronize the Mac user’s Documents/ directory to a network share. We are also using a exclude list here called .ib_backup_excludes.txt to prevent any unwanted/personal files and directories from being synchronized to the network share.

    $HOME/.rsync_excludes.txt
    /tmp/*
    /Network/*
    /cores/*
    */.Trash
    /afs/*
    /automount/*
    /private/tmp/*
    /private/var/run/*
    /private/var/spool/postfix/*
    /private/var/vm/*
    /Previous Systems.localized
    .Spotlight-*/
    ._*
    Personal
    personal

    Most importantly, if we create a directory called Personal or personal within our Documents/ directory and place all of our personal files, they will not be synchronized to our network files share.

  4. Lastly, we need way to automatically run this rsync script (and not run it when the network shares have disappeared). Here’s where Apple’s launchd comes in. We will give control of the above laptop_rsync.sh script to launchd by creating a launchd agent file. From the launchd.plist man page, the agent files can be deployed in several locations, depending on whether this is done by a regular user or by an administrator. In our example, I am using the administrator location and installing the launchd agent file in /Library/LaunchAgents/.

    The rsync launchd agent file is /Library/LaunchAgents/name.rajeev.laptop_rsync.plist

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
            <key>KeepAlive</key>
            <dict>
                    <key>PathState</key>
                    <dict>
                            <key>/Volumes/Documents/</key>
                            <true/>
                    </dict>
            </dict>
            <key>Label</key>
            <string>name.rajeev.laptop_rsync</string>
            <key>Program</key>
            <string>/usr/local/laptop_rsync.sh</string>
            <key>StartInterval</key>
            <integer>3600</integer>
            <key>ThrottleInterval</key>
            <integer>3600</integer>
            <key>WorkingDirectory</key>
            <string>/usr/local/</string>
    </dict>
    </plist>
    

    In the above plist agent file, note that we are using the key PathState. launchd monitors for this path specified by this key to become available and launches the script once the path becomes available. In our example, once this path becomes available (via the wakeup script which is in turn launched by SleepWatcher), the rsync backup job is kicked off. Also note that we are using a ThrottleInterval to prevent launchd from starting the rsync script too fast.

    This agent file can itself be loaded upon restart by using the $HOME/.launchd.conf or /etc/launchd.conf. The command to load and unload this agent into launchd is below:

    • launchd load /Library/LaunchAgents/name.rajeev.laptop_rsync.plist. This will load this agent into launchd
    • launchd unload /Library/LaunchAgents/name.rajeev.laptop_rsync.plist. This will unload this agent into launchd
Posted Monday afternoon, September 1st, 2008

How on earth did I miss this? Anyway, hilarious ...
Posted Saturday afternoon, August 16th, 2008

It’s “hack some shit up” Friday. Everybody ready?

Have a Time Capsule or Mac OS X Server configured to provide network time machine backups? Of course you do! Let’s examine the client / server interaction.

The Time Machine preference pane provides a list of available backup volumes. This list includes local volumes as well as network volumes. Mac OS X makes extensive use of multicast dns for service discovery, so let’s check there first. Get yourself a copy of Bonjour Browser, then fire it up and look under ‘local’ for things that seem interesting. Ooo there’s one called “adisk”, let’s check in there…

From http://www.dns-sd.org/ServiceTypes.html we see:

adisk          Automatic Disk Discovery
                Bob Bradley <bradley at apple.com>
                Defined TXT keys: sys, dkX

We could read a bit more about service discovery via multicast dns to learn that:

DNS-SD uses DNS TXT records to store arbitrary name/value pairs
conveying additional information about the named service. Each
name/value pair is encoded as its own constituent string within the
DNS TXT record, in the form "name=value".
...
The intention of DNS-SD TXT records is to convey a small amount of
useful additional information about a service. Ideally it SHOULD NOT
be necessary for a client to retrieve this additional information
before it can usefully establish a connection to the service. For a
well-designed TCP-based application protocol, it should be possible,
knowing only the host name and port number, to open a connection
to that listening process, and then perform version- or feature-
negotiation to determine the capabilities of the service instance.
For example, when connecting to an AppleShare server over TCP, the
client enters into a protocol exchange with the server to determine
which version of the AppleShare protocol the server implements, and
which optional features or capabilities (if any) are available.

Ok, so there’s a record there, and along with the standard parts of the record, there are two additional key / value pairs in the text record portion. The meaning of the keys and values is probably only known for sure by Time Machine and friends, but certainly some of that data looks familiar.

First there’s the IPv4 address… that’s probably provided automatically when the record is published, based on the machine’s current IP address. IPv6 address follows, again we needn’t worry about that. The port number is listed at 9, which is ‘discard’, so this is probably not used. What port *is* used, then? Well, we know that Time Machine over the network supports either AFP or SMB, so it’s going to be one of those two. Seems like AFP would be most common, just since it is the APPLE file protocol and whatnot…

Then we have the sys and dk0 items… well I dunno what waMA means (something something mac address?), but what follows certainly looks like a MAC address… and sure enough, it matches the MAC address on my Time Capsule.

Note also that the second key in the shown record is dk0, whereas the spec says the defined TXT keys are “sys, dkX”. The “X” here suggests that there might be more than one of these keys, and they are numbered starting from 0 and counting up. Let’s pick appart the dk0 entry.

dk0=adVF=0xa1,adVN=backupz,adVU=AF9AC8F1-BCF5-3E63-9EBD-CD171CF5061B
  • adVF: no idea
  • adVN: hmm… another “adV” prefix. air disk volume? air disk volume name?
  • adVU: air disk volume UUID! (I think)

Ok, now let’s have some fun. Fire up file sharing. Click the little + button to create a new share point. Select a folder / volume that has some eh… free space on it :) The name of the folder / volume as it appears in the list of share points is the AFP share point name, so we should try to use that for ‘adVN’.

Next, use diskutil to find the UUID for the volume on which your new share point resides. The argument after “info” is the path to the volume. “/” for the boot volume, “/Volumes/whatever” for something other than the boot volume.

sudo diskutil info / | grep "UUID"

Finally, find your system’s MAC address (the one that corresponds to your primary network interface).

netstat -rn | grep default | awk '{print $6}' | xargs ifconfig | grep ether

Now we should have enough information to try to create a record. Looking at the man page for dns-sd, we see the basic usage for registering a record is:

dns-sd -R name type domain port [key=value ...]

Let’s try it! We still don’t know what adVF is, so we’ll just use 0xa1 like the Time Capsule does and see what happens. In the command below, replace 00:11:22:33:44:55 with your MAC address, “Backupz” with your new share point name, and “AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE” with the UUID of the volume on which your share point resides.

sudo dns-sd -R "Back it on up" "_adisk._tcp." "local" "9" \
        "sys=waMA=00:11:22:33:44:55" \
        "dk0=adVF=0xa1,adVN=Backupz,adVU=AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"

You should see something similar to

Registering Service Back it on up._adisk._tcp..local port 9 TXT ...

Leave that window open. Open a new terminal window.

dns-sd -B _adisk._tcp

You should see your service instance listed. cntrl-c. Get the details:

dns-sd -L "Back it on up" _adisk._tcp local

Looks good. Now go to another mac on your local network. Open the Time Machine prefpane. Click “Choose Backup Disk…”.

Cool!

Limited testing has shown that once you pick a network Time Machine disk in this fashion, the multicast dns service advertisement record is no longer needed. Apparently the Time Machine client caches the information about the location of the network disk. Also, probably a good idea to make sure that your network time machine volume is not guest-accessible…

Here’s a little script to ease the process…

#!/bin/sh
# Instructions:
# 1. Use the Sharing preference pane to configure an AFP share point that will
# be your network backup volume. Connect from another mac using Finder -->
# Connect To Server. Make sure you can log in. Note the name of the AFP volume.
# 2. Use diskutil info to get the UUID from the volume on which your AFP
# sharepoint resides.
# 3. Customize the values below.

# Customize here
servicename="Back it on up"		# the name shown in parens in the TM browser
macaddy="00:11:22:33:44:55"		# MAC address of the AFP server
volname="backupz"				# AFP sharepoint name
voluuid="6E132F57-8292-4A73-9B37-5C0F82928E85" # volume UUID 

# 4. Make sure this script is executable (chmod +x), then run it. You will be
# prompted for your password (must be admin).
# 5. Go to your other mac, use the Time Machine prefpane to select your shared
# AFP volume. The other mac needs to be on the same local network.
# 6. After selecting the volume, you can cntrl-c this script to stop it.
# 7. Enjoy your network time machine backups!

# no touchy
servicetype="_adisk._tcp."
domain="local"
port="9"
adVF="0xa1"			# magic?

# we need at least two key / value pairs, like such:
# sys=waMA=00:1F:5B:34:BC:41
# something something Mac Address, I guess...
# dk0=adVF=0x0a1,adVN=TM Test,adVU=AF9AC8F1-BCF5-3E63-9EBD-CD171CF5061B
# dkn iterates starting with dk0, then dk1... these are backup volumes
# adVF is ... something. I used trial and error to find that 0xa1 works
# adVN is volume name
# adVU is volume UUID, which you can get from diskutil info.

# dns-sd -R name type domain port [key=value ...]
sudo dns-sd -R "$servicename" "$servicetype" "$domain" "$port" \
		"sys=waMA=$macaddy" \
		"dk0=adVF=$adVF,adVN=$volname,adVU=$voluuid"

Have fun :)

Posted late Saturday morning, August 9th, 2008
Posted Saturday night, July 26th, 2008

It’s a helluva thing, a trial by jury. It was a radical notion 200 years ago, and it’s still a radical notion today.

I served on a jury once. Getting called for jury duty sucks, because most of the time you just sit in the jury lounge all day and then go home. But actually serving on a jury is a totally different thing. I think everyone should experience it at least once. In one sense, it’s just like you see on TV, except everything takes longer and you can’t go to the bathroom every 12 minutes. But it’s weird, if you’ve seen it on TV, because you realize that you already know the script. “Burden of proof,” “innocent until proven guilty,” “proof beyond a reasonable doubt.” The judge lays it all out for you, from scratch, even though most people have seen the scene and heard the speech and read the words a hundred times.

Everything is slanted towards the defense. Big stuff, little stuff, process stuff, everything. We learned later (during sentencing) that the defendant in our case had several prior convictions, but the DA wasn’t allowed to bring them up during the trial. Witnesses were always being cut off in mid-sentence, but the defendant was given a wide berth to tell his version of events. We didn’t even know exactly what the charge meant until the defense lawyer made his closing argument. That, in particular, was incredibly frustrating. I made all sorts of notes like “is this important? don’t know, check later.” The judge said it was to force us to listen to everyone and everything as fairly as possible. It was frustrating on purpose, but it worked.

And I remember thinking at the time, “This thing. Right here, this thing. This is what we say America is about.” Everyone is innocent until proven guilty, and everybody gets their day in court. We suspected that Hans Reiser killed his wife. What did we give him? A trial. We suspected that Timothy McVeigh blew up a building and killed 168 people. What did we give him? A trial. (BTW, this is why people get so upset over our mishandling of terror suspects. What could possibly be more un-American than saying that some people don’t deserve a fair trial?)

The case I served on wasn’t anything you’ll ever hear about, or read about, or see on TV. Just some neighbors who had ongoing petty feuds for years and years, until one day one of them went too far and made a real death threat. You’d say he “crossed the line.” I think the actual charge was called “commencement of threats,” which basically means he crossed one of the invisible lines that holds society together.

Some people spend their whole life right on the edge of being able to function in a civilized society. I knew a guy like that, growing up outside Philadelphia. His name was Eric. He always seemed to be in trouble with the law. Never anything serious, and not your standard crimes like robbery or drugs or weapons. Just… not quite understanding the boundaries between himself and everyone else. He could make friends quickly, but then he lost them just as quickly. He never had a steady girlfriend. He couldn’t maintain any sort of long-term relationship. I think an ex-girlfriend got a restraining order against him one time. And he’d get arrested for stuff like “criminal trespass” and “commencement of threats.”

Society is about drawing lines that everyone acknowledges and respects. Some people see the lines and cross them anyway and hope they don’t get caught. Eric didn’t even see the lines. They didn’t make any sense to him, so when he crossed them, he didn’t understand why he got in trouble. And you just wanted to smack him and say, “Just stay out of trouble, Eric! Just leave people alone.” And he did, most of the time. But “most of the time” is not “all of the time.” And it’s a crude word, but I think he was a little bit crazy. Not really crazy, like Hannibal Lecter crazy. He just… couldn’t see the lines. You don’t have to be crazy all the time, to be crazy.

Anyway, our case came and went. The wheels of justice grind slowly, but there’s only so much you can say about neighbors yelling at each other. I think the whole trial only lasted a day and a half, from jury selection to sentencing. In the end, we deliberated and found the defendant guilty beyond a reasonable doubt. The victim asked, and the judge agreed, and we the jurors were pleased to hear, that he should get a suspended sentence and a mental health assessment, with mandatory followup counseling. I’d guess he was offered deals and plea bargains, and I’d guess that everyone in his life begged him to take it. But he wanted his day in court, and he wanted a trial by a jury of his peers, and so that’s what we did. And he mounted a vigorous defense, and he was innocent right up until the moment we decided he was guilty.

I’m not going to get all puffed up about the glory of the system or the honor of performing my civic duty or whatever. The system is broken in a lot of ways, and there’s a reason they call it a duty — because it sounds a lot better in the abstract than it feels in the particular, slogging through downtown traffic and standing in line at the metal detectors and sitting quietly while people go on and on about their fucked up lives. But I also got a glimpse of a marvelous and precarious machine, built up and crusted over from two centuries of radical tradition, grinding ever so slowly forward. And it’s a helluva thing.

Posted Wednesday afternoon, July 23rd, 2008