Building a puppetmaster with… puppet

The last couple of months I’ve been working on building a new Puppetmaster for our network. The running setup is filled with leftovers from when the infrastructure was bootstrapped one-and-a-half year ago, before there was any notion of modules or environments. I’ve tried to convert my existing setup to use modules and environments, but it’s really hard.

So I’ve actually chosen to build a new setup parallel to the existing one, and slowly convert what I have to the new setup. I’ve also chosen to go with another solution that the Apache/Mongrel based one I’m running now. Recently Christian Hofstaedtler made it possible to use Phusion Passenger together with Puppet, and I wanted to use that in my new setup. I also wanted to make a shift from Mercurial to Git, for reasons I won’t delve into here.

So, even though it’s not used that much, you can use the “puppet” executable to run puppet manifests, without having a puppetmaster. It’s not all manifests that you’re able to use that way, but constructed the right way, it’s possible. So I chose to write a puppet-bootstrap manifest, which I will present in this blogpost.

I opted for dividing it up into 3 classes: One for installing and puppetmaster, apache, passenger and all supporting software, and make sure that the right directories, configfiles and services was in place. Then one for setting up a server Git repo and lastly one for setting up our admin users so it’s possible for them to connect to the server, and publish the complete puppet catalog through Git.

Class puppetbootstrap

My puppetbootstrap class ended up looking like this:

# Install puppetmaster, apache2 and Phusion Passenger
# Set up the directories, configure apache and Passenger
# and ensure that the apache service is running
class puppetbootstrap {
	package {
		[ "apache2", "puppetmaster", "rubygems",
		  "apache2-threaded-dev", "ruby1.8-dev", "make",
		  "gcc", "g++" ]:
			ensure	=> "installed";
			ensure		=> "0.4.0",
			provider	=> "gem",
			require		=> Package["rubygems"];
			ensure		=> "installed",
			provider	=> "gem",
			require		=> [ Package["rubygems"], Package["apache2-threaded-dev"],
					Package["ruby1.8-dev"], Package["make"], Package["gcc"] ];
	exec {
			command	=> "a2enmod ssl",
			require	=> Package["apache2"];
			command	=> "a2enmod headers",
			require	=> Package["apache2"];
			command	=> "a2dissite 000-default",
			require	=> Package["apache2"];
	file {
		[ "/srv/www", "/srv/www/${fqdn}", "/srv/www/${fqdn}/public",
		  "/srv/www/${fqdn}/tmp" ]:
			ensure	=> "directory",
			require	=> [ Package["rack"], Package["puppetmaster"] ],
	file { "/etc/apache2/conf.d/${fqdn}.conf":
		source	=> "file:///root/apache.conf",
		require	=> Package["apache2"],
		notify	=> Service["apache2"],
	file { "/srv/www/${fqdn}/":
		source	=> "file:///root/",
		owner		=> "puppet",
		group		=> "puppet",
		require	=> Package["rack"],
		notify	=> Service["apache2"],
	service { "apache2":
		ensure			=> "running",
		enable			=> "true",
		hasstatus	=> "false"

Class gitrepo

My gitrepo class is simple:

# Setting up the top dir for the git
# repos
class gitrepo {
	package { "git-core":
		ensure	=> "installed",
	group { "puppeteers":
		ensure	=> "present",
	file { "/srv/gitrepos":
		ensure	=> "directory",
		owner	=> "root",
		mode	=> "775",
		group	=> "puppeteers",
		require	=> Group["puppeteers"],

Class admins

And lastly my admins class:

class admins {
	group { [ "adm", "adminuser" ]:
		ensure	=> "present",
	user { "adminuser":
		ensure	=> "present",
		home	=> "/home/adminuser",
		managehome	=> "true",
		gid	=> "adminuser",
		shell	=> "/bin/bash",
		groups	=> [ "adm", "puppeteers" ],
		require	=> [ Group["puppeteers"], Group["adm"] ],
	file { "/home/adminuser/.ssh":
		ensure	=> "directory",
		mode	=> "700",
		owner	=> "adminuser",
		group	=> "adminuser",
		require	=> User["adminuser"],
	ssh_authorized_key { "adminuser@laptop":
		ensure	=> "present",
		key	=> "AAAA....",
		target	=> "/home/adminuser/.ssh/authorized_keys",
		user	=> "adminuser",
		type	=> "ssh-rsa",
		require	=> Group["adminuser"],

Now I have put all these classes into one file called setup.pp, and included the classes like this:

include puppetbootstrap
include gitrepo
include admins

And to ensure that exec is behaving correctly:

# Set some sane defaults for Exec
Exec {
	path	=> "/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/bin:/usr/local/sbin"

Now to make this work I only need two configfiles, that Christian already has provided on the Puppet wiki, but I’ll provide them here in my adapted form:

# Author: Christian Hofstaedtler <>
# Copyright (c) 2007 Luke Kanies, 2008 Christian Hofstaedtler
# This file is mostly based on puppetmasterd, which is part of
# the standard puppet distribution.

require 'rack'
require 'puppet'
require 'puppet/network/http_server/rack'

# startup code stolen from bin/puppetmasterd
Puppet::Util::Log.level = :info
# A temporary solution, to at least make the master work for now.
Puppet::Node::Facts.terminus_class = :yaml
# Cache our nodes in yaml.  Currently not configurable.
Puppet::Node.cache_class = :yaml

# The list of handlers running inside this puppetmaster
handlers = {
	:Status => {},
	:FileServer => {},
	:Master => {},
	:CA => {},
	:FileBucket => {},
	:Report => {}

# Fire up the Rack-Server instance
server =

# prepare the rack app
app = proc do |env|

# Go.
run app

I haven’t edited that file in any way.


And finally the config file for Apache:

Listen 8140
<VirtualHost *:8140>
	LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-2.0.3/ext/apache2/
	PassengerRoot /var/lib/gems/1.8/gems/passenger-2.0.3
	PassengerRuby /usr/bin/ruby1.8

	SSLEngine on
	SSLCertificateFile      /var/lib/puppet/ssl/certs/
	SSLCertificateKeyFile   /var/lib/puppet/ssl/private_keys/
	SSLCertificateChainFile /var/lib/puppet/ssl/ca/ca_crt.pem
	SSLCACertificateFile    /var/lib/puppet/ssl/ca/ca_crt.pem
	# CRL checking should be enabled; if you have problems with Apache complaining about the CRL, disable the next line
	SSLCARevocationFile     /var/lib/puppet/ssl/ca/ca_crl.pem
	SSLVerifyClient optional
	SSLVerifyDepth  1
	SSLOptions +StdEnvVars

	# The following client headers allow the same configuration to work with Pound.
	RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
	RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
	RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e

	RackAutoDetect On
	DocumentRoot /srv/www/
	<Directory /srv/www/>
		Options None
		AllowOverride None
		Order allow,deny
		allow from all

And that should be it. Now all I did was running

puppet --verbose setup.pp

and watch puppet “do the right thing”. The only thing I couldn’t automate, was running “passenger-install-apache2-module” – you have to do that manually.

Feel free to make any comments.

  Relaterede indlæg


  1. _lunix_  November 14, 2008

    This should help you out. It emulates hitting enter twice at the prompts.

    printf “nn” | bin/passenger-install-apache2-module

    This was the only thing that stopped me from automating the install here.

    Mick Pollard

  2. Juri Rischel Jensen  November 14, 2008

    Thanks Mick! I’ve never got around trying that approach. I’ll add that to my manifest then…

  3. Marcello  November 2, 2010

    /usr/bin/passenger-install-apache2-module –auto
    is also an option.


Add a Comment