├── .gitignore ├── LICENSE ├── Puppetfile ├── README.md ├── Vagrantfile ├── data ├── common.yaml └── nodes │ └── demo.yaml ├── environment.conf ├── hiera.yaml ├── manifests └── site.pp ├── scripts ├── bootstrap.sh ├── puppify ├── start_vagrant.sh └── vagrant_provision.sh └── site-modules ├── profile ├── files │ └── puppet │ │ ├── papply.sh │ │ └── run-puppet.sh ├── manifests │ ├── ntp.pp │ ├── puppet.pp │ ├── ssh.pp │ ├── sudoers.pp │ ├── timezone.pp │ └── users.pp └── templates │ └── ssh │ └── sshd_config.epp └── role └── manifests └── demo.pp /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .vagrant/ 3 | modules/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 John Arundel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Puppetfile: -------------------------------------------------------------------------------- 1 | forge "http://forge.puppetlabs.com" 2 | 3 | # Modules from the Puppet Forge 4 | mod 'puppetlabs/accounts', '1.1.0' 5 | mod 'puppetlabs/ntp', '6.2.0' 6 | mod 'puppetlabs/stdlib', '4.19.0' 7 | mod 'saz/sudo', '4.2.0' 8 | mod 'saz/timezone', '3.5.0' 9 | mod 'stm/debconf', '2.0.0' 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an example Puppet infrastructure for the [Puppet Beginner's Guide, Third Edition](http://bitfieldconsulting.com/pbg3). It illustrates all the techniques and concepts described in the book, and draws them together into a complete working infrastructure which you can copy and use in your own projects. While you don't have to buy the book to use the demo repo, I'd obviously be very happy if you did. 2 | 3 | To try it out, clone the repo and then run: 4 | 5 | scripts/start_vagrant.sh 6 | 7 | from within the repo directory. (If you don't have Vagrant installed, go to the [Vagrant Downloads page](https://www.vagrantup.com/downloads.html)). 8 | 9 | Alternatively, to bootstrap a server, all you will need is the IP address or DNS name of the target server. Run the following command from the Puppet repo, replacing `TARGET_SERVER` with the address or name of the server, and `HOSTNAME` with the hostname that you want to set (for example `demo`): 10 | 11 | scripts/puppify TARGET_SERVER HOSTNAME 12 | 13 | The demo repo is built on a skeleton Puppet control repo available from [the Puppet GitHub account](https://github.com/puppetlabs/control-repo). 14 | 15 | It adds everything required for a typical Puppet infrastructure, including user accounts and SSH keys, SSH and sudoers config, timezone and NTP settings, Hiera data, resources to automatically update and run Puppet, and a bootstrap script for bringing new servers under Puppet management. It also includes a Vagrantfile so you can try out the repo on a Vagrant virtual machine. 16 | 17 | You can also find all the code examples from the book in the [Puppet Beginner's Guide example repo](https://github.com/bitfield/puppet-beginners-guide-3). 18 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | # If you have trouble running the 64-bit Vagrant VM, try this instead: 16 | # config.vm.box = "ubuntu/xenial32" 17 | config.vm.box = "ubuntu/xenial64" 18 | 19 | # Disable automatic box update checking. If you disable this, then 20 | # boxes will only be checked for updates when the user runs 21 | # `vagrant box outdated`. This is not recommended. 22 | # config.vm.box_check_update = false 23 | 24 | # Create a forwarded port mapping which allows access to a specific port 25 | # within the machine from a port on the host machine. In the example below, 26 | # accessing "localhost:8080" will access port 80 on the guest machine. 27 | config.vm.network "forwarded_port", guest: 80, host: 8080 28 | 29 | # Create a private network, which allows host-only access to the machine 30 | # using a specific IP. 31 | # config.vm.network "private_network", ip: "192.168.33.10" 32 | 33 | # Create a public network, which generally matched to bridged network. 34 | # Bridged networks make the machine appear as another physical device on 35 | # your network. 36 | # config.vm.network "public_network" 37 | 38 | # Share an additional folder to the guest VM. The first argument is 39 | # the path on the host to the actual folder. The second argument is 40 | # the path on the guest to mount the folder. And the optional third 41 | # argument is a set of non-required options. 42 | config.vm.synced_folder ".", "/etc/puppetlabs/code/environments/production" 43 | 44 | config.vm.hostname = "demo" 45 | 46 | # Provider-specific configuration so you can fine-tune various 47 | # backing providers for Vagrant. These expose provider-specific options. 48 | # Example for VirtualBox: 49 | # 50 | # config.vm.provider "virtualbox" do |vb| 51 | # # Display the VirtualBox GUI when booting the machine 52 | # vb.gui = true 53 | # 54 | # # Customize the amount of memory on the VM: 55 | # vb.memory = "1024" 56 | # end 57 | # 58 | # View the documentation for the provider you are using for more 59 | # information on available options. 60 | 61 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 62 | # such as FTP and Heroku are also available. See the documentation at 63 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 64 | # config.push.define "atlas" do |push| 65 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 66 | # end 67 | 68 | # Enable provisioning with a shell script. Additional provisioners such as 69 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 70 | # documentation for more information about their specific syntax and use. 71 | # config.vm.provision "shell", inline: <<-SHELL 72 | # apt-get update 73 | # apt-get install -y apache2 74 | # SHELL 75 | config.vm.provision "shell", path: "scripts/vagrant_provision.sh" 76 | end 77 | -------------------------------------------------------------------------------- /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | users: 3 | 'john': 4 | comment: 'John Arundel' 5 | uid: '1010' 6 | sshkeys: 7 | - 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA3ATqENg+GWACa2BzeqTdGnJhNoBer8x6pfWkzNzeM8Zx7/2Tf2pl7kHdbsiTXEUawqzXZQtZzt/j3Oya+PZjcRpWNRzprSmd2UxEEPTqDw9LqY5S2B8og/NyzWaIYPsKoatcgC7VgYHplcTbzEhGu8BsoEVBGYu3IRy5RkAcZik= john@susie' 8 | 'bridget': 9 | comment: 'Bridget X. Zample' 10 | uid: '1011' 11 | sshkeys: 12 | - 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCiTvHgzkf9TGBIpnIUBRbsWaRNBqzrLY/OoQpzqprGFLuTzzb8MaGI9Q6GPIhil5HyXE7PWPyuwcBA4mJhIHBXWqQC5o59cdtrkgJwAybSK7z5oVt67B0qieDG/cbasxr52nMAJC3dBEF4W1s69VSq5+wLFD01iW= bridget' 13 | allow_users: 14 | - 'john' 15 | - 'bridget' 16 | - 'ubuntu' 17 | sudoers: 18 | - 'john' 19 | - 'bridget' 20 | - 'ubuntu' 21 | classes: 22 | - profile::ntp 23 | - profile::puppet 24 | - profile::ssh 25 | - profile::sudoers 26 | - profile::timezone 27 | - profile::users 28 | -------------------------------------------------------------------------------- /data/nodes/demo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | classes: 3 | - role::demo 4 | -------------------------------------------------------------------------------- /environment.conf: -------------------------------------------------------------------------------- 1 | modulepath = "./modules:./site-modules:$basemodulepath" 2 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | defaults: 4 | # The default value for "datadir" is "data" under the same directory as the hiera.yaml 5 | # file (this file) 6 | # When specifying a datadir, make sure the directory exists. 7 | # See https://docs.puppet.com/puppet/latest/environments.html for further details on environments. 8 | # datadir: data 9 | # data_hash: yaml_data 10 | hierarchy: 11 | - name: "Per-node data (yaml version)" 12 | path: "nodes/%{facts.hostname}.yaml" 13 | - name: "Common data" 14 | path: "common.yaml" 15 | -------------------------------------------------------------------------------- /manifests/site.pp: -------------------------------------------------------------------------------- 1 | include(lookup('classes', Array[String], 'unique')) 2 | -------------------------------------------------------------------------------- /scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PUPPET_REPO=$1 3 | HOSTNAME=$2 4 | BRANCH=$3 5 | if [ "$#" -ne 3 ]; then 6 | echo "Usage: $0 PUPPET_REPO HOSTNAME BRANCH" 7 | exit 1 8 | fi 9 | hostname ${HOSTNAME} 10 | echo ${HOSTNAME} >/etc/hostname 11 | source /etc/lsb-release 12 | apt-key adv --fetch-keys http://apt.puppetlabs.com/DEB-GPG-KEY-puppet 13 | wget http://apt.puppetlabs.com/puppetlabs-release-${DISTRIB_CODENAME}.deb 14 | dpkg -i puppetlabs-release-${DISTRIB_CODENAME}.deb 15 | apt-get update 16 | apt-get -y install git puppet-agent 17 | cd /etc/puppetlabs/code/environments 18 | mv production production.orig 19 | git clone ${PUPPET_REPO} production 20 | cd production 21 | git checkout ${BRANCH} 22 | /opt/puppetlabs/puppet/bin/gem install r10k --no-rdoc --no-ri 23 | /opt/puppetlabs/puppet/bin/r10k puppetfile install --verbose 24 | /opt/puppetlabs/bin/puppet apply --environment=production /etc/puppetlabs/code/environments/production/manifests/ 25 | -------------------------------------------------------------------------------- /scripts/puppify: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PUPPET_REPO=https://github.com/bitfield/control-repo-3.git 3 | IDENTITY="-i /Users/john/.ssh/pbg.pem" 4 | if [ "$#" -lt 2 ]; then 5 | cat < stopped, # Puppet runs from cron 5 | enable => false, 6 | } 7 | 8 | cron { 'run-puppet': 9 | ensure => present, 10 | command => '/usr/local/bin/run-puppet', 11 | minute => '*/10', 12 | hour => '*', 13 | } 14 | 15 | file { '/usr/local/bin/run-puppet': 16 | source => 'puppet:///modules/profile/puppet/run-puppet.sh', 17 | mode => '0755', 18 | } 19 | 20 | file { '/usr/local/bin/papply': 21 | source => 'puppet:///modules/profile/puppet/papply.sh', 22 | mode => '0755', 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /site-modules/profile/manifests/ssh.pp: -------------------------------------------------------------------------------- 1 | # Manage sshd config 2 | class profile::ssh { 3 | ensure_packages(['openssh-server']) 4 | 5 | file { '/etc/ssh/sshd_config': 6 | content => epp('profile/ssh/sshd_config.epp', { 7 | 'allow_users' => lookup('allow_users', Array[String], 'unique'), 8 | }), 9 | notify => Service['ssh'], 10 | } 11 | 12 | service { 'ssh': 13 | ensure => running, 14 | enable => true, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /site-modules/profile/manifests/sudoers.pp: -------------------------------------------------------------------------------- 1 | # Manage user privileges 2 | class profile::sudoers { 3 | sudo::conf { 'secure_path': 4 | content => 'Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin"', 5 | priority => 0, 6 | } 7 | $sudoers = lookup('sudoers', Array[String], 'unique', []) 8 | $sudoers.each | String $user | { 9 | sudo::conf { $user: 10 | content => "${user} ALL=(ALL) NOPASSWD: ALL", 11 | priority => 10, 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /site-modules/profile/manifests/timezone.pp: -------------------------------------------------------------------------------- 1 | # Set the time zone for all nodes 2 | class profile::timezone { 3 | class { 'timezone': 4 | timezone => 'Etc/UTC', 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /site-modules/profile/manifests/users.pp: -------------------------------------------------------------------------------- 1 | # Set up users 2 | class profile::users { 3 | lookup('users', Hash, 'hash').each | String $username, Hash $attrs | { 4 | accounts::user { $username: 5 | * => $attrs, 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /site-modules/profile/templates/ssh/sshd_config.epp: -------------------------------------------------------------------------------- 1 | <%- | Array[String] $allow_users | -%> 2 | # File is managed by Puppet 3 | 4 | AcceptEnv LANG LC_* 5 | ChallengeResponseAuthentication no 6 | GSSAPIAuthentication no 7 | PermitRootLogin no 8 | PrintMotd no 9 | Subsystem sftp internal-sftp 10 | AllowUsers <%= join($allow_users, ' ') %> 11 | UseDNS no 12 | UsePAM yes 13 | X11Forwarding yes 14 | -------------------------------------------------------------------------------- /site-modules/role/manifests/demo.pp: -------------------------------------------------------------------------------- 1 | # Be the demo node 2 | class role::demo { 3 | notice("Hi, I'm the demo node!") 4 | } 5 | --------------------------------------------------------------------------------