diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index 2e5930280..c0e3fd1f6 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -1986,6 +1986,13 @@ def render(self, mode: ActionRenderMode = ActionRenderMode.DEFAULT): config["grub"] = self.grub return config + def systemd_shutdown_commands(self) -> list[list[str]]: + """Return a list of commands meant to be executed by systemd-shutdown. + We entrust the execution of `zpool export` commands to systemd-shutdown + instead of subiquity's _pre_shutdown hook, in hope that the commands + will more likely succeed.""" + return [["zpool", "export", zpool.name] for zpool in self._all(type="zpool")] + def load_probe_data(self, probe_data): for devname, devdata in probe_data["blockdev"].items(): if int(devdata["attrs"]["size"]) != 0: diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index f79d08b2d..0ae0f5a80 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -1644,5 +1644,3 @@ async def _pre_shutdown(self): ) else: await self.app.command_runner.run(["umount", "--recursive", "/target"]) - if len(self.model._all(type="zpool")) > 0: - await self.app.command_runner.run(["zpool", "export", "-a"]) diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index 282a7eedb..2424b3acc 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -326,6 +326,25 @@ async def curtin_install(self, *, context, source): fs_controller = self.app.controllers.Filesystem + def register_shutdown_commands() -> None: + cmds: list[list[str]] = fs_controller.model.systemd_shutdown_commands() + + if not cmds: + return + + shutdown_dir = root / "usr/lib/systemd/system-shutdown" + shutdown_script = shutdown_dir / "subiquity-storage.shutdown" + shutdown_dir.mkdir(parents=True, exist_ok=True) + with shutdown_script.open(mode="w", encoding="utf-8") as stream: + # Generating a bash script is .. ewww + # Should we place the commands in a JSON file and hardcode a + # script that calls jq or something? + stream.write("#!/bin/bash\n") + for cmd in cmds: + stream.write(shlex.join(cmd)) + stream.write("\n") + shutdown_script.chmod(0o755) + async def run_curtin_step(name, stages, step_config, source=None): config = copy.deepcopy(base_config) filename = f"subiquity-{name.replace(' ', '-')}.conf" @@ -349,6 +368,7 @@ async def run_curtin_step(name, stages, step_config, source=None): device_map_path=logs_dir / "device-map.json", ), ) + register_shutdown_commands() elif fs_controller.is_core_boot_classic(): await run_curtin_step( name="partitioning", @@ -368,6 +388,7 @@ async def run_curtin_step(name, stages, step_config, source=None): device_map_path=logs_dir / "device-map-format.json", ), ) + register_shutdown_commands() await run_curtin_step( name="extract", stages=["extract"], @@ -400,6 +421,7 @@ async def run_curtin_step(name, stages, step_config, source=None): ), source=source, ) + register_shutdown_commands() await run_curtin_step( name="extract", stages=["extract"],