I’ve had occasion to create some installer packages lately, and the topic of assigning a version number to them has rattled around in my head. The version number of an installer package is shown in the Finder’s Get Info windows, and in the preview pane of column view, so it has some usefulness just for telling one installer apart from another. And, importantly, the version number provides a way for Installer to determine whether a package has previously been installed and simply needs to be upgraded.
At first, I was just going to assign the date in YYYYMMDD format as the version, just to “keep things simple” by using:
Then I was rebuilding the packages several times a day, and wanted to distinguish between the builds, so I switched to YYYYMMDD.hhmm.
This seemed ridiculous after a day. Although I could distinguish between builds, the installers I was building weren’t really changing per se. They were the same payload and scripts and resources, just rebuilt by the command line packagemaker tool using The Luggage.
I crowdsourced my question about versioning, and Steve Losh helpfully responded with the idea to use Mercurial tags along with Semantic Versioning.
My package sources are already checked into Mercurial repositories, so this made a good deal of sense. It meant that I’d have to use tags to identify a version number — it would no longer be as automatic as using the current date. However, certain parts of the version string would be automatic, and the only manual part would be tagging revisions in Hg.
I had already bookmarked Semantic Versioning a while back, but had forgotten about it and never done much of anything with it. If I followed the Semantic Versioning standard, I’d create a “semver” tag to set up the repository, and then a new “vX.Y.Z” tag for actual releases thereafter. Steve further suggested the use of the “latest tag distance” and “short node hash” for the Hg revision, and that information is automatically available from Hg.
Semantic Versioning states, “when tagging releases in a version control system, the tag for a version MUST be ’vX.Y.Z’ e.g. ’v3.1.0’.” I felt I would have to strip the “v” from the version tag to use it cleanly as a package version. This results in commands like the following, where I’ve used sed to remove that character:
This is something I can build into makefiles for use with The Luggage, so I’m going to try it out. On first glance, I think it even fits in nicely with the concept of The Luggage. It automates much of the version-assignment process. And, it takes advantage of version control for both repeatable results and peer review.
I had interesting things happen when I ran out of disk space today.
The most notable one was that I saw “Process completed” — or some variant of that — every time I tried to open a Terminal window with a new shell session. I briefly staved it off by specifying Bash as my shell, but then it came right back after opening another tab or two in Terminal. Consulting Google led me to this “Terminal application quits” thread at Apple Discussions. On a lark, I tried deleting /usr/bin/login as one poster suggested. It worked!
…But only for a little while. The problem returned. In the meantime, I had freed up some disk space because I’d realized I couldn’t save files anywhere (“But ~/Pictures is writable!”). Clearly something else had become an issue, because disk space was available.
Then I found another thread, “Terminal’s ’Process Completed’ message and /usr/bin/login,” on Apple Discussions. The more permanent solution from that thread appears to be the removal of corrupted Apple System Log databases. Once I did that and restarted the ASL service, all was well and has stayed that way so far.
Filling up my disk must have corrupted the logs as they were being written or rotated, and led to this cascade failure. Like I said, interesting!
One of the posters in the second Apple Discussions thread indicated that the underlying database corruption issue is addressed in Snow Leopard. However, it seems that you could still see this on Leopard — my experience was with a recently-patched Mac OS X 10.5.8 system.
The default installation of Python on Mac OS X Snow Leopard is version 2.6.1. According to the man page for Python on Snow Leopard, Python 2.6 executes as a 64-bit application by default.
If, for some reason, you need to run it as a 32-bit application, this can be changed at the command line:
The preference can be set in either the User or Local filesystem domain in Mac OS X, following the normal precedence rules. To unset it, presumably you would change the boolean to “no” — or perhaps even delete the “Prefer-32-Bit” key.
There is also an environment variable that can override this preference.
After using Acquia Drupal for a while, I took advantage of a trial subscription to the Acquia Network. The network’s services showed me that I had files present in my install that the agent could not account for.
I suspected this was happening because of the way I manage my Acquia Drupal installation with Mercurial. So, I’ve modified my previous process (and updated my instructions) to extract the downloaded tar archive with the --recursive-unlink option. This option appears to successfully remove the contents of every directory before putting new files back into them.
When the archive is extracted in this way, my repository’s working directory shows modified, unknown, and deleted files. This allows me to treat each category of files individually before I commit the changes for a Drupal update as a revision.
$ hg status
The modified files will be tracked normally because they’ve already been added to the Mercurial repository, so I don’t need to do anything special for them.
The unknown files are ones that are completely new, and have not appeared in the same position in a previous revision. They have yet to be tracked by Mercurial, so I have to add them to the repository. To add just those unknown files, then, I have to pick them out from the status listing:
$ hg status --unknown
In order to operate just on those files to add them to the repository, I run a for loop:
This changes the “?” status to “A,” because the files were successfully being tracked by Mercurial.
I use the “--no-status” flag on the “status” command so that just the file paths are printed; the actual status code is not, which is appropriate for the target of the “add” command in the loop.
I do the same basic steps with deleted files. These are files that were in the previous revisions but have been deleted by the --recursive-unlink option from the tar extraction and not replaced with the extraction of the new Acquia Drupal tar archive. If the deleted files had been replaced by the tar extraction, they would either be unchanged (which would not show up in the “status” output) or marked as modified.
To remove the files that are marked as deleted from the repository’s working directory:
However, that may be the same as simply using the following, which I have to explore further:
$ hg remove --after
So, to follow all of these changes in the repository, I run the loop for the uknown files and the loop for the deleted files. The modified files are already tracked, so I don’t need to do anything additional for them. After that, a “commit” will record all of the changes — modifications, additions, and deletions — in the repo.
These commands are based on my current understanding of Mercurial, and they do work for me right now. There could certainly be another better way to do this in one fell swoop — or at least fewer steps. I would welcome that, so if you’re aware of a way, feel free to comment or contact me.
Update: I found that the “hg addremove” command cleanly replaces all of the shell loops I mentioned above. Therefore, I recommend using it instead of the “for” loops I described.
It should come as no surprise that Apple Installer installation packages can contain scripts. These scripts are supposed to conduct important operations during the course of the software installation.
However, when you are the system administrator of more than one Mac, you find that developers sometimes miss a good balance between what you think should be in the installer payload versus what should be in its scripts. The payload of a installer, by definition, are the files and links that should be installed, along with information on where they should be installed as well as how (i.e. ownership, permissions).
Therefore, developers should not need to run scripts that create or delete files — they should be created from the payload itself, and if a file must be deleted during the install then consider that perhaps you’re doing it wrong. Likewise, there should be little need move or copy files, because as many copies as desired can be installed from the paylod. Similarly, the need to change ownership or modify permissions should be taken care of in the payload.
Perhaps I’m being a purist here. I’m certainly accused of that, from time to time. However, this just makes sense to me and I happen to think that many developers are similarly logical people. They just aren’t the kind of logical people who happen to spend effort on software installation, especially the kind that results in a deployment-friendly installer package.
So how do we as administrators verify the quality of the scripts in installers? Is there a way we can quickly peer into them to decide if any of the scripts’ steps will be superfluous or even (gasp!) harmful?
Well, I have a quick suggestion for scanning packaged installers. The following one-liner shell command will search an installer package or metapackage for scripts that have the kinds of steps outlined above.
Note that this will only work for the traditional installer packages; it will not work with Leopard-style flat packages (which are documented so badly by Apple that the best description comes from reverse engineering by Iceberg's author). The one-liner will currently only find the defined install operations scripts: preflight, preinstall, preupgrade, postinstall, postupgrade, and postflight. (Any other scripts are likely to be called by one of those six.) It assumes those scripts will be shell scripts, currently, even though any of them could be written in other scripting languages installed with Mac OS X, like Python, Perl, or Ruby. It will also not work on the JavaScript-based system and volume requirements portions of the installation.
However, it’s a start. The output displays the offending file and line number, so you can conduct more careful examination of the matches it finds.
I haven’t run this on an exhaustive list of installation packages, but I have already seen at least one installer that produces worrisome output.
Update: I’ve changed the regex for the pre/postflight script so that it is more general that what I originally posted. I’m also having some problems with the snippet working with a certain installer whose scripts I know have cp and chmod commands. So, I may be back to the drawing board with this; comments are welcome.
In this MacEnterprise list thread about printing authentication, Greg Neagle mentions that:
Under Leopard, all local users are members of lpadmin, but I think network users are not. So this method won't grant network users CUPS rights.
To confirm Greg's suspicions, I ran the following shell snippet.
This loops through the fictional accounts, "mobile_account_user," "network_account_user," and "local_account_user." These accounts are, as you might expect, as a locally-cached mobile account from a network directory, a wholly network directory-based account, and a simple local admin account. While the accounts presented here are fictional, the results were confirmed on a live system bound to a directory service.
The rest of the snippet determines if the accounts are members of any of the specified computational groups that debuted in Leopard. The last group checked is the "lpadmin" group. By looking at these group memberships, we can determine whether Leopard thinks that the account being tested is a local or network account.
Running the snippet above, with the right accounts available, produces the following output:
So, it appears mobile and local users get added to the lpadmin group automatically in Leopard, but network accounts do not.
Note that I didn’t check whether membership in the “admin” group made a difference or not. I also didn’t isolate for that factor.
I found it interesting that the mobile account is a member of “netaccounts” but not “localaccounts.” (By group membership alone, I’m not sure you could identify whether an account was a mobile account or not. Yet, that kind of test is part of the point of having these computational groups in the first place.)
Here is a sequence of commands and output that show how I keep the Acquia Drupal open source content management system up to date with Mercurial, the open source distributed version control system.
In the example below, my Mercurial repositories for Drupal are located in the “drupal” subdirectory of my “repo” folder. Once I’ve moved into that directory, I download the Acquia Drupal distribution with curl and then extract it into my previously-created Mercurial working directory, “acquia_drupal,” using tar.
(Update: I added the --recursive-unlink option after I noticed that the Acquia Network control panel keeps track of extra — possibly unneeded — files and folders you have in your install. The recursive unlink option seems to avoid having stray files from old versions of modules hanging around in your repository after you install updates.)
After extracting Acquia Drupal my Mercurial working directory, I get the status of the repository. It shows there are changes from the last version I checked in — and this includes new files, denoted by a “?” at the beginning of their line.
Since there are new files, I have to add them so they’ll be tracked by the repository. I only need to add in the parent directory for any changed files, and any new files within it will also be added for tracking.
Excellent; the new files have been added. After this, I just need to accommodate the deleted files that no longer need to be tracked (created when using the “--recursive-unlink” option on tar). For that, see my newer instructions.
Now that the right files are being tracked, I need to commit the changes — modified, added, and deleted files — to the repository. This will create a new revision in the repository’s history, which I’ll tag with the text “Acquia Drupal 1.2.0.”
Once this revision is checked in, I can use it to propagate changes to other repositories. I keep the main Acquia Drupal distribution in its own repository, and then use the “hg fetch” command to pull its changes into one where I track contributed modules. That second repository is then pulled into a third repository which stores just the changes for my production Web site. The use of three repositories in this way modularizes and isolates the updates.
Radmind transcripts with symlinks will be damaged when edited in the Radmind Transcript Editor. I have confirmed this with RTE version 0.7.7 used in conjunction with the version 1.13.0 Radmind command line tools.
The problem appears to be an interaction between RTE 0.7.7 (which is old) and newer Radmind tools, according to posts on the Radmind-Users mailing list. It apparently relates to any version of the Radmind tools greater than 1.12.0, which introduced symlink ownership, when used in combination with RTE 0.7.7. This issue is in the Radmind bug report tracker and has been fixed in the CVS version of RTE. To use that newer version of RTE, you have to build the GUI tools from CVS.
You only see the problem — assuming you are using the right combination of versions — if you edit and save a transcript or create a new transcript within RTE (either by drag and drop or the “Add Item to Transcript” command). So, using the RTE to simply view the transcript file — and then editing with a different editor (which is an inconvenience) — is a workaround.
To get a count of the affected transcripts (on your Radmind server), use the following command:
You can simplify the grep search to only return the path of each match, and then process that with Awk to get just the basename of the file. Here’s how to use that technique to get the list of affected transcript files on a Radmind client:
As for actually fixing the damaged transcripts, it appears that the best way to do so is to recreate them from scratch.
I wanted to find a way to do syntax highlighting of code snippets on my Drupal blog. I came across the GeSHi Filter module, which lets Drupal sites take advantage of the apparently well-regarded GeSHi Generic Syntax Highlighter library that’s meant for just this purpose.
However, I ran into some roadblocks implementing it on my site. Here’s the short story of what I settled on after some trial and error.
My existing code snippets are in <code> blocks, and the initial GeSHi Filter settings applied badly to them. I made the decision to only use GeSHi on <blockcode> blocks, since I wasn’t using that tag yet and it wouldn’t conflict with the snippets already posted.
I most commonly write Bash/Zsh, Python, and AppleScript snippets on my blog. However, the Bash code I was using as part of my trial and error simply wasn’t highlighting; it was coming through as the default (and boring) plain text — but was at least boxed off from the rest of the blog post.
I thought that GeSHi wasn't correctly discovering that the code was written in UNIX shell syntax. I couldn’t find a way to specify the language for that blockcode tag, until I did some searching on the ’net. To change my blockquotes to choose a certain language — at least for the purposes of this Drupal module, if not for GeSHI in general — I needed to add the “lang=lang” style to the tag. For Bash, I could use “lang=bash,” for Python, “lang=python,” and for AppleScript, “lang=applescript.” That made sense.
However, my code was still not being syntax highlighted. I discovered that the Drupal module came with an initial set of languages enabled. The others were all turned off, but that could be changed in the module settings. Without turning them on, even properly-tagged <blockcode> sections did not get the benefit of syntax highlighting.
I changed the GeSHi Filter options to enable some of the languages that were initially disabled, and then disabled the ones I didn’t anticipate using. This allowed me to add Bash and AppleScript syntax highlighting support, as both had been turned off by default. After that, I saw the results I’d hoped for: a syntax-highlighted code snippet.
It took some work, but now that it’s done, I should be all set.
You can find out if an SSL certificate has expired with the command below. I’ve found it useful to be able to check for expired certificates in my use of Radmind, where you can uniquely identify clients to the server with them.
$ openssl x509 -in /path/to/cert.pem -noout -checkend 0
I mention this command primarily because I reviewed the the OpenSSL x509 man page (“man x509”) that comes with Mac OS X Leopard, and it didn’t show the “checkend” option for the command. That was odd, because that option was just what I needed.
I did, however, find it documented in the usage statement-style help for the command:
$ openssl x509 --help
In that usage statement, the “checkend” option is described (with little punctuation) as a way to “check whether the cert expires in the next arg seconds [sic] exit 1 if so, 0 if not.” So, using zero seconds shows you if the certificate has already expired, while an integer greater than zero will show if it will expire in the future. No matter how many seconds you check against, you must examine the results from the exit code (the “$?” shell variable) to see if the certificate is or has expired.
I find this is tremendously useful knowledge when dealing with certificates in Radmind, where an expired certificate can mean the failure of a client to connect to the Radmind server. It could be beneficial in other circumstances, of course — but I don't have those circumstances.
Taking this further, you could check for certificate expiration on a Radmind server — if your certificates are stored in the Radmind special directory for each hostname of a managed client. (Substitute one of your own managed clients’ hostnames for “hostname” in the path below.)
$ openssl x509 -in /var/radmind/special/hostname/private/var/radmind/cert/cert.pem -noout -checkend 0
Since you can do it for one client certificate, you could also loop through all of the certificates on a Radmind server. In this example, I’ll continue to use the path of /var/radmind even though, on Mac OS X, I’d generally prefer to specify the full /private/var/radmind; your Radmind server may not be on Mac OS X even if your clients are. Also, you may need to modify the “depth” parameter on your search to accommodate the paths on your server. Finally, I’ll change the “checkend” parameter to 604800, for seven days (60*60*24*7=604800). That produces something along the lines of:
Change the last line to “done | grep expiring” if you only want to see the expiring certificates.
It’s great to get just the CN of the certificate in these circumstances, since it’s likely you’ll want to act on just those that need attention. One way to do this relatively cleanly is to use OpenSSL x509’s “subject” and “nameopt” options, and then parse the output. Below, I’ll use awk for that. (Again, substitute one of your own managed clients’ hostnames for “hostname” in the path below.)
$ openssl x509 -in /private/var/radmind/special/hostname/private/var/radmind/cert/cert.pem -noout -subject -nameopt sep_multiline | awk '/CN/ {split($1,elements,"=") ; print elements[2] ;}'
To get the CNs of only those certificates needing attention:
Getting even fancier, you can find out just which certificate CNs are expiring or expired compared to the managed hosts listed in your config file. (Make sure you don’t get any that are commented out, and also watch out for curly braces.) You can accomplish much of that with this snippet:
Beyond checking for expiration on the server, it may be valuable to do so in your Radmind client scripts, especially if you favor SSL connections. If you find an expired certificate, you can take some remedial action right away that might allow the client to communicate with the server.
I thought about this a while, and the easiest way I came up with — after having already developed more complex logic — was to simply rename or remove the expired certificate from its normal path. Then, allow the client to connect with another authorization level where the client certificate is unnecessary. (Use of a client certificate implies Ramind’s “-w2” authorization level, while a lesser level would mean you’re performing hostname/DNS rather than certificate verification.) This would probably mean you have multiple Radmind server processes running, each on its own port, to accept such incoming requests on the server.