Mac OS X, System administration

Mystery of the failed SSH logins solved by discovery of empty files

I had a repeat of a problem that I’ve probably had many times over the years. I wanted to write down the solution before I forgot it, because it had been long enough since the last time that I’d forgotten the fix.

Let’s set the stage: I wanted to SSH into a remote system. In my case, that remote system was a Mac OS Snow Leopard system. The client was also Mac OS X.

On that remote system, I had enabled sshd. This is done on Snow Leopard with System Preferences > Sharing > Remote Login. (In the Sharing System Preferences, you set up the users that are allowed to remote log in, either allowing all users or setting up an ACL that only allows specific users. If you set up the ACL, it should supercede whatever additional user/group login restrictions you’ve configured in /private/etc/sshd_config.)

Then, I tried to SSH from my client system. Instead of a successful login, I was told the following — before I was prompted for a password.

$ ssh user@server.example.com
Connection closed by 192.168.1.10.

Scratching your head, you add verbosity to the SSH connection attempt.

$ ssh userid@server.example.com
[snip]
debug1: SSH2_MSG_KEXINIT sent
Connection closed by 192.168.1.10.

I searched for other people having the same problem with “debug1: SSH2_MSG_KEXINIT sent,” because other people always have the same problem and some of them wrote about it and some of those solved it. Right?

Well, in this case, the other solutions I found were not helpful to my specific situation. But many people responding to pleas for help did mention the always-good advice to “check the server logs.” Which I could do, so I did.

In the secure.log, I found several groups of lines like this corresponding to the times I had tried to log in:

Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_key
Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_rsa_key
Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_dsa_key
Nov 5 18:47:20 server sshd[1239]: Disabling protocol version 1. Could not load host key

That reminded me of the common problem with SSH host keys on Radmind-managed computers. See, Mac OS X will try to create the host keys if they are missing, but not if they are zero-length. On Radmind-managed computers, it was trivially easy to get zero-length SSH host key files in /private/etc because the tendency was to manage them with negative transcripts. Files listed in negative transcripts would be created if they were missing, but they would be created as empty files (by design).

Empty SSH host key files will prevent you from logging into that system with SSH.

I checked the server and — sure enough — the host key files were zero length. I deleted them, then stopped and restarted Remote Login for good measure. This solved the problem, and I could log in from the client.

Reinstall Xcode if easy_install fails with missing files like python.h

I was trying to install Dulwich on a Mac OS X Lion system this week and ran into difficulty. I kept getting installation failures that included a missing “python.h” and, eventually, llvm-gcc-4.2 failed to compile the module.

I found the situation frustrating, partly because I pretty much own the top search hits about how to install Dulwich and Hg-Git on Mac OS X Lion, thanks to some earlier article.

It turns out that I had reinstalled Lion about two weeks ago, and had not reinstalled Xcode 4. So, I updated to Xcode 4.2 and this completely eliminated my problem. Presumably, it would also work for you — and for future me, since I’m likely to repeat this — even if Lion hadn’t been reinstalled in between.

Things that didn’t work included but were not limited to:

  • cursing under my breath
  • stomping
  • hand-waving
  • complaining on Twitter
  • the silent treatment
  • waiting for 4, 12, and then 24 hours to see if it would fix itself
  • installing the latest version of setuptools, v0.6c11, from an egg
  • installing the current version of Distribute, new hotness or not
  • any steps involving Linux distribution package managers like apt-get.

Read preferences from a property list within a Mac OS X Python script

I had a need to read some settings from a Python script on Mac OS X recently. I wanted to be able to change selected parameters for the script — some of which could be site or implementation specific — without embedding them directly in the code. Since the script was Mac OS X-only, using a property list seemed like a good idea.

With customizable settings from a property list, a script could become useful and more customizable for a wider community — or even different internal audiences.

I thought reading preferences was going to take a lot of effort. I was, however, pleasantly surprised at how easily I was able to accomplish it.

Asking others who had been down this route before resulted in some links to Apple docs. I wanted a more concrete example of how it was done in Python, and I got a great one.

That example came from reading the source to munkilib from the open source project, Munki. Since Munki had its own preference file, it needed to read from it, and its example was very enlightening.

Frogor directed me to a specific spot in the Munki code. That spot demonstrated how to read a preferences file with CoreFoundation.

Munki also has its own internal defaults for preferences. The munkilib/munkicommon.py example showed how to implement default settings in their absence in a property list. That provides a fallback position so that you always have some value available. In Munki, setting the defaults was done within a function. It seemed that it would be more generic if those defaults were separated out of the preference-reading function.

My own example of how to read a plist is outlined below. This focuses on just what you need in order to read the preferences and provide default settings for a larger script. A single set of preferences can be shared between scripts, and each script can encode its set own defaults. While you can create your own keys and values, for the example below, I will use the keys “StringPreference,” “BooleanPreference,” “ArrayPreference,” and “DictionaryPreference.”

  1. Create a new property list. There are several ways to do this, including the property list editor that has been rolled into Xcode (and is no longer a standalone application) in v4.
  2. Save the file. The name will be used later.
  3. Set up the basic shell of the script that will read the plist and import CoreFoundation.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue
  4. Add in a variable for the script’s bundle ID. The bundle ID uniquely identifies your script’s preferences. It is written in reverse DNS notation, which should be familiar to almost anyone who has dealt with property lists before. It’s handy to have this defined globally for your script so that you can refer back to it as needed.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’
  5. Create a function to read a preference by its bundle ID. The business end of the function is the use of CFPreferencesCopyAppValue get a value for a key.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’

    def get_preference(preference_key, bundle_id=this_bundle_id):
        """
        Get the preference value for a given combination of preference
        key and bundle ID. Returns the requested preference value.
        """

        # Get the specified preference key from the specified preference
        # bundle ID using CoreFoundation
        preference_value = CFPreferencesCopyAppValue(preference_key, bundle_id)
        return preference_value
  6. Tie the get_preferences function together with a default_preferences object. This way, any missing preferences will fall back to the defaults encoded in your script. Any preferences that are set in a property list will override the defaults.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’
    default_preferences = {
        ‘StringPreference’: False,
        ‘BooleanPreference’: False,
        ‘ArrayPreference’: list(),
        ‘DictionaryPreference’: dict(),
    }

    def get_preference(preference_key, bundle_id=this_bundle_id):
        """
        Get the preference value for a given combination of preference
        key and bundle ID. Returns the preference value.
        """

        # Get the specified preference key from the specified preference
        # bundle ID using CoreFoundation
        preference_value = CFPreferencesCopyAppValue(preference_key, bundle_id)
        # If the value is not set in the property list, get a default value
        # from the default_preferences objects
        if preference_value == None:
            preference_value = default_preferences.get(preference_key)
        return preference_value

The script won’t do anything yet, until we create a main() or other functions to call the get_preferences function. Since you’d be reading preferences as part of a larger script, this is fine for now. However, with what’s written already, you can test out reading preferences interactively with the shell and Python interpreter.

  1. Start by checking the property list in the shell. In my case, I created an example property list that sets two of the four preferences.
    $ defaults read com.example.your-script-here
    {
        BooleanPreference = True;
        StringPreference = abc;
    }
  2. Open Terminal, run “python” at the command prompt, and paste the script code above at the interactive Python “>>>” prompt.
  3. Run the get_preferences function and print its output.
    >>> print(get_preference(‘StringPreference’))
    abc
    >>> print(get_preference(‘BooleanPreference’))
    True
    >>> print(get_preference(‘ArrayPreference’))
    []
    >>> print(get_preference(‘DictionaryPreference’))
    {}

That’s it, the expected results were returned. Notice that the output for StringPreference and BooleanPreference are taken from the property list, while the empty array and dictionary come from the default_preferences in the script.

Update: Greg pointed out that there is a certain degree of danger using #!/usr/bin/env python as the shebang line in the script. Should a system have a non-Apple installation of Python, then /usr/bin/env python might return that. Another Python is probably less likely to be able to bridge CoreFoundation, and thus wouldn’t be able to use the CFPreferencesCopyAppValue call to read preferences.

I think the overall shebang danger is small because few Mac OS X systems will have an alternative Python installed, but clearly your chances of that increase in certain situations. To eliminate this risk, do what Munki does and insert the #!/usr/bin/python shebang instead.

Install Hgsubversion with Mercurial 1.9 on Mac OS X

In previous articles, I described how to Install Mercurial 1.9, Dulwich, and Hg-Git on Mac OS X Snow Leopard and Install Mercurial 1.9, Dulwich, and Hg-Git on Mac OS X Lion. For either of those operating systems, we can further extend Mercurial with support for connecting to Subversion repositories. While we could enable the bundled Convert extension, the Hgsubversion extension adds live access to remote networked Subversion repos, so it sounds more interesting.

My personal goal in doing this is to be able to work with the InstaDMG repository. It is hosted on Google Code, uses a Subversion repo, and depends upon Subversion keyword substitution. It presents an interesting challenge.

So, let’s install Hgsubversion to add Subversion support to the Git support we’ve previously set up. We need to take the following additional steps, going beyond what is done in the previous articles. To continue, you must already have installed:

  1. the Xcode tools (including Subversion), as appropriate for your operating system (probably either Xcode 3.2.6 or 4.1 at this point)
  2. Mercurial 1.9.2 or later.

Hgsubversion also needs Subversion to be installed. The Mercurial extension then has two different ways of working with Subversion. The Hgsubversion docs I read indicated that Subvertpy is preferred over the other method — using Subversion’s SWIG bindings — so we’ll install this additional Python module first. The docs also recommend running tests on Hgsubversion before using it on projects, so we will try that, as well.

Start with installing Subvertpy:

  1. Run the easy_install command in Terminal.
    $ sudo easy_install ‘subvertpy>=0.8.7’
    Searching for subvertpy>=0.8.7
    Reading <a href="http://pypi.python.org/simple/subvertpy/
    Reading"
    title="http://pypi.python.org/simple/subvertpy/
    Reading"
    >http://pypi.python.org/simple/subvertpy/
    Reading</a> <a href="http://samba.org/~jelmer/subvertpy
    Reading"
    title="http://samba.org/~jelmer/subvertpy
    Reading"
    >http://samba.org/~jelmer/subvertpy
    Reading</a> <a href="http://launchpad.net/subvertpy
    Best"
    title="http://launchpad.net/subvertpy
    Best"
    >http://launchpad.net/subvertpy
    Best</a> match: subvertpy 0.8.7
    Downloading <a href="http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing"
    title="http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing"
    >http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing</a> subvertpy-0.8.7.tar.gz
    Running subvertpy-0.8.7/setup.py -q bdist_egg —dist-dir /tmp/easy_install-hU1kY_/subvertpy-0.8.7/egg-dist-tmp-Qgbloj
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_init’:
    subvertpy/_ra.c:2180: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_set_parameter’:
    subvertpy/_ra.c:2215: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘py_ssl_server_trust_prompt’:
    subvertpy/_ra.c:2681: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/wc.c: In function ‘py_dict_to_wcprop_changes’:
    subvertpy/wc.c:1127: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/_ra.c: In function ‘auth_init’:
    subvertpy/_ra.c:2180: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_set_parameter’:
    subvertpy/_ra.c:2215: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘py_ssl_server_trust_prompt’:
    subvertpy/_ra.c:2681: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/repos.c: In function ‘fs_root_file_length’:
    subvertpy/repos.c:747: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/wc.c: In function ‘py_dict_to_wcprop_changes’:
    subvertpy/wc.c:1127: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    zip_safe flag not set; analyzing archive contents…
    subvertpy.__init__: module references __file__
    subvertpy.ra_svn: module references __file__
    Adding subvertpy 0.8.7 to easy-install.pth file
    Installing subvertpy-fast-export script to /usr/local/bin

    Installed /Library/Python/2.7/site-packages/subvertpy-0.8.7-py2.7-macosx-10.7-intel.egg
    Processing dependencies for subvertpy>=0.8.7
    Finished processing dependencies for subvertpy>=0.8.7
  2. Clone the Hgsubversion repository from Bitbucket to a temporary location on your system. Under normal circumstances, I would use easy_install again to get hgsubversion. However, version 1.2.1 in PyPi didn’t work for me. So, to get a more current working version, I went to the source repository to get the latest software.
    $ cd /tmp
    $ hg clone <a href="https://bitbucket.org/durin42/hgsubversion" title="https://bitbucket.org/durin42/hgsubversion">https://bitbucket.org/durin42/hgsubversion</a> hgsubversion-work
  3. Run the setup tool from the repository.
    $ sudo python setup.py install
    running install
    running bdist_egg
    running egg_info
    creating hgsubversion.egg-info
    writing hgsubversion.egg-info/PKG-INFO
    writing top-level names to hgsubversion.egg-info/top_level.txt
    writing dependency_links to hgsubversion.egg-info/dependency_links.txt
    writing manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    reading manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    reading manifest template MANIFEST.in’
    warning: no files found matching ’*.rst’
    writing manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    installing library code to build/bdist.macosx-10.7-intel/egg
    running install_lib
    running build_py
    creating build
    creating build/lib
    creating build/lib/hgsubversion
    copying hgsubversion/__init__.py -> build/lib/hgsubversion
    copying hgsubversion/__version__.py -> build/lib/hgsubversion
    copying hgsubversion/editor.py -> build/lib/hgsubversion
    copying hgsubversion/maps.py -> build/lib/hgsubversion
    copying hgsubversion/pushmod.py -> build/lib/hgsubversion
    copying hgsubversion/replay.py -> build/lib/hgsubversion
    copying hgsubversion/stupid.py -> build/lib/hgsubversion
    copying hgsubversion/svncommands.py -> build/lib/hgsubversion
    copying hgsubversion/svnexternals.py -> build/lib/hgsubversion
    copying hgsubversion/svnmeta.py -> build/lib/hgsubversion
    copying hgsubversion/svnrepo.py -> build/lib/hgsubversion
    copying hgsubversion/util.py -> build/lib/hgsubversion
    copying hgsubversion/wrappers.py -> build/lib/hgsubversion
    creating build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/__init__.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/common.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/subvertpy_wrapper.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/svn_swig_wrapper.py -> build/lib/hgsubversion/svnwrap
    creating build/lib/hgsubversion/help
    copying hgsubversion/help/subversion.rst -> build/lib/hgsubversion/help
    creating build/bdist.macosx-10.7-intel
    creating build/bdist.macosx-10.7-intel/egg
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/__init__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/__version__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/editor.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion/help
    copying build/lib/hgsubversion/help/subversion.rst -> build/bdist.macosx-10.7-intel/egg/hgsubversion/help
    copying build/lib/hgsubversion/maps.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/pushmod.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/replay.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/stupid.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svncommands.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnexternals.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnmeta.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnrepo.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/__init__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/common.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/subvertpy_wrapper.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/svn_swig_wrapper.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/util.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/wrappers.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/__init__.py to __init__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/__version__.py to __version__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/editor.py to editor.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/maps.py to maps.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/pushmod.py to pushmod.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/replay.py to replay.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/stupid.py to stupid.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svncommands.py to svncommands.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnexternals.py to svnexternals.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnmeta.py to svnmeta.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnrepo.py to svnrepo.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/__init__.py to __init__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/common.py to common.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/subvertpy_wrapper.py to subvertpy_wrapper.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/svn_swig_wrapper.py to svn_swig_wrapper.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/util.py to util.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/wrappers.py to wrappers.pyc
    creating build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/PKG-INFO -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/SOURCES.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/dependency_links.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/top_level.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    zip_safe flag not set; analyzing archive contents…
    hgsubversion.__init__: module references __file__
    hgsubversion.util: module references __file__
    creating dist
    creating ‘dist/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg’ and adding ‘build/bdist.macosx-10.7-intel/egg’ to it
    removing ‘build/bdist.macosx-10.7-intel/egg’ (and everything under it)
    Processing hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    creating /Library/Python/2.7/site-packages/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    Extracting hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg to /Library/Python/2.7/site-packages
    Adding hgsubversion 1.2.1-34-f28e0f54a6ef to easy-install.pth file

    Installed /Library/Python/2.7/site-packages/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    Processing dependencies for hgsubversion==1.2.1-34-f28e0f54a6ef
    Finished processing dependencies for hgsubversion==1.2.1-34-f28e0f54a6ef
  4. Check the version of Hgsubversion. Since we haven’t enabled it yet, it won’t report anything terribly useful.
    $ hg version —svn
    hg version: option —svn not recognized
    hg version

    output version and copyright information

    use "hg help version" to show the full help text
  5. Add Hgsubversion to your ~/.hgrc to enable the extension. We previously enabled the “bookmarks” and “hggit” extensions.
    [extensions]
    hgext.bookmarks =
    hggit =
    hgsubversion =
  6. Check Hgsubversion’s version again. Now that it is enabled, it should report information similar to what you see below, which is very different than what we saw above.
    $ hg version —svn
    Mercurial Distributed SCM (version 1.9.2+20110831)
    (see <a href="http://mercurial.selenic.com" title="http://mercurial.selenic.com">http://mercurial.selenic.com</a> for more information)

    Copyright (C) 2005-2011 Matt Mackall and others
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    hgsubversion: 1.2.1+34-f28e0f54a6ef
    Subversion: 1.6.16
    bindings: Subvertpy 0.8.7
  7. Run the Hgsubversion test suite. In my case, I had failures, a situation which appears to already have been reported in the Hgsubversions issue tracker.
    $ python tests/run.py
    ………………………………………….F………FF…………………………..FEF………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
    ======================================================================
    ERROR: test_file_map_exclude_stupid (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 130, in test_file_map_exclude_stupid
        self.test_file_map_exclude(True)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 125, in test_file_map_exclude
        self.wc_path, filemap=self.filemap)
      File "/Library/Python/2.7/site-packages/mercurial/commands.py", line 1041, in clone
        branch=opts.get(‘branch’))
      File "/Library/Python/2.7/site-packages/mercurial/hg.py", line 334, in clone
        destrepo.clone(srcrepo, heads=revs, stream=stream)
      File "/Library/Python/2.7/site-packages/mercurial/localrepo.py", line 1946, in clone
        return self.pull(remote, heads)
      File "/private/tmp/hgsubversion_work/hgsubversion/svnrepo.py", line 48, in wrapper
        return fn(self, *args, **opts)
      File "/private/tmp/hgsubversion_work/hgsubversion/svnrepo.py", line 63, in pull
        return wrappers.pull(self, remote, heads, force)
      File "/private/tmp/hgsubversion_work/hgsubversion/wrappers.py", line 346, in pull
        firstrun)
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 642, in convert_rev
        ui, svn, meta, b, branches[b], r, parentctx)
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 228, in diff_branchrev
        files_data = patchrepo(ui, meta, parentctx, cStringIO.StringIO(d2))
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 168, in patchrepo
        ret = patch.patchbackend(ui, backend, patchfp, 0, touched)
      File "/Library/Python/2.7/site-packages/mercurial/patch.py", line 1412, in patchbackend
        raise PatchError(_(‘patch failed to apply’))
    PatchError: patch failed to apply

    ======================================================================
    FAIL: test_many_special_cases_diff (test_fetch_command.TestBasicRepoLayout)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 72, in test_many_special_cases_diff
        self._many_special_cases_checks(repo)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 83, in _many_special_cases_checks
        ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’)
    AssertionError: ‘13c5dc1514ad8619c589a8929bfe0ece5c00f18e’ != ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’

    ======================================================================
    FAIL: test_oldest_not_trunk_and_tag_vendor_branch (test_fetch_command.TestStupidPull)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 224, in test_oldest_not_trunk_and_tag_vendor_branch
        ‘1a6c3f30911d57abb67c257ec0df3e7bc44786f7’)
    AssertionError: ‘fa799f2781255dba874645e849d75af837472518’ != ‘1a6c3f30911d57abb67c257ec0df3e7bc44786f7’

    ======================================================================
    FAIL: test_stupid (test_fetch_command.TestStupidPull)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 201, in test_stupid
        ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’)
    AssertionError: ‘13c5dc1514ad8619c589a8929bfe0ece5c00f18e’ != ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’

    ======================================================================
    FAIL: test_file_map_exclude (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 127, in test_file_map_exclude
        self.assertEqual(node.hex(self.repo[‘default’].node()), ‘b37a3c0297b71f989064d9b545b5a478bbed7cc1’)
    AssertionError: ‘81ae7af456c0e414ddc380ff641b37da84a9df8f’ != ‘b37a3c0297b71f989064d9b545b5a478bbed7cc1’

    ======================================================================
    FAIL: test_file_map_stupid (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 115, in test_file_map_stupid
        self.test_file_map(True)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 112, in test_file_map
        self.assertEqual(node.hex(self.repo[‘default’].node()), ‘e524296152246b3837fe9503c83b727075835155’)
    AssertionError: ‘ecf9b521a1799ebb0e01c1d1e86305ea8b542d2e’ != ‘e524296152246b3837fe9503c83b727075835155’

    –––––––––––––––––––––––-
    Ran 492 tests in 1212.923s

    FAILED (failures=5, errors=1)

That’s it! Hgsubversion has been installed and enabled. Even though we got errors from the tests, we can proceed.

  1. Clone a Subversion repository, like the InstaDMG one.
    • This gets you just the trunk and current state:
      $ hg clone <a href="http://instadmg.googlecode.com/svn/trunk" title="http://instadmg.googlecode.com/svn/trunk">http://instadmg.googlecode.com/svn/trunk</a> /tmp/instadmg_trunk_work
    • This gets you the full history.
      $ hg clone <a href="http://instadmg.googlecode.com/svn/" title="http://instadmg.googlecode.com/svn/">http://instadmg.googlecode.com/svn/</a> /tmp/instadmg_full_work
  2. Enable the Mercurial Keywords extension in the cloned repo’s .hg/hgrc. (According to the documentation, you should not enable the Keywords extension on a global basis in your ~/.hgrc, but instead do so on a repo-by-repo basis in .hg/hgrc.) We’ll also set it so it only affects Bash scripts with the right filename extension, because we want keyword expansion on just the “instadmg.bash” file.
    [paths]
    default = http://instadmg.googlecode.com/svn/trunk/

    [extensions]
    keyword =

    [keyword]
    # expand keywords in all bash and python files in working dir
    **.bash =
  3. Set your HGUSER environment variable. The next step will need this. The following steps will do it temporarily for your current terminal session, so when you get a chance, you’ll want to read about how to do this more permanently.
    $ HGUSER="anonymous"
    $ export HGUSER
  4. Try kwdemo. You must be in the directory for the cloned repository and have enabled the Keyword extension.
    $ hg kwdemo —default

            configuration using default svn keywordset
    [extensions]
    keyword =
    [keyword]
    **.bash =
    demo.txt =
    [keywordset]
    svn = True
    [keywordmaps]
    Author = {author|user}
    Date = {date|svnisodate}
    Id = {file|basename},v {node|short} {date|svnutcdate} {author|user}
    LastChangedBy = {author|user}
    LastChangedDate = {date|svnisodate}
    LastChangedRevision = {node|short}
    Revision = {node|short}

            keywords expanded
    $Author: anonymous $
    $Date: 2011-09-26 14:32:06 -0400 (Mon, 26 Sep 2011) $
    $Id: demo.txt,v 5e86fb5c9fae 2011-09-26 18:32:06Z anonymous $
    $LastChangedBy: anonymous $
    $LastChangedDate: 2011-09-26 14:32:06 -0400 (Mon, 26 Sep 2011) $
    $LastChangedRevision: 5e86fb5c9fae $
    $Revision: 5e86fb5c9fae $
  5. Edit the .hg/hgrc file for the cloned repository so that you can set up keyword substitution for the “Revision” keyword. In my experience, you’ll need to set up a map for this, unless you want to get the Hg revision number that you see in the default output of kwdemo above.
    [paths]
    default = http://instadmg.googlecode.com/svn/

    [extensions]
    keyword =

    [keyword]
    # expand keywords in all bash and python files in working dir
    **.bash =

    [keywordmaps]
    Revision = {svnrev}
  6. Try the demo again. I never get this part to work, but you may have more luck. Actual shrinking/expansion, which we’ll see next, does work for me.
    $ hg kwdemo —rcfile .hg/hgrc

            configuration using custom keyword template maps
            extending current template maps
    [extensions]
    keyword =
    [keyword]
    **.bash =
    demo.txt =
    [keywordset]
    svn = False
    [keywordmaps]
    Revision = {svnrev}

            keywords expanded
    $Revision:  $
  7. Expand the keywords in your local InstaDMG repository.
    $ hg kwshrink # Run after each keyword configuration change
    $ grep Revision instadmg.bash
    SVN_REVISION=`/bin/echo ‘$Revision$’ | /usr/bin/awk ’{ print $2 }’`
    $ hg kwexpand # Run to expand the keywords in the repository. Be sure to kwshrink before making and committing changes.
    $ grep Revision instadmg.bash
    SVN_REVISION=`/bin/echo ‘$Revision: 425 $’ | /usr/bin/awk ’{ print $2 }’`
    $ hg kwshrink

Notice that “Revision: 425” is returned when keywords are expanded, replacing the “$Revision” keyword. This is precisely what we need with InstaDMG, where the revision information is reported when the instadmg.bash script is run.

Get the display resolution on Mac OS X with PyObjC

I came across this hint about display properties on StackOverflow and thought it was worthwhile to write down for later. If you want to get the screen or Desktop resolution of a Mac via Python, you can do so with PyObjC.

First, let’s get the information about the main screen:

>>> from AppKit import NSScreen
>>> print(NSScreen.mainScreen().frame())
<NSRect origin=<NSPoint x=0.0 y=0.0> size=<NSSize width=1920.0 height=1200.0>>

If you want just the horizontal and vertical resolution from that blob of data, you can pull the width and height out:

>>> print(NSScreen.mainScreen().frame().size.width)
1920.0
>>> print(NSScreen.mainScreen().frame().size.height)
1200.0
>>> width, height = NSScreen.mainScreen().frame().size.width, NSScreen.mainScreen().frame().size.height
>>> width, height
(1920.0, 1200.0)

This might be useful in situations where you don’t have any of the “hundred of portable libs in Python that give you access to that information” — such as in your stock Mac OS X Python installation. To clarify: I’m in no way meaning to belittle that there are portable libraries that would let you do the same thing, but you also have to program for your audience and its constraints. One of the reasons I appreciate Python over some scripting languages is that you get so much capability in the Standard Library. However, on Mac OS X, you don’t get modules like pygame by default (yet … and maybe never) while you do get PyObjC.

Remove filename extensions with os.path.splitext instead of the {lr}strip or replace methods in Python

Jesper Noehr explains why {l,r}strip are considered harmful for removing extensions from filenames with Python. I think he’s absolutely right on that score, and I would agree. The lstrip() and rstrip() methods shouldn’t be used for this purpose.

However, like the only commenter on that post, I’d also recommend os.path.splitext() as the proper tool for the extension-removing job.

Let’s take some example filenames you might come across on Mac OS X Snow Leopard:

>>> a = [ ‘Document.pages’, ‘Property List.plist’, ‘Application.app’, ‘Word.docx’, VPN (Cisco VPN).networkConnect’, ‘Dated Spreadsheet 2010.02.17.xlsx’]

If we had a list of filenames (or file paths) like this — perhaps created by os.walk() or some other generator-based process — we couldn’t easily use Jesper’s recommended solution. The replace() string method would give us a much harder time dealing with the range of filenames and extensions in that list. In the case where you don’t know the filename extensions in advance, replace() breaks down. The replace() method would have to be looped with many possible filename extensions.

What we need is a way to split filename from extension, even if we don’t know the extension beforehand. The os.path.splitext() alternative does just that, returning a tuple. Here, I’ll import the os module and then use a list comprehension to run os.path.splitext() through every filename in the list above.

>>> import os
>>> [os.path.splitext(x) for x in a]
[(‘Document’, ‘.pages’), (‘Property List’, ‘.plist’), (‘Application’, ‘.app’), (‘Word’, ‘.docx’), (VPN (Cisco VPN)’, ‘.networkConnect’), (‘Dated Spreadsheet 2010.02.17’, ‘.xlsx’)]

It becomes a simple matter to get just the filename from the tuple, as I do here by modifying the list comprehension to just get the zeroth item from it:

>>> [os.path.splitext(x)[0] for x in a]
[‘Document’, ‘Property List’, ‘Application’, ‘Word’, VPN (Cisco VPN)’, ‘Dated Spreadsheet 2010.02.17’]

Note that several interesting conditions are handled by os.path.splitext():

  • long filename extensions, including “.pages” and “.networkConnect”
  • spaces in filenames, as in “Property List.plist”
  • parentheses, as in “VPN (Cisco VPN).networkConnect”
  • periods within the filename, as seen in “Dated Spreadsheet 2010.02.17.xlsx”

Access to this item is restricted

I had an odd situation over the weekend that resulted in the inability to view the passwords associated with keys in my Mac OS X user keychain. Every time I clicked on the “Show password” checkbox in a key’s detail window, I’d get an “Access to this item is restricted” dialog.

Needless to say, this was disconcerting. I happened to have a lot of data in that keychain — this is what I get for keeping the same one around since Mac OS X 10.0 or 10.1. While I could revert to a backup, the newest backup wasn’t as recent as I would like. Plus, I just wanted to know why the problem had cropped up.

So, I asked about my problem on the Apple-CDSA mailing list. If anyone would be able to help with the obscure corners of keychains, I figured the people there would.

Very promptly, I got a reply from Ken McLeod, which led me to check the validity of the code signature on the Keychain Access utility.

$ codesign -vvv /Applications/Utilities/Keychain\ Access.app
/Applications/Utilities/Keychain Access.app: code or signature modified

Clearly, the signature and the application didn’t match. Something was amiss.

I reinstalled Mac OS X 10.6.2 on the system, using the latest combo update installer package, and cleared up the problem signature mismatch.

$ codesign -vvv /Applications/Utilities/Keychain\ Access.app
/Applications/Utilities/Keychain Access.app: valid on disk
/Applications/Utilities/Keychain Access.app: satisfies its Designated Requirement

In retrospect, although I wouldn’t have thought of this being a problem, this breakage between the signature and the app — and its affect on my ability to view stored passwords — gives me confidence that thought has been put into the code signing mechanism in Mac OS X. You wouldn’t want a compromised app displaying your unencrypted keychain items, after all.

The new Apple Education Licensing Program

The new Apple Education Licensing Program replaces the Apple Maintenance Program (AMP) as the primary way to purchase ongoing software upgrade rights for a school, college, or university’s fleet of Macintosh computers. The Apple Education Licensing Program (AELP? or just ELP?) is a yearly, renewable license.

I think this is a big deal because it’s awfully close to what I’ve asked Apple for year after year — especially at venues like WWDC (at least when they have presented the opportunity for giving constructive feedback).

The ELP licensing for Mac OS X is actually a bundle — called the “Mac Software Collection” — of the operating system combined with the iLife and iWork suites. (It aligns with the same bundle of software in the standalone “Mac Box Set.”) I believe that the bundling of iLife is the biggest win, because it provides upgrade rights to software that is bundled with every new Mac but frequently updated. If you are managing a larger group of Macs over several years, the licensing and deployment issues involved with iLife could be complex — moreso because it’s the kind of software that people want on their computers. It also doesn’t hurt that Keynote is included — anecdotally, it appears to sell a lot of Macs in organizations all by itself.

The ability to keep the operating system and core applications from the Mac Software Collection up-to-date across a range of Macs with a single renewable license could be a tremendous savings in administrative overhead.

Organizations must cover 100 percent of their installed base of owned or leased Macs, at least for the Mac Software Collection. This may be a deal-breaker for a number of universities and perhaps other organizations that tend to be decentralized in their operations.

Apple’s other software can also be obtained through ELP, but can be purchased to cover 100 percent of computers at the departmental level. It is possible that the exact nature of the organizational unit is at the organization’s discretion.

ELP allows the flexibility to cover faculty/staff home use as well as students, should the organization elect to pay for that additional coverage.

Each ELP purchase appears to incur a one-time 10 percent enrollment fee. If an organization doesn’t renew for a year but then buys again later, it may be subject to the enrollment fee again. And, if you have a lot of departments seeking Apple’s Pro Apps or IT-related software, those 10 percent charges could add up to significant overhead. I’m not sure if there’s a clear way to allow two or three departments to get the software they want without requiring an entire educational institution’s Macs be covered.

As part of the introduction of ELP, educational institutions are being encouraged to move to the new licensing program before December 13, 2009, through a waiver of the enrollment fee. It appears possible to crossgrade from AMP to ELP, at least if you talk to your Apple account team.

I am not aware whether Apple has rolled out something similar to organizations outside education.

Python 32-bit execution on Snow Leopard

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:

# Prefer 32-bit execution for Python 2.6.1 on Snow Leopard
$ defaults write com.apple.versioner.python Prefer-32-Bit -bool yes

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.

Sync the keychain passphrase with the login account password in Snow Leopard

Mac OS X Snow Leopard appears to roll in the functionality of the separate Keychain Minder tool. Keychain Minder has provided a way for system administrators to help keep the passphrase in sync with the login account password. That can be very helpful for users in a directory services environment, because users may change their password in ways outside Mac OS X, thereby leaving the keychain passphrase out of sync.

The keychain passphrase is separate from the password used to log in to a Mac OS X user account. By default, however, the password on the login account is set as the passphrase for that user’s default keychain. When the password and passphrase get out of sync, it can cause a lot of confusion for those who don’t understand what’s going on.

I’d wager it’s a rare Mac OS X user that intentionally sets their login account password and keychain passphrase to be different, as I do. Therefore, keeping the two in sync is a benefit in a large percentage of cases.

Snow Leopard implements this feature as a preference item in Keychain Access, under the First Aid tab. It’s labeled “Synchronize login keychain password with account.” (I would have rephrased that as “default keychain” since keychains by other names can be the default keychain; the default name just happens to be “login” nowadays.)

macosx-workstation-snowleopard-keychainaccess-syncaccountandkeychain.png

Keychain Minder stored its settings in the com.afp548.KeychainMinder.plist preferences file. This doesn’t seem to have any impact, one way or another, on this particular keychain preference.

So, I looked for and eventually discovered that the new built-in feature of Snow Leopard stores its state in the SyncLoginPassword key of the com.apple.keychainaccess.plist file. You can see this change by use of the defaults command in Terminal:

# Synchronize disabled in the Keychain Access preferences dialog
$ defaults read com.apple.keychainaccess SyncLoginPassword
0
# Synchronize enabled in the Keychain Access preferences dialog
$ defaults read com.apple.keychainaccess SyncLoginPassword
1

You will want to have this preference disabled on any user accounts — likely power users — whose login account passwords will differ from their keychain passphrases. Otherwise, they will get prompted regularly to “Synchronize,” “Create New,” or “Continue” during the login process.

Syndicate content