Setting Up Sal for Munki Reporting

Munki is an incredible tool for Mac deployment. Unlike other MDM or management software, it’s focused on a single purpose – delivering files from a central repository to a client machine based on some criteria. It doesn’t include features like inventory tracking, reporting, queries, or other items that you might find in commercial management solutions and suites.

Since Munki is open source and has a thriving community of dedicated users, it’s no surprise that solutions have been developed to add this kind of functionality to Munki. There are a number of options out there, but the one I’m going to focus on is the open-source version of Sal, written by Graham Gilbert.

Sal is Django-based web app that collects information from Munki clients whenever they run the Munki software. It allows for convenient access to inventory collection, which can give us an idea of what OSes we’re seeing on our clients, what software packages are installed, what updates are still pending, etc. Information and reporting is always good, and Sal does a great job.

Sal has solid documentation for using it and getting started, so I won’t reproduce all of that here. Instead, I’m just going to go through a simple setup of the Sal Docker container and installation on client devices, from start to finish.

Getting Sal running with Docker:

As suggested by the repo instructions, the Docker image is the officially recommended approach for setting up Sal.

First, you’ll need to get Graham’s customized Postgres database running. His customization allows for easy database creation by passing in environmental variables, which I used in a previous blog post about customizing Postgres.

I prefer to use data containers, to keep my data portable and not tied to a host. Here’s the data container for Sal’s Postgres database:
docker run --name "sal-db-data" -d --entrypoint /bin/echo grahamgilbert/postgres Data-only container for postgres-sal

Then the database:
docker run --name "postgres-sal" -d --volumes-from sal-db-data -e DB_NAME=sal -e DB_USER=saldbadmin -e DB_PASS=password --restart="always" grahamgilbert/postgres

The -e environment variables allow us to specify the database name, user, and password for access.

Now run Sal itself:
docker run -d --name sal -p 80:8000 --link postgres-sal:db -e DOCKER_SAL_TZ="America/Los_Angeles" -e ADMIN_PASS=password -e DB_NAME=sal -e DB_USER=saldbadmin -e DB_PASS=password macadmins/sal

Specify real passwords for use in production, obviously. I’ve also passed in the DOCKER_SAL_TZ timezone environmental variable to change it to PST, since I don’t live in London.

Configure Sal:

Open your web browser to http://localhost/ on the Docker host to log into Sal – using the password you specified earlier.

Create a Business Unit.

Create at least one Machine Group. Each Machine Group will generate a “key,” which you’ll need to add to the clients.

Set up the clients:

Install the provided Sal-scripts.pkg onto an OS X client with Munki installed.

If Sal has not been added to your DNS (if you’re testing this for the first time, this will almost certainly be true), you’ll need to modify /etc/hosts on the client to add in your Docker host as “sal”.

Next, you’ll need to add the proper client configuration to your OS X clients.

Change the URL to http://sal for this example, but you’ll need to set the key to the one of the keys you generated from a Machine Group:

defaults write /Library/Preferences/com.salsoftware.sal ServerURL http://sal
defaults write /Library/Preferences/com.salsoftware.sal key e4up7l5pzaq7w4x12en3c0d5y3neiutlezvd73z9qeac7zwybv3jj5tghhmlseorzy5kb4zkc7rnc2sffgir4uw79esdd60pfzfwszkukruop0mmyn5gnhark9n8lmx9

Now, you can simply run Munki’s managedsoftwareupdate to get Sal reporting:
sudo managedsoftwareupdate

At then end of the Munki run, you should see output similar to this:

    Performing postflight tasks...
    postflight stdout: 
Sal report submmitted for Mac.local.

Customizing Postgres in Docker

The Docker registry provides an official Postgres container that can be used in a wide variety of situations. However, at its default configuration, Postgres does not accept remote connections from other IP addresses.

This presents a problem when used with something like WebHelpDesk, which needs a Postgres database to slave off of.

To address this issue, we’re going to create a new Docker container based on Postgres that changes the appropriate settings in pg_hba.conf.

The repo for this project can be found here.

The Dockerfile

FROM postgres
ENV DB_NAME database
ENV DB_PASS password
ADD /docker-entrypoint-initdb.d/
RUN chmod 755 /docker-entrypoint-initdb.d/
ADD /docker-entrypoint-initdb.d/
RUN chmod 755 /docker-entrypoint-initdb.d/

FROM postgres
Start with the official Postgres image.

ENV DB_NAME database
ENV DB_PASS password

These three environment variables are going to be passed to the script. This technique was originally developed by Graham Gilbert for his Postgres docker container for the use of Sal. The purpose of these environment variables and the script are to create a starting database for us to use.

ADD /docker-entrypoint-initdb.d/
RUN chmod 755 /docker-entrypoint-initdb.d/

Add in the script to setup remote connections. The location of this script, /docker-entrypoint-initdb.d/ is a special directory provided by the default Postgres container to extend additional tasks on startup. All scripts located in this directory get run on startup – thus we can add Postgres configurations automatically. Chmod sets correct execute permissions on the script.

The script contains only one line of code:
sed -i '/host all all\/32 trust/a host all all\/16 trust' /var/lib/postgresql/data/pg_hba.conf
This sed statement modifies /var/lib/postgresql/data/pg_hba.conf to allow for remote access connections – see documentation here. It simply adds to the list of trusted access locations that are allowed to make database updates. The range is for Docker IP addresses – thus giving any other Docker container necessary access. This is specifically intended for use with WebHelpDesk, which requires it.

ADD /docker-entrypoint-initdb.d/
RUN chmod 755 /docker-entrypoint-initdb.d/

This script, as mentioned previously, was shamelessly stolen from Graham Gilbert – thanks for that. It sets up the initial database with correct role and privileges, using the environment variables.

The script looks like this:

TEST=`gosu postgres postgres --single <<- EOSQL
   SELECT 1 FROM pg_database WHERE datname='$DB_NAME';
if [[ $TEST == "1" ]]; then
    # database exists
    # $? is 0
    exit 0
gosu postgres postgres --single<<- EOSQL
gosu postgres postgres --single <<- EOSQL
gosu postgres postgres --single <<- EOSQL
echo ""
echo "******DOCKER DATABASE CREATED******"

The script executes on startup of the Docker container, and sets up the database we need to use by reading in the environment variables we pass in using the -e argument to docker run. We’ll see this in action later.

With this Dockerfile, and with our scripts ready, the container is ready to be built:
docker build -t name/postgres .
Or you can pull the automated build from the registry:
docker pull macadmins/postgres

Using the new Postgres database:

Running the database container by itself without anything special is simple:
docker run -d -e DB_NAME=db -e DB_USER=admin -e DB_PASS=password macadmins/postgres

Of course, we want to probably do a bit more than that. For example, it’s much easier to test and work with it if we can name it something we can refer to easily:
docker run -d -e DB_NAME=db -e DB_USER=admin -e DB_PASS=password --name postgres-test macadmins/postgres

You can then check to see if your database exists the way you expect by using psql:
docker exec -it postgres-test psql --dbname db --username admin
You’ll be at the psql prompt, and you can type \l to list all the databases. You’ll see the one we created, db, in the list (which does not format well, so I haven’t tried to copy and paste it here).

Looks good, right? Now the database is ready to be used by any application or service by linking the customized Postgres container to another one. We’ll do an example of this in another post.

An Alternative

Dockerfiles are pretty neat things. They allow us to do fun stuff, like take someone’s else’s image as a base and build more stuff on top of it. This is the basis for nearly all of the images I use – find someone else who did the hard work, like installing Nginx, or Apache, or a database like Postgres or MySQL, and then add the pieces I need to get the results I want.

I pointed out earlier that Graham Gilbert already has a great Postgres container that incorporates the database setup script. All I’m doing differently is configuring Postgres to allow remote connections as well.

So as an alternative to customizing from the base Postgres image, we can try just adding our changes to Graham’s Postgres container. It makes for a smaller Dockerfile:
FROM grahamgilbert/postgres
ENV DB_NAME database
ENV DB_PASS password
ADD /docker-entrypoint-initdb.d/
RUN chmod 755 /docker-entrypoint-initdb.d/

We can build this alternative version with a new tag:
docker build nmcspadden/postgres:alt .

Which is better?

If we look purely at image file size, we see that both our original customized version, and the version based off of Graham’s come out the same:
bash-3.2$ docker images
nmcspadden/postgres alt 21bbfdc5857f 49 seconds ago 213.4 MB
macadmins/postgres latest 2e2d24011a32 2 days ago 213.4 MB

So there’s no particular size advantage to doing it the “alternative” way, except that the Dockerfile is a bit smaller and we do less wheel-reinvention.

But there are some pros and cons, and it depends on what you want to do.

By using Graham’s Postgres container, it’s easy for us to set up an automated build on the Docker registry that rebuilds ours every time Graham rebuilds his. If he ever updates his database setup script, or updates his container for any reason, ours gets rebuilt to incorporate his changes. This is both a pro and a con, because it means that our Postgres database’s behavior might change in the latest build without our knowledge (or approval). If Graham decides to change his container to do something different at startup, and we’re designing our Postgres databases around assuming a specific behavior happens at startup every time, we could be in trouble if an unexpected change occurs.

On the other hand, if it turns out we do want to incorporate Graham’s changes automatically, using his image as the basis for our build saves a lot of time – I don’t have to make any changes to my Dockerfile to upstream those changes into our builds.

The shorter answer for me is that I need to make sure the database setup occurs the way I expect it to every time I use it – and thus I manually recreate Graham’s setup in my own Dockerfile. That way Graham can make any changes to his Dockerfile he wants and it won’t affect our own builds.

Ultimately, we end up with a Postgres database (which accepts remote connections on startup) that we can easily use again and again.