diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,24 @@
+propellor (3.2.0) unstable; urgency=medium
+
+  [ Sean Whitton ]
+  * Using ccache with Sbuild.built & Sbuild.builtFor is now toggleable: these
+    properties now take a parameter of type Sbuild.UseCcache.  (API Change)
+  * Sbuild.piupartsConf: no longer takes an Apt.Url. (API Change)
+  * Sbuild.piupartsConf & Sbuild.piupartsConfFor: does nothing if corresponding
+    schroot not built.
+    Previously, these properties built the schroot if it was missing.
+  * Sbuild.built & Sbuild.piupartsConf: add an additional alias to sid chroots.
+    This is for compatibility with `dgit sbuild`.
+  * Further improvements to Sbuild.hs haddock.
+
+  [ Joey Hess ]
+  * Tor.hiddenService: Converted port parameter from Int to Port. (API change)
+  * Tor.hiddenServiceAvailable: The hidden service hostname file may not
+    be available immedaitely after configuring tor; avoid ugly error in
+    this case.
+
+ -- Joey Hess <id@joeyh.name>  Sat, 10 Sep 2016 11:39:40 -0400
+
 propellor (3.1.2) unstable; urgency=medium
 
   [ Joey Hess ]
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,24 @@
+propellor (3.2.0) unstable; urgency=medium
+
+  [ Sean Whitton ]
+  * Using ccache with Sbuild.built & Sbuild.builtFor is now toggleable: these
+    properties now take a parameter of type Sbuild.UseCcache.  (API Change)
+  * Sbuild.piupartsConf: no longer takes an Apt.Url. (API Change)
+  * Sbuild.piupartsConf & Sbuild.piupartsConfFor: does nothing if corresponding
+    schroot not built.
+    Previously, these properties built the schroot if it was missing.
+  * Sbuild.built & Sbuild.piupartsConf: add an additional alias to sid chroots.
+    This is for compatibility with `dgit sbuild`.
+  * Further improvements to Sbuild.hs haddock.
+
+  [ Joey Hess ]
+  * Tor.hiddenService: Converted port parameter from Int to Port. (API change)
+  * Tor.hiddenServiceAvailable: The hidden service hostname file may not
+    be available immedaitely after configuring tor; avoid ugly error in
+    this case.
+
+ -- Joey Hess <id@joeyh.name>  Sat, 10 Sep 2016 11:39:40 -0400
+
 propellor (3.1.2) unstable; urgency=medium
 
   [ Joey Hess ]
diff --git a/joeyconfig.hs b/joeyconfig.hs
--- a/joeyconfig.hs
+++ b/joeyconfig.hs
@@ -31,6 +31,7 @@
 import qualified Propellor.Property.OS as OS
 import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost
 import qualified Propellor.Property.HostingProvider.Linode as Linode
+import qualified Propellor.Property.HostingProvider.DigitalOcean as DigitalOcean
 import qualified Propellor.Property.SiteSpecific.GitHome as GitHome
 import qualified Propellor.Property.SiteSpecific.GitAnnexBuilder as GitAnnexBuilder
 import qualified Propellor.Property.SiteSpecific.IABak as IABak
@@ -55,6 +56,7 @@
 	, elephant
 	, beaver
 	, pell
+	, keysafe
 	, iabak
 	] ++ monsters
 
@@ -88,6 +90,9 @@
 	& JoeySites.dkimMilter
 	& JoeySites.alarmClock "*-*-* 7:30" (User "joey")
 		"/usr/bin/timeout 45m /home/joey/bin/goodmorning"
+	& 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 "/tmp/img" c MSDOS (grubBooted PC)
 		[ partition EXT2 `mountedAt` "/boot"
@@ -452,6 +457,35 @@
 	& Linode.chainPVGrub 5
 	& Apt.unattendedUpgrades
 	& Branchable.server hosts
+
+keysafe :: Host
+keysafe = host "keysafe.joeyh.name" $ props
+	& ipv4 "139.59.17.168"
+	& Hostname.sane
+	& osDebian (Stable "jessie") X86_64
+	& Apt.stdSourcesList `onChange` Apt.upgrade
+	& DigitalOcean.distroKernel
+	-- This is a 500 mb VM, so need more ram to build propellor.
+	& Apt.serviceInstalledRunning "swapspace"
+	& Cron.runPropellor (Cron.Times "30 * * * *")
+	& Apt.installed ["etckeeper", "sudo"]
+	& Apt.removed ["nfs-common", "exim4", "exim4-base", "exim4-daemon-light", "rsyslog", "acpid", "rpcbind", "at"]
+
+	& User.hasSomePassword (User "root")
+	& User.accountFor (User "joey")
+	& User.hasSomePassword (User "joey")
+	& Sudo.enabledFor (User "joey")
+
+	& Ssh.installed
+	& Ssh.randomHostKeys
+	& User "root" `Ssh.authorizedKeysFrom` (User "joey", darkstar)
+	& User "joey" `Ssh.authorizedKeysFrom` (User "joey", darkstar)
+	& Ssh.noPasswords
+
+	& Tor.installed
+	& Tor.hiddenServiceAvailable "keysafe" (Port 4242)
+		`requires` Tor.hiddenServiceData "keysafe" hostContext
+	-- keysafe installed manually until package is available
 
 iabak :: Host
 iabak = host "iabak.archiveteam.org" $ props
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 3.1.2
+Version: 3.2.0
 Cabal-Version: >= 1.8
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
diff --git a/src/Propellor/Property/Debootstrap.hs b/src/Propellor/Property/Debootstrap.hs
--- a/src/Propellor/Property/Debootstrap.hs
+++ b/src/Propellor/Property/Debootstrap.hs
@@ -168,7 +168,7 @@
 				makeDevicesTarball
 				makeWrapperScript (localInstallDir </> subdir)
 				return MadeChange
-			_ -> errorMessage "debootstrap tar file did not contain exactly one dirctory"
+			_ -> errorMessage "debootstrap tar file did not contain exactly one directory"
 
 sourceRemove :: Property Linux
 sourceRemove = property "debootstrap not installed from source" $ liftIO $
diff --git a/src/Propellor/Property/Sbuild.hs b/src/Propellor/Property/Sbuild.hs
--- a/src/Propellor/Property/Sbuild.hs
+++ b/src/Propellor/Property/Sbuild.hs
@@ -12,8 +12,8 @@
 1. you want to build for a Debian release strictly newer than squeeze,
 or for a Buntish release newer than or equal to trusty
 
-2. if you want to build for Debian stretch or newer, you have sbuild
-0.70.0 or newer (there is a backport to jessie)
+2. if you want to build for Debian stretch or newer, you have sbuild 0.70.0 or
+newer (there is a backport to jessie)
 
 The latter is due to the migration from GnuPG v1 to GnuPG v2.1 in
 Debian stretch, which older sbuild can't handle.
@@ -21,7 +21,7 @@
 Suggested usage in @config.hs@:
 
 >  & Apt.installed ["piuparts", "autopkgtest"]
->  & Sbuild.builtFor (System (Debian Linux Unstable) X86_32)
+>  & Sbuild.builtFor (System (Debian Linux Unstable) X86_32) Sbuild.UseCcache
 >  & Sbuild.piupartsConfFor (System (Debian Linux Unstable) X86_32)
 >  & Sbuild.updatedFor (System (Debian Linux Unstable) X86_32) `period` Weekly 1
 >  & Sbuild.usableBy (User "spwhitton")
@@ -71,6 +71,7 @@
 module Propellor.Property.Sbuild (
 	-- * Creating and updating sbuild schroots
 	SbuildSchroot(..),
+	UseCcache(..),
 	built,
 	updated,
 	piupartsConf,
@@ -113,31 +114,37 @@
 instance Show SbuildSchroot where
 	show (SbuildSchroot suite arch) = suite ++ "-" ++ architectureToDebianArchString arch
 
+-- | Whether an sbuild schroot should use ccache during builds
+--
+-- ccache is generally useful but it breaks building some packages.  This data
+-- types allows you to toggle it on and off for particular schroots.
+data UseCcache = UseCcache | NoCcache
+
 -- | Build and configure a schroot for use with sbuild using a distribution's
 -- standard mirror
 --
 -- This function is a convenience wrapper around 'built', allowing the user to
 -- identify the schroot and distribution using the 'System' type
-builtFor :: System -> RevertableProperty DebianLike UnixLike
-builtFor sys = go <!> deleted
+builtFor :: System -> UseCcache -> RevertableProperty DebianLike UnixLike
+builtFor sys cc = go <!> deleted
   where
 	go = property' ("sbuild schroot for " ++ show sys) $
 		\w -> case (schrootFromSystem sys, stdMirror sys) of
 			(Just s, Just u)  -> ensureProperty w $
-				setupRevertableProperty $ built s u
+				setupRevertableProperty $ built s u cc
 			_ -> errorMessage
 				("don't know how to debootstrap " ++ show sys)
 	deleted = property' ("no sbuild schroot for " ++ show sys) $
 		\w -> case schrootFromSystem sys of
 			Just s  -> ensureProperty w $
-				undoRevertableProperty $ built s "dummy"
+				undoRevertableProperty $ built s "dummy" cc
 			Nothing -> noChange
 
 -- | Build and configure a schroot for use with sbuild
-built :: SbuildSchroot -> Apt.Url -> RevertableProperty DebianLike UnixLike
-built s@(SbuildSchroot suite arch) mirror =
-	(go
-	`requires` ccachePrepared
+built :: SbuildSchroot -> Apt.Url -> UseCcache -> RevertableProperty DebianLike UnixLike
+built s@(SbuildSchroot suite arch) mirror cc =
+	((go `before` enhancedConf)
+	`requires` ccacheMaybePrepared cc
 	`requires` installed
 	`requires` overlaysKernel)
 	<!> deleted
@@ -157,10 +164,7 @@
 			]
 		ifM (liftIO $
 			boolSystemEnv "sbuild-createchroot" params (Just de))
-			( ensureProperty w $
-				fixConfFile s
-				`before` aliasesLine
-				`before` commandPrefix
+			( ensureProperty w $ fixConfFile s
 			, return FailedChange
 			)
 	-- TODO we should kill any sessions still using the chroot
@@ -172,23 +176,31 @@
 				("/etc/sbuild/chroot" </> show s ++ "-sbuild")
 			makeChange $ nukeFile (schrootConf s)
 
+	enhancedConf =
+		combineProperties ("enhanced schroot conf for " ++ show s) $ props
+			& aliasesLine
+			-- enable ccache and eatmydata for speed
+			& ConfFile.containsIniSetting (schrootConf s)
+				( show s ++ "-sbuild"
+				, "command-prefix"
+				, intercalate "," commandPrefix
+				)
+
 	-- if we're building a sid chroot, add useful aliases
 	-- In order to avoid more than one schroot getting the same aliases, we
 	-- only do this if the arch of the chroot equals the host arch.
 	aliasesLine :: Property UnixLike
-	aliasesLine = property' "maybe set aliases line" $ \w -> do
-		maybeOS <- getOS
-		case maybeOS of
-			Nothing -> return NoChange
-			Just (System _ hostArch) ->
-				if suite == "unstable" && hostArch == arch
-				then ensureProperty w $
-					schrootConf s `File.containsLine` aliases
-				else return NoChange
-
-	-- enable ccache and eatmydata for speed
-	commandPrefix = File.containsLine (schrootConf s)
-		"command-prefix=/var/cache/ccache-sbuild/sbuild-setup,eatmydata"
+	aliasesLine = property' "maybe set aliases line" $ \w ->
+		sidHostArchSchroot s >>= \isSidHostArchSchroot ->
+			if isSidHostArchSchroot
+			then ensureProperty w $
+				ConfFile.containsIniSetting
+					(schrootConf s)
+					( show s ++ "-sbuild"
+					, "aliases"
+					, aliases
+					)
+			else return NoChange
 
 	-- If the user has indicated that this host should use
 	-- union-type=overlay schroots, we need to ensure that we have rebooted
@@ -214,8 +226,28 @@
 		, return False
 		)
 
-	aliases = "aliases=UNRELEASED,sid,rc-buggy,experimental"
+	aliases = intercalate ","
+		[ "sid"
+		-- if the user wants to build for experimental, they would use
+		-- their sid chroot and sbuild's --extra-repository option to
+		-- enable experimental
+		, "rc-buggy"
+		, "experimental"
+		-- we assume that building for UNRELEASED means building for
+		-- unstable
+		, "UNRELEASED"
+		-- the following is for dgit compatibility:
+		, "UNRELEASED-"
+			++ architectureToDebianArchString arch
+			++ "-sbuild"
+		]
 
+	commandPrefix = case cc of
+		UseCcache -> "/var/cache/ccache-sbuild/sbuild-setup":base
+		_ -> base
+	  where
+		base = ["eatmydata"]
+
 -- | Ensure that an sbuild schroot's packages and apt indexes are updated
 --
 -- This function is a convenience wrapper around 'updated', allowing the user to
@@ -273,9 +305,8 @@
 -- documentation for why you might want to use this property, and sample config.
 piupartsConfFor :: System -> Property DebianLike
 piupartsConfFor sys = property' ("piuparts schroot conf for " ++ show sys) $
-	\w -> case (schrootFromSystem sys, stdMirror sys) of
-			(Just s, Just u)  -> ensureProperty w $
-				piupartsConf s u
+	\w -> case schrootFromSystem sys of
+			Just s -> ensureProperty w $ piupartsConf s
 			_ -> errorMessage
 				("don't know how to debootstrap " ++ show sys)
 
@@ -291,48 +322,59 @@
 --   piuparts in their @~/.sbuildrc@, which is inconvenient.
 --
 -- To make use of this new schroot config, you can put something like this in
--- your ~/.sbuildrc:
+-- your ~/.sbuildrc (sbuild 0.71.0 or newer):
 --
 --  >  $run_piuparts = 1;
 --  >  $piuparts_opts = [
 --  >      '--schroot',
---  >      'unstable-i386-piuparts',
+--  >      '%r-%a-piuparts',
 --  >      '--fail-if-inadequate',
 --  >      '--fail-on-broken-symlinks',
 --  >      ];
-piupartsConf :: SbuildSchroot -> Apt.Url -> Property DebianLike
-piupartsConf s u = go
-	`requires` (setupRevertableProperty $ built s u)
-	`describe` ("piuparts schroot conf for " ++ show s)
+--
+-- This property has no effect if the corresponding sbuild schroot does not
+-- exist (i.e. you also need 'Sbuild.built' or 'Sbuild.builtFor').
+piupartsConf :: SbuildSchroot -> Property DebianLike
+piupartsConf s@(SbuildSchroot _ arch) =
+	check (doesFileExist (schrootConf s)) go
+	`requires` installed
   where
 	go :: Property DebianLike
-	go = tightenTargets $
-		check (not <$> doesFileExist f)
-			(File.basedOn f (schrootConf s, map munge))
-		`before`
-		ConfFile.containsIniSetting f (sec, "profile", "piuparts")
-		`before`
-		ConfFile.containsIniSetting f (sec, "aliases", "")
-		`before`
-		ConfFile.containsIniSetting f (sec, "command-prefix", "")
-		`before`
-		File.dirExists dir
-		`before`
-		File.isSymlinkedTo (dir </> "copyfiles")
-			(File.LinkTarget $ orig </> "copyfiles")
-		`before`
-		File.isSymlinkedTo (dir </> "nssdatabases")
-			(File.LinkTarget $ orig </> "nssdatabases")
-		`before`
-		File.basedOn (dir </> "fstab")
-			(orig </> "fstab", filter (/= aptCacheLine))
+	go = property' desc $ \w -> do
+		aliases <- aliasesLine
+		ensureProperty w $ combineProperties desc $ props
+			& check (not <$> doesFileExist f)
+				(File.basedOn f (schrootConf s, map munge))
+			& ConfFile.containsIniSetting f
+				(sec, "profile", "piuparts")
+			& ConfFile.containsIniSetting f
+				(sec, "aliases", aliases)
+			& ConfFile.containsIniSetting f
+				(sec, "command-prefix", "")
+			& File.dirExists dir
+			& File.isSymlinkedTo (dir </> "copyfiles")
+				(File.LinkTarget $ orig </> "copyfiles")
+			& File.isSymlinkedTo (dir </> "nssdatabases")
+				(File.LinkTarget $ orig </> "nssdatabases")
+			& File.basedOn (dir </> "fstab")
+				(orig </> "fstab", filter (/= aptCacheLine))
 
 	orig = "/etc/schroot/sbuild"
 	dir = "/etc/schroot/piuparts"
 	sec = show s ++ "-piuparts"
 	f = schrootPiupartsConf s
 	munge = replace "-sbuild]" "-piuparts]"
+	desc = "piuparts schroot conf for " ++ show s
 
+	-- normally the piuparts schroot conf has no aliases, but we have to add
+	-- one, for dgit compatibility, if this is the default sid chroot
+	aliasesLine = sidHostArchSchroot s >>= \isSidHostArchSchroot ->
+			return $ if isSidHostArchSchroot
+			then "UNRELEASED-"
+				++ architectureToDebianArchString arch
+				++ "-piuparts"
+			else ""
+
 -- | Bind-mount /var/cache/apt/archives in all sbuild chroots so that the host
 -- system and the chroot share the apt cache
 --
@@ -409,6 +451,11 @@
 			["kill $(cat /var/run/rngd.pid)"]
 			`assume` MadeChange
 
+ccacheMaybePrepared :: UseCcache -> Property DebianLike
+ccacheMaybePrepared cc = case cc of
+	UseCcache -> ccachePrepared
+	NoCcache  -> doNothing
+
 -- another script from wiki.d.o/sbuild
 ccachePrepared :: Property DebianLike
 ccachePrepared = propertyList "sbuild group ccache configured" $ props
@@ -468,3 +515,19 @@
 schrootPiupartsConf :: SbuildSchroot -> FilePath
 schrootPiupartsConf (SbuildSchroot s a) =
 	"/etc/schroot/chroot.d" </> s ++ "-" ++ architectureToDebianArchString a ++ "-piuparts-propellor"
+
+-- Determine whether a schroot is
+--
+-- (i)  Debian sid, and
+-- (ii) the same architecture as the host.
+--
+-- This is the "sid host arch schroot".  It is considered the default schroot
+-- for sbuild builds, so we add useful aliases that work well with the suggested
+-- ~/.sbuildrc given in the haddock
+sidHostArchSchroot :: SbuildSchroot -> Propellor Bool
+sidHostArchSchroot (SbuildSchroot suite arch) = do
+	maybeOS <- getOS
+	case maybeOS of
+		Nothing -> return False
+		Just (System _ hostArch) ->
+			return $ suite == "unstable" && hostArch == arch
diff --git a/src/Propellor/Property/SiteSpecific/JoeySites.hs b/src/Propellor/Property/SiteSpecific/JoeySites.hs
--- a/src/Propellor/Property/SiteSpecific/JoeySites.hs
+++ b/src/Propellor/Property/SiteSpecific/JoeySites.hs
@@ -71,12 +71,8 @@
 		, "fi"
 		, "echo Your game was recorded, as ID:$(basename \"$t\")"
 		, "echo if you would like to talk about how it went, email scroll@joeyh.name"
-		, "echo 'or, type comments below (finish with a dot on its own line)'"
-		, "echo"
-		, "echo Your comments:"
-		, "timeout --kill-after 1m --foreground 2h mail -E -s \"scroll test $t\" joey@kitenet.net"
+		, "read line"
 		] `onChange` (g `File.mode` (combineModes (ownerWriteMode:readModes ++ executeModes)))
-	& Apt.installed ["bsd-mailx"]
 	-- prevent port forwarding etc by not letting scroll log in via ssh
 	& Ssh.sshdConfig `File.containsLine` ("DenyUsers scroll")
 		`onChange` Ssh.restarted
diff --git a/src/Propellor/Property/Ssh.hs b/src/Propellor/Property/Ssh.hs
--- a/src/Propellor/Property/Ssh.hs
+++ b/src/Propellor/Property/Ssh.hs
@@ -362,7 +362,7 @@
 -- | Ensures that a local user's authorized_keys contains lines allowing
 -- logins from a remote user on the specified Host.
 --
--- The ssh keys of the remote user can be set using `keysImported`
+-- The ssh keys of the remote user can be set using `userKeys`
 --
 -- Any other lines in the authorized_keys file are preserved as-is.
 authorizedKeysFrom :: User -> (User, Host) -> Property UnixLike
diff --git a/src/Propellor/Property/Tor.hs b/src/Propellor/Property/Tor.hs
--- a/src/Propellor/Property/Tor.hs
+++ b/src/Propellor/Property/Tor.hs
@@ -119,17 +119,12 @@
 			`describe` ("tor BandwidthRate " ++ v)
 	Nothing -> property ("unable to parse " ++ s) noChange
 
-hiddenServiceAvailable :: HiddenServiceName -> Int -> Property DebianLike
-hiddenServiceAvailable hn port = hiddenServiceHostName $ hiddenService hn port
-  where
-	hiddenServiceHostName p =  adjustPropertySatisfy p $ \satisfy -> do
-		r <- satisfy
-		h <- liftIO $ readFile (varLib </> hn </> "hostname")
-		warningMessage $ unwords ["hidden service hostname:", h]
-		return r
-
-hiddenService :: HiddenServiceName -> Int -> Property DebianLike
-hiddenService hn port = ConfFile.adjustSection
+-- | Enables a hidden service for a given port.
+--
+-- If used without `hiddenServiceData`, tor will generate a new
+-- private key.
+hiddenService :: HiddenServiceName -> Port -> Property DebianLike
+hiddenService hn (Port port) = ConfFile.adjustSection
 	(unwords ["hidden service", hn, "available on port", show port])
 	(== oniondir)
 	(not . isPrefixOf "HiddenServicePort")
@@ -141,6 +136,20 @@
 	oniondir = unwords ["HiddenServiceDir", varLib </> hn]
 	onionport = unwords ["HiddenServicePort", show port, "127.0.0.1:" ++ show port]
 
+-- | Same as `hiddenService` but also causes propellor to display
+-- the onion address of the hidden service.
+hiddenServiceAvailable :: HiddenServiceName -> Port -> Property DebianLike
+hiddenServiceAvailable hn port = hiddenServiceHostName $ hiddenService hn port
+  where
+	hiddenServiceHostName p =  adjustPropertySatisfy p $ \satisfy -> do
+		r <- satisfy
+		mh <- liftIO $ tryIO $ readFile (varLib </> hn </> "hostname")
+		case mh of
+			Right h -> warningMessage $ unwords ["hidden service hostname:", h]
+			Left _e -> warningMessage "hidden service hostname not available yet"
+		return r
+
+-- | Load the private key for a hidden service from the privdata.
 hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property (HasInfo + DebianLike)
 hiddenServiceData hn context = combineProperties desc $ props
 	& installonion "hostname"
