packages feed

propellor 4.7.7 → 4.8.0

raw patch · 13 files changed

+458/−175 lines, 13 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor.Property.DiskImage: type DiskImage = FilePath
- Propellor.Property.DiskImage: vmdkBuiltFor :: FilePath -> RevertableProperty DebianLike UnixLike
- Propellor.Property.SiteSpecific.JoeySites: mumbleServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: obnamRepos :: [String] -> Property UnixLike
+ Propellor.Property.Borg: BorgRepo :: String -> BorgRepo
+ Propellor.Property.Borg: BorgRepoUsing :: [BorgRepoOpt] -> String -> BorgRepo
+ Propellor.Property.Borg: UseSshKey :: FilePath -> BorgRepoOpt
+ Propellor.Property.Borg: data BorgRepo
+ Propellor.Property.Borg: data BorgRepoOpt
+ Propellor.Property.Borg: type BorgParam = String
+ Propellor.Property.DiskImage: RawDiskImage :: FilePath -> RawDiskImage
+ Propellor.Property.DiskImage: VirtualBoxPointer :: FilePath -> VirtualBoxPointer
+ Propellor.Property.DiskImage: buildDiskImage :: DiskImage d => d -> RevertableProperty DebianLike Linux
+ Propellor.Property.DiskImage: class DiskImage d
+ Propellor.Property.DiskImage: describeDiskImage :: DiskImage d => d -> String
+ Propellor.Property.DiskImage: instance Propellor.Property.DiskImage.DiskImage Propellor.Property.DiskImage.RawDiskImage
+ Propellor.Property.DiskImage: instance Propellor.Property.DiskImage.DiskImage Propellor.Property.DiskImage.VirtualBoxPointer
+ Propellor.Property.DiskImage: newtype RawDiskImage
+ Propellor.Property.DiskImage: newtype VirtualBoxPointer
+ Propellor.Property.DiskImage: rawDiskImage :: DiskImage d => d -> RawDiskImage
+ Propellor.Property.Lvm: LogicalVolume :: String -> VolumeGroup -> LogicalVolume
+ Propellor.Property.Lvm: VolumeGroup :: String -> VolumeGroup
+ Propellor.Property.Lvm: YesReallyFormatLogicalVolume :: Eep
+ Propellor.Property.Lvm: data Eep
+ Propellor.Property.Lvm: data LogicalVolume
+ Propellor.Property.Lvm: installed :: RevertableProperty DebianLike DebianLike
+ Propellor.Property.Lvm: lvFormatted :: Eep -> LogicalVolume -> DataSize -> Fs -> RevertableProperty DebianLike UnixLike
+ Propellor.Property.Lvm: newtype VolumeGroup
+ Propellor.Property.Partition: parseFs :: String -> Maybe Fs
+ Propellor.Property.SiteSpecific.JoeySites: hasPostfixCert :: Context -> Property (HasInfo + UnixLike)
- Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux
+ Propellor.Property.DiskImage: imageBuilt :: DiskImage d => d -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux
- Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) UnixLike
+ Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage d => d -> FilePath -> TableType -> Finalization -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux
- Propellor.Property.DiskImage: imageExists :: FilePath -> ByteSize -> Property Linux
+ Propellor.Property.DiskImage: imageExists :: RawDiskImage -> ByteSize -> Property Linux
- Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux
+ Propellor.Property.DiskImage: imageRebuilt :: DiskImage d => d -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux

Files

CHANGELOG view
@@ -1,3 +1,27 @@+propellor (4.8.0) unstable; urgency=medium++  * DiskImage: Made a DiskImage type class, so that different disk image+    formats can be implemented. The properties in this module can generate+    any type that is a member of DiskImage. (API change)+    (To convert existing configs, convert the filename of the disk image+    to RawDiskImage filename.)+  * Removed DiskImage.vmdkBuiltFor property. (API change)+    Instead, use VirtualBoxPointer in the property that creates the disk+    image.+  * Apt.isInstalled: Fix handling of packages that are not known at all+    to apt.+  * Borg: Converted BorgRepo from a String alias to a data type.+    (API change)+  * Borg: Allow specifying ssh private key to use when accessing a borg+    repo by using the BorgRepoUsing constructor with UseSshKey.+  * Borg: Fix broken shell escaping in borg cron job.+  * Attic: Fix broken shell escaping in attic cron job.+  * Make lock file descriptors close-on-exec.+  * Lvm: New module for setting up LVM volumes.+    Thanks, Nicolas Schodet++ -- Joey Hess <id@joeyh.name>  Mon, 25 Sep 2017 14:37:52 -0400+ propellor (4.7.7) unstable; urgency=medium    * Locale: Display an error message when /etc/locale.gen does not contain
debian/changelog view
@@ -1,3 +1,27 @@+propellor (4.8.0) unstable; urgency=medium++  * DiskImage: Made a DiskImage type class, so that different disk image+    formats can be implemented. The properties in this module can generate+    any type that is a member of DiskImage. (API change)+    (To convert existing configs, convert the filename of the disk image+    to RawDiskImage filename.)+  * Removed DiskImage.vmdkBuiltFor property. (API change)+    Instead, use VirtualBoxPointer in the property that creates the disk+    image.+  * Apt.isInstalled: Fix handling of packages that are not known at all+    to apt.+  * Borg: Converted BorgRepo from a String alias to a data type.+    (API change)+  * Borg: Allow specifying ssh private key to use when accessing a borg+    repo by using the BorgRepoUsing constructor with UseSshKey.+  * Borg: Fix broken shell escaping in borg cron job.+  * Attic: Fix broken shell escaping in attic cron job.+  * Make lock file descriptors close-on-exec.+  * Lvm: New module for setting up LVM volumes.+    Thanks, Nicolas Schodet++ -- Joey Hess <id@joeyh.name>  Mon, 25 Sep 2017 14:37:52 -0400+ propellor (4.7.7) unstable; urgency=medium    * Locale: Display an error message when /etc/locale.gen does not contain
joeyconfig.hs view
@@ -24,7 +24,7 @@ import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.LetsEncrypt as LetsEncrypt import qualified Propellor.Property.Grub as Grub-import qualified Propellor.Property.Obnam as Obnam+import qualified Propellor.Property.Borg as Borg import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Systemd as Systemd import qualified Propellor.Property.Journald as Journald@@ -94,12 +94,11 @@ 	& Ssh.userKeys (User "joey") hostContext 		[ (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1YoyHxZwG5Eg0yiMTJLSWJ/+dMM6zZkZiR4JJ0iUfP+tT2bm/lxYompbSqBeiCq+PYcSC67mALxp1vfmdOV//LWlbXfotpxtyxbdTcQbHhdz4num9rJQz1tjsOsxTEheX5jKirFNC5OiKhqwIuNydKWDS9qHGqsKcZQ8p+n1g9Lr3nJVGY7eRRXzw/HopTpwmGmAmb9IXY6DC2k91KReRZAlOrk0287LaK3eCe1z0bu7LYzqqS+w99iXZ/Qs0m9OqAPnHZjWQQ0fN4xn5JQpZSJ7sqO38TBAimM+IHPmy2FTNVVn9zGM+vN1O2xr3l796QmaUG1+XLL0shfR/OZbb joey@darkstar") 		]-	& imageBuilt "/srv/test.img" mychroot MSDOS+	& imageBuilt (VirtualBoxPointer "/srv/test.vmdk") mychroot MSDOS 		[ partition EXT2 `mountedAt` "/boot" 		, partition EXT4 `mountedAt` "/" 		, swapPartition (MegaBytes 256) 		]-		`before` vmdkBuiltFor "/srv/test.img"   where 	mychroot d = debootstrapped mempty d $ props 		& osDebian Unstable X86_64@@ -131,6 +130,7 @@ 	& Apt.unattendedUpgrades 	& Systemd.persistentJournal 	& Journald.systemMaxUse "50MiB"+	& Apt.serviceInstalledRunning "swapspace"  	& Tor.isRelay 	& Tor.named "kite1"@@ -230,7 +230,7 @@ kite = host "kite.kitenet.net" $ props 	& standardSystemUnhardened Testing X86_64 [ "Welcome to kite!" ] 	& ipv4 "66.228.36.95"-	-- & ipv6 "2600:3c03::f03c:91ff:fe73:b0d2"+	& ipv6 "2600:3c03::f03c:91ff:fe73:b0d2" 	& alias "kitenet.net" 	& alias "wren.kitenet.net" -- temporary 	& Ssh.hostKeys (Context "kitenet.net")@@ -254,30 +254,28 @@ 	& Ssh.setSshdConfig "GatewayPorts" "clientspecified" 	& Apt.serviceInstalledRunning "ntp" 	& "/etc/timezone" `File.hasContent` ["US/Eastern"]--	& Obnam.backupEncrypted "/" (Cron.Times "33 1 * * *")-		[ "--repository=sftp://2318@usw-s002.rsync.net/~/kite-root.obnam"-		, "--client-name=kitenet.net"-		, "--exclude=/home"-		, "--exclude=/var/cache"-		, "--exclude=/var/tmp"+	+	& Borg.backup "/" (Borg.BorgRepo "joey@eubackup.kitenet.net:/home/joey/lib/backup/kite/kite.borg") Cron.Daily+		[ "--exclude=/proc/*"+		, "--exclude=/sys/*"+		, "--exclude=/run/*"+		, "--exclude=/tmp/*"+		, "--exclude=/var/tmp/*"+		, "--exclude=/var/cache/*"+		, "--exclude=/home/joey/lib"+		-- These directories are backed up and restored separately. 		, "--exclude=/srv/git" 		, "--exclude=/var/spool/oldusenet"-		, "--exclude=.*/tmp/"-		, "--one-file-system"-		, Obnam.keepParam [Obnam.KeepDays 7, Obnam.KeepWeeks 4, Obnam.KeepMonths 6]-		] Obnam.OnlyClient (Gpg.GpgKeyId "98147487")-		`requires` rootsshkey-		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")-	& Obnam.backupEncrypted "/home" (Cron.Times "33 3 * * *")-		[ "--repository=sftp://2318@usw-s002.rsync.net/~/kite-home.obnam"-		, "--client-name=kitenet.net"-		, "--exclude=/home/joey/lib"-		, "--one-file-system"-		, Obnam.keepParam [Obnam.KeepDays 7, Obnam.KeepWeeks 4, Obnam.KeepMonths 6]-		] Obnam.OnlyClient (Gpg.GpgKeyId "98147487")-		`requires` rootsshkey-		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")+		]+		[ Borg.KeepDays 7+		, Borg.KeepWeeks 4+		, Borg.KeepMonths 6+		]+		`requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "root")+		`requires` Ssh.userKeys (User "root")+			(Context "kite.kitenet.net")+			[ (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5Gza2sNqSKfNtUN4dN/Z3rlqw18nijmXFx6df2GtBoZbkIak73uQfDuZLP+AXlyfHocwdkdHEf/zrxgXS4EokQMGLZhJ37Pr3edrEn/NEnqroiffw7kyd7EqaziA6UOezcLTjWGv+Zqg9JhitYs4WWTpNzrPH3yQf1V9FunZnkzb4gJGndts13wGmPEwSuf+QHbgQvjMOMCJwWSNcJGdhDR66hFlxfG26xx50uIczXYAbgLfHp5W6WuR/lcaS9J6i7HAPwcsPDA04XDinrcpl29QwsMW1HyGS/4FSCgrDqNZ2jzP49Bka78iCLRqfl1efyYas/Zo1jQ0x+pxq2RMr root@kite")+			]  	& alias "smtp.kitenet.net" 	& alias "imap.kitenet.net"@@ -334,7 +332,13 @@ 	& alias "ns4.branchable.com" 	& branchableSecondary 	& Dns.secondaryFor ["animx"] hosts "animx.eu.org"-+	-- Use its own name server (amoung other things this avoids+	-- spamassassin URIBL_BLOCKED.+	& "/etc/resolv.conf" `File.hasContent`+		[ "nameserver 127.0.0.1"+		, "domain kitenet.net"+		, "search kitenet.net"+		] 	& alias "debug-me.joeyh.name" 	-- debug-me installed manually until package is available 	& Systemd.enabled "debug-me"@@ -344,10 +348,6 @@ 		(LetsEncrypt.AgreeTOS (Just "id@joeyh.name")) 	& alias "letsencrypt.joeyh.name"   where-	rootsshkey = Ssh.userKeys (User "root")-		(Context "kite.kitenet.net")-		[ (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5Gza2sNqSKfNtUN4dN/Z3rlqw18nijmXFx6df2GtBoZbkIak73uQfDuZLP+AXlyfHocwdkdHEf/zrxgXS4EokQMGLZhJ37Pr3edrEn/NEnqroiffw7kyd7EqaziA6UOezcLTjWGv+Zqg9JhitYs4WWTpNzrPH3yQf1V9FunZnkzb4gJGndts13wGmPEwSuf+QHbgQvjMOMCJwWSNcJGdhDR66hFlxfG26xx50uIczXYAbgLfHp5W6WuR/lcaS9J6i7HAPwcsPDA04XDinrcpl29QwsMW1HyGS/4FSCgrDqNZ2jzP49Bka78iCLRqfl1efyYas/Zo1jQ0x+pxq2RMr root@kite")-		]  elephant :: Host elephant = host "elephant.kitenet.net" $ props@@ -374,8 +374,7 @@ 	& Apt.serviceInstalledRunning "swapspace"  	& alias "eubackup.kitenet.net"-	& Apt.installed ["obnam", "sshfs", "rsync"]-	& JoeySites.obnamRepos ["pell", "kite"]+	& Apt.installed ["sshfs", "rsync", "borgbackup"] 	& JoeySites.githubBackup 	& JoeySites.rsyncNetBackup hosts @@ -387,9 +386,6 @@ 	& alias "kgb.kitenet.net" 	& JoeySites.kgbServer -	& alias "mumble.kitenet.net"-	& JoeySites.mumbleServer hosts- 	& alias "ns3.kitenet.net" 	& myDnsSecondary @@ -453,8 +449,8 @@ 	& alias "dist-bugs.kitenet.net" 	& alias "family.kitenet.net" -	& osDebian (Stable "jessie") X86_64-	& Apt.installed ["linux-image-amd64"]+	& osDebian (Stable "stretch") X86_64+	& Apt.installed ["linux-image-686-pae"] 	& Apt.unattendedUpgrades 	& Branchable.server hosts 	& Linode.serialGrub
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 4.7.7+Version: 4.8.0 Cabal-Version: >= 1.20 License: BSD2 Maintainer: Joey Hess <id@joeyh.name>@@ -127,6 +127,7 @@     Propellor.Property.LightDM     Propellor.Property.Locale     Propellor.Property.Logcheck+    Propellor.Property.Lvm     Propellor.Property.Mount     Propellor.Property.Network     Propellor.Property.Nginx
src/Propellor/Engine.hs view
@@ -97,6 +97,7 @@ 	lock = do 		createDirectoryIfMissing True (takeDirectory lockfile) 		l <- createFile lockfile stdFileMode+		setFdOption l CloseOnExec True 		setLock l (WriteLock, AbsoluteSeek, 0, 0) 			`catchIO` const alreadyrunning 		return l
src/Propellor/Property/Apt.hs view
@@ -331,7 +331,9 @@ isInstalled p = isInstalled' [p]  isInstalled' :: [Package] -> IO Bool-isInstalled' ps = all (== IsInstalled) <$> getInstallStatus ps+isInstalled' ps = do+	is <- getInstallStatus ps+	return $ all (== IsInstalled) is && length is == length ps  data InstallStatus = IsInstalled | NotInstalled 	deriving (Show, Eq)
src/Propellor/Property/Attic.hs view
@@ -108,7 +108,7 @@   where 	desc = backupdir ++ " attic backup" 	cronjob = Cron.niceJob ("attic_backup" ++ dir) crontimes (User "root") "/" $-		"flock " ++ shellEscape lockfile ++ " sh -c " ++ backupcmd+		"flock " ++ shellEscape lockfile ++ " sh -c " ++ shellEscape backupcmd 	lockfile = "/var/lock/propellor-attic.lock" 	backupcmd = intercalate ";" $ 		createCommand
src/Propellor/Property/Borg.hs view
@@ -3,7 +3,10 @@ -- Support for the Borg backup tool <https://github.com/borgbackup>  module Propellor.Property.Borg-	( installed+	( BorgParam+	, BorgRepo(..)+	, BorgRepoOpt(..)+	, installed 	, repoExists 	, init 	, restored@@ -17,10 +20,40 @@ import qualified Propellor.Property.Cron as Cron import Data.List (intercalate) +-- | Parameter to pass to a borg command. type BorgParam = String -type BorgRepo = FilePath+-- | A borg repository.+data BorgRepo+	-- | Location of the repository, eg+	-- `BorgRepo "root@myserver:/mnt/backup/git.borg"`+	= BorgRepo String+	-- | Location of the repository, and additional options to use+	-- when accessing the repository.+	| BorgRepoUsing [BorgRepoOpt] String +data BorgRepoOpt +	-- | Use to specify a ssh private key to use when accessing a+	-- BorgRepo.+	= UseSshKey FilePath++repoLoc :: BorgRepo -> String+repoLoc (BorgRepo s) = s+repoLoc (BorgRepoUsing _ s) = s++runBorg :: BorgRepo -> [CommandParam] -> IO Bool+runBorg repo ps = case runBorgEnv repo of+	[] -> boolSystem "borg" ps+	environ -> do+		environ' <- addEntries environ <$> getEnvironment+		boolSystemEnv "borg" ps (Just environ')++runBorgEnv :: BorgRepo -> [(String, String)]+runBorgEnv (BorgRepo _) = []+runBorgEnv (BorgRepoUsing os _) = map go os+  where+	go (UseSshKey k) = ("BORG_RSH", k)+ installed :: Property DebianLike installed = withOS desc $ \w o -> case o of 	(Just (System (Debian _ (Stable "jessie")) _)) -> ensureProperty w $@@ -31,19 +64,20 @@         desc = "installed borgbackup"  repoExists :: BorgRepo -> IO Bool-repoExists repo = boolSystem "borg" [Param "list", File repo]+repoExists repo = runBorg repo [Param "list", Param (repoLoc repo)]  -- | Inits a new borg repository init :: BorgRepo -> Property DebianLike-init backupdir = check (not <$> repoExists backupdir) (cmdProperty "borg" initargs)-	`requires` installed+init repo = check (not <$> repoExists repo)+	(cmdPropertyEnv "borg" initargs (runBorgEnv repo))+		`requires` installed   where 	initargs = 		[ "init"-		, backupdir+		, repoLoc repo 		] --- | Restores a directory from an borg backup.+-- | Restores a directory from a borg backup. -- -- Only does anything if the directory does not exist, or exists, -- but is completely empty.@@ -51,7 +85,7 @@ -- The restore is performed atomically; restoring to a temp directory -- and then moving it to the directory. restored :: FilePath -> BorgRepo -> Property DebianLike-restored dir backupdir = go `requires` installed+restored dir repo = go `requires` installed   where 	go :: Property DebianLike 	go = property (dir ++ " restored by borg") $ ifM (liftIO needsRestore)@@ -64,9 +98,9 @@ 	needsRestore = null <$> catchDefaultIO [] (dirContents dir)  	restore = withTmpDirIn (takeDirectory dir) "borg-restore" $ \tmpdir -> do-		ok <- boolSystem "borg" $+		ok <- runBorg repo $ 			[ Param "extract"-			, Param backupdir+			, Param (repoLoc repo) 			, Param tmpdir 			] 		let restoreddir = tmpdir ++ "/" ++ dir@@ -88,53 +122,61 @@ -- to a host, while also ensuring any changes made to it get backed up. -- For example: ----- >	& Borg.backup "/srv/git" "root@myserver:/mnt/backup/git.borg" Cron.Daily+-- >	& Borg.backup "/srv/git"+-- >		(BorgRepo "root@myserver:/mnt/backup/git.borg") +-- >		Cron.Daily -- >		["--exclude=/srv/git/tobeignored"] -- >		[Borg.KeepDays 7, Borg.KeepWeeks 4, Borg.KeepMonths 6, Borg.KeepYears 1] ----- Note that this property does not make borg encrypt the backup--- repository.+-- Note that this property does not initialize the backup repository,+-- so that will need to be done once, before-hand. -- -- Since borg uses a fair amount of system resources, only one borg -- backup job will be run at a time. Other jobs will wait their turns to -- run. backup :: FilePath -> BorgRepo -> Cron.Times -> [BorgParam] -> [KeepPolicy] -> Property DebianLike-backup dir backupdir crontimes extraargs kp = backup' dir backupdir crontimes extraargs kp-	`requires` restored dir backupdir+backup dir repo crontimes extraargs kp = backup' dir repo crontimes extraargs kp+	`requires` restored dir repo  -- | Does a backup, but does not automatically restore. backup' :: FilePath -> BorgRepo -> Cron.Times -> [BorgParam] -> [KeepPolicy] -> Property DebianLike-backup' dir backupdir crontimes extraargs kp = cronjob+backup' dir repo crontimes extraargs kp = cronjob 	`describe` desc 	`requires` installed   where-	desc = backupdir ++ " borg backup"+	desc = repoLoc repo ++ " borg backup" 	cronjob = Cron.niceJob ("borg_backup" ++ dir) crontimes (User "root") "/" $-		"flock " ++ shellEscape lockfile ++ " sh -c " ++ backupcmd+		"flock " ++ shellEscape lockfile ++ " sh -c " ++ shellEscape backupcmd 	lockfile = "/var/lock/propellor-borg.lock"-	backupcmd = intercalate ";" $-		createCommand-		: if null kp then [] else [pruneCommand]+	backupcmd = intercalate ";" $ concat+		[ concatMap exportenv (runBorgEnv repo)+		, [createCommand]+		, if null kp then [] else [pruneCommand]+		]+	exportenv (k, v) = +		[ k ++ "=" ++ shellEscape v+		, "export " ++ k+		] 	createCommand = unwords $ 		[ "borg" 		, "create" 		, "--stats" 		] 		++ map shellEscape extraargs ++-		[ shellEscape backupdir ++ "::" ++ "$(date --iso-8601=ns --utc)"+		[ shellEscape (repoLoc repo) ++ "::" ++ "$(date --iso-8601=ns --utc)" 		, shellEscape dir 		] 	pruneCommand = unwords $ 		[ "borg" 		, "prune"-		, shellEscape backupdir+		, shellEscape (repoLoc repo) 		] 		++ 		map keepParam kp  -- | Constructs an BorgParam that specifies which old backup generations to -- keep. By default, all generations are kept. However, when this parameter is--- passed to the `backup` property, they will run borg prune to clean out+-- passed to the `backup` property, it will run borg prune to clean out -- generations not specified here. keepParam :: KeepPolicy -> BorgParam keepParam (KeepHours n) = "--keep-hourly=" ++ val n
src/Propellor/Property/DiskImage.hs view
@@ -8,12 +8,13 @@ 	-- * Partition specification 	module Propellor.Property.DiskImage.PartSpec, 	-- * Properties-	DiskImage,+	DiskImage(..),+	RawDiskImage(..),+	VirtualBoxPointer(..), 	imageBuilt, 	imageRebuilt, 	imageBuiltFrom, 	imageExists,-	vmdkBuiltFor, 	Grub.BIOS(..), ) where @@ -42,8 +43,49 @@ import qualified Data.ByteString.Lazy as L import System.Posix.Files -type DiskImage = FilePath+-- | Type class of disk image formats.+class DiskImage d where+	-- | Get the location where the raw disk image should be stored.+	rawDiskImage :: d -> RawDiskImage+	-- | Describe the disk image (for display to the user)+	describeDiskImage :: d -> String+	-- | Convert the raw disk image file in the+	-- `rawDiskImage` location into the desired disk image format.+	-- For best efficiency, the raw disk imasge file should be left+	-- unchanged on disk.+	buildDiskImage :: d -> RevertableProperty DebianLike Linux +-- | A raw disk image, that can be written directly out to a disk.+newtype RawDiskImage = RawDiskImage FilePath++instance DiskImage RawDiskImage where+	rawDiskImage = id+	describeDiskImage (RawDiskImage f) = f+	buildDiskImage (RawDiskImage _) = doNothing <!> doNothing++-- | A virtualbox .vmdk file, which contains a pointer to the raw disk+-- image. This can be built very quickly.+newtype VirtualBoxPointer = VirtualBoxPointer FilePath++instance DiskImage VirtualBoxPointer where+	rawDiskImage (VirtualBoxPointer f) = RawDiskImage $+		dropExtension f ++ ".img"+	describeDiskImage (VirtualBoxPointer f) = f+	buildDiskImage (VirtualBoxPointer vmdkfile) = (setup <!> cleanup)+		`describe` (vmdkfile ++ " built")+	  where+		setup = cmdProperty "VBoxManage"+			[ "internalcommands", "createrawvmdk"+			, "-filename", vmdkfile+			, "-rawdisk", diskimage+			]+			`changesFile` vmdkfile+			`onChange` File.mode vmdkfile (combineModes (ownerWriteMode : readModes))+			`requires` Apt.installed ["virtualbox"]+			`requires` File.notPresent vmdkfile+		cleanup = tightenTargets $ File.notPresent vmdkfile+		RawDiskImage diskimage = rawDiskImage (VirtualBoxPointer vmdkfile)+ -- | Creates a bootable disk image. -- -- First the specified Chroot is set up, and its properties are satisfied.@@ -70,7 +112,7 @@ -- > import Propellor.Property.Chroot -- >  -- > foo = host "foo.example.com" $ props--- > 	& imageBuilt "/srv/diskimages/disk.img" mychroot+-- > 	& imageBuilt (RawDiskImage "/srv/diskimages/disk.img") mychroot -- >		MSDOS -- >		[ partition EXT2 `mountedAt` "/boot" -- >			`setFlag` BootFlag@@ -95,7 +137,7 @@ -- -- > foo :: Host -- > foo = host "foo.example.com" $ props--- >	& imageBuilt "/srv/diskimages/bar-disk.img"+-- >	& imageBuilt (RawDiskImage "/srv/diskimages/bar-disk.img") -- >		(hostChroot bar (Debootstrapped mempty)) -- >		MSDOS -- >		[ partition EXT2 `mountedAt` "/boot"@@ -111,30 +153,31 @@ -- >	& Apt.installed ["linux-image-amd64"] -- >	& Grub.installed PC -- >	& hasPassword (User "root")-imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux+imageBuilt :: DiskImage d => d -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux imageBuilt = imageBuilt' False  -- | Like 'built', but the chroot is deleted and rebuilt from scratch each -- time. This is more expensive, but useful to ensure reproducible results -- when the properties of the chroot have been changed.-imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux+imageRebuilt :: DiskImage d => d -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux imageRebuilt = imageBuilt' True -imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux+imageBuilt' :: DiskImage d => Bool -> d -> (FilePath -> Chroot) -> TableType -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux imageBuilt' rebuild img mkchroot tabletype partspec = 	imageBuiltFrom img chrootdir tabletype final partspec 		`requires` Chroot.provisioned chroot 		`requires` (cleanrebuild <!> (doNothing :: Property UnixLike)) 		`describe` desc   where-	desc = "built disk image " ++ img+	desc = "built disk image " ++ describeDiskImage img+	RawDiskImage imgfile = rawDiskImage img 	cleanrebuild :: Property Linux 	cleanrebuild 		| rebuild = property desc $ do 			liftIO $ removeChroot chrootdir 			return MadeChange 		| otherwise = doNothing-	chrootdir = img ++ ".chroot"+	chrootdir = imgfile ++ ".chroot" 	chroot = 		let c = propprivdataonly $ mkchroot chrootdir 		in setContainerProps c $ containerProps c@@ -161,10 +204,11 @@ 	skipit = doNothing :: Property UnixLike  -- | Builds a disk image from the contents of a chroot.-imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) UnixLike+imageBuiltFrom :: DiskImage d => d -> FilePath -> TableType -> Finalization -> [PartSpec ()] -> RevertableProperty (HasInfo + DebianLike) Linux imageBuiltFrom img chrootdir tabletype final partspec = mkimg <!> rmimg   where-	desc = img ++ " built from " ++ chrootdir+	desc = describeDiskImage img ++ " built from " ++ chrootdir+	dest@(RawDiskImage imgfile) = rawDiskImage img 	mkimg = property' desc $ \w -> do 		-- Unmount helper filesystems such as proc from the chroot 		-- first; don't want to include the contents of those.@@ -176,14 +220,17 @@ 		let (mnts, mntopts, parttable) = fitChrootSize tabletype partspec $ 			map (calcsz mnts) mnts 		ensureProperty w $-			imageExists' img parttable+			imageExists' dest parttable 				`before`-			kpartx img (mkimg' mnts mntopts parttable)+			kpartx imgfile (mkimg' mnts mntopts parttable)+				`before`+			buildDiskImage img 	mkimg' mnts mntopts parttable devs = 		partitionsPopulated chrootdir mnts mntopts devs 			`before` 		imageFinalized final mnts mntopts devs parttable-	rmimg = undoRevertableProperty (imageExists' img dummyparttable)+	rmimg = undoRevertableProperty (buildDiskImage img)+		`before` undoRevertableProperty (imageExists' dest dummyparttable) 	dummyparttable = PartTable tabletype []  partitionsPopulated :: FilePath -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> Property DebianLike@@ -255,8 +302,8 @@ -- If the file doesn't exist, or is too small, creates a new one, full of 0's. -- -- If the file is too large, truncates it down to the specified size.-imageExists :: FilePath -> ByteSize -> Property Linux-imageExists img isz = property ("disk image exists" ++ img) $ liftIO $ do+imageExists :: RawDiskImage -> ByteSize -> Property Linux+imageExists (RawDiskImage img) isz = property ("disk image exists" ++ img) $ liftIO $ do 	ms <- catchMaybeIO $ getFileStatus img 	case ms of 		Just s@@ -278,14 +325,14 @@ -- -- Avoids repartitioning the disk image, when a file of the right size -- already exists, and it has the same PartTable.-imageExists' :: FilePath -> PartTable -> RevertableProperty DebianLike UnixLike-imageExists' img parttable = (setup <!> cleanup) `describe` desc+imageExists' :: RawDiskImage -> PartTable -> RevertableProperty DebianLike UnixLike+imageExists' dest@(RawDiskImage img) parttable = (setup <!> cleanup) `describe` desc   where 	desc = "disk image exists " ++ img 	parttablefile = img ++ ".parttable" 	setup = property' desc $ \w -> do 		oldparttable <- liftIO $ catchDefaultIO "" $ readFileStrict parttablefile-		res <- ensureProperty w $ imageExists img (partTableSize parttable)+		res <- ensureProperty w $ imageExists dest (partTableSize parttable) 		if res == NoChange && oldparttable == show parttable 			then return NoChange 			else if res == FailedChange@@ -385,20 +432,3 @@ toSysDir chrootdir d = case makeRelative chrootdir d of 		"." -> "/" 		sysdir -> "/" ++ sysdir---- | Builds a VirtualBox .vmdk file for the specified disk image file.-vmdkBuiltFor :: FilePath -> RevertableProperty DebianLike UnixLike-vmdkBuiltFor diskimage = (setup <!> cleanup)-	`describe` (vmdkfile ++ " built")-  where-	vmdkfile = diskimage ++ ".vmdk"-	setup = cmdProperty "VBoxManage"-		[ "internalcommands", "createrawvmdk"-		, "-filename", vmdkfile-		, "-rawdisk", diskimage-		]-		`changesFile` vmdkfile-		`onChange` File.mode vmdkfile (combineModes (ownerWriteMode : readModes))-		`requires` Apt.installed ["virtualbox"]-		`requires` File.notPresent vmdkfile-	cleanup = File.notPresent vmdkfile
+ src/Propellor/Property/Lvm.hs view
@@ -0,0 +1,171 @@+-- | Maintainer: Nicolas Schodet <nico@ni.fr.eu.org>+--+-- Support for LVM logical volumes.++module Propellor.Property.Lvm (+	lvFormatted,+	installed,+	Eep(..),+	VolumeGroup(..),+	LogicalVolume(..),+) where++import Propellor+import Propellor.Base+import Utility.DataUnits+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Mount as Mount+import qualified Propellor.Property.Partition as Partition++data Eep = YesReallyFormatLogicalVolume++type DataSize = String++newtype VolumeGroup = VolumeGroup String+data LogicalVolume = LogicalVolume String VolumeGroup++-- | Create or resize a logical volume, and make sure it is formatted.  When+-- reverted, remove the logical volume.+--+-- Example use:+--+-- > import qualified Propellor.Property.Lvm as Lvm+-- > import qualified Propellor.Property.Partition as Partition+-- > Lvm.lvFormatted Lvm.YesReallyFormatLogicalVolume+-- >         (Lvm.LogicalVolume "test" (Lvm.VolumeGroup "vg0")) "16m"+-- >         Partition.EXT4+--+-- If size and filesystem match, nothing is done.+--+-- Volume group must have been created already.+lvFormatted+	:: Eep+	-> LogicalVolume+	-> DataSize+	-> Partition.Fs+	-> RevertableProperty DebianLike UnixLike+lvFormatted YesReallyFormatLogicalVolume lv sz fs =+	setup <!> cleanup+  where+	setup :: Property DebianLike+	setup = property' ("formatted logical volume " ++ (vglv lv)) $ \w -> do+		es <- liftIO $ vgExtentSize vg+		case es of+			Nothing -> errorMessage $+				"can not get extent size, does volume group "+				++ vgname ++ " exist?"+			Just extentSize -> do+				case parseSize of+					Nothing -> errorMessage +						"can not parse volume group size"+					Just size -> do+						state <- liftIO $ lvState lv+						let rsize = roundSize extentSize size+						ensureProperty w $+							setupprop rsize state++	cleanup :: Property UnixLike+	cleanup = property' ("removed logical volume " ++ (vglv lv)) $ \w -> do+		exists <- liftIO $ lvExists lv+		ensureProperty w $ if exists+			then removedprop+			else doNothing++	-- Parse size.+	parseSize :: Maybe Integer+	parseSize = readSize dataUnits sz++	-- Round size to next extent size multiple.+	roundSize :: Integer -> Integer -> Integer+	roundSize extentSize s =+		(s + extentSize - 1) `div` extentSize * extentSize++	-- Dispatch to the right props.+	setupprop :: Integer -> (Maybe LvState) -> Property DebianLike+	setupprop size Nothing = createdprop size `before` formatprop+	setupprop size (Just (LvState csize cfs))+		| size == csize && fsMatch fs cfs = doNothing+		| size == csize = formatprop+		| fsMatch fs cfs = tightenTargets $ resizedprop size True+		| otherwise = resizedprop size False `before` formatprop++	createdprop :: Integer -> Property UnixLike+	createdprop size =+		cmdProperty "lvcreate"+			(bytes size $ [ "-n", lvname, "--yes", vgname ])+			`assume` MadeChange++	resizedprop :: Integer -> Bool -> Property UnixLike+	resizedprop size rfs =+		cmdProperty "lvresize"+			(resizeFs rfs $ bytes size $ [ vglv lv ])+			`assume` MadeChange+	  where+		resizeFs True l = "-r" : l+		resizeFs False l = l++	removedprop :: Property UnixLike+	removedprop = cmdProperty "lvremove" [ "-f", vglv lv ]+		`assume` MadeChange++	formatprop :: Property DebianLike+	formatprop = Partition.formatted Partition.YesReallyFormatPartition+		fs (path lv)++	fsMatch :: Partition.Fs -> Maybe Partition.Fs -> Bool+	fsMatch a (Just b) = a == b+	fsMatch _ _ = False++	bytes size l = "-L" : ((show size) ++ "b") : l++	(LogicalVolume lvname vg@(VolumeGroup vgname)) = lv++-- | Make sure needed tools are installed.+installed :: RevertableProperty DebianLike DebianLike+installed = install <!> remove+  where+	install = Apt.installed ["lvm2"]+	remove = Apt.removed ["lvm2"]++data LvState = LvState Integer (Maybe Partition.Fs)++-- Check for logical volume existance.+lvExists :: LogicalVolume -> IO Bool+lvExists lv = doesFileExist (path lv)++-- Return Nothing if logical volume does not exists (or error), else return+-- its size and maybe file system.+lvState :: LogicalVolume -> IO (Maybe LvState)+lvState lv = do+	exists <- lvExists lv+	if not exists+		then return Nothing+		else do+			s <- readLvSize+			fs <- maybe Nothing Partition.parseFs <$> readFs+			return $ do+				size <- s+				return $ LvState size fs+  where+	readLvSize = catchDefaultIO Nothing $ readish+		<$> readProcess "lvs" [ "-o", "size", "--noheadings",+			"--nosuffix", "--units", "b", vglv lv ]+	readFs = Mount.blkidTag "TYPE" (path lv)++-- Read extent size (or Nothing on error).+vgExtentSize :: VolumeGroup -> IO (Maybe Integer)+vgExtentSize (VolumeGroup vgname) =+	catchDefaultIO Nothing $ readish+		<$> readProcess "vgs" [ "-o", "vg_extent_size",+			"--noheadings", "--nosuffix", "--units", "b", vgname ]++-- Give "vgname/lvname" for a LogicalVolume.+vglv :: LogicalVolume -> String+vglv lv =+	vgname </> lvname+  where+	(LogicalVolume lvname (VolumeGroup vgname)) = lv++-- Give device path.+path :: LogicalVolume -> FilePath+path lv = "/dev" </> (vglv lv)
src/Propellor/Property/Partition.hs view
@@ -15,6 +15,20 @@ data Fs = EXT2 | EXT3 | EXT4 | BTRFS | REISERFS | XFS | FAT | VFAT | NTFS | LinuxSwap 	deriving (Show, Eq) +-- | Parse commonly used names of filesystems.+parseFs :: String -> Maybe Fs+parseFs "ext2" = Just EXT2+parseFs "ext3" = Just EXT3+parseFs "ext4" = Just EXT4+parseFs "btrfs" = Just BTRFS+parseFs "reiserfs" = Just REISERFS+parseFs "xfs" = Just XFS+parseFs "fat" = Just FAT+parseFs "vfat" = Just VFAT+parseFs "ntfs" = Just NTFS+parseFs "swap" = Just LinuxSwap+parseFs _ = Nothing+ data Eep = YesReallyFormatPartition  -- | Formats a partition.
src/Propellor/Property/SiteSpecific/Branchable.hs view
@@ -8,6 +8,8 @@ import qualified Propellor.Property.Postfix as Postfix import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Sudo as Sudo+import qualified Propellor.Property.Borg as Borg+import qualified Propellor.Property.Cron as Cron  server :: [Host] -> Property (HasInfo + DebianLike) server hosts = propertyList "branchable server" $ props@@ -37,18 +39,24 @@ 	& Postfix.installed 	& Postfix.mainCf ("mailbox_command", "procmail -a \"$EXTENSION\"") 	-	-- Obnam is run by a cron job in ikiwiki-hosting.-	& "/etc/obnam.conf" `File.hasContent`-		[ "[config]"-		, "repository = sftp://joey@eubackup.kitenet.net/home/joey/lib/backup/pell.obnam"-		, "log = /var/log/obnam.log"-		, "encrypt-with = " ++ obnamkey-		, "log-level = info"-		, "log-max = 1048576"-		, "keep = 7d,5w,12m"-		, "upload-queue-size = 128"-		, "lru-size = 128"+	& Borg.backup "/" (Borg.BorgRepo "joey@eubackup.kitenet.net:/home/joey/lib/backup/branchable/pell.borg") Cron.Daily+		[ "--exclude=/proc/*"+		, "--exclude=/sys/*"+		, "--exclude=/run/*"+		, "--exclude=/tmp/*"+		, "--exclude=/var/tmp/*"+		, "--exclude=/var/backups/ikiwiki-hosting-web/*"+		, "--exclude=/var/cache/*"+		, "--exclude=/home/*/source/*"+		, "--exclude=/home/*/public_html/*"+		, "--exclude=/home/*/.git/*" 		]+		[ Borg.KeepDays 7+		, Borg.KeepWeeks 5+		, Borg.KeepMonths 12+		, Borg.KeepYears 1+		]+	-- gpg key that can be used to decrypt the borg backup key 	& Gpg.keyImported (Gpg.GpgKeyId obnamkey) (User "root") 	& Ssh.userKeys (User "root") (Context "branchable.com") 		[ (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2PqTSupwncqeffNwZQXacdEWp7L+TxllIxH7WjfRMb3U74mQxWI0lwqLVW6Fox430DvhSqF1y5rJBvTHh4i49Tc9lZ7mwAxA6jNOP6bmdfteaKKYmUw5qwtJW0vISBFu28qBO11Nq3uJ1D3Oj6N+b3mM/0D3Y3NoGgF8+2dLdi81u9+l6AQ5Jsnozi2Ni/Osx2oVGZa+IQDO6gX8VEP4OrcJFNJe8qdnvItcGwoivhjbIfzaqNNvswKgGzhYLOAS5KT8HsjvIpYHWkyQ5QUX7W/lqGSbjP+6B8C3tkvm8VLXbmaD+aSkyCaYbuoXC2BoJdS7Jh8phKMwPJmdYVepn")
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -15,7 +15,7 @@ import qualified Propellor.Property.Cron as Cron import qualified Propellor.Property.Service as Service import qualified Propellor.Property.User as User-import qualified Propellor.Property.Obnam as Obnam+import qualified Propellor.Property.Borg as Borg import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.Postfix as Postfix import qualified Propellor.Property.Systemd as Systemd@@ -141,17 +141,17 @@ 		)  	oldUseNetBackup :: Property (HasInfo + DebianLike)-	oldUseNetBackup = Obnam.backup datadir (Cron.Times "33 4 * * *")-		[ "--repository=sftp://2318@usw-s002.rsync.net/~/olduse.net"-		, "--client-name=spool"-		, "--ssh-key=" ++ keyfile-		, Obnam.keepParam [Obnam.KeepDays 30]-		] Obnam.OnlyClient+	oldUseNetBackup = Borg.backup datadir borgrepo +		(Cron.Times "33 4 * * *")+		[]+		[Borg.KeepDays 30] 		`requires` Ssh.userKeyAt (Just keyfile) 			(User "root") 			(Context "olduse.net") 			(SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0F6L76SChMCIGmeyGhlFMUTgZ3BoTbATiOSs0A7KXQoI1LTE5ZtDzzUkrQRJVpJ640pfMR7cQZyBm8tv+kYIPp0238GrX43c1vgm0L78agDnBU7r2iNMyWIwhssK8O3ZAhp8Q4KCz1r8hP2nIiD0y1D1VWW8h4KWOS7I1XCEAjOTvFvEjTh6a9MyHrcIkv7teUUzTBRjNrsyijCFRk1+pEET54RueoOmEjQcWd/sK1tYRiMZjegRLBOus2wUWsUOvznJ2iniLONUTGAWRnEV+O7hLN6CD44osJ+wkZk8bPAumTS0zcSLckX1jpdHJicmAyeniWSd4FCqm1YE6/xDD")-		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")+		`requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "root")+	borgrepo = Borg.BorgRepoUsing [Borg.UseSshKey keyfile]+		"joey@eubackup.kitenet.net:/home/joey/lib/backup/olduse.net/olduse.net.borg" 	keyfile = "/root/.ssh/olduse.net.key"  oldUseNetShellBox :: Property DebianLike@@ -162,13 +162,13 @@ oldUseNetInstalled :: Apt.Package -> Property DebianLike oldUseNetInstalled pkg = check (not <$> Apt.isInstalled pkg) $ 	propertyList ("olduse.net " ++ pkg) $ props-		& Apt.installed (words "build-essential devscripts debhelper git libncursesw5-dev libpcre3-dev pkg-config bison libicu-dev libidn11-dev libcanlock2-dev libuu-dev ghc libghc-strptime-dev libghc-hamlet-dev libghc-ifelse-dev libghc-hxt-dev libghc-utf8-string-dev libghc-missingh-dev libghc-sha-dev")+		& Apt.installed (words "build-essential devscripts debhelper git libncursesw5-dev libpcre3-dev pkg-config bison libicu-dev libidn11-dev libcanlock2-dev libuu-dev ghc libghc-ifelse-dev libghc-hxt-dev libghc-utf8-string-dev libghc-missingh-dev libghc-sha-dev haskell-stack") 			`describe` "olduse.net build deps" 		& scriptProperty 			[ "rm -rf /root/tmp/oldusenet" -- idenpotency 			, "git clone git://olduse.net/ /root/tmp/oldusenet/source" 			, "cd /root/tmp/oldusenet/source/"-			, "dpkg-buildpackage -us -uc"+			, "HOME=/root dpkg-buildpackage -us -uc" 			, "dpkg -i ../" ++ pkg ++ "_*.deb || true" 			, "apt-get -fy install" -- dependencies 			, "rm -rf /root/tmp/oldusenet"@@ -193,42 +193,20 @@ 					`onChange` Service.running "kgb-bot" 		_ -> error "kgb server needs Debian unstable (for kgb-bot 1.31+)" -mumbleServer :: [Host] -> Property (HasInfo + DebianLike)-mumbleServer hosts = combineProperties hn $ props-	& Apt.serviceInstalledRunning "mumble-server"-	& Obnam.backup "/var/lib/mumble-server" (Cron.Times "55 5 * * *")-		[ "--repository=sftp://2318@usw-s002.rsync.net/~/" ++ hn ++ ".obnam"-		, "--ssh-key=" ++ sshkey-		, "--client-name=mumble"-		, Obnam.keepParam [Obnam.KeepDays 30]-		] Obnam.OnlyClient-		`requires` Ssh.userKeyAt (Just sshkey)-			(User "root")- 			(Context hn)-			(SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSXXSM3mM8SNu+qel9R/LkDIkjpV3bfpUtRtYv2PTNqicHP+DdoThrr0ColFCtLH+k2vQJvR2n8uMzHn53Dq2IO3TtD27+7rJSsJwAZ8oftNzuTir8IjAwX5g6JYJs+L0Ny4RB0ausd+An0k/CPMRl79zKxpZd2MBMDNXt8hyqu0vS0v1ohq5VBEVhBBvRvmNQvWOCj7PdrKQXpUBHruZOeVVEdUUXZkVc1H0t7LVfJnE+nGKyWbw2jM+7r3Rn5Semc4R1DxsfaF8lKkZyE88/5uZQ/ddomv8ptz6YZ5b+Bg6wfooWPC3RWAALjxnHaC2yN1VONAvHmT0uNn1o6v0b")-		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")-	& cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"]-		`assume` NoChange-  where-	hn = "mumble.debian.net"-	sshkey = "/root/.ssh/mumble.debian.net.key"- -- git.kitenet.net and git.joeyh.name gitServer :: [Host] -> Property (HasInfo + DebianLike) gitServer hosts = propertyList "git.kitenet.net setup" $ props-	& Obnam.backupEncrypted "/srv/git" (Cron.Times "33 3 * * *")-		[ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net"-		, "--ssh-key=" ++ sshkey-		, "--client-name=wren" -- historical-		, Obnam.keepParam [Obnam.KeepDays 30]-		] Obnam.OnlyClient (Gpg.GpgKeyId "1B169BE1")+	& Borg.backup "/srv/git" borgrepo+		(Cron.Times "33 3 * * *")+		[]+		[Borg.KeepDays 30] 		`requires` Ssh.userKeyAt (Just sshkey) 			(User "root") 			(Context "git.kitenet.net")-			(SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0F6L76SChMCIGmeyGhlFMUTgZ3BoTbATiOSs0A7KXQoI1LTE5ZtDzzUkrQRJVpJ640pfMR7cQZyBm8tv+kYIPp0238GrX43c1vgm0L78agDnBU7r2iNMyWIwhssK8O3ZAhp8Q4KCz1r8hP2nIiD0y1D1VWW8h4KWOS7I1XCEAjOTvFvEjTh6a9MyHrcIkv7teUUzTBRjNrsyijCFRk1+pEET54RueoOmEjQcWd/sK1tYRiMZjegRLBOus2wUWsUOvznJ2iniLONUTGAWRnEV+O7hLN6CD44osJ+wkZk8bPAumTS0zcSLckX1jpdHJicmAyeniWSd4FCqm1YE6/xDD")-		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")-		`requires` Ssh.authorizedKeys (User "family") (Context "git.kitenet.net")-		`requires` User.accountFor (User "family")+			(SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLwUUkpkI9c2Wcnv/E4v9bJ7WcpiNkToltXfzRDd1F31AYrucfSMgzu3rtDpEL+wSnQLua/taJkWUWT/pyXOAh+90K6O/YeBZmY5CK01rYDz3kSTAtwHkMqednsRjdQS6NNJsuWc1reO8a4pKtsToJ3G9VAKufCkt2b8Nhqz0yLvLYwwU/mdI8DmfX6IgXhdy9njVEG/jsQnLFXY6QEfwKbIPs9O6qo4iFJg3defXX+zVMLsh3NE1P2i2VxMjxJEQdPdy9Z1sVpkiQM+mgJuylQQ5flPK8sxhO9r4uoK/JROkjPJNYoJMlsN+QlK04ABb7JV2JwhAL/Y8ypjQ13JdT")+		`requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "root")+	& Ssh.authorizedKeys (User "family") (Context "git.kitenet.net")+	& User.accountFor (User "family") 	& Apt.installed ["git", "rsync", "cgit"] 	& Apt.installed ["git-annex"] 	& Apt.installed ["kgb-client"]@@ -257,6 +235,8 @@ 	& Apache.modEnabled "cgi"   where 	sshkey = "/root/.ssh/git.kitenet.net.key"+	borgrepo = Borg.BorgRepoUsing [Borg.UseSshKey sshkey]+		"joey@eubackup.kitenet.net:/home/joey/lib/backup/git.kitenet.net/git.kitenet.net.borg" 	website hn = Apache.httpsVirtualHost' hn "/srv/web/git.kitenet.net/" letos 		[ Apache.iconDir 		, "  <Directory /srv/web/git.kitenet.net/>"@@ -440,16 +420,6 @@ 	desc = "backups copied from " ++ srchost ++ " on boot" 	cmd = "sleep 30m && rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir </> srchost -obnamRepos :: [String] -> Property UnixLike-obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) $-	toProps (mkbase : map mkrepo rs)-  where-	mkbase = mkdir "/home/joey/lib/backup"-		`requires` mkdir "/home/joey/lib"-	mkrepo r = mkdir ("/home/joey/lib/backup/" ++ r ++ ".obnam")-	mkdir d = File.dirExists d-		`before` File.ownerGroup d (User "joey") (Group "joey")- podcatcher :: Property DebianLike podcatcher = Cron.niceJob "podcatcher run hourly" (Cron.Times "55 * * * *") 	(User "joey") "/home/joey/lib/sound/podcasts"@@ -520,6 +490,7 @@  	& "/etc/aliases" `File.hasPrivContentExposed` ctx 		`onChange` Postfix.newaliases+	& hasPostfixCert ctx  	& "/etc/postfix/mydomain" `File.containsLines` 		[ "/.*\\.kitenet\\.net/\tOK"@@ -582,9 +553,9 @@ 		, "milter_default_action = accept"  		, "# TLS setup -- server"-		, "smtpd_tls_CAfile = /etc/letsencrypt/live/kitenet.net/fullchain.pem"-		, "smtpd_tls_cert_file = /etc/letsencrypt/live/kitenet.net/cert.pem"-		, "smtpd_tls_key_file = /etc/letsencrypt/live/kitenet.net/privkey.pem"+		, "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"@@ -592,9 +563,9 @@ 		, "smtpd_tls_session_cache_database = sdbm:/etc/postfix/smtpd_scache"  		, "# TLS setup -- client"-		, "smtp_tls_CAfile = /etc/letsencrypt/live/kitenet.net/fullchain.pem"-		, "smtp_tls_cert_file = /etc/letsencrypt/live/kitenet.net/cert.pem"-		, "smtp_tls_key_file = /etc/letsencrypt/live/kitenet.net/privkey.pem"+		, "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"@@ -613,12 +584,6 @@ 		"!include auth-passwdfile.conf.ext" 		`onChange` Service.restarted "dovecot" 		`describe` "dovecot auth.conf"-	& "/etc/dovecot/conf.d/10-ssl.conf" `File.containsLines`-		[ "ssl_cert = </etc/letsencrypt/live/kitenet.net/fullchain.pem"-		, "ssl_key = </etc/letsencrypt/live/kitenet.net/privkey.pem"-		]-		`onChange` Service.restarted "dovecot"-		`describe` "dovecot letsencrypt certs" 	& File.hasPrivContent dovecotusers ctx 		`onChange` (dovecotusers `File.mode` 			combineModes [ownerReadMode, groupReadMode])@@ -718,6 +683,11 @@ 		, "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd" 		] 		`onChange` Postfix.reloaded++hasPostfixCert :: Context -> Property (HasInfo + UnixLike)+hasPostfixCert ctx = combineProperties "postfix tls cert installed" $ props+	& "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx+	& "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx  -- Legacy static web sites and redirections from kitenet.net to newer -- sites.