diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+propellor (3.2.2) unstable; urgency=medium
+
+  * Added Linode.serialGrub property.
+  * Clean up build warnings about redundant constraints when built with ghc 8.0.
+  * Added Group.hasUser property. Thanks, Daniel Brooks
+
+ -- Joey Hess <id@joeyh.name>  Fri, 11 Nov 2016 17:54:44 -0400
+
 propellor (3.2.1) unstable; urgency=medium
 
   * Simplify Debootstrap.sourceInstall since #770217 was fixed.
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+propellor (3.2.2) unstable; urgency=medium
+
+  * Added Linode.serialGrub property.
+  * Clean up build warnings about redundant constraints when built with ghc 8.0.
+  * Added Group.hasUser property. Thanks, Daniel Brooks
+
+ -- Joey Hess <id@joeyh.name>  Fri, 11 Nov 2016 17:54:44 -0400
+
 propellor (3.2.1) unstable; urgency=medium
 
   * Simplify Debootstrap.sourceInstall since #770217 was fixed.
diff --git a/joeyconfig.hs b/joeyconfig.hs
--- a/joeyconfig.hs
+++ b/joeyconfig.hs
@@ -12,6 +12,7 @@
 import qualified Propellor.Property.Cron as Cron
 import qualified Propellor.Property.Sudo as Sudo
 import qualified Propellor.Property.User as User
+import qualified Propellor.Property.Group as Group
 import qualified Propellor.Property.Hostname as Hostname
 import qualified Propellor.Property.Tor as Tor
 import qualified Propellor.Property.Dns as Dns
@@ -168,7 +169,7 @@
 oyster = host "oyster.kitenet.net" $ props
 	& standardSystem Unstable X86_64
 		[ "Unreliable server. Anything here may be lost at any time!" ]
-	& ipv4 "104.167.117.109"
+	& ipv4 "64.137.221.146"
 
 	& CloudAtCost.decruft
 	& Ssh.hostKeys hostContext
@@ -260,15 +261,14 @@
 
 	& Network.static "eth0" `requires` Network.cleanInterfacesFile
 	& Apt.installed ["linux-image-amd64"]
-	& Linode.chainPVGrub 5
+	& Linode.serialGrub
 	& Linode.mlocateEnabled
 	& Apt.unattendedUpgrades
 	& Systemd.installed
 	& Systemd.persistentJournal
 	& Journald.systemMaxUse "500MiB"
 	& Ssh.passwordAuthentication True
-	-- Since ssh password authentication is allowed:
-	& Fail2Ban.installed
+	& Fail2Ban.installed -- since ssh password authentication is allowed
 	& Apt.serviceInstalledRunning "ntp"
 	& "/etc/timezone" `File.hasContent` ["US/Eastern"]
 
@@ -446,8 +446,10 @@
 	& alias "www.olduse.net"
 	& alias "www.kitenet.net"
 	& alias "joeyh.name"
+	& alias "www.joeyh.name"
 	& alias "campaign.joeyh.name"
 	& alias "ikiwiki.info"
+	& alias "www.ikiwiki.info"
 	& alias "git.ikiwiki.info"
 	& alias "l10n.ikiwiki.info"
 	& alias "dist-bugs.kitenet.net"
@@ -456,6 +458,7 @@
 	& Apt.installed ["linux-image-amd64"]
 	& Apt.unattendedUpgrades
 	& Branchable.server hosts
+	& Linode.serialGrub
 
 -- See https://joeyh.name/code/keysafe/servers/ for requirements.
 keysafe :: Host
@@ -525,21 +528,29 @@
 		, (SshEcdsa, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHb0kXcrF5ThwS8wB0Hez404Zp9bz78ZxEGSqnwuF4d/N3+bymg7/HAj7l/SzRoEXKHsJ7P5320oMxBHeM16Y+k=")
 		]
 	& Apt.installed ["etckeeper", "sudo"]
-	& Apt.installed ["vim", "screen", "tmux", "less", "emax-nox", "netcat"]
+	-- vital but generic tools
+	& Apt.installed ["vim", "screen", "tmux", "less", "emacs-nox", "netcat", "nano"]
+	-- tools for creating shards
+	& Apt.installed ["jq", "python3", "python3-aiohttp"]
 	& User.hasSomePassword (User "root")
 	& propertyList "admin accounts"
-		(toProps $ map User.accountFor admins ++ map Sudo.enabledFor admins)
+		(toProps $ map User.accountFor admins
+		        ++ map (Group.hasUser (Group "staff")) admins
+		        ++ map Sudo.enabledFor admins)
 	& User.hasSomePassword (User "joey")
 	& GitHome.installedFor (User "joey")
 	& Ssh.authorizedKey (User "db48x") "ssh-rsa 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 db48x@anglachel"
-	& Apt.installed ["sudo"]
+	& Ssh.authorizedKey (User "db48x") "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJQkqIgZ7D8WHW5Y3o+fpZC/4xtv/3IQrORJrTPCt7KY db48x@erebor"
+	& Ssh.authorizedKey (User "hcross") "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP5OhU2Lita9RdjPkX9N0w9wZnmVlednUDEx24bVn4Mk IABAK key - Harry C"
+	& Ssh.authorizedKey (User "kaz") "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHhFYMd9Htlf9wPZzIDyqbYYNwuo3m+kWQ9/pfAD/TE9 Kaz IABAK"
+	& Ssh.authorizedKey (User "yipdw") "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEo2mGPw2TTJMHp7G86hMBh6n9/+abzg1oXIIlkwWwzo trythil@aglarond"
 	& Ssh.noPasswords
 	& IABak.gitServer monsters
 	& IABak.registrationServer monsters
 	& IABak.graphiteServer
 	& IABak.publicFace
   where
-	admins = map User ["joey", "db48x"]
+	admins = map User ["joey", "db48x", "hcross", "kaz", "yipdw"]
 
        --'                        __|II|      ,.
      ----                      __|II|II|__   (  \_,/\
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 3.2.1
+Version: 3.2.2
 Cabal-Version: >= 1.8
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
@@ -38,6 +38,8 @@
 Executable propellor
   Main-Is: wrapper.hs
   GHC-Options: -threaded -Wall -fno-warn-tabs -O0
+  if impl(ghc >= 8.0)
+    GHC-Options: -fno-warn-redundant-constraints
   Extensions: TypeOperators
   Hs-Source-Dirs: src
   Build-Depends:
@@ -53,6 +55,8 @@
 Executable propellor-config
   Main-Is: config.hs
   GHC-Options: -threaded -Wall -fno-warn-tabs -O0
+  if impl(ghc >= 8.0)
+    GHC-Options: -fno-warn-redundant-constraints
   Extensions: TypeOperators
   Hs-Source-Dirs: src
   Build-Depends:
@@ -63,6 +67,8 @@
 
 Library
   GHC-Options: -Wall -fno-warn-tabs -O0
+  if impl(ghc >= 8.0)
+    GHC-Options: -fno-warn-redundant-constraints
   Extensions: TypeOperators
   Hs-Source-Dirs: src
   Build-Depends:
diff --git a/src/Propellor/Container.hs b/src/Propellor/Container.hs
--- a/src/Propellor/Container.hs
+++ b/src/Propellor/Container.hs
@@ -43,6 +43,9 @@
 	::
 		-- Since the children being added probably have info,
 		-- require the Property's metatypes to have info.
+		-- -Wredundant-constraints is turned off because
+		-- this constraint appears redundant, but is actually
+		-- crucial.
 		( IncludesInfo metatypes ~ 'True
 		, IsContainer c
 		)
diff --git a/src/Propellor/EnsureProperty.hs b/src/Propellor/EnsureProperty.hs
--- a/src/Propellor/EnsureProperty.hs
+++ b/src/Propellor/EnsureProperty.hs
@@ -37,6 +37,9 @@
 -- with the property to be lost.
 ensureProperty
 	::
+		-- -Wredundant-constraints is turned off because
+		-- this constraint appears redundant, but is actually
+		-- crucial.
 		( Cannot_ensureProperty_WithInfo inner ~ 'True
 		, (Targets inner `NotSuperset` Targets outer) ~ 'CanCombine
 		)
@@ -45,7 +48,7 @@
 	-> Propellor Result
 ensureProperty _ = catchPropellor . getSatisfy
 
--- The name of this was chosen to make type errors a more understandable.
+-- The name of this was chosen to make type errors a bit more understandable.
 type family Cannot_ensureProperty_WithInfo (l :: [a]) :: Bool
 type instance Cannot_ensureProperty_WithInfo '[] = 'True
 type instance Cannot_ensureProperty_WithInfo (t ': ts) =
diff --git a/src/Propellor/Info.hs b/src/Propellor/Info.hs
--- a/src/Propellor/Info.hs
+++ b/src/Propellor/Info.hs
@@ -38,6 +38,9 @@
 --
 -- The new Property will include HasInfo in its metatypes.
 setInfoProperty
+	-- -Wredundant-constraints is turned off because
+	-- this constraint appears redundant, but is actually
+	-- crucial.
 	:: (MetaTypes metatypes' ~ (+) HasInfo metatypes, SingI metatypes')
 	=> Property metatypes
 	-> Info
@@ -47,6 +50,9 @@
 
 -- | Adds more info to a Property that already HasInfo.
 addInfoProperty
+	-- -Wredundant-constraints is turned off because
+	-- this constraint appears redundant, but is actually
+	-- crucial.
 	:: (IncludesInfo metatypes ~ 'True)
 	=> Property metatypes
 	-> Info
diff --git a/src/Propellor/Message.hs b/src/Propellor/Message.hs
--- a/src/Propellor/Message.hs
+++ b/src/Propellor/Message.hs
@@ -73,7 +73,7 @@
 actionMessageOn :: (MonadIO m, MonadMask m, ActionResult r) => HostName -> Desc -> m r -> m r
 actionMessageOn = actionMessage' . Just
 
-actionMessage' :: (MonadIO m, MonadMask m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r
+actionMessage' :: (MonadIO m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r
 actionMessage' mhn desc a = do
 	liftIO $ outputConcurrent
 		=<< whenConsole (setTitleCode $ "propellor: " ++ desc)
diff --git a/src/Propellor/PropAccum.hs b/src/Propellor/PropAccum.hs
--- a/src/Propellor/PropAccum.hs
+++ b/src/Propellor/PropAccum.hs
@@ -51,6 +51,9 @@
 (&)
 	::
 		( IsProp p
+		-- -Wredundant-constraints is turned off because
+		-- this constraint appears redundant, but is actually
+		-- crucial.
 		, MetaTypes y ~ GetMetaTypes p
 		, CheckCombinable x y ~ 'CanCombine
 		)
@@ -63,6 +66,9 @@
 (&^)
 	::
 		( IsProp p
+		-- -Wredundant-constraints is turned off because
+		-- this constraint appears redundant, but is actually
+		-- crucial.
 		, MetaTypes y ~ GetMetaTypes p
 		, CheckCombinable x y ~ 'CanCombine
 		)
@@ -73,6 +79,9 @@
 
 -- | Adds a property in reverted form.
 (!)
+	-- -Wredundant-constraints is turned off because
+	-- this constraint appears redundant, but is actually
+	-- crucial.
 	:: (CheckCombinable x z ~ 'CanCombine)
 	=> Props (MetaTypes x)
 	-> RevertableProperty (MetaTypes y) (MetaTypes z)
diff --git a/src/Propellor/Property.hs b/src/Propellor/Property.hs
--- a/src/Propellor/Property.hs
+++ b/src/Propellor/Property.hs
@@ -345,7 +345,7 @@
 
 -- | Apply a property to each element of a list.
 applyToList
-	:: (Foldable t, Functor t, IsProp p, Combines p p, p ~ CombinedType p p)
+	:: (Foldable t, Functor t, Combines p p, p ~ CombinedType p p)
 	=> (b -> p)
 	-> t b
 	-> p
diff --git a/src/Propellor/Property/Group.hs b/src/Propellor/Property/Group.hs
--- a/src/Propellor/Property/Group.hs
+++ b/src/Propellor/Property/Group.hs
@@ -1,6 +1,7 @@
 module Propellor.Property.Group where
 
 import Propellor.Base
+import Propellor.Property.User (hasGroup)
 
 type GID = Int
 
@@ -12,3 +13,6 @@
 	test = not . elem group' . words <$> readProcess "cut" ["-d:", "-f1", groupFile]
 	args Nothing = [group']
 	args (Just gid) = ["--gid", show gid, group']
+
+hasUser :: Group -> User -> Property DebianLike
+hasUser = flip hasGroup
diff --git a/src/Propellor/Property/HostingProvider/Linode.hs b/src/Propellor/Property/HostingProvider/Linode.hs
--- a/src/Propellor/Property/HostingProvider/Linode.hs
+++ b/src/Propellor/Property/HostingProvider/Linode.hs
@@ -5,9 +5,22 @@
 import qualified Propellor.Property.File as File
 import Utility.FileMode
 
--- | Linode's pv-grub-x86_64 does not currently support booting recent
--- Debian kernels compressed with xz. This sets up pv-grub chaining to enable
--- it.
+-- | Configures grub to use the serial console as set up by Linode.
+-- Useful when running a distribution supplied kernel.
+-- <https://www.linode.com/docs/tools-reference/custom-kernels-distros/run-a-distribution-supplied-kernel-with-kvm>
+serialGrub :: Property DebianLike
+serialGrub = "/etc/default/grub" `File.containsLines`
+	[ "GRUB_CMDLINE_LINUX=\"console=ttyS0,19200n8\""
+	, "GRUB_DISABLE_LINUX_UUID=true"
+	, "GRUB_SERIAL_COMMAND=\"serial --speed=19200 --unit=0 --word=8 --parity=no --stop=1\""
+	, "GRUB_TERMINAL=serial"
+	]
+	`onChange` Grub.mkConfig
+	`requires` Grub.installed Grub.PC
+
+-- | Linode's pv-grub-x86_64 (only used for its older XEN instances)
+-- does not support booting recent Debian kernels compressed
+-- with xz. This sets up pv-grub chaining to enable it.
 chainPVGrub :: Grub.TimeoutSecs -> Property DebianLike
 chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda"
 
diff --git a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs
--- a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs
+++ b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs
@@ -80,7 +80,7 @@
 buildDepsNoHaskellLibs = Apt.installed
 	["git", "rsync", "moreutils", "ca-certificates",
 	"debhelper", "ghc", "curl", "openssh-client", "git-remote-gcrypt",
-	"liblockfile-simple-perl", "cabal-install", "vim", "less",
+	"liblockfile-simple-perl", "locales", "cabal-install", "vim", "less",
 	-- needed by haskell libs
 	"libxml2-dev", "libidn11-dev", "libgsasl7-dev", "libgnutls28-dev",
 	"libmagic-dev", "alex", "happy", "c2hs"
