diff --git a/examples/example.nix b/examples/example.nix index 7204c13..e1a06fd 100644 --- a/examples/example.nix +++ b/examples/example.nix @@ -78,5 +78,6 @@ ''; }) ); + systemd.tmpfiles.rules = [ "D /var/tmp/system-manager 0755 root root -" ]; }; } diff --git a/nix/lib.nix b/nix/lib.nix index 5662671..0a5cb0c 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -361,7 +361,7 @@ in ''; passthru = { - runVM = hostPkgs.writeShellScriptBin "run-vm" + driverInteractive = hostPkgs.writeShellScriptBin "run-vm" (defaultTest { extraDriverArgs = "--interactive"; }); diff --git a/nix/modules/default.nix b/nix/modules/default.nix index bb5ed23..8019f6d 100644 --- a/nix/modules/default.nix +++ b/nix/modules/default.nix @@ -9,6 +9,7 @@ ./environment.nix ./etc.nix ./systemd.nix + ./tmpfiles.nix ./upstream/nixpkgs ]; diff --git a/nix/modules/tmpfiles.nix b/nix/modules/tmpfiles.nix new file mode 100644 index 0000000..33a9894 --- /dev/null +++ b/nix/modules/tmpfiles.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + inherit (lib) types; +in +{ + options = { + systemd.tmpfiles.rules = lib.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "d /tmp 1777 root root 10d" ]; + description = lib.mdDoc '' + Rules for creation, deletion and cleaning of volatile and temporary files + automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + }; + + config = { + environment.etc."tmpfiles.d/00-system-manager.conf".text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + ${lib.concatStringsSep "\n" config.systemd.tmpfiles.rules} + ''; + }; +} diff --git a/src/activate.rs b/src/activate.rs index 039b7d8..22ab114 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -1,5 +1,6 @@ mod etc_files; mod services; +mod tmp_files; use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -80,6 +81,17 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> { match etc_files::activate(store_path, old_state.file_tree, ephemeral) { Ok(etc_tree) => { + log::info!("Activating tmp files..."); + match tmp_files::activate() { + Ok(_) => { + log::debug!("Successfully created tmp files"); + } + Err(e) => { + log::error!("Error during activation of tmp files"); + log::error!("{e}"); + } + }; + log::info!("Activating systemd services..."); match services::activate(store_path, old_state.services, ephemeral) { Ok(services) => State { @@ -104,6 +116,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> { } } .write_to_file(state_file)?; + Ok(()) } @@ -184,6 +197,7 @@ pub fn deactivate() -> Result<()> { } } .write_to_file(state_file)?; + Ok(()) } diff --git a/src/activate/tmp_files.rs b/src/activate/tmp_files.rs new file mode 100644 index 0000000..4bb66d4 --- /dev/null +++ b/src/activate/tmp_files.rs @@ -0,0 +1,29 @@ +use crate::activate; + +use super::ActivationResult; +use std::process; + +type TmpFilesActivationResult = ActivationResult<()>; + +pub fn activate() -> TmpFilesActivationResult { + let mut cmd = process::Command::new("systemd-tmpfiles"); + cmd.arg("--create") + .arg("--remove") + .arg("/etc/tmpfiles.d/00-system-manager.conf"); + let output = cmd + .stdout(process::Stdio::inherit()) + .stderr(process::Stdio::inherit()) + .output() + .expect("Error forking process"); + + output.status.success().then_some(()).ok_or_else(|| { + activate::ActivationError::WithPartialResult { + result: (), + source: anyhow::anyhow!( + "Error while creating tmpfiles\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(output.stdout.as_ref()), + String::from_utf8_lossy(output.stderr.as_ref()) + ), + } + }) +} diff --git a/test/nix/modules/default.nix b/test/nix/modules/default.nix index dbe42b2..987821f 100644 --- a/test/nix/modules/default.nix +++ b/test/nix/modules/default.nix @@ -126,19 +126,24 @@ forEachUbuntuImage node1.wait_for_unit("system-manager.target") node1.succeed("systemctl status service-9.service") - node1.succeed("cat /etc/baz/bar/foo2") - node1.succeed("cat /etc/a/nested/example/foo3") - node1.succeed("cat /etc/foo.conf") + node1.succeed("test -f /etc/baz/bar/foo2") + node1.succeed("test -f /etc/a/nested/example/foo3") + node1.succeed("test -f /etc/foo.conf") node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf") node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf") + node1.succeed("test -d /var/tmp/system-manager") + ${system-manager.lib.activateProfileSnippet { node = "node1"; profile = newConfig; }} node1.succeed("systemctl status new-service.service") node1.fail("systemctl status service-9.service") - node1.fail("cat /etc/a/nested/example/foo3") - node1.fail("cat /etc/baz/bar/foo2") - node1.fail("cat /etc/systemd/system/nginx.service") - node1.succeed("cat /etc/foo_new") + node1.fail("test -f /etc/a/nested/example/foo3") + node1.fail("test -f /etc/baz/bar/foo2") + node1.fail("test -f /etc/systemd/system/nginx.service") + node1.succeed("test -f /etc/foo_new") + + node1.succeed("test -d /var/tmp/system-manager") + node1.succeed("touch /var/tmp/system-manager/foo1") # Simulate a reboot, to check that the services defined with # system-manager start correctly after a reboot. @@ -152,13 +157,14 @@ forEachUbuntuImage node1.succeed("systemctl status new-service.service") node1.fail("systemctl status service-9.service") - node1.fail("cat /etc/a/nested/example/foo3") - node1.fail("cat /etc/baz/bar/foo2") - node1.succeed("cat /etc/foo_new") + node1.fail("test -f /etc/a/nested/example/foo3") + node1.fail("test -f /etc/baz/bar/foo2") + node1.succeed("test -f /etc/foo_new") ${system-manager.lib.deactivateProfileSnippet { node = "node1"; profile = newConfig; }} node1.fail("systemctl status new-service.service") - node1.fail("cat /etc/foo_new") + node1.fail("test -f /etc/foo_new") + #node1.fail("test -f /var/tmp/system-manager/foo1") ''; }) ]; @@ -197,29 +203,36 @@ forEachUbuntuImage node1.wait_for_unit("default.target") - ${system-manager.lib.activateProfileSnippet { node = "node1"; }} - + ${system-manager.lib.prepopulateProfileSnippet { node = "node1"; }} node1.systemctl("daemon-reload") - node1.systemctl("start default.target") + + # Simulate a reboot, to check that the services defined with + # system-manager start correctly after a reboot. + # TODO: can we find an easy way to really reboot the VM and not + # loose the root FS state? + node1.systemctl("isolate rescue.target") + # We need to send a return character to dismiss the rescue-mode prompt + node1.send_key("ret") + node1.systemctl("isolate default.target") node1.wait_for_unit("system-manager.target") node1.succeed("systemctl status service-9.service") - node1.succeed("cat /etc/baz/bar/foo2") - node1.succeed("cat /etc/a/nested/example/foo3") - node1.succeed("cat /etc/foo.conf") + node1.succeed("test -f /etc/baz/bar/foo2") + node1.succeed("test -f /etc/a/nested/example/foo3") + node1.succeed("test -f /etc/foo.conf") node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf") node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf") ${system-manager.lib.activateProfileSnippet { node = "node1"; profile = newConfig; }} node1.succeed("systemctl status new-service.service") node1.fail("systemctl status service-9.service") - node1.fail("cat /etc/a/nested/example/foo3") - node1.fail("cat /etc/baz/bar/foo2") - node1.succeed("cat /etc/foo_new") + node1.fail("test -f /etc/a/nested/example/foo3") + node1.fail("test -f /etc/baz/bar/foo2") + node1.succeed("test -f /etc/foo_new") ${system-manager.lib.deactivateProfileSnippet { node = "node1"; profile = newConfig; }} node1.fail("systemctl status new-service.service") - node1.fail("cat /etc/foo_new") + node1.fail("test -f /etc/foo_new") ''; } )