graham gilbert

Mac administration and assorted nerdity

First-boot-pkg

| Comments

There are some packages that can’t be deployed to an unbooted OS, such as when building an image with AutoDMG. If you are using Greg Neagle’s createOSXinstallPkg, the OS X installer environment doesn’t have everything a full OS X install has. For times like this, you need to install the packages at first boot. For a long time, I’ve used Rich Trouton’s First Boot Package Install, however I found myself repeating things quite a bit and having a folder full of first boot packages.

So, I made my own. The main features of first-boot-pkg are:

  • It is designed with scripting and automation in mind, with options able to be configured with a configuration plist or via options on the command line (or a mixture of both)
  • It will re-try failed packages a specified number of times (in case of Active Directory not being available, for example)
  • Will wait for the network to be available before installing (optional, can be disabled if desired just in case your package is going to let the Mac get onto the network)

If you’re happy with using Git, I’d recommend just making a clone of the repository and doing a git pull to keep the script updated. If the thought of all those gits and pulls makes you run away, you can download a zip of the project.

This script makes use of Per Olofsson’s LoginLog for displaying the log file whilst the script is running, so massive thanks to him for releasing it.

Updating Boxen

| Comments

As you might know, I’m a bit of a fan of Munki and Puppet for managing the Macs I look after. Around a year ago, I really wanted to be able to automate my own setup across my own Macs the same way. I was forever finding that the particular git repository or app wasn’t on the Mac I was working on. Then there came the time when I wanted to do a clean install – that was easily a day down the drain there!

Automate all of the things

Then Boxen was released – based on Puppet, but targeted at setting up individual’s machines. I got on board just over a year ago, and haven’t really looked back – manually installing an app on my Mac seems very strange now. I’m not going to cover how to get started with Boxen, as there are many getting started guides out there (however, Gary Larizza’s is rather good).

There will come a time when you need to update the core part of Boxen. This happened to me when I clean installed 10.9 on my work laptop – all kinds of shit broke (somehow it managed to survive the upgrade process – go figure). I looked around, but couldn’t really find a definitive guide, so here it is (it’s shorter than this piece of rambling).

Ok, stop talking

As Boxen is made by GitHub, updating it is much like updating any other project on there that you’ve made a fork of. First we’re going to add it as a remote repository:

1
2
$ cd ~/src/our-boxen
$ git remote add upstream https://github.com/boxen/our-boxen.git

Then we’re going to fetch the stuff from the upstream repository:

1
$ git fetch upstream

Now we’re going to merge the updated repository with our own:

1
2
$ git checkout master
$ git merge upstream/master

If you haven’t modified any of the core Boxen files (Puppetfile, Gemfile or manifests/site.pp in my case), you might get away without having to fix any conflicts (you can ignore any in Puppetfile.lock and Gemfile.lock, we’ll deal with those next). I had conflicts as I had previously:

  • Been stupid and tried to update Boxen by just changing the Puppet Module and Gem versions
  • Edited site.pp as I didn’t want Nginx or node.js installed
  • Been dumb and put my custom Puppet modules in the wrong place in my Puppetfile

None of these were particularly arduous to fix, but annoying none the less. If you find you have loads, you might want to run:

1
$ git mergetool

The next step is to update your Puppet modules and RubyGems. First delete Puppetfile.lock and Gemfile.lock. Now go back to your trusty Terminal and:

1
2
$ bundle install --without development
$ bundle exec librarian-puppet install --clean

At this point, you might want to go through the custom modules you’ve added to your Puppetfile and update those, although this is by no means required – some apps I’ve installed through Boxen don’t have a built in updater, so Boxen is more convenient than hunting for installers on various vendor’s websites. Once your modules are up to date in your Puppetfile, you’re done! You can now get your Mac back to how you like it by issuing the usual:

1
$ boxen

Binding to Active Directory With Munki

| Comments

Many organisations need to bind their Macs to AD. There are quite a few options however, that need to be changed. It’s quite a straightforward process to automate this with Munki, although you do have a few options to consider.

First off, how are you going to deliver the actual bind script? You have the option of a no-pkg pkginfo file, with the script directly in the pkginfo plist. Whilst the script is now easily editable in the pkginfo, it does pose a security issue in that the catalog is kept in /Library/Managed Installs/catalogs, which will contain your script. Along with your AD bind account’s details. Whoops!

Prepare the Bind!

My preferred way of deploying the bind script is with a payload-free package made with The Luggage. My bind script is nothing special, it was originally borrowed from DeployStudio. You can find the script and the Makefile on my macscripts repo. If you need a primer on The Luggage, I wrote about it in August 2013. You just need to edit the variables at the top of the script to suit your environment and build the package.

So you’ve got the machine bound to AD. Great. What happens if the binding doesn’t go to plan? Or a well meaning tech manages to unbind the machine, but can’t manage to re-bind it? Or even worse, the user manages to unbind it themselves? We need to make Munki check that the Mac is still bound to AD.

Writing Plugins for Sal: Part 3

| Comments

We’ve already got a fairly decent plugin – it shows us how many machines we have that aren’t able to run 10.9. However, quite a few people won’t have any machines that fall into this category, and just want to know when one manages to sneak under the radar, so let’s hide the plugin if we don’t need to see it.

Previously on Lost

In the first part, you might remember that we had to tell Sal how much space our plugin needed. Well, we’re going to cover the eventuality of it not needing any space. First off, mavcompatibility.py.

grahamgilbert/mavcompatibility/mavcompatibility.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from yapsy.IPlugin import IPlugin
from yapsy.PluginManager import PluginManager
from django.template import loader, Context
from django.db.models import Count
from server.models import *

class MavCompatibility(IPlugin):
    def show_widget(self, page, machines=None, theid=None):

        if page == 'front':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/front.html')

        if page == 'bu_dashboard':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/id.html')

        if page == 'group_dashboard':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/id.html')


        not_compatible = machines.filter(condition__condition_name='supported_major_os_upgrades').exclude(condition__condition_name='supported_major_os_upgrades', condition__condition_data__contains='10.9').count()

        if not_compatible:
            size = 3
        else:
            size = 0

        c = Context({
            'title': '10.9 Compatibility',
            'not_compatible': not_compatible,
            'page': page,
            'theid': theid
        })
        return t.render(c), size

    def filter_machines(self, machines, data):
        if data == 'notcompatible':
            machines = machines.filter(condition__condition_name='supported_major_os_upgrades').exclude(condition__condition_name='supported_major_os_upgrades', condition__condition_data__contains='10.9')
            title = 'Macs not compatible with OS X 10.9'
        else:
            machines = None
            title = None

        return machines, title

Take a look at lines 22 – 25. If we get any results from the query on line 20, we’re going to be showing the plugin. If there aren’t any applicable machines in our inventory, we don’t need to show the plugin. We are returning the size to Sal on line 33. Easy so far.

All that’s left to do now is make our templates not do anything if they don’t need to.

grahamgilbert/mavcompatibility/templates/front.html
1
2
3
4
5
6
7
8
9
{% if not_compatible > 0 %}
<div class="span3">
    <legend>{{ title }}</legend>
        <a href="{% url 'machine_list_front' 'MavCompatibility' 'notcompatible' %}" class="btn btn-danger">
            <span class="bigger"> {{ not_compatible }} </span><br />
            Not Compatible
        </a>
</div>
{% endif %}

Notice the if statement on line 1? If the number of machines is 0, we don’t need to show anything. You’ll need to make a similar change on grahamgilbert/mavcompatibility/templates/id.html.

That’s it – a simple plugin for Sal. You can find this completed plugin in my sal-plugins repository.

Writing Plugins for Sal: Part 2

| Comments

And now, time for the shocking second part of our series on how to write plugins for Sal.

In the previous part, we got our basic widget working. This time, we’re going to link it up so we can get lists of those pesky non-10.9 compatible Macs when we click on the button.

It’s a list, Jim

When displaying the list of machines, Sal will call the filter_machines function in your plugin. I’m sure you don’t want to disappoint, so here’s that function added on to the plugin we wrote last time.

grahamgilbert/mavcompatibility/mavcompatibility.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from yapsy.IPlugin import IPlugin
from yapsy.PluginManager import PluginManager
from django.template import loader, Context
from django.db.models import Count
from server.models import *

class MavCompatibility(IPlugin):
    def show_widget(self, page, machines=None, theid=None):

        if page == 'front':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/front.html')

        if page == 'bu_dashboard':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/id.html')

        if page == 'group_dashboard':
            t = loader.get_template('grahamgilbert/mavcompatibility/templates/id.html')


        not_compatible = machines.filter(condition__condition_name='supported_major_os_upgrades').exclude(condition__condition_name='supported_major_os_upgrades', condition__condition_data__contains='10.9').count()

        c = Context({
            'title': '10.9 Compatibility',
            'not_compatible': not_compatible,
            'page': page,
            'theid': theid
        })
        return t.render(c), 3

    def filter_machines(self, machines, data):
        if data == 'notcompatible':
            machines = machines.filter(condition__condition_name='supported_major_os_upgrades').exclude(condition__condition_name='supported_major_os_upgrades', condition__condition_data__contains='10.9')
            title = 'Macs not compatible with OS X 10.9'
        else:
            machines = None
            title = None

        return machines, title

You’ll notice that our filter on the machines is pretty much identical to what we were looking for before – that’s because we’re looking for the same machines. We’re taking some input (a bunch of machines, and a string that we’ll come back to), and giving back the machine that fit our search and a title to show at the top of the page.

More templating

So, how did we pass that string? How do we even get to the page where a list of the machines is shown?

We need to edit the templates. First off, the template that is show on the front page of Sal:

grahamgilbert/mavcompatibility/templates/front.html
1
2
3
4
5
6
7
<div class="span3">
    <legend>{{ title }}</legend>
        <a href="{% url 'machine_list_front' 'MavCompatibility' 'notcompatible' %}" class="btn btn-danger">
            <span class="bigger"> {{ not_compatible }} </span><br />
            Not Compatible
        </a>
</div>

The only difference here from last time is we’ve filled out the URL. The options for the first part are machine_list_front or machine_list_id – depending on whether you are coming from the front page (all of the Business Units) or from deeper in the application (the machines are limited), then we’re just passing the name of our plugin.

There isn’t a huge amount you need to change for the other template – just tell Sal what type of page you came from (group or business unit) and the ID of the page you came from.

grahamgilbert/mavcompatibility/templates/id.html
1
2
3
4
5
6
7
<div class="span3">
    <legend>{{ title }}</legend>
        <a href="{% url 'machine_list_id' 'MavCompatibility' 'notcompatible' page theid %}" class="btn btn-danger">
            <span class="bigger"> {{ not_compatible }} </span><br />
            Not Compatible
        </a>
</div>

There you go – a simple plugin for Sal. But don’t go away thinking we’re done. Whilst this is functional, it certainly leaves a fair bit to be desired. In the last part of this series, we’ll tidy everything up.