2014-11-20 21:18:26 +00:00
|
|
|
module Propellor.Property.Systemd (
|
|
|
|
installed,
|
2014-11-21 16:35:07 +00:00
|
|
|
started,
|
|
|
|
stopped,
|
|
|
|
enabled,
|
2014-11-21 18:37:19 +00:00
|
|
|
disabled,
|
2014-11-20 21:18:26 +00:00
|
|
|
persistentJournal,
|
2014-11-21 16:18:03 +00:00
|
|
|
Container,
|
2014-11-20 21:18:26 +00:00
|
|
|
container,
|
|
|
|
nspawned,
|
|
|
|
) where
|
|
|
|
|
|
|
|
import Propellor
|
|
|
|
import qualified Propellor.Property.Chroot as Chroot
|
|
|
|
import qualified Propellor.Property.Apt as Apt
|
2014-11-21 16:17:03 +00:00
|
|
|
import qualified Propellor.Property.File as File
|
2014-11-20 21:18:26 +00:00
|
|
|
import Utility.SafeCommand
|
2014-11-21 16:17:03 +00:00
|
|
|
import Utility.FileMode
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
import Data.List.Utils
|
|
|
|
|
2014-11-21 16:35:07 +00:00
|
|
|
type ServiceName = String
|
|
|
|
|
2014-11-20 21:18:26 +00:00
|
|
|
type MachineName = String
|
|
|
|
|
2014-11-21 19:55:27 +00:00
|
|
|
data Container = Container MachineName Chroot.Chroot Host
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
instance Hostlike Container where
|
2014-11-21 19:55:27 +00:00
|
|
|
(Container n c h) & p = Container n c (h & p)
|
|
|
|
(Container n c h) &^ p = Container n c (h &^ p)
|
2014-11-21 18:31:13 +00:00
|
|
|
getHost (Container _ _ h) = h
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
-- dbus is only a Recommends of systemd, but is needed for communication
|
|
|
|
-- from the systemd inside a container to the one outside, so make sure it
|
|
|
|
-- gets installed.
|
|
|
|
installed :: Property
|
|
|
|
installed = Apt.installed ["systemd", "dbus"]
|
|
|
|
|
2014-11-21 16:35:07 +00:00
|
|
|
-- | Starts a systemd service.
|
|
|
|
started :: ServiceName -> Property
|
|
|
|
started n = trivial $ cmdProperty "systemctl" ["start", n]
|
|
|
|
`describe` ("service " ++ n ++ " started")
|
|
|
|
|
|
|
|
-- | Stops a systemd service.
|
|
|
|
stopped :: ServiceName -> Property
|
|
|
|
stopped n = trivial $ cmdProperty "systemctl" ["stop", n]
|
|
|
|
`describe` ("service " ++ n ++ " stopped")
|
|
|
|
|
|
|
|
-- | Enables a systemd service.
|
|
|
|
enabled :: ServiceName -> Property
|
|
|
|
enabled n = trivial $ cmdProperty "systemctl" ["enable", n]
|
|
|
|
`describe` ("service " ++ n ++ " enabled")
|
|
|
|
|
2014-11-21 18:37:19 +00:00
|
|
|
-- | Disables a systemd service.
|
|
|
|
disabled :: ServiceName -> Property
|
2014-11-21 20:02:10 +00:00
|
|
|
disabled n = trivial $ cmdProperty "systemctl" ["disable", n]
|
2014-11-21 18:37:19 +00:00
|
|
|
`describe` ("service " ++ n ++ " disabled")
|
|
|
|
|
2014-11-21 16:17:03 +00:00
|
|
|
-- | Enables persistent storage of the journal.
|
2014-11-20 21:18:26 +00:00
|
|
|
persistentJournal :: Property
|
|
|
|
persistentJournal = check (not <$> doesDirectoryExist dir) $
|
2014-11-21 16:35:07 +00:00
|
|
|
combineProperties "persistent systemd journal"
|
2014-11-20 21:18:26 +00:00
|
|
|
[ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]
|
|
|
|
, cmdProperty "setfacl" ["-R", "-nm", "g:adm:rx,d:g:adm:rx", dir]
|
2014-11-21 16:35:07 +00:00
|
|
|
, started "systemd-journal-flush"
|
2014-11-20 21:18:26 +00:00
|
|
|
]
|
|
|
|
`requires` Apt.installed ["acl"]
|
|
|
|
where
|
|
|
|
dir = "/var/log/journal"
|
|
|
|
|
2014-11-21 19:55:27 +00:00
|
|
|
-- | Defines a container with a given machine name.
|
2014-11-20 21:18:26 +00:00
|
|
|
--
|
2014-11-21 19:55:27 +00:00
|
|
|
-- Properties can be added to configure the Container.
|
|
|
|
--
|
|
|
|
-- > container "webserver" (Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty)
|
2014-11-21 18:31:13 +00:00
|
|
|
-- > & Apt.installedRunning "apache2"
|
|
|
|
-- > & ...
|
2014-11-21 19:55:27 +00:00
|
|
|
container :: MachineName -> (FilePath -> Chroot.Chroot) -> Container
|
|
|
|
container name mkchroot = Container name c h
|
2014-11-21 18:31:13 +00:00
|
|
|
& os system
|
2014-11-21 19:55:27 +00:00
|
|
|
where
|
|
|
|
c@(Chroot.Chroot _ system _ _) = mkchroot (containerDir name)
|
|
|
|
h = Host name [] mempty
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
-- | Runs a container using systemd-nspawn.
|
|
|
|
--
|
|
|
|
-- A systemd unit is set up for the container, so it will automatically
|
|
|
|
-- be started on boot.
|
|
|
|
--
|
|
|
|
-- Systemd is automatically installed inside the container, and will
|
|
|
|
-- communicate with the host's systemd. This allows systemctl to be used to
|
|
|
|
-- examine the status of services running inside the container.
|
|
|
|
--
|
|
|
|
-- When the host system has persistentJournal enabled, journactl can be
|
|
|
|
-- used to examine logs forwarded from the container.
|
|
|
|
--
|
|
|
|
-- Reverting this property stops the container, removes the systemd unit,
|
|
|
|
-- and deletes the chroot and all its contents.
|
|
|
|
nspawned :: Container -> RevertableProperty
|
2014-11-21 19:55:27 +00:00
|
|
|
nspawned c@(Container name (Chroot.Chroot loc system builderconf _) h) =
|
|
|
|
RevertableProperty setup teardown
|
2014-11-20 21:18:26 +00:00
|
|
|
where
|
2014-11-21 18:11:02 +00:00
|
|
|
setup = combineProperties ("nspawned " ++ name) $
|
2014-11-21 17:55:42 +00:00
|
|
|
map toProp steps ++ [containerprovisioned]
|
2014-11-21 18:11:02 +00:00
|
|
|
teardown = combineProperties ("not nspawned " ++ name) $
|
2014-11-21 17:55:42 +00:00
|
|
|
map (toProp . revert) (reverse steps)
|
2014-11-21 17:49:17 +00:00
|
|
|
steps =
|
|
|
|
[ enterScript c
|
|
|
|
, chrootprovisioned
|
|
|
|
, nspawnService c
|
|
|
|
]
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
-- When provisioning the chroot, pass a version of the Host
|
|
|
|
-- that only has the Property of systemd being installed.
|
|
|
|
-- This is to avoid starting any daemons in the chroot,
|
|
|
|
-- which would not run in the container's namespace.
|
2014-11-21 18:11:02 +00:00
|
|
|
chrootprovisioned = Chroot.provisioned' (Chroot.propigateChrootInfo chroot) $
|
2014-11-21 16:17:03 +00:00
|
|
|
mkChroot $ h { hostProperties = [installed] }
|
|
|
|
|
|
|
|
-- Use nsenter to enter container and and run propellor to
|
|
|
|
-- finish provisioning.
|
2014-11-21 18:11:02 +00:00
|
|
|
containerprovisioned = Chroot.propellChroot chroot
|
2014-11-21 16:17:03 +00:00
|
|
|
(enterContainerProcess c)
|
|
|
|
|
2014-11-21 19:55:27 +00:00
|
|
|
mkChroot = Chroot.Chroot loc system builderconf
|
2014-11-21 18:11:02 +00:00
|
|
|
chroot = mkChroot h
|
2014-11-20 21:18:26 +00:00
|
|
|
|
|
|
|
nspawnService :: Container -> RevertableProperty
|
2014-11-21 18:31:13 +00:00
|
|
|
nspawnService (Container name _ _) = RevertableProperty setup teardown
|
2014-11-20 21:18:26 +00:00
|
|
|
where
|
|
|
|
service = nspawnServiceName name
|
|
|
|
servicefile = "/etc/systemd/system/multi-user.target.wants" </> service
|
|
|
|
|
|
|
|
setup = check (not <$> doesFileExist servicefile) $
|
2014-11-21 18:37:19 +00:00
|
|
|
started service
|
|
|
|
`requires` enabled service
|
2014-11-20 21:18:26 +00:00
|
|
|
|
2014-11-21 18:37:19 +00:00
|
|
|
teardown = check (doesFileExist servicefile) $
|
|
|
|
disabled service
|
|
|
|
`requires` stopped service
|
2014-11-20 21:18:26 +00:00
|
|
|
|
2014-11-21 16:17:03 +00:00
|
|
|
-- | Installs a "enter-machinename" script that root can use to run a
|
|
|
|
-- command inside the container.
|
|
|
|
--
|
|
|
|
-- This uses nsenter to enter the container, by looking up the pid of the
|
|
|
|
-- container's init process and using its namespace.
|
|
|
|
enterScript :: Container -> RevertableProperty
|
2014-11-21 18:31:13 +00:00
|
|
|
enterScript c@(Container name _ _) = RevertableProperty setup teardown
|
2014-11-21 16:17:03 +00:00
|
|
|
where
|
|
|
|
setup = combineProperties ("generated " ++ enterScriptFile c)
|
|
|
|
[ scriptfile `File.hasContent`
|
|
|
|
[ "#!/bin/sh"
|
|
|
|
, "# Generated by propellor"
|
|
|
|
, "pid=\"$(machinectl show " ++ shellEscape name ++ " -p Leader | cut -d= -f2)\" || true"
|
|
|
|
, "if [ -n \"$pid\" ]; then"
|
2014-11-21 17:57:58 +00:00
|
|
|
, "\tnsenter -p -u -n -i -m -t \"$pid\" \"$@\""
|
2014-11-21 16:17:03 +00:00
|
|
|
, "else"
|
2014-11-21 17:55:42 +00:00
|
|
|
, "\techo container not running >&2"
|
2014-11-21 16:17:03 +00:00
|
|
|
, "\texit 1"
|
|
|
|
, "fi"
|
|
|
|
]
|
|
|
|
, scriptfile `File.mode` combineModes (readModes ++ executeModes)
|
|
|
|
]
|
|
|
|
teardown = File.notPresent scriptfile
|
|
|
|
scriptfile = enterScriptFile c
|
|
|
|
|
|
|
|
enterScriptFile :: Container -> FilePath
|
2014-11-21 18:31:13 +00:00
|
|
|
enterScriptFile (Container name _ _ ) = "/usr/local/bin/enter-" ++ mungename name
|
2014-11-21 16:17:03 +00:00
|
|
|
|
|
|
|
enterContainerProcess :: Container -> [String] -> CreateProcess
|
|
|
|
enterContainerProcess = proc . enterScriptFile
|
|
|
|
|
2014-11-21 16:35:07 +00:00
|
|
|
nspawnServiceName :: MachineName -> ServiceName
|
2014-11-20 21:18:26 +00:00
|
|
|
nspawnServiceName name = "systemd-nspawn@" ++ name ++ ".service"
|
|
|
|
|
|
|
|
containerDir :: MachineName -> FilePath
|
2014-11-21 16:35:07 +00:00
|
|
|
containerDir name = "/var/lib/container" </> mungename name
|
2014-11-21 16:17:03 +00:00
|
|
|
|
|
|
|
mungename :: MachineName -> String
|
|
|
|
mungename = replace "/" "_"
|