I’ve been using zsh for a while as my preferred shell. I have a hacked-together zshrc file, and yet really wanted to use it across multiple systems. Some of those systems are running Mac OS X, others Solaris, and still others Linux. Executables are in a different locations and even have different switches across this range of systems, so my cobbled zshrc was not helping me.
As I was about to fall asleep last night, it finally hit me that fixing my zshrc would be a good thing to do. I jotted down some notes about an idea to reorganize it, and did something about it today.
Of course, since I’ve checked my zshrc and other dotfiles into a Mercurial repository, I could experiment without fear.
I created three top-level functions, with one “case” statement in each. Case statements may be evil in some fashion, but they are one of the things I like about shell scripting. These statements do allow the script to make choices based on the host, operating system, or shell that it was running in. (Yeah, it’s a zshrc, but I sometimes do stupid things — like sourcing it in bash on the one Linux system that won’t let me switch to zsh. Site5, I’m looking at you.)
I separated all the important sections of my zshrc into their own individual function calls. Each of those function calls was placed into one of the applicable case statements.
The case statement functions figure out the conditions the zshrc is running in, and then run the other functions to set up my environment.
The changes tested well from first try across the various platforms and hosts I log in to. I did have a minor problem with the `hostname` command, because Solaris doesn’t have a “-s” flag for it. Eventually, I solved that — and the odd “uname: error in setting name: Not owner” error I got, even though I wasn’t directly running `uname` there — by replacing `hostname` with `uname`.
Thankfully, it works for me, and it should be a little easier to manage changes in the future.
The Lullabot Web site has a clever way to help answer the question of “Is that site running Drupal?” Angie Byron mentions that the HTTP “Expires” header returned by Drupal corresponds to a specific default date. Look for that date in the HTTP headers, and you can make a reasonable guess that a site is a Drupal site — or at least one that hasn’t modified one of the core files.
Some commenters posted notes about how to do the same thing with wget and then curl. I expanded on the curl instructions to make them a little more robust (especially in the case of redirects or URL rewriting), and here’s the result:
$ curl -fsIL http://jaharmi.com/ 2>&1 | grep -q -m 1 "Expires: Sun, 19 Nov 1978 05:00:00 GMT" && echo "Yes, this appears to be a Drupal site." || echo "No, this does not appear to be a Drupal site."
Yes, this appears to be a Drupal site.
Drat! I’ve learned that the commands module for Python, which I use, is deprecated and removed in Python according to PEP 3108. That means I can no longer safely call commands.getstatusoutput() anymore. I’ve frequently used this call in the past because it seemed the sanest, easiest way to call for a shell utility and get both its output and return status (for success or error).
I’ll have to find some other way to perform the same function — preferably one that will work on Python 2.3 from Mac OS X Tiger, Python 2.5.1 from Leopard, and future Pythons. The stated replacement for a number of similar modules (including popen2, which frankly kind-of frightened me off with its name) is the subprocess module from PEP 324, but I don’t know if that will work for my purposes.
There are also a bunch of Mac-specific modules being removed. I don’t use any of them right now, but that doesn’t mean they wouldn’t have been useful.
This kind of thing is spirit-crushing to me for some reason. I’m especially annoyed that Python has been around for so long and it is still reorganizing the ways it calls shell commands. Just settle on something! It seems hard to take it seriously as a system administration scripting language when things like this happen.
On the other hand, I love so much of the Python Standard Library, which has afforded me a lot for system administration …
Under normal circumstances, the latest Radmind tools that communicate with the server report client status updates in the Radmind server’s system log. These standard messages can include ones like:
May 8 03:14:56 RadmindServerHost radmind[7890]: report radmind-client.example.com 192.168.7.42 - - ktcheck No updates needed
May 15 03:15:25 RadmindServerHost radmind[24531]: report radmind-client.example.com 192.168.7.42 - - ktcheck Updates retrieved
May 15 03:21:48 RadmindServerHost radmind[24534]: report radmind-client.example.com 192.168.7.42 - - lapply Changes applied successfully
May 15 03:31:07 RadmindServerHost radmind[24356]: report radmind-client.example.com 192.168.7.42 CertificateCN - lapply Error, changes made
The Radmind repo, or “report,” tool provides the ability to send arbitrary messages to the Radmind server process. But how are these messages formatted and sent?
$ repo -e "Debug" -h radmindserverhost.example.com -w2 "Test message"
… results in the system log message:
May 15 03:31:56 RadmindServerHost radmind[25236]: report radmind-client.example.com 192.168.7.42 CertificateCN - Debug Test message
Here, we can see that an entry created with repo looks like the standard Radmind log messages above. The client hostname and IP address are reported after the “report” text. The CertificateCN for the client — if the highest authorization level is specified (with the -w2 flag) — is also listed; if not, a dash takes its place. I haven’t seen a case where the second dash is substituted, however.
Finally, where the Radmind command/tool used would normally be, the “event” specified by repo will printed. After that, the message text appears.
The value proposition is that if you’re using Radmind, the repo command can help you send arbitrary messages to the server for logging. As bonus, if you’ve taken the time and effort to build the certificate infrastructure for Radmind, you can send these messages securely between the clients and the server cloaked in SSL.
If you’re using multiple servers, you may want to combine their logs in one location so that you can get all of the clients’ reports in one location. You may also want or need to retain these reports for more time. In either case, determine what policies you should apply to the syslog or Apple System Logger (ASL, for Mac OS X) configuration for your server systems.
Whether or not you use repo, it’s good to know that the tools do some logging. The logging can be followed to try to determine the status of your clients, or whether they are failing their updates.
Unfortunately, the most common client failures I have seen tend to involve the lapply tool, and the default level of detail I’ve seen reported back to the server does not provide an indication of what problem has been encountered. You see only that there was an error. Still, even though you may not get enough detail to remotely resolve the problem, it’s something for you to go by find problems in the first place.
The Distutils Python module includes functions to obtain information about the Python installation. This may be useful for system administrators, and it certainly caught my eye when I read about it.
The results below are from Apple’s bundled build of Python 2.5.1 in Mac OS X Leopard. Credit for the comments describing each function comes from the distutils.sysconfig documentation.
>>> import distutils.sysconfig
>>> distutils.sysconfig.get_python_version() # Get the major Python version without patchlevel
'2.5'
>>> distutils.sysconfig.get_python_lib(standard_lib=True) # Return the directory containing the Python library; if 'standard_lib' is true, return the directory containing standard Python library modules
'/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5'
>>> distutils.sysconfig.get_python_lib() # Otherwise, return the directory for site-specific modules
'/Library/Python/2.5/site-packages'
>>> distutils.sysconfig.get_python_lib(plat_specific=True) # Return any platform-specific modules from a non-pure-Python module distribution
'/Library/Python/2.5/site-packages'
You’d install your own modules for system-wide use in the directory returned by distutils.sysconfig.get_python_lib().
When I come across software I might need to add into Mac OS X that requires compilation, I typically want to produce one Universal Binary. Make it a four-way UB and you get both 32- and 64-bit support.
A single binary is ideal for a Radmind transcript (or other package, if you wanted to bundle it into an installer) that can be deployed on both PowerPC and Intel Macs on Leopard.
Since rsync 3.0.2 with some patches is apparently working quite well at preserving Mac OS X data and metadata — passing the Backup Bouncer tests — I thought I’d try my hand at a four-way Universal Binary.
What worked for me, using a Mac Pro 4x2.8 GHz with Mac OS X 10.5.2 and Xcode 3.0, was to start with Mike Bombich’s instructions and modify them with some fairly standard Universal Binary build steps. The configure and compile were both less than a minute on this system.
$ ./prepare-source
$ env CFLAGS="-O -g -isysroot /Developer/SDKs/MacOSX10.5.sdk -arch i386 -arch ppc -arch ppc64 -arch x86_64" \
LDFLAGS="-arch i386 -arch ppc -arch ppc64 -arch x86_64" \
./configure --prefix=/usr/local --disable-dependency-tracking
$ make
$ sudo make install
I have seen the use of “-Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk,” in the LDFLAGS environment variable when compiling some applications but this did not work for me with rsync; when I removed it, rsync 3.0.2 configured successfully for me.
The result of the above build process appears to be a full four-way UB:
$ lipo -info /usr/local/bin/rsync
Architectures in the fat file: /usr/local/bin/rsync are: i386 ppc7400 ppc64 x86_64
A local transfer on the build system appears to have worked correctly. I did not test with Backup Bouncer, sync with a non-Mac system, or when shuttling data between architectures. So, accept these results with a grain of salt; I’m just happy I got rsync to compile for now.
Here’s an example of how you can use the DirectoryService dscl utility to find which local user accounts have UIDs greater than 100. These users are by convention most likely to be non-system accounts, and therefore of interest for some tasks in a tool like Applejack. (I pulled this example from some comments in the Applejack 1.4.3 source.)
$ dscl /Local/Default -list /Users UniqueID | awk '$2 >= 100 { print $1; }'
supersecretadminaccount
christen
jeremy
elijah
demoguy
What’s going on in this example?
I’m getting a list of users from the default local DirectoryService node, whose path is /Local/Default. The output of this step gives you all of the local user accounts’ short usernames, followed by their UIDs, because that’s the property I was requesting. The data is arranged in two columns.
A drop into awk can make quick work of processing this columnar data, so I pipe the output of the first command in. Taking the output of dscl, I wanted to find whether the number in the second column was greater than or equal to 100, so I compare $2 to the desired number. When the UID column’s data matches, I print out only the short username from the first column, $1.
This gives you a list of usernames whose UIDs are greater than or equal to 100. The output is one per line, which is what you want if you have further processing steps.
“UniqueID” is the DirectoryService record type for UID. You could also use “uid” instead, and in this instance I’ve found that it works equally well.
$ dscl /Local/Default -list /Users uid | awk '$2 >= 100 { print $1; }'
supersecretadminaccount
christen
jeremy
elijah
demoguy
Substituting “uid” for “UniqueID” may not always work. It probably will in most cases, since the output is formatted similarly, but it depends on what subsequent processing steps are expecting. If those steps are splitting the columns/fields by whitespace, they should be fine either way.
$ dscl /Local/Default -read /Users/jeremy uid
dsAttrTypeNative:uid: 503
$ dscl /Local/Default -read /Users/jeremy UniqueID
UniqueID: 503
Another point of interest would be accounts greater than 500, as the Mac OS X Setup Assistant and Accounts System Preferences pane create local accounts starting with UIDs 501 by convention. Whether you choose greater than or greater than or equal to 500 is up to you. I’ve come across some situations where ID 500 gets used (somewhat unexpectedly), so you may want to find such UIDs or GIDs if it suits your purposes.
The difference between those accounts whose UIDs are greater than or equal to 100 and those greater than or equal to 500 would yield a list non-system accounts that are hidden by default from the login window or Accounts System Preferences pane. While you could generate two lists and compare with sort and uniq, you could just add to your awk statement.
$ dscl /Local/Default -list /Users UniqueID | awk '$2 >= 100 && $2 < 500 { print $1; }'
On most Mac OS X systems (as of this writing at least), there will be no accounts listed from the above statement. So, you’ll just get your command prompt back.
This should work in Tiger and Leopard, since dscl first became available in Tiger and replaced the NetInfo nicl utility entirely in Leopard.
A few posts on the MacEnterprise mailing list have reported that Leopard does support authenticated printing with Active Directory. This is a good step forward for Mac OS X. However, it’s unclear if it is using Kerberos to do so.
Leopard includes CUPS 1.3.3. I don’t exactly know how to tell this from the command line. There’s no cups -V at all, as the main executable is cupsd. But, there’s no cupsd -V, either. So, I resorted to the CUPS Web administration page, which is found at http://localhost:631/ on any modern Mac.
The What’s new in CUPS page — which as of this writing documents version 1.3 — says that Kerberos is now supported. So it’s reasonable to guess that Kerberos could be in use on Leopard for this type of authenticated printing.
So, I took a moment to ask on the Apple Printing mailing list, and got immediate results. Right away, Michael Sweet posted that no, by default it doesn’t … but it can be activated with the “Negotiate” option in cupsd.conf. There is one caveat: it reportedly doesn’t work with Windows Server 2003R2, however. You need CUPS 1.3.4 for that.
I found out that CUPS 1.3.3 in Leopard can potentially be replaced with version 1.3.4 at your own risk. You should only do that if you are comfortable with compiling applications, if you absolutely need to make Kerberized authenticated printing work with Windows Server 2003R2, and you are willing to test the changes before you deploy it to more than your non-production test computer. Otherwise, the following risk is not worth it. However, if you still feel the need to try CUPS 1.3.4 despite these warnings:
$ ./configure --with-archflags="-arch i386 -arch ppc -arch x86_64 -arch ppc64"$ makeThe new libcups.2.dylib could then be copied to other computers if your testing with it is successful and it fixes the problem with authenticated printing through Windows Server 2003R2. You’re on your own if you try any of this; I’m not suggesting you do it, and can’t help you if you try. It’s unsupported and YMMV.
(By the way, replacing one key file like this is a really great opportunity to use a Radmind overload, if you’re into that sort of thing.)
Wow, Apple bought the Common UNIX Printing System (CUPS) back in February, and the announcement has just come out. (I have to wonder why the delay … perhaps it has something to do with Leopard?) The software continues to be licensed under its regular terms.
Michael Sweet, one of the principals behind Easy Software Products and developer of CUPS, is now an Apple employee.
I’m sure this all means something.
I’ll admit it: I’m a bit slow when it comes to the shell. I use it a lot, but never feel like I’m using it as well as I could. But today, I figured out how to turn on Z Shell completion system. And it is very, very good.
This guide helped me, leading me to add the following to my .zshrc file:
autoload -U compinit
compinit
Once I’d done that, I could begin completing various commands and parameters. Within a few moments, using the tutorial above, I’d already completed:
This is cool.