<commit_before>module Propellor.Property.Systemd (
	installed,
	persistentJournal,
	Container,
	container,
	nspawned,
) where

import Propellor
import qualified Propellor.Property.Chroot as Chroot
import qualified Propellor.Property.Apt as Apt
import qualified Propellor.Property.File as File
import Utility.SafeCommand
import Utility.FileMode

import Data.List.Utils

type MachineName = String

type NspawnParam = CommandParam

data Container = Container MachineName System [CommandParam] Host

instance Hostlike Container where
        (Container n s ps h) & p = Container n s ps (h & p)
        (Container n s ps h) &^ p = Container n s ps (h &^ p)
        getHost (Container _ _ _ h) = h

-- 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"]

-- | Enables persistent storage of the journal.
persistentJournal :: Property
persistentJournal = check (not <$> doesDirectoryExist dir) $ 
	combineProperties "persistent systetemd journal"
		[ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]
		, cmdProperty "setfacl" ["-R", "-nm", "g:adm:rx,d:g:adm:rx", dir]
		]
		`requires` Apt.installed ["acl"]
  where
	dir = "/var/log/journal"

-- | Defines a container with a given machine name, containing the specified
-- System. Properties can be added to configure the Container.
--
-- > container "webserver" (System (Debian Unstable) "amd64") []
container :: MachineName -> System -> [NspawnParam] -> Container
container name system ps = Container name system ps (Host name [] mempty)

-- | 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
nspawned c@(Container name system _ h) = RevertableProperty setup teardown
  where
	setup = containerprovisioned
		`requires` toProp (nspawnService c)
		`requires` toProp chrootprovisioned
		`requires` toProp (enterScript c)

	teardown = toProp (revert (chrootprovisioned))
		`requires` toProp (revert (nspawnService c))
		`requires` toProp (revert (enterScript c))

	-- 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.
	chrootprovisioned = Chroot.provisioned $
		mkChroot $ h { hostProperties = [installed] }

	-- Use nsenter to enter container and and run propellor to
	-- finish provisioning.
	containerprovisioned = Chroot.propellChroot
		(mkChroot h)
		(enterContainerProcess c)

	mkChroot = Chroot.Chroot (containerDir name) system

nspawnService :: Container -> RevertableProperty
nspawnService (Container name _ ps _) = RevertableProperty setup teardown
  where
	service = nspawnServiceName name
	servicefile = "/etc/systemd/system/multi-user.target.wants" </> service

	setup = check (not <$> doesFileExist servicefile) $
		combineProperties ("container running " ++ service)
			[ cmdProperty "systemctl" ["enable", service]
			, cmdProperty "systemctl" ["start", service]
			]
	-- TODO ^ adjust execStart line to reflect ps

	teardown = undefined

-- | 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
enterScript c@(Container name _ _ _) = RevertableProperty setup teardown
  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"
			, "\tnsenter -p -u -n -i -m -t \"$pid\""
			, "else"
			, "\texit 1"
			, "fi"
			]
		, scriptfile `File.mode` combineModes (readModes ++ executeModes)
		]
	teardown = File.notPresent scriptfile
	scriptfile = enterScriptFile c

enterScriptFile :: Container -> FilePath
enterScriptFile (Container name _ _ _ ) = "enter-" ++ mungename name

enterContainerProcess :: Container -> [String] -> CreateProcess
enterContainerProcess = proc . enterScriptFile

nspawnServiceName :: MachineName -> String
nspawnServiceName name = "systemd-nspawn@" ++ name ++ ".service"

containerDir :: MachineName -> FilePath
containerDir name = "/var/lib/container" ++ mungename name

mungename :: MachineName -> String
mungename = replace "/" "_"<commit_msg>propellor spin<commit_after>module Propellor.Property.Systemd (
	installed,
	started,
	stopped,
	enabled,
	persistentJournal,
	Container,
	container,
	nspawned,
) where

import Propellor
import qualified Propellor.Property.Chroot as Chroot
import qualified Propellor.Property.Apt as Apt
import qualified Propellor.Property.File as File
import Utility.SafeCommand
import Utility.FileMode

import Data.List.Utils

type ServiceName = String

type MachineName = String

type NspawnParam = CommandParam

data Container = Container MachineName System [CommandParam] Host

instance Hostlike Container where
        (Container n s ps h) & p = Container n s ps (h & p)
        (Container n s ps h) &^ p = Container n s ps (h &^ p)
        getHost (Container _ _ _ h) = h

-- 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"]

-- | 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")

-- | Enables persistent storage of the journal.
persistentJournal :: Property
persistentJournal = check (not <$> doesDirectoryExist dir) $ 
	combineProperties "persistent systemd journal"
		[ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]
		, cmdProperty "setfacl" ["-R", "-nm", "g:adm:rx,d:g:adm:rx", dir]
		, started "systemd-journal-flush"
		]
		`requires` Apt.installed ["acl"]
  where
	dir = "/var/log/journal"

-- | Defines a container with a given machine name, containing the specified
-- System. Properties can be added to configure the Container.
--
-- > container "webserver" (System (Debian Unstable) "amd64") []
container :: MachineName -> System -> [NspawnParam] -> Container
container name system ps = Container name system ps (Host name [] mempty)

-- | 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
nspawned c@(Container name system _ h) = RevertableProperty setup teardown
  where
	setup = containerprovisioned
		`requires` toProp (nspawnService c)
		`requires` toProp chrootprovisioned
		`requires` toProp (enterScript c)

	teardown = toProp (revert (chrootprovisioned))
		`requires` toProp (revert (nspawnService c))
		`requires` toProp (revert (enterScript c))

	-- 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.
	chrootprovisioned = Chroot.provisioned $
		mkChroot $ h { hostProperties = [installed] }

	-- Use nsenter to enter container and and run propellor to
	-- finish provisioning.
	containerprovisioned = Chroot.propellChroot
		(mkChroot h)
		(enterContainerProcess c)

	mkChroot = Chroot.Chroot (containerDir name) system

nspawnService :: Container -> RevertableProperty
nspawnService (Container name _ ps _) = RevertableProperty setup teardown
  where
	service = nspawnServiceName name
	servicefile = "/etc/systemd/system/multi-user.target.wants" </> service

	setup = check (not <$> doesFileExist servicefile) $
		combineProperties ("container running " ++ service)
			[ enabled service
			, started service
			]
	-- TODO ^ adjust execStart line to reflect ps

	teardown = undefined

-- | 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
enterScript c@(Container name _ _ _) = RevertableProperty setup teardown
  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"
			, "\tnsenter -p -u -n -i -m -t \"$pid\""
			, "else"
			, "\texit 1"
			, "fi"
			]
		, scriptfile `File.mode` combineModes (readModes ++ executeModes)
		]
	teardown = File.notPresent scriptfile
	scriptfile = enterScriptFile c

enterScriptFile :: Container -> FilePath
enterScriptFile (Container name _ _ _ ) = "enter-" ++ mungename name

enterContainerProcess :: Container -> [String] -> CreateProcess
enterContainerProcess = proc . enterScriptFile

nspawnServiceName :: MachineName -> ServiceName
nspawnServiceName name = "systemd-nspawn@" ++ name ++ ".service"

containerDir :: MachineName -> FilePath
containerDir name = "/var/lib/container" </> mungename name

mungename :: MachineName -> String
mungename = replace "/" "_"
{'fraction_non_alphanumeric': 0.10463502636691646, 'fraction_numerical': 0.0007401239707651032, 'mean_word_length': 3.974689369535205, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3608547', 'n_tokens_mistral': 3330, 'n_tokens_neox': 3174, 'n_words': 1167}