In this document, I will describe how to implement Reposado to provide an Apple software update service. Additionally, our software update server will be completely transparent to the end user – both via DNS and their OS versions.
There are a few documents describing a “transparent software update service” but always seem to miss a few key points. Hopefully this guide will address that. I’ve been using Reposado on a network of ~2200 Macs with OS versions ranging from Tiger (10.4) to Lion (10.7).
Reposado (https://github.com/wdas/reposado) is an open source project by some guys from Walt Disney. As far as public usage goes, it’s still in its infancy, but has gained a lot of respect in such a short amount of time. The Apple Software Update service included with Mac OS X Server is notoriously temperamental and unpredictable.
Reposado is a replacement for Apple’s default software. They will not co-exist.
The full package I’m using here to provide a complete software update service is: Apache, Reposado, and BIND.
Table of Contents
Configure DNS
The prerequisite to this is to setup DNS to point internal traffic to the internal software update server when they request the Apple software update server.
This is the biggest part of a “completely transparent” service inside your network. Using DNS, you do not have to make any modifications to the client machines. The clients will lookup “swscan.apple.com” within your network and it will resolve to your local software update server. When off the network and not using your DNS servers, they’ll resolve the correct public address.
Additionally, you need to ensure your DNS server will let your software update server resolve the public address at all times, otherwise, it will be checking itself for updates and never receive any! In BIND, use views to accomplish this. Here’s an example of such a BIND view:
In /etc/bind/named.conf.local I have something like this to define a view for host that should use the public Apple software update server (notice the match-clients declaration and the acl at the bottom:
// #################################################################### // # Software Update service // #################################################################### // This view *only* matches the software update servers, listed below via an ACL view "swupd" { match-clients { swupdatephonehome; }; recursion yes; include "/etc/bind/zones.rfc1918"; include "/etc/bind/named.conf.default-zones"; zone "organization.internal." { type master; notify yes; file "/var/cache/bind/internal/db.organization.internal"; allow-update { key dhcpupdate; }; allow-transfer { slaves; }; }; zone "30.172.in-addr.arpa" { type master; notify yes; file "/var/cache/bind/internal/db.172.30"; allow-update { key dhcpupdate; }; allow-transfer { slaves; }; }; }; // swupd view // List the hosts that should use the public Apple swupdate service here acl "swupdatephonehome" { 172.30.192.120; // your software update server(s) }; |
Then, I have the normal view for the rest of the network. Notice the match-clients declaration here says “all internals except for those listed in the swupdatephonehome group”.
// #################################################################### // # Local zone (organization.internal) // #################################################################### view "organization.internal" { match-clients { internals;!swupdatephonehome; }; recursion yes; include "/etc/bind/zones.rfc1918"; include "/etc/bind/named.conf.default-zones"; zone "swscan.apple.com." { type master; notify yes; file "/var/cache/bind/custom/db.swscan.apple.com"; allow-update { trusted; }; allow-transfer { slaves; }; }; zone "swquery.apple.com." { type master; notify yes; file "/var/cache/bind/custom/db.swscan.apple.com"; allow-update { trusted; }; allow-transfer { slaves; }; }; zone "swcdn.apple.com." { type master; notify yes; file "/var/cache/bind/custom/db.swscan.apple.com"; allow-update { trusted; }; allow-transfer { slaves; }; }; .............. snip .............. }; |
Setup Reposado
Reposado is currently command-line only. The best way to install it is by cloning the git repo.
Create the directories to serve the updates from:
mkdir -p /var/www/swupdate/html mkdir /var/www/swupdate/metadata |
Follow the “getting started” guide at https://github.com/wdas/reposado/tree/master/docs to get it installed.
You’ll get asked three questions during the process (read the guide there). My examples here all assume the following:
Path to store replicated catalogs and updates: /var/www/swupdate/html
Path to store Reposado metadata: /var/www/swupdate/metadata
Base URL for your local Software Update Service http://swupdates.internal/
Important: The configuration below shows that I’ve configured reposado to store its data to /var/www/swupdate
After that, you’ll need to configure Apache on the server. Keep in mind – reposado is cross-platform. You don’t have to be installing it on a Mac server.
Configuring Apache
The software update service is all done by using a web server. Apple uses Apache by default and so will we.
If you’ve previously used Apple’s software update service, it will default to port 8088. If this is the case, you’ve likely already pointed your clients to the internal server on that port. To avoid touching clients again and to ensure backwards-compatibility, I have Apache listening on both ports 80 and 8088 (the default). If you were running the service on a different server than you will be now, you can use some port fowarding trickery to get this to work (if it was on a Mac, you can use the firewall (ipfw) to forward the port to the new server.
On the server, go to the directory /etc/apache2/sites/
You may see several files in there. Open, or create, the file 0000_swupdates.conf and place the following in it:
<VirtualHost *:80> ServerName swupdates:80 ServerAdmin you@organization.net DocumentRoot "/var/www/swupdate/html" DirectoryIndex index.html index.php CustomLog "/var/log/apache2/swupdate_access_log" "%h %l %u %t "%r" %>s %b" ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var <IfModule mod_mem_cache.c> CacheEnable mem / MCacheSize 4096 </IfModule> LogLevel warn <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP_USER_AGENT} Darwin/9 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-leopard.merged-1.sucatalog RewriteRule ^/index-leopard.merged-1.sucatalog /content/catalogs/others/index-leopard.merged-1.sucatalog RewriteCond %{HTTP_USER_AGENT} Darwin/10 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog RewriteRule ^/index-leopard-snowleopard.merged-1.sucatalog$ /content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog RewriteCond %{HTTP_USER_AGENT} Darwin/11 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog RewriteRule ^/index-lion-snowleopard-leopard.merged-1.sucatalog$ /content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog RewriteCond %{HTTP_USER_AGENT} Darwin/12 RewriteRule ^/index\.sucatalog$ /content/catalogs/others/index-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog </IfModule> </VirtualHost> |
After that, we’ll make one to listen on port 8088. Create a new file named 0001_swupdate_8088.conf and place the following in it:
<VirtualHost *:8088> ServerName default ServerAdmin you@organization.net DocumentRoot "/var/www/swupdate/html" DirectoryIndex index.html index.php CustomLog "/var/log/apache2/swupdate_access_log" "%h %l %u %t "%r" %>s %b" ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var Loglevel warn <IfModule mod_mem_cache.c> CacheEnable mem / MCacheSize 4096 </IfModule> <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP_USER_AGENT} Darwin/9 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-leopard.merged-1.sucatalog RewriteRule ^/index-leopard.merged-1.sucatalog /content/catalogs/others/index-leopard.merged-1.sucatalog RewriteCond %{HTTP_USER_AGENT} Darwin/10 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog RewriteRule ^/index-leopard-snowleopard.merged-1.sucatalog$ /content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog RewriteCond %{HTTP_USER_AGENT} Darwin/11 RewriteRule ^/index.sucatalog$ /content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog RewriteRule ^/index-lion-snowleopard-leopard.merged-1.sucatalog$ /content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog </IfModule> </VirtualHost> |
The important stuff in both of those configs is the Rewrite rules. This makes everything transparent. Basically, a client will just connect to http://swupdates.internal/ and not request a particular catalog – the rewrite rules will figure out which one to provide based on the version of the client (the HTTP_USER_AGENT Darwin/x rules do that). Then, they’ll point them to the right catalog. The above configurations support Tiger thru Lion.
This is it for the Apache configuration. Do a restart of Apache just to make sure things are happy.
Test
Now we want to test it. Since it’s a web server, first connect using a browser to http://swupdate.internal/ (substitute for the server’s name).
You’ll probably get a 403 Forbidden HTTP error. If so, that’s good. If it times out, make sure apache is running and that you didn’t goof up the configs.
Now test to see if you can retrieve a catalog:
http://swupdates.internal/content/catalogs/others/index-leopard.merged-1.sucatalog
That URL should pull down an XML formatted file listing.
Now test it on a client by running software update. If DNS isn’t setup or if you just want to test, you can specify the software update server to use:
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate CatalogURL 'http://swupdates.internal/' |
If your rewrites are good, that should automagically pull the correct catalog for the system version you’re running on.
If you get an error, try specifying the full address for the catalog:
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate CatalogURL 'http://swupdates.internal/content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog' |
(That’s for Snow Leopard).
If specifying the full address works but the short one doesn’t, then you need to check the rewrite rules. The Apache logs are helpful here (/var/log/apache2/swupdate_access.log)
If you don’t get the rewrites working, you won’t have a completely transparent software update server.
Schedule
You’ll want to schedule reposado to sync on a regular basis. One day should be good in most cases. I’ll provide an example of twice a day, however.
For a Mac OS X Server
We’ll use a LaunchDaemon for this.
Create a new file on the server in /Library/LaunchDaemons/org.reposado.reposado.plist and paste the following:
<?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>Label</key> <string>org.reposado.reposado</string> <key>ProgramArguments</key> <array> <string>/usr/bin/python</string> <string>/usr/local/reposado/code/repo_sync</string> </array> <key>ServiceDescription</key> <string>Reposado Apple Software Update Service</string> <key>StartCalendarInterval</key> <dict> <!-- Run at 07:10am daily --> <key>Hour</key> <integer>07</integer> <key>Minute</key> <integer>10</integer> <!-- Run at 12:00 noon daily --> <key>Hour</key> <integer>12</integer> <key>Minute</key> <integer>00</integer> </dict> </dict> </plist> |
Then load it into the Launch Daemon:
sudo launchctl load /Library/LaunchDaemons/org.reposado.reposado.plist |
For a non-Mac server (Linux)
We’ll just use a cronjob:
0 6,23 * * * /usr/local/reposado/code/repo_sync |
Conclusion
At this point, you should have a completely transparent software update server running. Clients should connect to it inside the district and connect to Apple when they’re outside of our network, all without having to configure a single thing on the client machine.
I realize the above instructions might look like a jumbled, confusing mess, but there’s basically just three components to this:
- Apache
- Reposado
- DNS (BIND)
Listening on 80 and 8088. Have ReWrite rules in place for the catalogs.
Obviously, this is what grabs the updates and catalogs them on your server.
To make internal clients resolve the Apple servers to your local server’s address, and to allow your server to resolve to Apple’s official update server. DNS trickery is optional if you don’t mind specifying the address to your server on each client.
As always, feel free to contact me for further assistance.
Download Reposado Apple Software Update Server as PDF

(+10 rating, 10 votes)
Pingback: Software Update Server Guide | Cocoanetics
Pingback: Mac Software Update Server, ON LINUX! « simonhayre