Tuesday, March 25, 2008

Creating an iPhone AppTapp Repository.

I had a project where I needed to create a repository for AppTapp (Installer.app), the application used for over the air software installation on jailbroken iphones.

It's near impossible to find instructions on how to do this. If you google for them, all you get are "how to jailbreak your phone" and "how do I install applications" questions. Nothing about how to actually create a repository!

Thankfully, I eventually found the a wiki on ipodtouchfans with a detailed description.

The repository file is an XML document using Apple's plist format. Since it's XML it is both easy to pick up and nasty to use at the same time. The header portion of the plist file is self-explanatory, check out the wiki link. Here's the header from my test repository.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>info</key>
 <dict>
  <key>name</key>
  <string>Testing</string>
  <key>maintainer</key>
  <string>Jason Pollock</string>
  <key>contact</key>
  <string>jason@pollock.ca</string>
  <key>url</key>
  <string>http://www.pollock.ca/repos/baen</string>
  <key>category</key>
  <string>TestRepositories</string>
 </dict>
 <key>packages</key>
 <array>
        </array>
</dict>
</plist>

The individual package instructions go in the final array. However, this is a fully specified (if empty) repository. If you place it on a publicly accessible URL, people will be able to add it to Installer.app and track updates!

Next are the individual packages and installation scripts. The package themselves are separated into scripts and .zip archives. There is no provision to deliver software in any other format.

The install/uninstall scripts are part of the repository XML, not the .zip archive. This means that you can host the .zip files on a cheap, low cost volume server, while the xml document (since it is generally smaller) can be hosted closer to you. Web servers are more able to cache the repository document. Hopefully, with a single http request, the application is able to tell from the header response if the repository has changed, keeping transfers to a minimum. To keep from having someone change the .zip file on you, both a size and an md5 hash are provided in the plist file. Finally, the separation between script and installed files allows other people to provide fixed installers for your application!

The install/uninstall scripts are also XML, complete with if/then/else syntax. This is near impossible to either understand or get right due to all of the tag nesting, and chaining of siblings without explicit keyword enclosures. For example, let's look at an extremely simple "if" block to check the firmware version.

First, the logic in C, just to let you see what it should be doing:


   if (! FirmwareVersionIs("1.1.2")) {
       AbortOperation("Firmware 1.1.2 is required to update to 1.1.3.");
   }

Now for the insanely verbose XML code.


           <array>
             <string>IfNot</string>
             <array>
               <array>
                 <string>FirmwareVersionIs</string>
                 <array>
                   <string>1.1.2</string>
                 </array>
               </array>
             </array>
             <array>
               <array>
                 <string>AbortOperation</string>
                 <string>Firmware 1.1.2 is required to update to 1.1.3.</string>
               </array>
             </array>
           </array>

Completely impenetrable.

Installer.app works best when the .zip is already in the destination layout. Then the installation process is relatively simple. However, if not, each file must be individually copied into its target location, using the <CopyPath> command.


<array>
    <string>CopyPath</string>
    <string>iZoo.app/</string>
    <string>/Applications/iZoo.app</string>
</array>

There are several gotchas.

First, if you create directories, Installer.app creates them with no permissions what so ever, you will need to perform a chmod!

Second, the location of the Media tree was moved between 1.1.2 and 1.1.3. In current releases of the iPhone firmware, applications run as the user mobile. This means that the Media tree (where the music/etc is stored), is now in /var/mobile/Media instead of /var/root/Media. Since applications are installed on both 1.1.2 and later versions of the firmware, they need to work on both. To help with this, Installer.app has added support for "~", to refer to the home directory for the user. Again, however, this depends on a specific version of Installer.app being present. Personally, I chose to ignore all of this, since the application I was using didn't know about /var/mobile/Media anyways. Still, it is something that developers will need to figure out.

Third the syntax in the scripts is nasty. If you find yourself writing a fair bit of it, write a tool to do it for you. I'm sure it is possible to come up with a more human readable syntax that will generate the XML for you.

Finally, debugging your packages is painful and slow. However, that's for the next post.

Other than that, there's nothing too difficult about the whole thing. If the zip file you are trying to install is already in the target layout, it's dead simple.

Enjoy!

1 comment:

Anonymous said...

Thank you for the information, I couldn't believe how limited the information on creating Installer repositories was. I followed the link to the ipodtouchfans wiki on the subject. But I'm left with a question, how is it that I would go about chmodding the directories created by CopyPath? I'm new at this so I'm lost in the dark your help would be much appreciated. You can contact me at ace1and11@yahoo.com