packages feed

propellor 0.8.1 → 0.8.2

raw patch · 13 files changed

+727/−109 lines, 13 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Propellor.Info: aliasMap :: [Host] -> Map HostName Host
+ Propellor.Info: findAlias :: [Host] -> HostName -> Maybe Host
+ Propellor.Property.Apache: allowAll :: String
+ Propellor.Property.File: hasPrivContent' :: (String -> FilePath -> IO ()) -> FilePath -> Context -> Property
+ Propellor.Property.Hostname: searchDomain :: Property
+ Propellor.Property.Postfix: dedupCf :: [String] -> [String]
+ Propellor.Property.Postfix: dedupMainCf :: Property
+ Propellor.Property.Postfix: getMainCf :: String -> IO (Maybe String)
+ Propellor.Property.Postfix: mainCf :: (String, String) -> Property
+ Propellor.Property.Postfix: mainCfFile :: FilePath
+ Propellor.Property.Postfix: mainCfIsSet :: String -> IO Bool
+ Propellor.Property.Postfix: mappedFile :: FilePath -> (FilePath -> Property) -> Property
+ Propellor.Property.Postfix: newaliases :: Property
+ Propellor.Property.Postfix: reloaded :: Property
+ Propellor.Property.Postfix: restarted :: Property
+ Propellor.Property.SiteSpecific.JoeySites: backupsBackedupTo :: [Host] -> HostName -> FilePath -> Property
+ Propellor.Property.SiteSpecific.JoeySites: hasJoeyCAChain :: Property
+ Propellor.Property.SiteSpecific.JoeySites: kiteMailServer :: Property
+ Propellor.Property.SiteSpecific.JoeySites: kitenetHttps :: Property
+ Propellor.Property.SiteSpecific.JoeySites: legacyWebSites :: Property
+ Propellor.Property.SiteSpecific.JoeySites: podcatcher :: Property
+ Propellor.Property.SiteSpecific.JoeySites: rsyncNetBackup :: [Host] -> Property
+ Propellor.Property.SiteSpecific.JoeySites: userDirHtml :: Property
- Propellor.Property.Apache: siteCfg :: HostName -> FilePath
+ Propellor.Property.Apache: siteCfg :: HostName -> [FilePath]
- Propellor.Property.SiteSpecific.JoeySites: annexWebSite :: [Host] -> RepoUrl -> HostName -> AnnexUUID -> [(String, RepoUrl)] -> Property
+ Propellor.Property.SiteSpecific.JoeySites: annexWebSite :: RepoUrl -> HostName -> AnnexUUID -> [(String, RepoUrl)] -> Property

Files

CHANGELOG view
@@ -1,3 +1,16 @@+propellor (0.8.2) unstable; urgency=medium++  * Fix bug in File.containsLines that caused lines that were already in the+    file to sometimes be appended to the end.+  * Hostname.sane also configures /etc/mailname.+  * Fixed Postfix.satellite to really configure relayhost = smtp.domain.+  * Avoid reconfiguring postfix unncessarily when it already has a relayhost.+  * Deal with apache 2.4's change in the name of site-available config files.+  * Hostname aliases can now be used in several places, including --spin+    and Ssh.knownHost.++ -- Joey Hess <joeyh@debian.org>  Mon, 04 Aug 2014 01:12:19 -0400+ propellor (0.8.1) unstable; urgency=medium    * Run apt-get update in initial bootstrap.
config-joey.hs view
@@ -8,6 +8,7 @@ import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Network as Network import qualified Propellor.Property.Ssh as Ssh+import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Cron as Cron import qualified Propellor.Property.Sudo as Sudo import qualified Propellor.Property.User as User@@ -22,6 +23,7 @@ import qualified Propellor.Property.Postfix as Postfix import qualified Propellor.Property.Service as Service import qualified Propellor.Property.Grub as Grub+import qualified Propellor.Property.Obnam as Obnam import qualified Propellor.Property.HostingProvider.DigitalOcean as DigitalOcean import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost import qualified Propellor.Property.HostingProvider.Linode as Linode@@ -41,7 +43,7 @@  		& Apt.buildDep ["git-annex"] `period` Daily 		& Docker.configured-		& Docker.docked hosts "android-git-annex"+		! Docker.docked hosts "android-git-annex"  	, standardSystem "clam.kitenet.net" Unstable "amd64" 		[ "Unreliable server. Anything here may be lost at any time!" ]@@ -61,38 +63,70 @@ 		[ "Main git-annex build box." ] 		& ipv4 "138.38.108.179" -		& Hostname.sane 		& Apt.unattendedUpgrades 		& Postfix.satellite 		& Docker.configured 		& Docker.docked hosts "amd64-git-annex-builder" 		& Docker.docked hosts "i386-git-annex-builder"-		& Docker.docked hosts "armel-git-annex-builder-companion"-		& Docker.docked hosts "armel-git-annex-builder" 		& Docker.docked hosts "android-git-annex-builder"+		-- not currently working+		! Docker.docked hosts "armel-git-annex-builder-companion"+		! Docker.docked hosts "armel-git-annex-builder" 		& Docker.garbageCollected `period` Daily 		& Apt.buildDep ["git-annex"] `period` Daily 	-  	, standardSystem "kite.kitenet.net" Unstable "amd64"+	-- This is not a complete description of kite, since it's a+	-- multiuser system with eg, user passwords that are not deployed+	-- with propellor.+  	, standardSystemUnhardened "kite.kitenet.net" Unstable "amd64" 		[ "Welcome to the new kitenet.net server!" 		, "This is still under construction and not yet live.." 		] 	  	& ipv4 "66.228.36.95" 		& ipv6 "2600:3c03::f03c:91ff:fe73:b0d2"+		-- & alias "kitenet.net" -- not yet live!  		& Apt.installed ["linux-image-amd64"] 		& Linode.chainPVGrub 5-		& Hostname.sane 		& Apt.unattendedUpgrades 		& Apt.installed ["systemd"] 		& Ssh.hostKeys (Context "kitenet.net")-	+		-- Since ssh password authentication is allowed:+		& Apt.serviceInstalledRunning "fail2ban"+		& Obnam.backup "/" "33 1 * * *"+			[ "--repository=sftp://joey@eubackup.kitenet.net/~/lib/backup/kite.obnam"+			, "--client-name=kitenet.net"+			, "--encrypt-with="+			, "--exclude=/var/cache"+			, "--exclude=/var/tmp"+			, "--exclude=/home/joey/lib"+			, "--exclude=.*/tmp/"+			, "--one-file-system"+			] Obnam.OnlyClient+			`requires` Gpg.keyImported "98147487" "root"+			`requires` Ssh.keyImported SshRsa "root"+				(Context "kite.kitenet.net")+			`requires` Ssh.knownHost hosts "eubackup.kitenet.net" "root"++		-- & alias "smtp.kitenet.net" -- not yet live!+		-- & alias "imap.kitenet.net" -- not yet live!+		-- & alias "mail.kitenet.net" -- not yet live!+		& JoeySites.kiteMailServer++		& JoeySites.legacyWebSites++		& Apt.installed+			["git-annex", "myrepos"+			, "build-essential", "make"+			-- Some users have zsh as their login shell.+			, "zsh"+			]+   	, standardSystem "diatom.kitenet.net" Stable "amd64" 	  	[ "Important stuff that needs not too much memory or CPU." ] 		& ipv4 "107.170.31.195"  		& DigitalOcean.distroKernel-		& Hostname.sane 		& Ssh.hostKeys (Context "diatom.kitenet.net") 		& Apt.unattendedUpgrades 		& Apt.serviceInstalledRunning "ntp"@@ -103,10 +137,7 @@ 		& Apt.serviceInstalledRunning "swapspace" 	 		& Apt.serviceInstalledRunning "apache2"-		& File.hasPrivContent "/etc/ssl/certs/web.pem" (Context "kitenet.net")-		& File.hasPrivContent "/etc/ssl/private/web.pem" (Context "kitenet.net")-		& File.hasPrivContent "/etc/ssl/certs/startssl.pem" (Context "kitenet.net")-		& Apache.modEnabled "ssl"+		& JoeySites.kitenetHttps 		& Apache.multiSSL 		& File.ownerGroup "/srv/web" "joey" "joey" 		& Apt.installed ["analog"]@@ -116,14 +147,16 @@ 		& JoeySites.gitServer hosts 	 		& alias "downloads.kitenet.net"-		& JoeySites.annexWebSite hosts "/srv/git/downloads.git"+		& JoeySites.annexWebSite "/srv/git/downloads.git" 			"downloads.kitenet.net" 			"840760dc-08f0-11e2-8c61-576b7e66acfd"-			[("turtle", "ssh://turtle.kitenet.net/~/lib/downloads/")]+			[("usbackup", "ssh://usbackup.kitenet.net/~/lib/downloads/")]+			`requires` Ssh.keyImported SshRsa "joey" (Context "downloads.kitenet.net")+			`requires` Ssh.knownHost hosts "usbackup.kitenet.net" "joey" 		& JoeySites.gitAnnexDistributor  		& alias "tmp.kitenet.net"-		& JoeySites.annexWebSite hosts "/srv/git/joey/tmp.git"+		& JoeySites.annexWebSite "/srv/git/joey/tmp.git" 			"tmp.kitenet.net" 			"26fd6e38-1226-11e2-a75f-ff007033bdba" 			[]@@ -148,25 +181,28 @@  	, let ctx = Context "elephant.kitenet.net" 	  in standardSystem "elephant.kitenet.net" Unstable "amd64"-	  	[ "Storage, big data, and backups, omnomnom!" ]+	  	[ "Storage, big data, and backups, omnomnom!"+		, "(Encrypt all data stored here.)"+		] 		& ipv4 "193.234.225.114"  		& Grub.chainPVGrub "hd0,0" "xen/xvda1" 30-		& Hostname.sane 		& Postfix.satellite 		& Apt.unattendedUpgrades 		& Ssh.hostKeys ctx+		& sshPubKey "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAJkoPRhUGT8EId6m37uBdYEtq42VNwslKnc9mmO+89ody066q6seHKeFY6ImfwjcyIjM30RTzEwftuVNQnbEB0=" 		& Ssh.keyImported SshRsa "joey" ctx 		& Apt.serviceInstalledRunning "swapspace"  		& alias "eubackup.kitenet.net" 		& Apt.installed ["obnam", "sshfs", "rsync"]+		& JoeySites.obnamRepos ["wren", "pell", "kite"] 		& JoeySites.githubBackup-		& JoeySites.obnamRepos ["wren", "pell"]-		& Ssh.knownHost hosts "usw-s002.rsync.net" "joey"+		& JoeySites.rsyncNetBackup hosts+		& JoeySites.backupsBackedupTo hosts "usbackup.kitenet.net" "lib/backup/eubackup"  		& alias "podcatcher.kitenet.net"-		& Apt.installed ["git-annex"]+		& JoeySites.podcatcher 		 		& alias "znc.kitenet.net" 		& JoeySites.ircBouncer@@ -201,9 +237,9 @@ 			`onChange` Service.restarted "ssh" 		 		-- temp-		& Docker.docked hosts "amd64-git-annex-builder"-		& Docker.docked hosts "i386-git-annex-builder"-		& Docker.docked hosts "android-git-annex-builder"+		! Docker.docked hosts "amd64-git-annex-builder"+		! Docker.docked hosts "i386-git-annex-builder"+		! Docker.docked hosts "android-git-annex-builder"   	    --'                        __|II|      ,.@@ -261,8 +297,17 @@  -- This is my standard system setup. standardSystem :: HostName -> DebianSuite -> Architecture -> Motd -> Host-standardSystem hn suite arch motd = host hn+standardSystem hn suite arch motd = standardSystemUnhardened hn suite arch motd+	-- Harden the system, but only once root's authorized_keys+	-- is safely in place.+	& check (Ssh.hasAuthorizedKeys "root")+		(Ssh.passwordAuthentication False)++standardSystemUnhardened :: HostName -> DebianSuite -> Architecture -> Motd -> Host+standardSystemUnhardened hn suite arch motd = host hn 	& os (System (Debian suite) arch)+	& Hostname.sane+	& Hostname.searchDomain 	& File.hasContent "/etc/motd" ("":motd++[""]) 	& Apt.stdSourcesList `onChange` Apt.upgrade 	& Apt.cacheCleaned@@ -270,10 +315,6 @@ 	& Apt.installed ["ssh"] 	& GitHome.installedFor "root" 	& User.hasSomePassword "root" (Context hn)-	-- Harden the system, but only once root's authorized_keys-	-- is safely in place.-	& check (Ssh.hasAuthorizedKeys "root")-		(Ssh.passwordAuthentication False) 	& User.accountFor "joey" 	& User.hasSomePassword "joey" (Context hn) 	& Sudo.enabledFor "joey"@@ -350,6 +391,7 @@ 		& ipv4 "67.223.19.96" 		& ipv6 "2001:4978:f:2d9::2" 		& alias "backup.kitenet.net"+		& alias "usbackup.kitenet.net" 		& sshPubKey "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAokMXQiX/NZjA1UbhMdgAscnS5dsmy+Q7bWrQ6tsTZ/o+6N/T5cbjoBHOdpypXJI3y/PiJTDJaQtXIhLa8gFg/EvxMnMz/KG9skADW1361JmfCc4BxicQIO2IOOe6eilPr+YsnOwiHwL0vpUnuty39cppuMWVD25GzxXlS6KQsLCvXLzxLLuNnGC43UAM0q4UwQxDtAZEK1dH2o3HMWhgMP2qEQupc24dbhpO3ecxh2C9678a3oGDuDuNf7mLp3s7ptj5qF3onitpJ82U5o7VajaHoygMaSRFeWxP2c13eM57j3bLdLwxVXFhePcKXARu1iuFTLS5uUf3hN6MkQcOGw==" 	, host "wren.kitenet.net" 		& ipv4 "80.68.85.49"@@ -359,37 +401,46 @@ 		& alias "ftp.kitenet.net" 		& alias "mail.kitenet.net" 		& alias "smtp.kitenet.net"-		& alias "sows-ear.kitenet.net"-		& alias "www.sows-ear.kitenet.net"-		& alias "wortroot.kitenet.net"-		& alias "www.wortroot.kitenet.net"-		& alias "joey.kitenet.net"-		& alias "anna.kitenet.net" 		& alias "bitlbee.kitenet.net" 		{- Remaining services on kite:+		 -+		 - / = ready to go on kite.kitenet.net 		 -  		 - mail-		 -   postfix-		 -   postgrey+		 -   /postfix+		 -   /postgrey 		 -   mailman-		 -   spamassassin-		 -   sqwebmail-		 -   courier-		 -     imap-		 -     tls-		 - apache-		 -   some static websites-		 - bitlbee-		 - prosody-		 -   (used by daddy's git-annex)-		 - named-		 -   (branchable is still pushing to here-		 -    (thinking it's ns2.branchable.com), but it's no-		 -   longer a primary or secondary for anything)+		 -   /spamassassin+		 -   sqwebmail (cannot use this with dovecot, alternatives?)+		 -   /imap server+		 -   /pop server+		 - /apache+		 -   (need to re-rsync /srv/web to new kite.kitenet.net+		 -   server before decommissioning)+		 - bitlbee (EOL?)+		 - prosody (EOL?) 		 - ftpd (EOL) 		 --		 - user shell stuff:-		 -   pine, zsh, make, git-annex, myrepos, ...+		 - Pre-transition:+		 - - re-rsync /home (skip ~joey and .pine*)+		 - +		 - Transition plan:+		 - - on darkstar: offlineimap run & disable cron job+		 -    & move offlineimap files to tmp+		 - - take down wren pstfix, imap, pop servers+		 - - log all users out of wren+		 - - final /home rsync (skip ~joey and .pine*)+		 - - rsync /var/mail+		 - - rsync mailman and mailman list archives dirs+		 - - switch kitenet.net dns and enable pop.kitenet.net etc aliass+		 - - point wren.kitenet.net at kite.kitenet.net temporarily+		 -   (make real-wren.kitenet.net alias)+		 - - reconfigure errol's email client to use new server+		 - - re-run offlinimap against new server+		 - - test mail+		 - - test virus filtering+		 - - test http://kitenet.net/~kyle/ (user home dirs)+		 - - migrate user cron jobs 		 -} 	, host "mouse.kitenet.net" 		& ipv6 "2001:4830:1600:492::2"
debian/changelog view
@@ -1,3 +1,16 @@+propellor (0.8.2) unstable; urgency=medium++  * Fix bug in File.containsLines that caused lines that were already in the+    file to sometimes be appended to the end.+  * Hostname.sane also configures /etc/mailname.+  * Fixed Postfix.satellite to really configure relayhost = smtp.domain.+  * Avoid reconfiguring postfix unncessarily when it already has a relayhost.+  * Deal with apache 2.4's change in the name of site-available config files.+  * Hostname aliases can now be used in several places, including --spin+    and Ssh.knownHost.++ -- Joey Hess <joeyh@debian.org>  Mon, 04 Aug 2014 01:12:19 -0400+ propellor (0.8.1) unstable; urgency=medium    * Run apt-get update in initial bootstrap.
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 0.8.1+Version: 0.8.2 Cabal-Version: >= 1.6 License: BSD3 Maintainer: Joey Hess <joey@kitenet.net>
src/Propellor/Info.hs view
@@ -43,11 +43,15 @@ -- problems with CNAMEs, and also means that when multiple hosts have the -- same alias, a DNS round-robin is automatically set up. alias :: Domain -> Property-alias = addDNS . CNAME . AbsDomain+alias d = pureInfoProperty ("alias " ++ d) $ mempty+	{ _aliases = S.singleton d+	-- A CNAME is added here, but the DNS setup code converts it to an+	-- IP address when that makes sense.+	, _dns = S.singleton $ CNAME $ AbsDomain d+	}   addDNS :: Record -> Property-addDNS r = pureInfoProperty (rdesc r) $-	mempty { _dns = S.singleton r }+addDNS r = pureInfoProperty (rdesc r) $ mempty { _dns = S.singleton r }   where 	rdesc (CNAME d) = unwords ["alias", ddesc d] 	rdesc (Address (IPv4 addr)) = unwords ["ipv4", addr]@@ -71,8 +75,15 @@ hostMap :: [Host] -> M.Map HostName Host hostMap l = M.fromList $ zip (map hostName l) l  +aliasMap :: [Host] -> M.Map HostName Host+aliasMap = M.fromList . concat .+	map (\h -> map (\aka -> (aka, h)) $ S.toList $ _aliases $ hostInfo h)+ findHost :: [Host] -> HostName -> Maybe Host-findHost l hn = M.lookup hn (hostMap l)+findHost l hn = maybe (findAlias l hn) Just (M.lookup hn (hostMap l))++findAlias :: [Host] -> HostName -> Maybe Host+findAlias l hn = M.lookup hn (aliasMap l)  getAddresses :: Info -> [IPAddr] getAddresses = mapMaybe getIPAddr . S.toList . _dns
src/Propellor/Property/Apache.hs view
@@ -10,20 +10,21 @@ siteEnabled :: HostName -> ConfigFile -> RevertableProperty siteEnabled hn cf = RevertableProperty enable disable   where-	enable = trivial $ cmdProperty "a2ensite" ["--quiet", hn]+	enable = trivial (cmdProperty "a2ensite" ["--quiet", hn]) 		`describe` ("apache site enabled " ++ hn) 		`requires` siteAvailable hn cf 		`requires` installed 		`onChange` reloaded-	disable = trivial $ File.notPresent (siteCfg hn)-		`describe` ("apache site disabled " ++ hn)+	disable = trivial $ combineProperties+		("apache site disabled " ++ hn) +		(map File.notPresent (siteCfg hn)) 		`onChange` cmdProperty "a2dissite" ["--quiet", hn] 		`requires` installed 		`onChange` reloaded  siteAvailable :: HostName -> ConfigFile -> Property-siteAvailable hn cf = siteCfg hn `File.hasContent` (comment:cf)-	`describe` ("apache site available " ++ hn)+siteAvailable hn cf = combineProperties ("apache site available " ++ hn) $+	map (`File.hasContent` (comment:cf)) (siteCfg hn)   where 	comment = "# deployed with propellor, do not modify" @@ -39,8 +40,15 @@ 		`requires` installed 		`onChange` reloaded -siteCfg :: HostName -> FilePath-siteCfg hn = "/etc/apache2/sites-available/" ++ hn+-- This is a list of config files because different versions of apache+-- use different filenames. Propellor simply writen them all.+siteCfg :: HostName -> [FilePath]+siteCfg hn =+	-- Debian pre-2.4+	[ "/etc/apache2/sites-available/" ++ hn+	-- Debian 2.4++	, "/etc/apache2/sites-available/" ++ hn ++ ".conf"+	]   installed :: Property installed = Apt.installed ["apache2"]@@ -60,3 +68,19 @@ 	] 	`describe` "apache SNI enabled" 	`onChange` reloaded++-- | Config file fragment that can be inserted into a <Directory>+-- stanza to allow global read access to the directory.+--+-- Works with multiple versions of apache that have different ways to do+-- it.+allowAll :: String+allowAll = unlines+	[ "<IfVersion < 2.4>"+	, "Order allow,deny"+	, "allow from all"+	, "</IfVersion>"+	, "<IfVersion >= 2.4>"+	, "Require all granted"+	, "</IfVersion>"+	]
src/Propellor/Property/Cron.hs view
@@ -4,6 +4,7 @@ import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import Utility.SafeCommand+import Utility.FileMode  import Data.Char @@ -19,22 +20,33 @@ -- -- The cron job's output will only be emailed if it exits nonzero. job :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property-job desc times user cddir command = cronjobfile `File.hasContent`-	[ "# Generated by propellor"-	, ""-	, "SHELL=/bin/sh"-	, "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"-	, ""-	, times ++ "\t" ++ user ++ "\t"-		++ "chronic flock -n " ++ shellEscape cronjobfile-		++ " sh -c " ++ shellEscape cmdline+job desc times user cddir command = combineProperties ("cronned " ++ desc)+	[ cronjobfile `File.hasContent`+		[ "# Generated by propellor"+		, ""+		, "SHELL=/bin/sh"+		, "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"+		, ""+		, times ++ "\t" ++ user ++ "\tchronic " ++ shellEscape scriptfile+		]+	-- Use a separate script because it makes the cron job name +	-- prettier in emails, and also allows running the job manually.+	, scriptfile `File.hasContent`+		[ "#!/bin/sh"+		, "# Generated by propellor"+		, "set -e"+		, "flock -n " ++ shellEscape cronjobfile+			++ " sh -c " ++ shellEscape cmdline+		]+	, scriptfile `File.mode` combineModes (readModes ++ executeModes) 	] 	`requires` Apt.serviceInstalledRunning "cron" 	`requires` Apt.installed ["util-linux", "moreutils"]-	`describe` ("cronned " ++ desc)   where 	cmdline = "cd " ++ cddir ++ " && ( " ++ command ++ " )"-	cronjobfile = "/etc/cron.d/" ++ map sanitize desc+	cronjobfile = "/etc/cron.d/" ++ name+	scriptfile = "/usr/local/bin/" ++ name ++ "_cronjob"+	name = map sanitize desc 	sanitize c 		| isAlphaNum c = c 		| otherwise = '_'@@ -42,7 +54,7 @@ -- | Installs a cron job, and runs it niced and ioniced. niceJob :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property niceJob desc times user cddir command = job desc times user cddir-	("nice ionice -c 3 " ++ command)+	("nice ionice -c 3 sh -c " ++ shellEscape command)  -- | Installs a cron job to run propellor. runPropellor :: CronTimes -> Property
src/Propellor/Property/File.hs view
@@ -18,28 +18,32 @@ -- The file's permissions are preserved if the file already existed. -- Otherwise, they're set to 600. hasPrivContent :: FilePath -> Context -> Property-hasPrivContent f context = withPrivData (PrivFile f) context $ \getcontent -> -	property desc $ getcontent $ \privcontent -> -		ensureProperty $ fileProperty' writeFileProtected desc-			(\_oldcontent -> lines privcontent) f-  where-	desc = "privcontent " ++ f+hasPrivContent = hasPrivContent' writeFileProtected --- | Leaves the file world-readable.+-- | Leaves the file at its default or current mode,+-- allowing "private" data to be read.+--+-- Use with caution! hasPrivContentExposed :: FilePath -> Context -> Property-hasPrivContentExposed f context = hasPrivContent f context `onChange`-	mode f (combineModes (ownerWriteMode:readModes))+hasPrivContentExposed = hasPrivContent' writeFile +hasPrivContent' :: (String -> FilePath -> IO ()) -> FilePath -> Context -> Property+hasPrivContent' writer f context = +	withPrivData (PrivFile f) context $ \getcontent -> +		property desc $ getcontent $ \privcontent -> +			ensureProperty $ fileProperty' writer desc+				(\_oldcontent -> lines privcontent) f+  where+	desc = "privcontent " ++ f+ -- | Ensures that a line is present in a file, adding it to the end if not. containsLine :: FilePath -> Line -> Property f `containsLine` l = f `containsLines` [l]  containsLines :: FilePath -> [Line] -> Property-f `containsLines` l = fileProperty (f ++ " contains:" ++ show l) go f+f `containsLines` ls = fileProperty (f ++ " contains:" ++ show ls) go f   where-	go ls-		| all (`elem` ls) l = ls-		| otherwise = ls++l+	go content = content ++ filter (`notElem` content) ls  -- | Ensures that a line is not present in a file. -- Note that the file is ensured to exist, so if it doesn't, an empty
src/Propellor/Property/Hostname.hs view
@@ -3,10 +3,14 @@ import Propellor import qualified Propellor.Property.File as File +import Data.List+ -- | Ensures that the hostname is set using best practices. -- -- Configures /etc/hostname and the current hostname. --+-- Configures /etc/mailname with the domain part of the hostname.+-- -- /etc/hosts is also configured, with an entry for 127.0.1.1, which is -- standard at least on Debian to set the FDQN. --@@ -29,6 +33,8 @@ 			else Just $ trivial $ hostsline "127.0.1.1" [hn, basehost] 		, Just $ trivial $ hostsline "127.0.0.1" ["localhost"] 		, Just $ trivial $ cmdProperty "hostname" [basehost]+		, Just $ "/etc/mailname" `File.hasContent`+			[if null domain then hn else domain] 		] 	 	hostsline ip names = File.fileProperty desc@@ -37,3 +43,21 @@ 	addhostsline ip names ls = 		(ip ++ "\t" ++ (unwords names)) : filter (not . hasip ip) ls 	hasip ip l = headMaybe (words l) == Just ip++-- | Makes /etc/resolv.conf contain search and domain lines for +-- the domain that the hostname is in.+searchDomain :: Property+searchDomain = property desc (ensureProperty . go =<< asks hostName)+  where+	desc = "resolv.conf search and domain configured"+	go hn =+		let (_basehost, domain) = separate (== '.') hn+		in  File.fileProperty desc (use domain) "/etc/resolv.conf"+	use domain ls = filter wanted $ nub (ls ++ cfgs)+	  where+		cfgs = ["domain " ++ domain, "search " ++ domain]+		wanted l+			| l `elem` cfgs = True+			| "domain " `isPrefixOf` l = False+			| "search " `isPrefixOf` l = False+			| otherwise = True
src/Propellor/Property/Obnam.hs view
@@ -33,8 +33,8 @@ -- >		[ "--repository=sftp://2318@usw-s002.rsync.net/~/mygitrepos.obnam" -- >		, "--encrypt-with=1B169BE1" -- >		] Obnam.OnlyClient--- >		`requires` Gpg.keyImported "1B169BE1" "root"--- >		`requires` Ssh.keyImported SshRsa "root"+-- >		`requires` Gpg.keyImported "1B169BE1" "root" +-- >		`requires` Ssh.keyImported SshRsa "root" (Context hostname) -- -- How awesome is that? backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property
src/Propellor/Property/Postfix.hs view
@@ -2,24 +2,120 @@  import Propellor import qualified Propellor.Property.Apt as Apt+import Propellor.Property.File+import qualified Propellor.Property.Service as Service +import qualified Data.Map as M+import Data.List+import Data.Char+ installed :: Property installed = Apt.serviceInstalledRunning "postfix" +restarted :: Property+restarted = Service.restarted "postfix"++reloaded :: Property+reloaded = Service.reloaded "postfix"+ -- | Configures postfix as a satellite system, which --- relats all mail through a relay host, which defaults to smtp.domain. +-- relays all mail through a relay host, which defaults to smtp.domain.  -- -- The smarthost may refuse to relay mail on to other domains, without -- futher coniguration/keys. But this should be enough to get cron job -- mail flowing to a place where it will be seen. satellite :: Property-satellite = setup `requires` installed+satellite = check (not <$> mainCfIsSet "relayhost") setup+	`requires` installed   where 	setup = trivial $ property "postfix satellite system" $ do 		hn <- asks hostName-		ensureProperty $ Apt.reConfigure "postfix"-			[ ("postfix/main_mailer_type", "select", "Satellite system")-			, ("postfix/root_address", "string", "root")-			, ("postfix/destinations", "string", " ")-			, ("postfix/mailname", "string", hn)+		let (_, domain) = separate (== '.') hn+		ensureProperties +			[ Apt.reConfigure "postfix"+				[ ("postfix/main_mailer_type", "select", "Satellite system")+				, ("postfix/root_address", "string", "root")+				, ("postfix/destinations", "string", " ")+				, ("postfix/mailname", "string", hn)+				]+			, mainCf ("relayhost", domain)+				`onChange` reloaded 			]++-- | Sets up a file by running a property (which the filename is passed+-- to). If the setup property makes a change, postmap will be run on the+-- file, and postfix will be reloaded.+mappedFile :: FilePath -> (FilePath -> Property) -> Property+mappedFile f setup = setup f+	`onChange` cmdProperty "postmap" [f]++-- | Run newaliases command, which should be done after changing+-- /etc/aliases.+newaliases :: Property+newaliases = trivial $ cmdProperty "newaliases" []++-- | The main config file for postfix.+mainCfFile :: FilePath+mainCfFile = "/etc/postfix/main.cf"++-- | Sets a main.cf name=value pair. Does not reload postfix immediately.+mainCf :: (String, String) -> Property+mainCf (name, value) = check notset set+		`describe` ("postfix main.cf " ++ setting)+  where+	setting = name ++ "=" ++ value+	notset = (/= Just value) <$> getMainCf name+	set = cmdProperty "postconf" ["-e", setting]++-- | Gets a man.cf setting.+getMainCf :: String -> IO (Maybe String)+getMainCf name = parse . lines <$> readProcess "postconf" [name]+  where+	parse (l:_) = Just $ +		case separate (== '=') l of+			(_, (' ':v)) -> v+			(_, v) -> v+	parse [] = Nothing++-- | Checks if a main.cf field is set. A field that is set to "" +-- is considered not set.+mainCfIsSet :: String -> IO Bool+mainCfIsSet name = do+	v <- getMainCf name+	return $ v /= Nothing && v /= Just ""++-- | Parses main.cf, and removes any initial configuration lines that are+-- overridden to other values later in the file.+--+-- For example, to add some settings, removing any old settings:+--+-- > mainCf `File.containsLines`+-- >	[ "# I like bars."+-- >	, "foo = bar"+-- >	] `onChange` dedupMainCf+--+-- Note that multiline configurations that continue onto the next line+-- are not currently supported.+dedupMainCf :: Property+dedupMainCf = fileProperty "postfix main.cf dedupped" dedupCf mainCfFile++dedupCf :: [String] -> [String]+dedupCf ls =+	let parsed = map parse ls+	in dedup [] (keycounts $ rights parsed) parsed+  where	+	parse l+		| "#" `isPrefixOf` l = Left l+		| "=" `isInfixOf` l = +			let (k, v) = separate (== '=') l+			in Right ((filter (not . isSpace) k), v)+		| otherwise = Left l+	fmt k v = k ++ " =" ++ v++	keycounts = M.fromListWith (+) . map (\(k, _v) -> (k, (1 :: Integer)))++	dedup c _ [] = reverse c+	dedup c kc ((Left v):rest) = dedup (v:c) kc rest+	dedup c kc ((Right (k, v)):rest) = case M.lookup k kc of+		Just n | n > 1 -> dedup c (M.insert k (n - 1) kc) rest+		_ -> dedup (fmt k v:c) kc rest
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -14,12 +14,14 @@ import qualified Propellor.Property.User as User import qualified Propellor.Property.Obnam as Obnam import qualified Propellor.Property.Apache as Apache+import qualified Propellor.Property.Postfix as Postfix import Utility.SafeCommand import Utility.FileMode import Utility.Path  import Data.List import System.Posix.Files+import Data.String.Utils  oldUseNetServer :: [Host] -> Property oldUseNetServer hosts = propertyList ("olduse.net server")@@ -59,9 +61,7 @@ 		, "  <Directory " ++ datadir ++ "/>" 		, "    Options Indexes FollowSymlinks" 		, "    AllowOverride None"-		-- I had this in the file before.-		-- This may be needed by a newer version of apache?-		--, "    Require all granted"+		, Apache.allowAll 		, "  </Directory>" 		] 	]@@ -114,11 +114,11 @@ 	[ Apt.serviceInstalledRunning "mumble-server" 	, Obnam.latestVersion 	, Obnam.backup "/var/lib/mumble-server" "55 5 * * *"-		[ "--repository=sftp://joey@turtle.kitenet.net/~/lib/backup/" ++ hn ++ ".obnam"+		[ "--repository=sftp://joey@usbackup.kitenet.net/~/lib/backup/" ++ hn ++ ".obnam" 		, "--client-name=mumble" 		] Obnam.OnlyClient 		`requires` Ssh.keyImported SshRsa "root" (Context hn)-		`requires` Ssh.knownHost hosts "turtle.kitenet.net" "root"+		`requires` Ssh.knownHost hosts "usbackup.kitenet.net" "root" 	, trivial $ cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"] 	]   where@@ -142,7 +142,7 @@ 	, Obnam.backup "/srv/git" "33 3 * * *" 		[ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net" 		, "--encrypt-with=1B169BE1"-		, "--client-name=wren"+		, "--client-name=wren" -- historical 		] Obnam.OnlyClient 		`requires` Gpg.keyImported "1B169BE1" "root" 		`requires` Ssh.keyImported SshRsa "root" (Context "git.kitenet.net")@@ -191,8 +191,8 @@ type AnnexUUID = String  -- | A website, with files coming from a git-annex repository.-annexWebSite :: [Host] -> Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property-annexWebSite hosts origin hn uuid remotes = propertyList (hn ++" website using git-annex")+annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property+annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-annex") 	[ Git.cloned "joey" origin dir Nothing 		`onChange` setup 	, postupdatehook `File.hasContent`@@ -206,8 +206,6 @@ 	dir = "/srv/web/" ++ hn 	postupdatehook = dir </> ".git/hooks/post-update" 	setup = userScriptProperty "joey" setupscript-		`requires` Ssh.keyImported SshRsa "joey" (Context hn)-		`requires` Ssh.knownHost hosts "turtle.kitenet.net" "joey" 	setupscript =  		[ "cd " ++ shellEscape dir 		, "git config annex.uuid " ++ shellEscape uuid@@ -348,8 +346,27 @@ 	, let f = "/home/joey/.github-keys" 	  in File.hasPrivContent f anyContext 		`onChange` File.ownerGroup f "joey" "joey"+	, Cron.niceJob "github-backup run" "30 4 * * *" "joey"+		"/home/joey/lib/backup" $ intercalate "&&"+			[ "mkdir -p github"+			, "cd github"+			, ". $HOME/.github-keys && github-backup joeyh"+			] 	] +rsyncNetBackup :: [Host] -> Property+rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" "30 5 * * *"+	"joey" "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net"+	`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "joey"++backupsBackedupTo :: [Host] -> HostName -> FilePath -> Property+backupsBackedupTo hosts desthost destdir = Cron.niceJob desc+	"1 1 * * 3" "joey" "/" cmd+	`requires` Ssh.knownHost hosts desthost "joey"+  where+	desc = "backups copied to " ++ desthost ++ " weekly"+	cmd = "rsync -az --delete /home/joey/lib/backup " ++ desthost ++ ":" ++ destdir+ obnamRepos :: [String] -> Property obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) 	(mkbase : map mkrepo rs)@@ -360,3 +377,354 @@ 	mkdir d = File.dirExists d 		`before` File.ownerGroup d "joey" "joey" +podcatcher :: Property+podcatcher = Cron.niceJob "podcatcher run hourly" "55 * * * *"+	"joey" "/home/joey/lib/sound/podcasts"+	"xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update"+	`requires` Apt.installed ["git-annex", "myrepos"]++kiteMailServer :: Property+kiteMailServer = propertyList "kitenet.net mail server"+	[ Postfix.installed+	, Apt.installed ["postfix-pcre"]+	, Apt.serviceInstalledRunning "postgrey"++	, Apt.serviceInstalledRunning "spamassassin"+	, "/etc/default/spamassassin" `File.containsLines`+		[ "# Propellor deployed"+		, "ENABLED=1"+		, "CRON=1"+		, "OPTIONS=\"--create-prefs --max-children 5 --helper-home-dir\""+		, "CRON=1"+		, "NICE=\"--nicelevel 15\""+		] `onChange` Service.restarted "spamassassin"+		`describe` "spamd enabled"+		`requires` Apt.serviceInstalledRunning "cron"+	+	, Apt.serviceInstalledRunning "spamass-milter"+	-- Add -m to prevent modifying messages Subject or body.+	, "/etc/default/spamass-milter" `File.containsLine`+		"OPTIONS=\"-m -u spamass-milter -i 127.0.0.1\""+		`onChange` Service.restarted "spamass-milter"+		`describe` "spamass-milter configured"+	+	, Apt.serviceInstalledRunning "amavisd-milter"+	, "/etc/default/amavisd-milter" `File.containsLines`+		[ "# Propellor deployed"+		, "MILTERSOCKET=/var/spool/postfix/amavis/amavis.sock"+		, "MILTERSOCKETOWNER=\"postfix:postfix\""+		, "MILTERSOCKETMODE=\"0660\""+		]+		`onChange` Service.restarted "amavisd-milter"+		`describe` "amavisd-milter configured for postfix"+	, Apt.serviceInstalledRunning "clamav-freshclam"++	, Apt.installed ["maildrop"]+	, "/etc/maildroprc" `File.hasContent`+		[ "# Global maildrop filter file (deployed with propellor)"+		, "DEFAULT=\"$HOME/Maildir\""+		, "MAILBOX=\"$DEFAULT/.\""+		, "# Filter spam to a spam folder, unless .keepspam exists"+		, "if (/^X-Spam-Status: Yes/)"+		, "{"+		, "  `test -e \"$HOME/.keepspam\"`"+		, "  if ( $RETURNCODE != 0 )"+		, "  to ${MAILBOX}spam"+		, "}"+		]+		`describe` "maildrop configured"++	, "/etc/aliases" `File.hasPrivContentExposed` ctx+		`onChange` Postfix.newaliases+	, hasJoeyCAChain+	, "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx+	, "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx++	, "/etc/postfix/mydomain" `File.containsLines`+		[ "/.*\\.kitenet\\.net/\tOK"+		, "/ikiwiki\\.info/\tOK"+		, "/joeyh\\.name/\tOK"+		]+		`onChange` Postfix.reloaded+		`describe` "postfix mydomain file configured"+	, "/etc/postfix/obscure_client_relay.pcre" `File.containsLine`+		"/^Received: from ([^.]+)\\.kitenet\\.net.*using TLS.*by kitenet\\.net \\(([^)]+)\\) with (E?SMTPS?A?) id ([A-F[:digit:]]+)(.*)/ IGNORE"+		`onChange` Postfix.reloaded+		`describe` "postfix obscure_client_relay file configured"+	, Postfix.mappedFile "/etc/postfix/virtual"+		(flip File.containsLines+			[ "# *@joeyh.name to joey"+			, "@joeyh.name\tjoey"+			]+		) `describe` "postfix virtual file configured"+		`onChange` Postfix.reloaded+	, Postfix.mappedFile "/etc/postfix/relay_clientcerts" $+		flip File.hasPrivContentExposed ctx+	, Postfix.mainCfFile `File.containsLines`+		[ "myhostname = kitenet.net"+		, "mydomain = $myhostname"+		, "append_dot_mydomain = no"+		, "myorigin = kitenet.net"+		, "mydestination = $myhostname, localhost.$mydomain, $mydomain, kite.$mydomain., localhost, regexp:$config_directory/mydomain"+		, "mailbox_command = maildrop"+		, "virtual_alias_maps = hash:/etc/postfix/virtual"++		, "# Allow clients with trusted certs to relay mail through."+		, "relay_clientcerts = hash:/etc/postfix/relay_clientcerts"+		, "smtpd_relay_restrictions = permit_mynetworks,permit_tls_clientcerts,permit_sasl_authenticated,reject_unauth_destination"++		, "# Filter out client relay lines from headers."+		, "header_checks = pcre:$config_directory/obscure_client_relay.pcre"++		, "# Enable postgrey."+		, "smtpd_recipient_restrictions = permit_mynetworks,reject_unauth_destination,check_policy_service inet:127.0.0.1:10023"++		, "# Enable spamass-milter and amavis-milter."+		, "smtpd_milters = unix:/spamass/spamass.sock unix:amavis/amavis.sock"+		, "milter_connect_macros = j {daemon_name} v {if_name} _"++		, "# TLS setup -- server"+		, "smtpd_tls_CAfile = /etc/ssl/certs/joeyca.pem"+		, "smtpd_tls_cert_file = /etc/ssl/certs/postfix.pem"+		, "smtpd_tls_key_file = /etc/ssl/private/postfix.pem"+		, "smtpd_tls_loglevel = 1"+		, "smtpd_tls_received_header = yes"+		, "smtpd_use_tls = yes"+		, "smtpd_tls_ask_ccert = yes"+		, "smtpd_tls_session_cache_database = sdbm:/etc/postfix/smtpd_scache"++		, "# TLS setup -- client"+		, "smtp_tls_CAfile = /etc/ssl/certs/joeyca.pem"+		, "smtp_tls_cert_file = /etc/ssl/certs/postfix.pem"+		, "smtp_tls_key_file = /etc/ssl/private/postfix.pem"+		, "smtp_tls_loglevel = 1"+		, "smtp_use_tls = yes"+		, "smtp_tls_session_cache_database = sdbm:/etc/postfix/smtp_scache"+		]+		`onChange` Postfix.dedupMainCf+		`onChange` Postfix.reloaded+		`describe` "postfix configured"+	+	, Apt.serviceInstalledRunning "dovecot-imapd"+	, Apt.serviceInstalledRunning "dovecot-pop3d"+	, "/etc/dovecot/conf.d/10-mail.conf" `File.containsLine`+		"mail_location = maildir:~/Maildir"+		`onChange` Service.reloaded "dovecot"+		`describe` "dovecot mail.conf"+	, "/etc/dovecot/conf.d/10-auth.conf" `File.containsLine`+		"!include auth-passwdfile.conf.ext"+		`onChange` Service.restarted "dovecot"+		`describe` "dovecot auth.conf"+	, File.hasPrivContent dovecotusers ctx+		`onChange` (dovecotusers `File.mode`+			combineModes [ownerReadMode, groupReadMode])+	, File.ownerGroup dovecotusers "root" "dovecot"++	, Apt.installed ["mutt", "bsd-mailx", "alpine"]++	, pinescript `File.hasContent`+		[ "#!/bin/sh"+		, "# deployed with propellor"+		, "set -e"+		, "pass=$HOME/.pine-password"+		, "if [ ! -e $pass ]; then"+		, "\ttouch $pass"+		, "fi"+		, "chmod 600 $pass"+		, "exec alpine -passfile $pass \"$@\""+		]+		`onChange` (pinescript `File.mode`+			combineModes (readModes ++ executeModes))+		`describe` "pine wrapper script"+	, "/etc/pine.conf" `File.containsLines`+		[ "inbox-path={localhost/novalidate-cert}inbox"+		]+		`describe` "pine configured to use local imap server"+	]+  where+	ctx = Context "kitenet.net"+	pinescript = "/usr/local/bin/pine"+	dovecotusers = "/etc/dovecot/users"++hasJoeyCAChain :: Property+hasJoeyCAChain = "/etc/ssl/certs/joeyca.pem" `File.hasPrivContentExposed`+	Context "joeyca.pem"++kitenetHttps :: Property+kitenetHttps = propertyList "kitenet.net https certs"+	[ File.hasPrivContent "/etc/ssl/certs/web.pem" ctx+	, File.hasPrivContent "/etc/ssl/private/web.pem" ctx+	, File.hasPrivContent "/etc/ssl/certs/startssl.pem" ctx+	, toProp $ Apache.modEnabled "ssl"+	]+  where+	ctx = Context "kitenet.net"++-- Legacy static web sites and redirections from kitenet.net to newer+-- sites.+legacyWebSites :: Property+legacyWebSites = propertyList "legacy web sites"+	[ Apt.serviceInstalledRunning "apache2"+	, toProp $ Apache.modEnabled "rewrite"+	, toProp $ Apache.modEnabled "cgi"+	, toProp $ Apache.modEnabled "speling"+	, userDirHtml+	, kitenetHttps+	, toProp $ Apache.siteEnabled "kitenet.net" $ apachecfg "kitenet.net" True+		-- /var/www is empty+		[ "DocumentRoot /var/www"+		, "<Directory /var/www>"+		, "  Options Indexes FollowSymLinks MultiViews ExecCGI Includes"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		, "ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/"++		-- for mailman cgi scripts+		, "<Directory /usr/lib/cgi-bin>"+		, "  AllowOverride None"+		, "  Options ExecCGI"+		, Apache.allowAll+		, "</Directory>"+		, "Alias /pipermail/ /var/lib/mailman/archives/public/"+		, "<Directory /var/lib/mailman/archives/public/>"+		, "  Options Indexes MultiViews FollowSymlinks"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		, "Alias /images/ /usr/share/images/"+		, "<Directory /usr/share/images/>"+		, "  Options Indexes MultiViews"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"++		, "RewriteEngine On"+		, "# Force hostname to kitenet.net"+		, "RewriteCond %{HTTP_HOST} !^kitenet\\.net [NC]"+		, "RewriteCond %{HTTP_HOST} !^$"+		, "RewriteRule ^/(.*) http://kitenet\\.net/$1 [L,R]"++		, "# Moved pages"+		, "RewriteRule /programs/debhelper http://joeyh.name/code/debhelper/ [L]"+		, "RewriteRule /programs/satutils http://joeyh.name/code/satutils/ [L]"+		, "RewriteRule /programs/filters http://joeyh.name/code/filters/ [L]"+		, "RewriteRule /programs/ticker http://joeyh.name/code/ticker/ [L]"+		, "RewriteRule /programs/pdmenu http://joeyh.name/code/pdmenu/ [L]"+		, "RewriteRule /programs/sleepd http://joeyh.name/code/sleepd/ [L]"+		, "RewriteRule /programs/Lingua::EN::Words2Nums http://joeyh.name/code/Words2Nums/ [L]"+		, "RewriteRule /programs/wmbattery http://joeyh.name/code/wmbattery/ [L]"+		, "RewriteRule /programs/dpkg-repack http://joeyh.name/code/dpkg-repack/ [L]"+		, "RewriteRule /programs/debconf http://joeyh.name/code/debconf/ [L]"+		, "RewriteRule /programs/perlmoo http://joeyh.name/code/perlmoo/ [L]"+		, "RewriteRule /programs/alien http://joeyh.name/code/alien/ [L]"+		, "RewriteRule /~joey/blog/entry/(.+)-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9].html http://joeyh.name/blog/entry/$1/ [L]"+		, "RewriteRule /~anna/.* http://waldeneffect\\.org/ [R]"+		, "RewriteRule /~anna/.* http://waldeneffect\\.org/ [R]"+		, "RewriteRule /~anna http://waldeneffect\\.org/ [R]"+		, "RewriteRule /simpleid/ http://openid.kitenet.net:8081/simpleid/"+		, "# Even the kite home page is not here any more!"+		, "RewriteRule ^/$ http://www.kitenet.net/ [R]"+		, "RewriteRule ^/index.html http://www.kitenet.net/ [R]"+		, "RewriteRule ^/joey http://www.kitenet.net/joey/ [R]"+		, "RewriteRule ^/joey/index.html http://www.kitenet.net/joey/ [R]"+		, "RewriteRule ^/wifi http://www.kitenet.net/wifi/ [R]"+		, "RewriteRule ^/wifi/index.html http://www.kitenet.net/wifi/ [R]"+		+		, "# Old ikiwiki filenames for kitenet.net wiki."+		, "rewritecond $1 !^/~"+		, "rewritecond $1 !^/doc/"+		, "rewritecond $1 !^/pipermail/"+		, "rewritecond $1 !^/cgi-bin/"+		, "rewritecond $1 !.*/index$"+		, "rewriterule (.+).html$ $1/ [r]"++		, "# Old ikiwiki filenames for joey's wiki."+		, "rewritecond $1 ^/~joey/"+		, "rewritecond $1 !.*/index$"+		, "rewriterule (.+).html$ http://kitenet.net/$1/ [L,R]"++		, "# ~joey to joeyh.name"+		, "rewriterule /~joey/(.*) http://joeyh.name/$1 [L]"++		, "# Old familywiki location."+		, "rewriterule /~family/(.*).html http://family.kitenet.net/$1 [L]"+		, "rewriterule /~family/(.*).rss http://family.kitenet.net/$1/index.rss [L]"+		, "rewriterule /~family(.*) http://family.kitenet.net$1 [L]"++		, "rewriterule /~kyle/bywayofscience(.*) http://bywayofscience.branchable.com$1 [L]"+		, "rewriterule /~kyle/family/wiki/(.*).html http://macleawiki.branchable.com/$1 [L]"+		, "rewriterule /~kyle/family/wiki/(.*).rss http://macleawiki.branchable.com/$1/index.rss [L]"+		, "rewriterule /~kyle/family/wiki(.*) http://macleawiki.branchable.com$1 [L]"+		]+	, alias "anna.kitenet.net"+	, toProp $ Apache.siteEnabled "anna.kitenet.net" $ apachecfg "anna.kitenet.net" False+		[ "DocumentRoot /home/anna/html"+		, "<Directory /home/anna/html/>"+		, "  Options Indexes ExecCGI"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		]+	, alias "sows-ear.kitenet.net"+	, alias "www.sows-ear.kitenet.net"+	, toProp $ Apache.siteEnabled "sows-ear.kitenet.net" $ apachecfg "sows-ear.kitenet.net" False+		[ "ServerAlias www.sows-ear.kitenet.net"+		, "DocumentRoot /srv/web/sows-ear.kitenet.net"+		, "<Directory /srv/web/sows-ear.kitenet.net>"+		, "  Options FollowSymLinks"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		]+	, alias "wortroot.kitenet.net"+	, alias "www.wortroot.kitenet.net"+	, toProp $ Apache.siteEnabled "wortroot.kitenet.net" $ apachecfg "wortroot.kitenet.net" False+		[ "ServerAlias www.wortroot.kitenet.net"+		, "DocumentRoot /srv/web/wortroot.kitenet.net"+		, "<Directory /srv/web/wortroot.kitenet.net>"+		, "  Options FollowSymLinks"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		]+	, alias "creeksidepress.com"+	, toProp $ Apache.siteEnabled "creeksidepress.com" $ apachecfg "creeksidepress.com" False+		[ "ServerAlias www.creeksidepress.com"+		, "DocumentRoot /srv/web/www.creeksidepress.com"+		, "<Directory /srv/web/www.creeksidepress.com>"+		, "  Options FollowSymLinks"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"+		]+	, alias "joey.kitenet.net"+	, toProp $ Apache.siteEnabled "joey.kitenet.net" $ apachecfg "joey.kitenet.net" False+		[ "DocumentRoot /home/joey/html"+		, "<Directory /home/joey/html/>"+		, "  Options Indexes ExecCGI"+		, "  AllowOverride None"+		, Apache.allowAll+		, "</Directory>"++		, "RewriteEngine On"++		, "# Old ikiwiki filenames for joey's wiki."+		, "rewritecond $1 !.*/index$"+		, "rewriterule (.+).html$ http://joeyh.name/$1/ [l]"++		, "rewritecond $1 !.*/index$"+		, "rewriterule (.+).rss$ http://joeyh.name/$1/index.rss [l]"+		+		, "# Redirect all to joeyh.name."+		, "rewriterule (.*) http://joeyh.name$1 [r]"+		]+	]++userDirHtml :: Property+userDirHtml = File.fileProperty "apache userdir is html" (map munge) conf+	`onChange` Apache.reloaded+	`requires` (toProp $ Apache.modEnabled "userdir")+  where+	munge = replace "public_html" "html"+	conf = "/etc/apache2/mods-available/userdir.conf"
src/Propellor/Types/Info.hs view
@@ -12,6 +12,7 @@ 	{ _os :: Val System 	, _privDataFields :: S.Set (PrivDataField, Context) 	, _sshPubKey :: Val String+	, _aliases :: S.Set HostName 	, _dns :: S.Set Dns.Record 	, _namedconf :: Dns.NamedConfMap 	, _dockerinfo :: DockerInfo@@ -19,11 +20,12 @@ 	deriving (Eq, Show)  instance Monoid Info where-	mempty = Info mempty mempty mempty mempty mempty mempty+	mempty = Info mempty mempty mempty mempty mempty mempty mempty 	mappend old new = Info 		{ _os = _os old <> _os new 		, _privDataFields = _privDataFields old <> _privDataFields new 		, _sshPubKey = _sshPubKey old <> _sshPubKey new+		, _aliases = _aliases old <> _aliases new 		, _dns = _dns old <> _dns new 		, _namedconf = _namedconf old <> _namedconf new 		, _dockerinfo = _dockerinfo old <> _dockerinfo new