view manifests/templates.pp @ 468:8a3c3eb60fdd default tip

Fix IPs on Ubuntu again )hopefully) We need the "auto" line for each alias. Also broke it out into separate files, because it's easier to extend
author IBBoard <dev@ibboard.co.uk>
date Sat, 06 Apr 2024 19:01:04 +0100
parents 9ae251433074
children
line wrap: on
line source

# Make sure packages come after their repos
File<| tag == 'repo-config' |>
-> anchor { 'Repo-config': }
-> YumRepo<| |>
-> Apt::Source<| |>
-> anchor { 'Repos': }
-> Package<| |>

# Make sure all files are in  place before starting services
# FIXME: Title matches are to fix a dependency cycle
File<| tag != 'post-service' and title != '/etc/sysconfig/ip6tables' and title != '/etc/sysconfig/iptables' |>
-> anchor { 'Pre-Service Files': }
-> Service<| |>

# Set some shortcut variables
#$os = $operatingsystem
$osver = $operatingsystemmajrelease
$server = ''


class basenode {
	include sudo

	include defaultusers
	include logwatch

	file { '/etc/puppet/hiera.yaml':
		ensure => present,
		content => "
# Let the system set defaults
version: 5
",
	}
	service { 'puppet':
		ensure => stopped,
		enable => false,
	}
	if $operatingsystem == 'Ubuntu' {
		package { 'locales':
			ensure => present
		} ->
		file { '/etc/locale.gen':
			ensure => present,
			content => "en_GB.UTF-8 UTF-8",
			notify => Exec['Regen locales']
		}
		exec { 'Regen locales':
			command => 'locale-gen',
			refreshonly => true
		}
		# Don't waste space with Snap and do everything properly with system packages
		[ 'lxd', 'core18', 'core20', 'snapd'].each |$snap| {
			exec { "remove $snap snap package":
				command => "snap remove $snap",
				onlyif => "which snap && snap list $snap",
				tag => 'snap',
			}
		}
		Exec<| tag == 'snap' |> ->
		package { 'snapd':
			ensure => purged,
		}
	}
}

class basevpsnode (
	$primary_ip,
	$gateway_ip = undef,
	$proxy_4to6_ip_prefix = undef,
	$proxy_upstream = undef,
	$nat64_ranges = [],
	$mailserver,
	$imapserver,
	$mailrelays = [],
	$firewall_cmd = 'iptables',
	) {

	if $firewall_cmd == 'iptables' {
		class { 'vpsfirewall':
			fw_protocol => $primary_ip =~ Stdlib::IP::Address::V6 ? { true => 'IPv6', default => 'IPv4'},
		}
	}

	#VPS is a self-mastered Puppet machine, so bodge a Hosts file
	if $primary_ip =~ Stdlib::IP::Address::V6 {
		$lo_ip = '::1'
	} else {
		$lo_ip = '127.0.0.1'
	}
	file { '/etc/hosts':
		ensure => present,
		content => "${lo_ip}   localhost\n${primary_ip} ${fqdn}",
	}

	if $proxy_4to6_ip_prefix != undef {
		# …:1 to …:9 for websites, …:10 for mail
		$ipv6_addresses = Integer[1, 10].map |$octet| { "$proxy_4to6_ip_prefix:$octet" }
		if $operatingsystem == 'Ubuntu' {
			# Ubuntu can't parse the existing file, so we need to brute-force it with a template
			file { "/etc/network/interfaces.d/eth0":
				content => epp('privat/eth0.epp',
					{
						default_address => $primary_ip,
						gateway_address => $gateway_ip,
					}
				),
				notify => Exec["restart_networking"],
			}
			# …:1 to …:9 for websites, …:10 for mail
			Integer[1, 10].each |$octet| {
				file { "/etc/network/interfaces.d/eth0:$octet":
					content => epp('privat/eth0-alias.epp',
						{
							prefix_address => $proxy_4to6_ip_prefix,
							octet => $octet,
						}
					),
					notify => Exec["restart_networking"],
				}
			}
		}
		else {
			# …:1 to …:9 for websites, …:10 for mail
			$ipv6_addresses = Integer[1, 10].map |$octet| { "$proxy_4to6_ip_prefix:$octet" }
			$ipv6_secondaries = join($ipv6_addresses, " ")

			augeas {'IPv6 secondary addresses':
				context => "/files/etc/sysconfig/network-scripts/ifcfg-eth0",
				changes => "set IPV6ADDR_SECONDARIES '\"$ipv6_secondaries\"'",
				notify => Exec["restart_networking"],
			}
		}

		Exec { 'restart_networking':
			command => 'systemctl restart networking',
			refreshonly => true,
		}
	}

	require repos
	include basenode
	include privat
	include dnsresolver
	include ::privat::params
	class { '::ssh':
		sshd_config_port => $::privat::params::ssh_port[$::fqdn]
	}
	include vcs::server
	include vcs::client
	class { 'webserver':
		primary_ip => $primary_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_4to6_mask => 124,
		proxy_upstream => $proxy_upstream,
	}
	include cronjobs
	include logrotate
	class { 'fail2ban':
		firewall_cmd => $firewall_cmd,
	}
	include tools
	class { 'email':
		mailserver => $mailserver,
		imapserver => $imapserver,
		mailserver_ip => $primary_ip,
		proxy_ip => $proxy_4to6_ip_prefix != undef ? { true => "${proxy_4to6_ip_prefix}:10", default => undef },
		proxy_upstream => $proxy_upstream,
		nat64_ranges => $nat64_ranges,
		mailrelays => $mailrelays,
	}
}

## Classes to allow facet behaviour using preconfigured setups of classes

class vpsfirewall ($fw_protocol) {
	resources { "firewall":
		purge => false,
	}
	class { "my_fw":
		ip_version => $fw_protocol,
	}
	# Control what does and doesn't get pruned in the main filter chain
	firewallchain { "INPUT:filter:$fw_protocol":
		purge => true,
		ignore => [
			'-j f2b-[^ ]+$',
			'^(:|-A )f2b-',
			'--comment "Great Firewall of China"',
			'--comment "Do not purge',
			],
	}
	if ($fw_protocol != "IPv6") {
		firewall { '010 Whitelist Googlebot':
			source => '66.249.64.0/19',
			dport => [80,443],
			proto => tcp,
			action => accept,
		}
		# Block a spammer hitting our contact forms (also on StopForumSpam list A LOT)
		firewall { '099 Blacklist spammers 1':
			source => '107.181.78.172',
			dport => [80, 443],
			proto => tcp,
			action => 'reject',
		}
		firewall { '099 Blacklist IODC bot':
			# IODC bot makes too many bad requests, and contact form is broken
			# They don't publish a robots.txt name, so firewall it!
			source => '86.153.145.149',
			dport => [ 80, 443 ],
			proto => tcp,
			action => 'reject',
		}
		firewall { '099 Blacklist Baidu Brazil':
			#Baidu got a Brazilian netblock and are hitting us hard
			#Baidu doesn't honour "crawl-delay" in robots.txt
			#Baidu gets firewalled
			source => '131.161.8.0/22',
			dport => [ 80, 443 ],
			proto => tcp,
			action => 'reject',
		}
	}
	firewallchain { "GREATFIREWALLOFCHINA:filter:$fw_protocol":
		ensure => present,
	}
	firewall { '050 Check our Great Firewall Against China':
		chain => 'INPUT',
		jump => 'GREATFIREWALLOFCHINA',
	}
	firewallchain { "Fail2Ban:filter:$fw_protocol":
		ensure => present,
	}
	firewall { '060 Check Fail2Ban':
		chain => 'INPUT',
		jump => 'Fail2Ban',
	}
}

class dnsresolver {
	package { 'unbound':
		ensure => present,
	}
	package { 'named':
		ensure => absent,
	}

	service { 'named':
		ensure => stopped,
		enable => false,
	}
	service { 'unbound':
		ensure => running,
		enable => true,
	}

	file { '/etc/named.conf':
		ensure => absent,
	}
	file { '/etc/unbound/unbound.conf':
		ensure => present,
		source => [
                        "puppet:///common/unbound.conf-${::hostname}",
                        "puppet:///common/unbound.conf",
                ],
		require => Package['unbound'],
		notify => Service['unbound'],
	}
	file { ['/etc/NetworkManager', '/etc/NetworkManager/conf.d']:
		ensure => directory
	}
	file { '/etc/NetworkManager/conf.d/local-dns-resolver.conf':
		ensure => present,
		content => "[main]
dns=none",
	}

	file { '/etc/sysconfig/named':
		ensure => absent,
	}
	file { '/etc/resolv.conf':
		ensure => file,
		# "ipaddress" key only exists for machines with IPv4 addresses
		content => has_key($facts, 'ipaddress') ? { true => "nameserver 127.0.0.1", default => "nameserver ::1" },
		require => Service['unbound'],
		tag => 'post-service',
	}
}

class repos {
	include ::python::params
	if $operatingsystem == 'CentOS' {
		yumrepo { 'epel':
			mirrorlist => 'https://mirrors.fedoraproject.org/metalink?repo=epel-$releasever&arch=$basearch',
			descr => "Extra Packages for Enterprise Linux",
			enabled => 1,
			failovermethod => absent,
			gpgcheck => 1,
			gpgkey => "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$osver",
		}
		file { "/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$osver":
			ensure => present,
			source => "puppet:///common/RPM-GPG-KEY-EPEL-$osver",
			tag => 'repo-config',
		}
		yumrepo { 'ibboard':
			baseurl => 'https://download.opensuse.org/repositories/home:/IBBoard:/server/CentOS_$releasever/',
			descr => 'Extra packages from IBBoard',
			enabled => 1,
			gpgcheck => 1,
			gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ibboard',
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-ibboard':
			ensure => present,
			source => 'puppet:///common/RPM-GPG-KEY-ibboard',
			tag => 'repo-config',
		}
		yumrepo { 'webtatic':
			ensure => absent,
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-webtatic-andy':
			ensure => absent,
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-webtatic-el7':
			ensure => absent,
		}
		# Python requires the `devel` package on CentOS, but by default the module tries to uninstall it
		$dev = 'present'
	}
	else {
		# Other distros can take the default devel status
		$dev = $::python::params::dev
		# CentOS-like distros have PKI by default. Others need it creating.
		file { '/etc/pki/':
			ensure => directory,
		}
		file { '/etc/apt/trusted.gpg.d/home_IBBoard_server.gpg':
			# https://download.opensuse.org/repositories/home:/IBBoard:/server/xUbuntu_22.04/Release.key
			# Then GPG exported with `cat Release.key | gpg --dearmour -o common/home_IBBoard_server.gpg
			source => 'puppet:///common/home_IBBoard_server.gpg'
		}

		apt::source {
			'ibboard':
				location => 'http://download.opensuse.org/repositories/home:/IBBoard:/server/xUbuntu_22.04/',
				release => '/',
				repos => '',
				keyring => '/etc/apt/trusted.gpg.d/home_IBBoard_server.gpg',
		}
	}

	if $operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0 {
		# The following may possibly work to ensure a CentOS Streams install.
		# Or it might fail for inexplicable reasons.
		# FIXME: Should be "centos-release-stream" to migrate (provides repos), but then that gets replaced by centos-stream-release,
		# which Puppet doesn't recognise as the same and so keeps trying to re-install. May need an "unless" or maybe "allow_virtual"
		package { 'centos-stream-release':
			ensure => installed,
			notify => Exec['migrate to streams'];
		}
		exec { 'migrate to streams':
			command => '/usr/bin/dnf swap centos-linux-repos centos-stream-repos; /usr/bin/dnf distro-sync -y',
			refreshonly => true
		}
	}

	class { 'python':
		ensure => 'present',
		version => 'python3',
		pip => 'present',
		use_epel => false,
		dev => $dev,
	}
}

class tools {
	$packages = [ 'sqlite', 'bash-completion', 'nano', 'zip', 'bzip2', 'mlocate', 'patch', 'tmux', 'wget', 'rsync' ]
	package { $packages:
		ensure => installed;
	}
	if $osfamily == 'RedHat' {
		package { 'yum-utils':
			ensure => installed
		}
	}
	elsif $osfamily == 'Debian' {
		package { 'dnsutils':
			ensure => installed
		}
	}
}

class logrotate {
	package { 'logrotate':
		ensure => installed;
	}
	file { '/etc/logrotate.d/httpd':
		ensure => present,
		source => 'puppet:///common/logrotate-httpd',
		require => Package['logrotate'],
	}
}

class logwatch {
	package { 'logwatch':
		ensure => installed;
	}
	File {
		ensure => present,
		require => Package['logwatch'],
	}
	file { '/etc/cron.daily/0logwatch':
		source => 'puppet:///common/0logwatch';
	}
	$logwatch_dirs = [
		'/etc/logwatch/',
		'/etc/logwatch/conf/',
		'/etc/logwatch/conf/logfiles/',
		'/etc/logwatch/conf/services/',
		'/etc/logwatch/scripts/',
		'/etc/logwatch/scripts/services/',
	]
	file { $logwatch_dirs:
		ensure => directory,
	}
	file { '/etc/logwatch/conf/logwatch.conf':
		content => 'Detail = Med',
	}
	file { '/etc/logwatch/conf/logfiles/http.conf':
		source => 'puppet:///common/logwatch/logfiles_http.conf',
	}
	file { '/etc/logwatch/conf/logfiles/http-error.conf':
		source => 'puppet:///common/logwatch/logfiles_http-error.conf',
	}
	file { '/etc/logwatch/conf/logfiles/mysql.conf':
		source => 'puppet:///common/logwatch/logfiles_mysql.conf',
	}
	file { '/etc/logwatch/conf/logfiles/php.conf':
		source => 'puppet:///common/logwatch/logfiles_php.conf',
	}
	file { '/etc/logwatch/conf/services/php.conf':
		source => 'puppet:///common/logwatch/services_php.conf',
	}
	file { '/etc/logwatch/conf/services/contact-form.conf':
		source => 'puppet:///common/logwatch/services_contact-form.conf',
	}
	file { '/etc/logwatch/scripts/services/dovecot':
		source => 'puppet:///common/logwatch/dovecot',
	}
	file { '/etc/logwatch/scripts/services/postfix':
		source => 'puppet:///common/logwatch/postfix',
	}
	file { '/etc/logwatch/scripts/services/systemd':
		source => 'puppet:///common/logwatch/systemd',
	}
	file { '/etc/logwatch/scripts/services/php':
		source => 'puppet:///common/logwatch/php',
	}
	file { '/etc/logwatch/scripts/services/contact-form':
		source => 'puppet:///common/logwatch/contact-form',
	}
}

#Our web server with our configs, not just a stock one
class webserver (
	$primary_ip,
	$proxy_4to6_ip_prefix = undef,
	$proxy_4to6_mask = undef,
	$proxy_upstream = undef,
	) {

	#Setup base website parameters
	class { 'website':
		base_dir => '/srv/sites',
		primary_ip => $primary_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_4to6_mask => $proxy_4to6_mask,
		proxy_upstream => $proxy_upstream,
		default_owner => $defaultusers::default_user,
		default_group => $defaultusers::default_user,
		default_tld => 'co.uk',
		default_extra_tlds => [ 'com' ],
	}

	if $operatingsystem == 'CentOS' {
		$php_suffix = ''
		$ini_prefix_20 = '20-'
		$ini_prefix_30 = '30-'
		$ini_prefix_40 = '40-'
		$variant_prefix = 'php-'
		$extra_prefix = 'pecl-'
		$extra_extras = {
			'posix' => {
			        ini_prefix => '20-',
			},
			# Sodium has been bundled since 7.2, but CentOS packages it separately
			'sodium' => {
			        ini_prefix => '20-',
			},
			# JSON is integrated into PHP 8+ and so it's only a plugin in CentOS
			'json' => {
			        ini_prefix => '20-',
			},
		}
		if versioncmp($operatingsystemrelease, '8') >= 0 {
			yumrepo { 'remirepo-safe':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/safe/$basearch/mirror',
				descr => "Extra CentOS packages from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			yumrepo { 'remirepo-php':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/8/modular/$basearch/mirror',
				descr => 'Remi\'s Modular repository for Enterprise Linux 8 - $basearch',
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-remi':
				ensure => present,
				source => 'puppet:///common/RPM-GPG-KEY-remi.el8',
				tag => 'repo-config',
			}
		} else {
			yumrepo { 'remirepo-safe':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/safe/mirror',
				descr => "Extra CentOS packages from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			yumrepo { 'remirepo-php':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/php74/mirror',
				descr => "PHP7.4 for CentOS from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-remi':
				ensure => present,
				source => 'puppet:///common/RPM-GPG-KEY-remi',
				tag => 'repo-config',
			}
		}
	}
	elsif $operatingsystem == 'Ubuntu' {
		$php_suffix = ''
		$ini_prefix_20 = ''
		$ini_prefix_30 = ''
		$ini_prefix_40 = ''
		$variant_prefix = 'php-'
		$extra_prefix = ''
		# Work around constant re-install by enabling virtual packages
		# https://github.com/voxpupuli/puppet-php/issues/387
		Package {
			allow_virtual => true
		}
		$extra_extras = {
			'xdebug' => {
				# Occasional profiling retained in case I want to dig into
				# Wordpress page load inefficiencies any more
				ensure => absent,
#				settings => {
#					"xdebug.mode" => "profile",
#					"xdebug.start_with_request" => "trigger",
#				}
			}
		}
	}

	#Configure the PHP version to use
	class { 'website::php':
		suffix => $php_suffix,
		module => ($operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0) ? { true => 'remi-7.4', default => undef },
		extras => {
			'bcmath' => {
			        ini_prefix => $ini_prefix_20,
			},
			'curl' => {
			        ini_prefix => $ini_prefix_20,
			},
			'dom' => {
			        ini_prefix => $ini_prefix_20,
			},
			'enchant' => {
			        ini_prefix => $ini_prefix_20,
			},
			'exif' => {
			        ini_prefix => $ini_prefix_20,
			},
			'fileinfo' => {
			        ini_prefix => $ini_prefix_20,
			},
			'gmp' => {
			        ini_prefix => $ini_prefix_20,
			},
			'intl' => {
			        ini_prefix => $ini_prefix_20,
			},
			'mysqlnd' => {
				ini_prefix => $ini_prefix_20,
			},
			'pdo' => {
				ini_prefix => $ini_prefix_20,
			},
			'simplexml' => {
			        ini_prefix => $ini_prefix_20,
			},
			'soap' => {
			        ini_prefix => $ini_prefix_20,
			},
			'xmlwriter' => {
			        ini_prefix => $ini_prefix_20,
			},
			'mysqli' => {
				ini_prefix => $ini_prefix_30,
			},
			'pdo_mysql' => {
				ini_prefix => $ini_prefix_30,
				# Provided by the php-mysql package in CentOS and declared with "Provides"
				# And Ubuntu is the same but without the "Provides"
				provider => "none",
			},
			'xmlreader' => {
				ini_prefix => $ini_prefix_30,
			},
			'zip' => {
			        ini_prefix => $ini_prefix_30,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
			'apcu' => {
			        ini_prefix => $ini_prefix_40,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
			'imagick' => {
			        ini_prefix => $ini_prefix_40,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
		} + $extra_extras,
	}

	#Setup MySQL, using (private) templates to make sure that we set non-std passwords and a default user
	if $operatingsystem == 'CentOS' {
		if versioncmp($operatingsystemrelease, '7') >= 0 {
			$mysqlpackage = 'mariadb'
			$mysqlsuffix = ''

			# Required for SELinux rule setting/status checks
			if versioncmp($operatingsystemrelease, '8') >= 0 {
				$semanage_package_name = 'policycoreutils-python-utils'
			} else {
				$semanage_package_name = 'policycoreutils-python'
			}

			package { 'policycoreutils-python':
				name => $semanage_package_name,
				ensure => present,
			}

			$extra_packages = [
				'perl-Sys-Syslog', #Required for Perl SPF checking
			]

			package { $extra_packages:
				 ensure => installed
			}
		}
		else {
			$mysqlpackage = 'mysql'
			$mysqlsuffix = '55w'
		}
		$phpmysqlsuffix = 'nd'
	}
	elsif $operatingsystem == 'Ubuntu' {
		$mysqlpackage = 'mariadb'
		$mysqlsuffix = ''
		$phpmysqlsuffix = ''
	}
	else {
		fail("No MySQL support for ${operatingsystem}")
	}
	include ::defaultusers::params
	class { 'website::mysql':
		mysqluser => $::defaultusers::params::mysql_user,
		mysqlpassword => $::defaultusers::params::mysql_password,
		mysqlprefix => $mysqlpackage,
		mysqlsuffix => $mysqlsuffix,
		phpsuffix => $php_suffix,
		phpmysqlsuffix => $phpmysqlsuffix
	}

	# Additional supporting directories that aren't served as sites
	file { [ '/srv/sites/errorhandling', '/srv/sites/private', '/srv/cms' ]:
		ensure => directory,
	}
}

class ibboardvpsnode (
	$primary_ip,
	$gateway_ip = undef,
	$proxy_4to6_ip_prefix = undef,
	$proxy_upstream = undef,
	$nat64_ranges = [],
	$mailserver,
	$imapserver,
	$mailrelays = [],
	$firewall_cmd = 'iptables',
	){
	class { 'basevpsnode':
		primary_ip => $primary_ip,
		gateway_ip => $gateway_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_upstream => $proxy_upstream,
		nat64_ranges => $nat64_ranges,
		mailserver => $mailserver,
		imapserver => $imapserver,
		mailrelays => $mailrelays,
		firewall_cmd => $firewall_cmd,
	}

	# Set timezone to something sensible
	file { "/etc/localtime":
		ensure => 'link',
		target => '/usr/share/zoneinfo/Europe/London',
	}

	file { "${apache::conf_dir}/conf.d/greatfirewallagainstchina.conf":
		ensure => file,
	}

	# Debian doesn't handle sensible depends like module names because of the underscore
	# So we need to use the package name
	package { "mod_cspnonce":
		name => $osfamily == 'Debian' ? { true => "libapache2-mod-cspnonce", default => "mod_cspnonce" },
		ensure => installed,
	}

	# Common modules used by multiple sites (mod_auth_basic is safe because we HTTPS all the things)
	$mods = [
		'auth_basic',
		'authn_core',
		'authn_file',
		'authz_user',
		'deflate',
		'xsendfile',
		'cspnonce'
		]
	apache::mod {
		$mods:;
	}

	#Configure our sites, using templates for the custom fragments where the extra content is too long
	class { "devsite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:01", default => undef }
	}
	class { "adminsite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:02", default => undef }
	}
	website::https::multitld { 'www.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		custom_fragment => template("privat/apache/ibboard.fragment"),
		letsencrypt_name => 'ibboard.co.uk',
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"default-src" => "'none'",
			"img-src" => "'self' https://live.staticflickr.com/",
			"script-src" => "'self' https://auth-server.herokuapp.com/proxy https://api.tumblr.com/",
			"style-src" => "'self'",
			"font-src" => "'self'",
			"form-action" => "'self'",
			"connect-src" => "'self' https://api.tumblr.com/",
		}
	}
	website::https::redir { 'mail.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		redir => 'https://ibboard.co.uk/',
		docroot => "${website::basedir}/ibboard",
		letsencrypt_name => 'ibboard.co.uk',
		separate_log => true,
	}
	website::https::redir { 'imap.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		redir => 'https://ibboard.co.uk/',
		docroot => "${website::basedir}/ibboard",
		letsencrypt_name => 'ibboard.co.uk',
		separate_log => true,
	}
	class { "hiveworldterrasite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:04", default => undef }
	}
	class { "bdstrikesite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:05", default => undef }
	}
	website::https::multitld { 'www.abiknight':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:06", default => undef },
		custom_fragment => "$website::htmlphpfragment
	ErrorDocument 404 /error.php",
		letsencrypt_name => 'abiknight.co.uk',
	}
	class { "webmailpimsite":
		proxy_4to6_ip_pim => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:08", default => undef },
		proxy_4to6_ip_webmail => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:09", default => undef },
	}
}

class adminsite ($proxy_4to6_ip) {
	apache::mod { 'info':; 'status':; 'cgi':; }
	website::https::multitld { 'admin.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip,
		force_no_index => false,
		ssl_ca_chain => '',
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"img-src" => "'self' data:",
		},
		csp_report_override => {
			"img-src" => "'self' data:",
		},
		custom_fragment => template("privat/apache/admin.fragment"),
	}
	if $osfamily == 'RedHat' {
		$cron_user = 'apache'
	}
	elsif $osfamily == 'Debian' {
		$cron_user = 'www-data'
	}
	cron { 'loadavg':
		command => '/usr/local/bin/run-loadavg-logger',
		user => $cron_user,
		minute => '*/6'
	}
	cron { 'awstats':
		command => '/usr/local/bin/update-awstats > /srv/sites/admin/awstats.log',
		user => $cron_user,
		hour => '*/6',
		minute => '0'
	}
}

class hiveworldterrasite ($proxy_4to6_ip) {
	website::https::multitld { 'www.hiveworldterra':
		proxy_4to6_ip => $proxy_4to6_ip,
		force_no_www => false,
		letsencrypt_name => 'hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/hwt.fragment"),
	}
	website::https::multitld { 'forums.hiveworldterra': 
		proxy_4to6_ip => $proxy_4to6_ip,
		letsencrypt_name => 'forums.hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/forums.fragment"),
	}
	website::https::multitld { 'skins.hiveworldterra':
		proxy_4to6_ip => $proxy_4to6_ip,
		letsencrypt_name => 'skins.hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/skins.fragment"),
	}
	website::https::redir { 'hiveworldterra.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip,
		redir => 'https://www.hiveworldterra.co.uk/',
		docroot => "${website::basedir}/hiveworldterra",
		letsencrypt_name => 'hiveworldterra.ibboard.co.uk',
		separate_log => true,
	}
}
class bdstrikesite ($proxy_4to6_ip) {
	website::https::multitld { 'www.bdstrike':
		proxy_4to6_ip => $proxy_4to6_ip,
		docroot_owner => $defaultusers::secondary_user,
		docroot_group => 'editors',
		letsencrypt_name => 'bdstrike.co.uk',
		custom_fragment => template("privat/apache/bdstrike.fragment"),
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"font-src" => "'self' https://fonts.gstatic.com/ https://s0.wp.com/i/fonts/inter/ data:",
			"img-src" => "'self' https://secure.gravatar.com/ https://ps.w.org/ https://s.w.org/ data:",
			"style-src" => "'self' https://fonts.googleapis.com/ 'unsafe-inline'",
			"connect-src" => "'self' https://www.sandbox.paypal.com/ https://www.paypal.com/",
			"frame-ancestors" => "'self'"
		},
		csp_report_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/reportOnly",
			"font-src" => "'self' https://fonts.gstatic.com/ https://s0.wp.com/i/fonts/inter/ data:", # TODO: What's generating it?
			"img-src" => "'self' https://secure.gravatar.com/ data:",
			"style-src" => "'self' https://fonts.googleapis.com/ https://ajax.googleapis.com/ajax/libs/jqueryui/ 'nonce-%{CSP_NONCE}e' 'unsafe-hashes' 'sha256-anQSeQoEnQnBulZOQkDOFf+e6xBIGmqh7M8YFT992co=' 'sha256-zJDyuABAg68wtWDFyIh+RRe+6Vm/r+BLwaNRCGNVyXI=' 'sha256-qMalr/MPLUDW4lX/rq/cGp1Eu/H0cu0Yg98pdu69Jxs=' 'sha256-mshqJ+hidJMRDeNLHknuDAeYLOPg2OTIIA3nZmHgi9U=' 'sha256-YnRUd/QjP/NuFgfjMHhNfMCqXh0RQIGdvQfMCOf6qkw=' 'sha256-EwdiFJgqhefinoeAymrWxOYW4kza2Ekos5MY0PlXYI0=' 'sha256-G4K9vh8e+37+l69S+lHTyX3CfcK95mQUgyxYPCb7uME=' 'sha256-t6oewASd7J1vBg5mQtX4hl8bg8FeegYFM3scKLIhYUc=' 'sha256-mAQYxa3mIYqoLBrm1zLu6sLajr8vUHVFLYNpl6dAakM=' 'sha256-A8foknjCsFBi1PlRehOrHq0pVySigUurqAUgZ2y2U8c=' 'sha256-biLFinpqYMtWHmXfkA1BPeCY0/fNt46SAZ+BBk5YUog=' 'sha256-WzSByVQ8yW/DKrr77TWVt7WEMzueRcfJZImOkjTBKmc=' 'sha256-efof3agGBAL/yN8TplyNbLEgDZ3wIGMK3UMYbe8slkA='",
			"script-src" => "'self' 'nonce-%{CSP_NONCE}e' 'sha256-hPnbct+H2uwUiwoh3kect6TJt4waDlLPfj47TO58lXc=' 'sha256-80Mr5Xc2f6hVSJwvFRRcNjAI9RMcnuTVAIzr6pIQswI=' 'sha256-zwGmIUR+Z6gWKbwoJ2Z3yGxI/XLETLqDqCRIV0qt/WA='",
			"connect-src" => "'self' https://www.sandbox.paypal.com/ https://www.paypal.com/",
		},
	}

	cron { 'wordpress_cron':
		# Run "php -f wp-cron.php" on a schedule so that we can auto-update
		# without giving Apache full write access!
		command => "/usr/local/bin/bdstrike-cron",
		user => $website::apache_user,
		minute => '*/15',
	}
}
class devsite ($proxy_4to6_ip) {
	if $operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0 {
		$package_name = 'python3-mod_wsgi'
		$mod_path = 'mod_wsgi_python3.so'
	} else {
		$package_name = undef
		$mod_path = undef
	}
	class { 'apache::mod::wsgi':
		  package_name => $package_name,
		  mod_path => $mod_path,
	}

	website::https::multitld { 'dev.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip,
		#Make sure we're the first one hit for the tiny fraction of "no support" cases we care about (potentially Python for Mercurial!)
		# http://en.wikipedia.org/wiki/Server_Name_Indication#No_support
		priority => 1,
		letsencrypt_name => 'dev.ibboard.co.uk',
		custom_fragment => template("privat/apache/dev.fragment"),
		proxy_fragment => template("privat/apache/dev-proxy.fragment"),
		force_no_index => false,
	}
}

class webmailpimsite ($proxy_4to6_ip_pim, $proxy_4to6_ip_webmail) {
	# Webmail and Personal Information Management (PIM) sites
	website::https { 'webmail.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_webmail,
		force_no_index => false,
		letsencrypt_name => 'webmail.ibboard.co.uk',
		custom_fragment => template("privat/apache/webmail.fragment"),
	}
	include ::apache::params
	website::https { 'pim.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_pim,
		docroot_owner => $apache::params::user,
		docroot_group => 'editors',
		force_no_index => false,
		lockdown_requests => false,
		letsencrypt_name => 'pim.ibboard.co.uk',
		csp => false,
		csp_report => false,
		custom_fragment => template("privat/apache/pim.fragment"),
	}
	cron { 'owncloudcron':
		command => "/usr/local/bin/owncloud-cron",
		user => $apache::params::user,
		minute => '*/15',
	}
}

class email (
	$mailserver,
	$imapserver,
	$mailserver_ip,
	$proxy_ip = undef,
	$proxy_upstream = [],
	$nat64_ranges = [],
	$mailrelays = [],
	){
	class { 'postfix':
		mailserver => $mailserver,
		mailserver_ip => $mailserver_ip,
		mailserver_proxy => $proxy_ip,
		proxy_upstream => $proxy_upstream,
		mailrelays => $mailrelays,
		nat64_ranges => $nat64_ranges,
		protocols  => $mailserver_ip =~ Stdlib::IP::Address::V6 ? { true => 'all', default => 'ipv4' },
	}
	class { 'dovecot':
		imapserver => $imapserver,
		imapserver_ip => $mailserver_ip,
		imapserver_proxy => $proxy_ip,
		proxy_upstream => $proxy_upstream,
	}
	# Unspecified SpamAssassin config dependencies that started
	# showing up as errors in our logs
	if $osfamily == 'RedHat' {
		$spamassassin_deps = ['perl-File-MimeInfo']
		$spamassassin_dir = '/etc/mail/spamassassin/'
		$amavis_config = '/etc/amavisd/amavisd.conf'
		$amavis_rundir = '/var/run/amavisd'
		$amavis_spooldir = '/var/spool/amavisd'
		$amavis_quarantinedir = '/var/spool/amavisd/quarantine'
		$amavis_service = 'amavisd'
		# CentOS has a Clam service, but we call on demand (Ubuntu doesn't have a service)
		service { 'clamd@amavisd':
			ensure => 'stopped',
			enable=> 'mask',
		}
	}
	elsif $osfamily == 'Debian' {
		$spamassassin_deps = ['libfile-mimeinfo-perl']
		$spamassassin_dir = '/etc/spamassassin/'
		$amavis_config = '/etc/amavis/conf.d/60-puppeted'
		$amavis_rundir = '/var/run/amavis'
		$amavis_spooldir = '/var/lib/amavis'
		$amavis_quarantinedir = '/var/spool/amavisd/quarantine'
		$amavis_service = 'amavis'
	}
	package { $spamassassin_deps:
		ensure => installed,
	}
	package { [ 'amavisd-new' ]:
		ensure => installed,
		tag => 'av',
	}
	service { $amavis_service:
		ensure => 'running',
		enable => 'true',
	}
	file { $amavis_config:
		ensure => present,
		content => epp('privat/postfix/amavis.conf.epp',
			{
				fqdn => $::fqdn,
				rundir => $amavis_rundir,
				spooldir => $amavis_spooldir,
				quarantinedir => $amavis_quarantinedir,
			}
		),
		tag => 'av',
	}
	file { "${spamassassin_dir}local.cf":
		ensure => present,
		source => 'puppet:///private/postfix/spamassassin-local.cf',
		tag => 'av',
	}
	file { "${spamassassin_dir}ole2macro.cf":
		ensure => present,
		source => 'puppet:///common/ole2macro.cf',
		tag => 'av',
	}
	file { "${spamassassin_dir}ole2macro.pm":
		ensure => present,
		source => 'puppet:///common/spamassassin-vba-macro-master/ole2macro.pm',
		tag => 'av',
	}
	Package<| tag == 'av' |> -> File<| tag == 'av' |>
	File<| tag == 'av' |> {
		notify => Service[$amavis_service],
	}
	cron { 'Postwhite':
		command => "/usr/local/bin/postwhite 2>&1| grep -vE '^(Starting|Recursively|Getting|Querying|Removing|Sorting|$)'",
		user => 'root',
		weekday => 0,
		hour => 2,
		minute => 0,
	}
}

class cronjobs {
	package { 'cron':
		ensure => installed,
		name => $operatingsystem == 'Ubuntu' ? { true => 'cron', default => 'cronie' }
	}
	# Add Mutt for scripts that send emails, but stop it clogging the disk by keeping copies of emails
	package { 'mutt':
		ensure => installed,
	}
	file { '/etc/Muttrc.local':
		content => 'set copy = no',
		require => Package['mutt'],
	}

	# General server-wide cron jobs
	Cron { user => 'root' }
	cron { 'backupalldbs':
		command => "/usr/local/bin/backupalldbs",
		monthday => "*/2",
		hour => "4",
		minute => "9"
	}
	# Only run the Great Firewall Against China on IPv4 (since we don't have an IPv6 list
	# and the PROXY forwards the IPs to services, but not at the network level)
	cron { 'greatfirewallofchina':
		command => '/usr/local/bin/update-great-firewall-of-china',
		ensure => has_key($facts, 'ipaddress') ? { true => "present", default => "absent" },
		hour => 3,
		minute => 30
	}
	cron { 'permissions':
		command => '/usr/local/bin/set-permissions',
		hour => 3,
		minute => 2
	}
	# Since we're only managing the local server, use our script that wraps "puppet apply" instead of PuppetMaster
	cron { 'puppet':
		command => '/usr/local/bin/puppet-apply | grep -v "Compiled catalog for\|Finished catalog run in\|Applied catalog in"',
		hour => '*/6',
		minute => 5
	}
	cron { 'purgecaches':
		command => "/usr/local/bin/purge-caches",
		hour => '4',
		minute => '15',
		weekday => '1',
	}
	# Notify of uncommitted files
	cron { 'check-mercurial-committed':
		command => "/usr/local/bin/check-hg-status",
		hour => '4',
		minute => '20',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
	# Notify of available updates
	cron { 'check-yum-updates':
		ensure => absent,
	}
	cron { 'check-for-updates':
		command => '/usr/local/bin/check-updates',
		hour => '4',
		minute => '30',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
	# And check whether anything needs restarting
	cron { 'check-needs-restarting':
		command => '/usr/local/bin/needs-restarting',
		hour => '4',
		minute => '45',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
}