[kde-linux/kde-linux] mkosi.extra/live/usr: Port subvol script to python and use libcalamares for better progress reports.
Hadi Chokr <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit e9be2155575ebb58b05b86af007b6a78f0724745 by Hadi Chokr. Committed on 01/08/2026 at 16:44. Pushed by silverhadch into branch 'master'. Port subvol script to python and use libcalamares for better progress reports. Closes #710 Signed-off-by: Hadi Chokr <[email protected]> M +14 -15 mkosi.extra/live/usr/calamares/desktop/branding/kde-linux/HeaderBar.qml M +3 -5 mkosi.extra/live/usr/calamares/desktop/settings.conf A +373 -0 mkosi.extra/live/usr/lib/calamares/modules/subvol/main.py R +6 -5 mkosi.extra/live/usr/lib/calamares/modules/subvol/module.desc [from: mkosi.extra/live/usr/calamares/desktop/modules/[email protected] - 052% similarity] D +0 -141 mkosi.extra/live/usr/lib/calamares@subvol https://invent.kde.org/kde-linux/kde-linux/-/commit/e9be2155575ebb58b05b86af007b6a78f0724745 diff --git a/mkosi.extra/live/usr/calamares/desktop/branding/kde-linux/HeaderBar.qml b/mkosi.extra/live/usr/calamares/desktop/branding/kde-linux/HeaderBar.qml index cbdd0f18..709111ac 100644 --- a/mkosi.extra/live/usr/calamares/desktop/branding/kde-linux/HeaderBar.qml +++ b/mkosi.extra/live/usr/calamares/desktop/branding/kde-linux/HeaderBar.qml @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-FileCopyrightText: 2024 Harald Sitter <[email protected]> +// SPDX-FileCopyrightText: 2026 Hadi Chokr <[email protected]> import QtQuick import QtQuick.Layouts @@ -9,26 +10,23 @@ import org.kde.kirigami as Kirigami import io.calamares.ui as Calamares RowLayout { + readonly property var stepIcons: ({ + "welcome": "kde-symbolic", + "locale": "globe-symbolic", + "keyboard": "input-keyboard-symbolic", + "partition": "drive-multipartition-symbolic", + "users": "system-users-symbolic", + "exec": "run-install-symbolic", + "finished": "run-install-symbolic" + }) + RowLayout { Layout.fillWidth: true Kirigami.Icon { // TODO should be the page icon but calamares has no actual notion of icons for pages source: { - switch (control.currentIndex) { - case 0: - return "kde-symbolic" - case 1: - return "globe-symbolic" - case 2: - return "input-keyboard-symbolic" - case 3: - return "drive-multipartition-symbolic" - case 4: - return "system-users-symbolic" - case 5: - return "run-install-symbolic" - } - return "kde-symbolic" + const step = Calamares.ViewManager.currentStepName + return control.stepIcons[step] || "kde-symbolic" } } Kirigami.Heading { @@ -45,6 +43,7 @@ RowLayout { spacing: Kirigami.Units.mediumSpacing property int currentIndex: Calamares.ViewManager.currentStepIndex + readonly property var stepIcons: parent.stepIcons Rectangle { id: backgroundLine diff --git a/mkosi.extra/live/usr/calamares/desktop/settings.conf b/mkosi.extra/live/usr/calamares/desktop/settings.conf index bcee4bdf..4ad2d085 100644 --- a/mkosi.extra/live/usr/calamares/desktop/settings.conf +++ b/mkosi.extra/live/usr/calamares/desktop/settings.conf @@ -2,13 +2,11 @@ # SPDX-FileCopyrightText: 2018-2021 Harald Sitter <[email protected]> # SPDX-FileCopyrightText: 2017 Jonathan Riddell <[email protected]> # SPDX-FileCopyrightText: 2016 Rohan Garg <[email protected]> +# SPDX-FileCopyrightText: 2026 Hadi Chokr <[email protected]> --- -modules-search: [ local, /usr/lib/calamares/modules, /usr/share/calamares/modules ] +modules-search: [ /usr/lib/calamares/modules, /usr/share/calamares/modules ] instances: -- id: subvol - module: shellprocess - config: [email protected] - id: uki module: shellprocess config: [email protected] @@ -22,7 +20,7 @@ sequence: - exec: - partition - mount - - shellprocess@subvol + - subvol - machineid - displaymanager - hwclock diff --git a/mkosi.extra/live/usr/lib/calamares/modules/subvol/main.py b/mkosi.extra/live/usr/lib/calamares/modules/subvol/main.py new file mode 100644 index 00000000..8bebdab3 --- /dev/null +++ b/mkosi.extra/live/usr/lib/calamares/modules/subvol/main.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +# SPDX-FileCopyrightText: 2026 Hadi Chokr <[email protected]> + +# NOTE: this runs outside the chroot! + + +from __future__ import annotations + +import os +import platform +import re +import subprocess +import time +import traceback +from dataclasses import dataclass, field +from pathlib import Path + +import libcalamares +from libcalamares.utils import host_env_process_output +import gettext +_ = gettext.translation( + "calamares-python", localedir=libcalamares.utils.gettext_path(), + languages=libcalamares.utils.gettext_languages(), fallback=True, +).gettext + +status: str = "" + +# Copying the image is the only step that takes long enough to be worth +# reporting progress inside of, so it gets a range rather than a fixed value. +IMAGE_COPY_START = 0.10 +IMAGE_COPY_END = 0.85 + + +def pretty_status_message() -> str: + return status + + +def _report(fraction: float, message: str) -> None: + global status + status = message + libcalamares.job.setprogress(fraction) + + +STATE_DIR = Path("/run/kde-linux-install") + + +def _image_version() -> str: + # IMAGE_VERSION comes from os-release, not the environment. + env = os.environ.get("IMAGE_VERSION", "") + if env: + return env + + try: + version = platform.freedesktop_os_release().get("IMAGE_VERSION", "") + if version: + return version + except (OSError, AttributeError): + pass + + for candidate in (Path("/etc/os-release"), Path("/usr/lib/os-release")): + try: + content = candidate.read_text() + except OSError: + continue + for line in content.splitlines(): + key, sep, value = line.partition("=") + if sep and key.strip() == "IMAGE_VERSION": + return value.strip().strip('"').strip("'") + + return "" + + +@dataclass +class InstallState: + root: Path + tmpdir: Path + device: str + blockdev: str + partnum: str | None + espdev: str | None = None + image_version: str = field(default_factory=_image_version) + + +def run() -> tuple[str, str] | None: + root_dir = libcalamares.globalstorage.value("rootMountPoint") + if not root_dir: + return ("subvol", _("No target root (rootMountPoint) was provided.")) + + root = Path(root_dir) + STATE_DIR.mkdir(parents=True, exist_ok=True) + + try: + state = _resolve_state(root) + _correct_partition_type(state) + _teardown_existing(state) + _create_subvolumes(state) + _copy_image(state) + _build_mount_stack(state) + _populate_base_filesystem(state) + _apply_factory_and_sysusers(state) + _apply_presets_and_tmpfiles(state) + except _StageError as err: + libcalamares.utils.error(str(err)) + return ("subvol", str(err)) + except Exception as err: + libcalamares.utils.error(traceback.format_exc()) + return ("subvol", _("Unexpected error: {}").format(err)) + + _report(1.0, _("System image installed.")) + return None + + +class _StageError(RuntimeError): + pass + + +def _run(cmd: list[str], **kwargs) -> str: + """basically `set -ex`""" + check = kwargs.pop("check", True) + kwargs.setdefault("text", True) + libcalamares.utils.debug("running: {}".format(" ".join(cmd))) + try: + result = subprocess.run(cmd, capture_output=True, check=check, **kwargs) + except subprocess.CalledProcessError as err: + stderr = (err.stderr or "").strip() + raise _StageError( + _("Command failed ({code}): {cmd}\n{stderr}").format( + code=err.returncode, cmd=" ".join(cmd), stderr=stderr + ) + ) from err + except FileNotFoundError as err: + raise _StageError(_("Command not found: {}").format(cmd[0])) from err + return (result.stdout or "").strip() + + +def _empty_directory(directory: Path) -> None: + """Unmount and remove everything inside directory (but not the directory itself).""" + entries = sorted(entry.name for entry in directory.iterdir()) + if not entries: + return + _run(["umount", "-R", *entries], cwd=directory, check=False) + _run(["rm", "-rf", "--", *entries], cwd=directory) + + +def _resolve_state(root: Path) -> InstallState: + _report(0.0, _("Preparing installation…")) + + device = _run(["findmnt", "--noheadings", "--nofsroot", "--output", "SOURCE", str(root)]) + if not device: + raise _StageError(_("Could not determine the source device for {}.").format(root)) + + blockdev, partnum = _resolve_blockdev_and_partnum(device) + + tmpdir = STATE_DIR / "subvol-tmp" + tmpdir.mkdir(parents=True, exist_ok=True) + + state = InstallState(root=root, tmpdir=tmpdir, device=device, blockdev=blockdev, partnum=partnum) + + # Before teardown wipes the target. + if not state.image_version: + raise _StageError(_("Could not determine IMAGE_VERSION from os-release.")) + libcalamares.utils.debug(f"image version: {state.image_version}") + + return state + + +def _resolve_blockdev_and_partnum(device: str) -> tuple[str, str | None]: + # Correct the GPT partition type if necessary (e.g., from manual partitioning) + # This ensures /dev/gpt-auto-root is available for systemd detection + realdevice = _run(["realpath", "--relative-to", "/dev", device]) + + dm_dir = Path(f"/sys/block/{realdevice}/dm") + if dm_dir.is_dir(): + slaves_dir = Path(f"/sys/block/{realdevice}/slaves") + for slave in sorted(slaves_dir.iterdir()): + realdevice = slave.name + break + + parent_link = Path(f"/sys/class/block/{realdevice}/..") + resolved = _run(["readlink", "--canonicalize", str(parent_link)]) + blockdev = f"/dev/{Path(resolved).name}" + + # Extract partition number from device path + match = re.search(r"[^0-9](\d+)$", device) + partnum = match.group(1) if match else None + + return blockdev, partnum + + +def _correct_partition_type(state: InstallState) -> None: + _report(0.02, _("Checking partition type…")) + + root_guids = { + "x86_64": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "i686": "44479540-f297-41b2-9af7-d131d5f0458a", + "i386": "44479540-f297-41b2-9af7-d131d5f0458a", + "aarch64": "b921b045-1df0-41c3-af44-4c6f280d3fae", + "armv7l": "69dad710-2ce4-4e3c-b16c-21a1d49abed3", + "armv7h": "69dad710-2ce4-4e3c-b16c-21a1d49abed3", + "riscv64": "72ec70a6-cf74-40e6-bd49-4bda08e8f224", + "loongarch64": "77055800-792c-4f94-b39a-98c91b762bb6", + } + arch = _run(["uname", "-m"]) + root_guid = root_guids.get(arch, "") + if not root_guid: + # Unknown architecture: $arch, skipping partition type correction + libcalamares.utils.debug(f"unknown architecture {arch}, not correcting the partition type") + return + + if not state.partnum: + return + + # Check current partition type and correct if needed + current_guid = _run( + ["sfdisk", "--part-type", state.blockdev, state.partnum], check=False + ).lower() + + if current_guid and current_guid != root_guid: + # Correcting partition type to the architecture's Linux root type + _run(["sfdisk", "--part-type", state.blockdev, state.partnum, root_guid], check=False) + # Reload partition table + _run(["partprobe", state.blockdev], check=False) + # Give udev a moment to update /dev/gpt-auto-root + _run(["udevadm", "settle"], check=False) + time.sleep(1) + + +def _teardown_existing(state: InstallState) -> None: + _report(0.05, _("Clearing existing mount…")) + + root = str(state.root) + + # Calamares likes to mount stuff even with an empty config. Throw it away again. + _empty_directory(state.root) + _run(["btrfs", "subvolume", "sync", root], check=False) + # unmount is important as otherwise we still hold a subvolume open and it can never sync deletion + _run(["umount", "-R", "--lazy", root], check=False) + + +def _create_subvolumes(state: InstallState) -> None: + _report(0.08, _("Creating subvolumes…")) + + Path("/system").mkdir(exist_ok=True) + if not os.path.ismount("/system"): + _run(["mount", "-o", "ro", "/dev/gpt-auto-root", "/system"]) + _run(["mount", "-o", "rw", state.device, str(state.tmpdir)]) + + tmp = str(state.tmpdir) + _empty_directory(state.tmpdir) + _run(["btrfs", "subvolume", "sync", tmp], check=False) + + _run(["btrfs", "quota", "enable", "--simple", tmp]) + _run(["btrfs", "subvolume", "create", f"{tmp}/@system"]) + _run(["btrfs", "subvolume", "create", f"{tmp}/@system/etc"]) + for sub in ("boot", "proc", "sys", "dev", "run", "usr"): + (state.tmpdir / "@system" / sub).mkdir(parents=True, exist_ok=True) + + +def _copy_image(state: InstallState) -> None: + src = "/dev/gpt-auto-root" + dst = state.tmpdir / f"kde-linux_{state.image_version}.erofs" + + _report(IMAGE_COPY_START, _("Copying system image…")) + + fd = os.open(src, os.O_RDONLY) + try: + total = os.lseek(fd, 0, os.SEEK_END) # st_size is 0 for block devices + os.lseek(fd, 0, os.SEEK_SET) + if total <= 0: + raise _StageError(_("Could not determine the size of {}.").format(src)) + + done = 0 + chunk_size = 16 << 20 # 16 MiB + with open(dst, "wb") as out: + while True: + chunk = os.read(fd, chunk_size) + if not chunk: + break + out.write(chunk) + done += len(chunk) + fraction = IMAGE_COPY_START + (IMAGE_COPY_END - IMAGE_COPY_START) * (done / total) + _report( + fraction, + _("Copying system image ({}/{} MiB)").format(done >> 20, total >> 20), + ) + out.flush() + os.fsync(out.fileno()) + finally: + os.close(fd) + + +def _build_mount_stack(state: InstallState) -> None: + _report(IMAGE_COPY_END, _("Assembling mount stack…")) + + root = state.root + + # Overmount calamares' mount with the subvol mount + _run(["mount", "-o", "subvol=@system", state.device, str(root)]) + _run(["mount", "-t", "proc", "proc", str(root / "proc")]) + _run(["mount", "-t", "sysfs", "sys", str(root / "sys")]) + _run(["mount", "-o", "bind", "/dev", str(root / "dev")]) + _run(["mount", "-t", "tmpfs", "tmpfs", str(root / "run")]) + # This is not part of @system but rather the $ROOT (do not move this to the mkdir list of @system!) + (root / "run" / "udev").mkdir(parents=True, exist_ok=True) + _run(["mount", "-o", "bind", "/run/udev", str(root / "run" / "udev")]) + efivars = root / "sys" / "firmware" / "efi" / "efivars" + if efivars.is_dir(): + _run(["mount", "-t", "efivarfs", "efivarfs", str(efivars)], check=False) + _run(["mount", "-o", "ro,X-mount.subdir=usr", "/dev/gpt-auto-root", str(root / "usr")]) + + # ESP is a bit tricky. We ask systemd for an ESP on the block device of the + # root partition (which we already resolved, luks devices and all). + espdev = _run(["/usr/lib/find-esp", state.blockdev]) + if not espdev: + raise _StageError( + _("Could not locate an EFI system partition on {}.").format(state.blockdev) + ) + state.espdev = espdev + _run(["mount", espdev, str(root / "boot")]) + + +def _populate_base_filesystem(state: InstallState) -> None: + _report(0.90, _("Populating base filesystem…")) + + # Bit of a crutch to get systemd's base_filesystem_create() to run so the + # / gets populated with symlinks. Exit code is not indicative of to the + # function having run - ignore it. + _run(["systemd-nspawn", "-D", str(state.root), "true"], check=False) + + +def _apply_factory_and_sysusers(state: InstallState) -> None: + root = str(state.root) + + _report(0.92, _("Applying factory configuration…")) + # Apply the factory + host_env_process_output( + ["/usr/lib/etc-factory", "--sysroot", f"{root}/", "--replace", "--migrate"], + None, + ) + + _report(0.95, _("Initializing users…")) + # Initialize systemd stuff + _run(["systemd-sysusers", f"--root={root}"]) + + # Add a marker file to indicate this install included plasma-setup. + # This ensures plasma-setup won't run for existing installations. + marker_dir = state.root / "var" / "lib" / "kde-linux" + marker_dir.mkdir(parents=True, mode=0o755, exist_ok=True) + marker = marker_dir / "installed-with-plasma-setup" + marker.touch(mode=0o644, exist_ok=True) + + +def _apply_presets_and_tmpfiles(state: InstallState) -> None: + root = str(state.root) + + _report(0.97, _("Applying service presets…")) + # Make sure presets are applied + _run(["systemctl", f"--root={root}", "preset-all"]) + _run(["systemctl", f"--root={root}", "preset-all", "--global"]) + + _report(0.99, _("Creating tmpfiles…")) + # tmpfiles are final and should come at last + # exclude /usr because some tmpfiles are rubbish and assume /usr is writable... + _run( + [ + "systemd-tmpfiles", + f"--root={root}", + "--exclude-prefix=/usr", + "--create", + ], + check=False, + ) diff --git a/mkosi.extra/live/usr/calamares/desktop/modules/[email protected] b/mkosi.extra/live/usr/lib/calamares/modules/subvol/module.desc similarity index 52% rename from mkosi.extra/live/usr/calamares/desktop/modules/[email protected] rename to mkosi.extra/live/usr/lib/calamares/modules/subvol/module.desc index 02b1314e..f8d0bf0d 100644 --- a/mkosi.extra/live/usr/calamares/desktop/modules/[email protected] +++ b/mkosi.extra/live/usr/lib/calamares/modules/subvol/module.desc @@ -1,10 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -# SPDX-FileCopyrightText: 2023 Harald Sitter <[email protected]> +# SPDX-FileCopyrightText: 2026 Hadi Chokr <[email protected]> # # NOTE: this runs outside the chroot! # Rejigger the subvolumes. --- -dontChroot: true -timeout: 3600 -script: - - /usr/lib/calamares@subvol ${ROOT} +type: "job" +name: "subvol" +interface: "python" +script: "main.py" +noconfig: true diff --git a/mkosi.extra/live/usr/lib/calamares@subvol b/mkosi.extra/live/usr/lib/calamares@subvol deleted file mode 100755 index 1c144e0f..00000000 --- a/mkosi.extra/live/usr/lib/calamares@subvol +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -# SPDX-FileCopyrightText: 2023 Harald Sitter <[email protected]> - -# NOTE: this runs outside the chroot! -# Mangle subvol setup from calamares to systemd conforming lineup. - -set -ex - -ROOT=$1 -[ "$ROOT" = "" ] && exit 1 - -device=$(findmnt --noheadings --nofsroot --output SOURCE "$ROOT") -[ "$device" = "" ] && exit 1 - -# Correct the GPT partition type if necessary (e.g., from manual partitioning) -# This ensures /dev/gpt-auto-root is available for systemd detection -realdevice=$(realpath --relative-to /dev "$device") -if [ -r "/sys/block/$realdevice/dm/" ]; then - for slave in /sys/block/"$realdevice"/slaves/*; do - realdevice=$(basename "$slave") - break - done -fi -blockdev=/dev/$(basename "$(readlink --canonicalize "/sys/class/block/$realdevice/..")") - -# Extract partition number from device path -partnum=$(echo "$device" | sed -n 's/.*[^0-9]\([0-9]\+\)$/\1/p') - -arch=$(uname -m) -case "$arch" in - x86_64) - root_guid="4f68bce3-e8cd-4db1-96e7-fbcaf984b709" - ;; - i686|i386) - root_guid="44479540-f297-41b2-9af7-d131d5f0458a" - ;; - aarch64) - root_guid="b921b045-1df0-41c3-af44-4c6f280d3fae" - ;; - armv7l|armv7h) - root_guid="69dad710-2ce4-4e3c-b16c-21a1d49abed3" - ;; - riscv64) - root_guid="72ec70a6-cf74-40e6-bd49-4bda08e8f224" - ;; - loongarch64) - root_guid="77055800-792c-4f94-b39a-98c91b762bb6" - ;; - *) - echo "Unknown architecture: $arch, skipping partition type correction" - root_guid="" - ;; -esac - -if [ -n "$partnum" ] && command -v sfdisk >/dev/null 2>&1; then - # Check current partition type and correct if needed - # 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 is Linux root (x86-64) - current_guid=$(sfdisk --part-type "$blockdev" "$partnum" 2>/dev/null || echo "") - echo "current_guid: $current_guid" - echo "root_guid: $root_guid" - if [ -n "$current_guid" ] && [ "$current_guid" != "$root_guid" ]; then - echo "Correcting partition type from $current_guid to Linux root (x86-64)" - sfdisk --part-type "$blockdev" "$partnum" "$root_guid" || true - # Reload partition table - partprobe "$blockdev" 2>/dev/null || true - # Give udev a moment to update /dev/gpt-auto-root - sleep 1 - fi -fi - - -. /etc/os-release - -tmpdir=$(mktemp --directory) - -# Calamares likes to mount stuff even with an empty config. Throw it away again. -cd "$ROOT" -umount -R ./* -rm -rfv ./* -btrfs subvolume sync . || true -umount -R --lazy "$ROOT" # unmount is important as otherwise we still hold a subvolume open and it can never sync deletion - -mount -o ro /dev/gpt-auto-root /system -mount -o rw "$device" "$tmpdir" -cd "$tmpdir" -rm -rfv ./* -btrfs subvolume sync . || true - -btrfs quota enable --simple . -btrfs subvolume create @system -btrfs subvolume create @system/etc -mkdir @system/boot @system/proc @system/sys @system/dev @system/run @system/usr -cp /dev/gpt-auto-root kde-linux_$IMAGE_VERSION.erofs - -# Overmount calamares' mount with the subvol mount -mount -o "subvol=@system" "$device" "$ROOT" -mount -t proc proc "$ROOT/proc" -mount -t sysfs sys "$ROOT/sys" -mount -o bind /dev "$ROOT/dev" -mount -t tmpfs tmpfs "$ROOT/run" -mkdir "$ROOT/run/udev" # This is not part of @system but rather the $ROOT (do not move this to the mkdir list of @system!) -mount -o bind /run/udev "$ROOT/run/udev" -mount -t efivarfs efivarfs "$ROOT/sys/firmware/efi/efivars" -mount -o ro,X-mount.subdir=usr /dev/gpt-auto-root "$ROOT/usr" - -# ESP is a bit tricky. Find the block device of the root partition and then we'll ask systemd for an ESP on that device. -# ... and luks devices are even more tricky because we need to get the real device first -realdevice=$(realpath --relative-to /dev "$device") -if [ -r "/sys/block/$realdevice/dm/" ]; then - for slave in /sys/block/"$realdevice"/slaves/*; do - realdevice=$(basename "$slave") - break - done -fi -blockdev=/dev/$(basename "$(readlink --canonicalize "/sys/class/block/$realdevice/..")") -espdev=$(/usr/lib/find-esp "$blockdev") -mount "$espdev" "$ROOT/boot" - -# Bit of a crutch to get systemd's base_filesystem_create() to run so the / gets populated with symlinks. -# Exit code is not indicative of to the function having run - ignore it. -systemd-nspawn -D "$ROOT" "true" || true - -# Apply the factory -/usr/lib/etc-factory --sysroot "$ROOT/" --replace --migrate - -# Initialize systemd stuff -systemd-sysusers --root="$ROOT" - -# Add a marker file to indicate this install included plasma-setup. -# This ensures plasma-setup won't run for existing installations. -install -d -m 0755 "$ROOT/var/lib/kde-linux" -install -m 0644 /dev/null "$ROOT/var/lib/kde-linux/installed-with-plasma-setup" - -# Make sure presets are applied -systemctl --root="$ROOT" preset-all -systemctl --root="$ROOT" preset-all --global - -# tmpfiles are final and should come at last -# exclude /usr because some tmpfiles are rubbish and assume /usr is writable... -systemd-tmpfiles --root="$ROOT" --exclude-prefix=/usr --create