diff --git a/bin/systemd-major b/bin/systemd-major new file mode 100755 index 00000000..4ae2c5b6 --- /dev/null +++ b/bin/systemd-major @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: $(basename "$0") " >&2 + exit 2 +} + +[ $# -ge 1 ] || usage +root="$1" +[ -d "$root" ] || { echo "error: chroot path not found: $root" >&2; exit 2; } + +# Try to run systemd --version inside the chroot and extract the major number. +# Requires privileges to chroot into . +ver="$( + chroot "$root" /bin/sh -eu -c ' + PATH=/usr/sbin:/usr/bin:/sbin:/bin + for b in /usr/lib/systemd/systemd /lib/systemd/systemd systemd; do + if [ "$b" = systemd ]; then + command -v systemd >/dev/null 2>&1 || continue + cmd=systemd + else + [ -x "$b" ] || continue + cmd="$b" + fi + "$cmd" --version 2>/dev/null | sed -n "s/^systemd \([0-9][0-9]*\).*/\1/p" | head -n1 + exit 0 + done + exit 127 + ' 2>/dev/null || true +)" + +[ -n "${ver:-}" ] || { echo "error: could not determine systemd version in $root" >&2; exit 1; } +printf '%s\n' "$ver" diff --git a/config/bookworm-minbase-ab.yaml b/config/bookworm-minbase-ab.yaml index 8224c984..82846a72 100644 --- a/config/bookworm-minbase-ab.yaml +++ b/config/bookworm-minbase-ab.yaml @@ -3,8 +3,9 @@ device: image: layer: image-rota - boot_part_size: 200% - system_part_size: 300% + boot_part_size: 128M + system_part_size: 512M + data_part_size: 1G name: deb12-arm64-min-ab layer: diff --git a/config/trixie-minbase-ab.yaml b/config/trixie-minbase-ab.yaml index df6c074e..3356698c 100644 --- a/config/trixie-minbase-ab.yaml +++ b/config/trixie-minbase-ab.yaml @@ -3,8 +3,9 @@ device: image: layer: image-rota - boot_part_size: 200% - system_part_size: 300% + boot_part_size: 128M + system_part_size: 512M + data_part_size: 1G name: deb13-arm64-min-ab layer: diff --git a/docs/layer/image-rota.html b/docs/layer/image-rota.html index b621ff76..d3d3a204 100644 --- a/docs/layer/image-rota.html +++ b/docs/layer/image-rota.html @@ -123,10 +123,9 @@

image-rota

image - v3.1.0 -

Rotational OTA and AB support with GPT. - This layout supports redundancy of boot and system slot components and - provides a seperate user data partition.

+ v4.0.0 +

Immutable GPT A/B layout for rotational OTA updates, + boot/system redundancy, and a shared persistent data partition.

@@ -134,6 +133,165 @@

image-rota

Additional Documentation

+

Immutable root and mount points

+
+
+

This system uses an A/B, read‑only root with all writable state on a single persistent partition.

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Mount pointBackingTypeNotes

/

/dev/disk/by-slot/active/system

ext4

Read‑only system root (active slot A or B)

/boot/firmware

/dev/disk/by-slot/active/boot

vfat

Boot files (active slot A or B)

/bootfs

BOOTFS

vfat

Boot metadata

/persistent

PERSISTENT

ext4

Shared persistent storage

/var

/persistent/slots/<slot>/var

bind

Per‑slot runtime state (systemd, caches, etc.)

/home

/persistent/home

bind

User data shared across slots

/var/log/journal

/persistent/log/journal

bind

Single log directory used by both slots

+
+
    +
  • +

    Rationale (immutable root + A/B)

    +
    +
      +
    • +

      Supports delta/incremental OTA updates by treating root as a static image.

      +
    • +
    • +

      Reliable rollbacks: slot can be flipped if a new root fails health checks.

      +
    • +
    • +

      Reduced write amplification and storage wear, clearer separation of state.

      +
    • +
    • +

      Predictable per‑slot state in /var.

      +
    • +
    • +

      Shared /home for slot agnostic user storage.

      +
    • +
    • +

      Shared journalling: centralised point for device logging.

      +
    • +
    • +

      Preserves SBOM accuracy: executing software matches the manifest exactly.

      +
    • +
    • +

      Blocks on-device package installs, preventing SBOM drift.

      +
    • +
    • +

      Enables auditable, reproducible releases and stronger supply-chain assurances.

      +
    • +
    +
    +
  • +
  • +

    Logging

    +
    +
      +
    • +

      A single persistent journal directory at /persistent/log/journal stores logs from either slot.

      +
    • +
    • +

      Journaling is configured for endurance and reliability.

      +
    • +
    +
    +
  • +
  • +

    Machine Identity (systemd)

    +
    +
      +
    • +

      /etc/machine-id is synchronised early with /persistent/common/etc/machine-id using a oneshot unit.

      +
    • +
    +
    +
  • +
+
+
+ + + + + +
+
Warning
+
+
+

Slot partition GPT labels are mandatory to associate the immutable root with its matching persistent storage.

+
+
+
    +
  • +

    Root slots must have stable PARTLABELs: e.g. system_a and system_b.

    +
  • +
  • +

    At boot, a generator reads the root slot’s PARTLABEL to select /persistent/slots/<slot>/var.

    +
  • +
+
+
+

If slot GPT labels are missing/duplicated, /var binding will fail.

+
+
+

If slot GPT labels can’t be guaranteed, this layout is not suitable for your device.

+
+
+
+
+
+

Slot Selection (run-time)

@@ -227,6 +385,7 @@

Relationships

image-base device-base rpi-ab-slot-mapper + systemd-min
@@ -263,11 +422,11 @@

Configuration Variables

IGconf_image_boot_part_size - Boot partition size per slot. + Boot partition size per-slot. - 100% + 96M @@ -279,11 +438,11 @@

Configuration Variables

IGconf_image_system_part_size - System partition size per slot. + System partition size per-slot. - 100% + 512M @@ -295,11 +454,12 @@

Configuration Variables

IGconf_image_data_part_size - Data partition retained across rotations. + Writable storage partition retained across + slot rotations. - 256M + 1G @@ -329,7 +489,8 @@

Configuration Variables

IGconf_image_pmap Provisioning Map type for this image layout. All partitions will be provisioned unencrypted (clear). - System partitions will be provisioned encrypted (crypt). + System partitions will be provisioned encrypted (crypt). + System B will be provisioned encrypted (hybrid). Development only. diff --git a/docs/layer/index.html b/docs/layer/index.html index e5714aa2..79376d65 100644 --- a/docs/layer/index.html +++ b/docs/layer/index.html @@ -843,6 +843,10 @@

General

rpi-misc-utils- Raspberry Pi system utilities
+
+ rpi-splash-screen- Raspberry Pi fullscreen splash screen support with custom image configuration. +
+
rpi-user-credentials- Raspberry Pi base layer for local user admin. Creates a local account for user IGconf_device_user1, a home directory... @@ -867,9 +871,8 @@

Image

- image-rota- Rotational OTA and AB support with GPT. - This layout supports redundancy of boot and system slot components and - prov... + image-rota- Immutable GPT A/B layout for rotational OTA updates, + boot/system redundancy, and a shared persistent data partition.
diff --git a/docs/layer/systemd-min.html b/docs/layer/systemd-min.html index 43cd7925..63b721bb 100644 --- a/docs/layer/systemd-min.html +++ b/docs/layer/systemd-min.html @@ -138,6 +138,8 @@

Relationships

Required by:

+ image-rota + rpi-connect rpi-connect-lite diff --git a/image/gpt/ab_userdata/device/provisionmap-clear.json b/image/gpt/ab_userdata/device/provisionmap-clear.json index 184530e4..e65bba0b 100644 --- a/image/gpt/ab_userdata/device/provisionmap-clear.json +++ b/image/gpt/ab_userdata/device/provisionmap-clear.json @@ -75,7 +75,7 @@ { "partitions": [ { - "image": "data" + "image": "persistent" } ] } diff --git a/image/gpt/ab_userdata/device/provisionmap-crypt.json b/image/gpt/ab_userdata/device/provisionmap-crypt.json index 53dbf057..26589798 100644 --- a/image/gpt/ab_userdata/device/provisionmap-crypt.json +++ b/image/gpt/ab_userdata/device/provisionmap-crypt.json @@ -76,14 +76,12 @@ } ] } - } + }, + "partitions": [ + { + "image": "persistent" + } + ] } - }, - { - "partitions": [ - { - "image": "data" - } - ] } ] diff --git a/image/gpt/ab_userdata/device/provisionmap-hybrid.json b/image/gpt/ab_userdata/device/provisionmap-hybrid.json index cff93420..807b67eb 100644 --- a/image/gpt/ab_userdata/device/provisionmap-hybrid.json +++ b/image/gpt/ab_userdata/device/provisionmap-hybrid.json @@ -86,7 +86,7 @@ { "partitions": [ { - "image": "data" + "image": "persistent" } ] } diff --git a/image/gpt/ab_userdata/device/rootfs-overlay/data/.empty b/image/gpt/ab_userdata/device/rootfs-overlay/data/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/image/gpt/ab_userdata/device/rootfs-overlay/etc/systemd/system/machine-id-sync.service b/image/gpt/ab_userdata/device/rootfs-overlay/etc/systemd/system/machine-id-sync.service new file mode 100644 index 00000000..b1d3ba91 --- /dev/null +++ b/image/gpt/ab_userdata/device/rootfs-overlay/etc/systemd/system/machine-id-sync.service @@ -0,0 +1,15 @@ +[Unit] +Description=Sync machine-id to/from persistent storage +DefaultDependencies=no +Requires=persistent.mount +After=persistent.mount +Before=systemd-journald.service sysinit.target +ConditionPathExists=/persistent/common/etc +RequiresMountsFor=/persistent/common/etc/machine-id + +[Service] +Type=oneshot +ExecStart=/bin/sh -eu -c 'P=/persistent/common/etc/machine-id; R=/run/machine-id; if [ -s "$P" ]; then cat "$P" > "$R"; else /usr/bin/install -m0644 "$R" "$P"; fi' + +[Install] +WantedBy=sysinit.target diff --git a/image/gpt/ab_userdata/device/rootfs-overlay/usr/lib/systemd/system-generators/slot-perst-generator b/image/gpt/ab_userdata/device/rootfs-overlay/usr/lib/systemd/system-generators/slot-perst-generator new file mode 100755 index 00000000..06ca2c54 --- /dev/null +++ b/image/gpt/ab_userdata/device/rootfs-overlay/usr/lib/systemd/system-generators/slot-perst-generator @@ -0,0 +1,69 @@ +#!/bin/sh +# systemd generator to bind per-slot /var based on GPT PARTLABEL + +set -eu + +OUT_DIR="/run/systemd/generator" +PERSIST_LABEL="${PERSIST_LABEL:-PERSISTENT}" # label of the shared persistent partition + +mkdir -p "$OUT_DIR" + + +# 1) Determine current slot +SLOT="" +ROOT_DEV="$(findmnt -no SOURCE / || true)" +if [ -n "$ROOT_DEV" ]; then + PARTLABEL="$(blkid -s PARTLABEL -o value -- "$ROOT_DEV" 2>/dev/null || true)" + case "$(printf '%s' "$PARTLABEL" | tr '[:upper:]' '[:lower:]')" in + system_a) SLOT="system_a" ;; + system_b) SLOT="system_b" ;; + esac +fi + +# Optional fallback if label is missing/unexpected +: "${SLOT:=${SLOT_FALLBACK:-system_a}}" + +VAR_SRC="/persistent/slots/${SLOT}/var" + + +# 2) Emit persistent.mount (mount the shared persistent partition by label) +# +# Mount with: +# noatime: avoids atime writes entirely (better than relatime) +# lazytime: defer inode timestamps to memory, flushed with fsync/mtime changes / 24h. +# commit=60: batches journal commits (fewer writes, risk up to 60s metadata loss on power loss). +# errors=remount-ro: safer failure mode +cat >"$OUT_DIR/persistent.mount" <"$OUT_DIR/var.mount" <. { partition-type-uuid = L } - partition data { + partition persistent { in-partition-table = true - image = data.ext4 + image = persistent.ext4 partition-type-uuid = L } } @@ -106,13 +106,12 @@ image system.ext4 { exec-pre = " SYSTEM" } -image data.ext4 { - empty = true +image persistent.ext4 { ext4 { use-mke2fs = true - label = "USERDATA" + label = "PERSISTENT" extraargs = "" } - size = - mountpoint = "/data" + mountpoint = "/persistent" + size = } diff --git a/image/gpt/ab_userdata/image.adoc b/image/gpt/ab_userdata/image.adoc index a119400b..07248635 100644 --- a/image/gpt/ab_userdata/image.adoc +++ b/image/gpt/ab_userdata/image.adoc @@ -1,5 +1,55 @@ = image-rota +== Immutable root and mount points + +This system uses an A/B, read‑only root with all writable state on a single persistent partition. + +[cols="1,3,1,4", options="header"] +|=== +| Mount point | Backing | Type | Notes + +| / | /dev/disk/by-slot/active/system | ext4 | Read‑only system root (active slot A or B) +| /boot/firmware | /dev/disk/by-slot/active/boot | vfat | Boot files (active slot A or B) +| /bootfs | BOOTFS | vfat | Boot metadata +| /persistent | PERSISTENT | ext4 | Shared persistent storage + +| /var | /persistent/slots//var | bind | Per‑slot runtime state (systemd, caches, etc.) +| /home | /persistent/home | bind | User data shared across slots +| /var/log/journal | /persistent/log/journal | bind | Single log directory used by both slots +|=== + +* **Rationale (immutable root + A/B)** + ** Supports delta/incremental OTA updates by treating root as a static image. + ** Reliable rollbacks: slot can be flipped if a new root fails health checks. + ** Reduced write amplification and storage wear, clearer separation of state. + ** Predictable per‑slot state in `/var`. + ** Shared `/home` for slot agnostic user storage. + ** Shared journalling: centralised point for device logging. + ** Preserves SBOM accuracy: executing software matches the manifest exactly. + ** Blocks on-device package installs, preventing SBOM drift. + ** Enables auditable, reproducible releases and stronger supply-chain assurances. + +* **Logging** + ** A single persistent journal directory at `/persistent/log/journal` stores logs from either slot. + ** Journaling is configured for endurance and reliability. + +* **Machine Identity (systemd)** + ** `/etc/machine-id` is synchronised early with `/persistent/common/etc/machine-id` using a oneshot unit. + + +[WARNING] +==== +Slot partition GPT labels are mandatory to associate the immutable root with its matching persistent storage. + +- Root slots must have stable PARTLABELs: e.g. `system_a` and `system_b`. +- At boot, a generator reads the root slot’s `PARTLABEL` to select `/persistent/slots//var`. + +If slot GPT labels are missing/duplicated, `/var` binding will fail. + +If slot GPT labels can’t be guaranteed, this layout is not suitable for your device. +==== + + == Slot Selection (run-time) Handled entirely by layer `rpi-ab-slot-mapper`. diff --git a/image/gpt/ab_userdata/image.yaml b/image/gpt/ab_userdata/image.yaml index 732533e7..a8a29177 100644 --- a/image/gpt/ab_userdata/image.yaml +++ b/image/gpt/ab_userdata/image.yaml @@ -1,31 +1,31 @@ # METABEGIN # X-Env-Layer-Name: image-rota # X-Env-Layer-Category: image -# X-Env-Layer-Desc: Rotational OTA and AB support with GPT. -# This layout supports redundancy of boot and system slot components and -# provides a seperate user data partition. -# X-Env-Layer-Version: 3.1.0 -# X-Env-Layer-Requires: image-base,device-base,rpi-ab-slot-mapper +# X-Env-Layer-Desc: Immutable GPT A/B layout for rotational OTA updates, +# boot/system redundancy, and a shared persistent data partition. +# X-Env-Layer-Version: 4.0.0 +# X-Env-Layer-Requires: image-base,device-base,rpi-ab-slot-mapper,systemd-min # X-Env-Layer-Provides: image # # X-Env-VarRequires: IGconf_device_storage_type # # X-Env-VarPrefix: image # -# X-Env-Var-boot_part_size: 100% -# X-Env-Var-boot_part_size-Desc: Boot partition size per slot. +# X-Env-Var-boot_part_size: 96M +# X-Env-Var-boot_part_size-Desc: Boot partition size per-slot. # X-Env-Var-boot_part_size-Required: n # X-Env-Var-boot_part_size-Valid: size # X-Env-Var-boot_part_size-Set: y # -# X-Env-Var-system_part_size: 100% -# X-Env-Var-system_part_size-Desc: System partition size per slot. +# X-Env-Var-system_part_size: 512M +# X-Env-Var-system_part_size-Desc: System partition size per-slot. # X-Env-Var-system_part_size-Required: n # X-Env-Var-system_part_size-Valid: size # X-Env-Var-system_part_size-Set: y # -# X-Env-Var-data_part_size: 256M -# X-Env-Var-data_part_size-Desc: Data partition retained across rotations. +# X-Env-Var-data_part_size: 1G +# X-Env-Var-data_part_size-Desc: Writable storage partition retained across +# slot rotations. # X-Env-Var-data_part_size-Required: n # X-Env-Var-data_part_size-Valid: size # X-Env-Var-data_part_size-Set: y @@ -40,6 +40,7 @@ # X-Env-Var-pmap-Desc: Provisioning Map type for this image layout. # All partitions will be provisioned unencrypted (clear). # System partitions will be provisioned encrypted (crypt). +# System B will be provisioned encrypted (hybrid). Development only. # X-Env-Var-pmap-Required: n # X-Env-Var-pmap-Valid: clear,hybrid # X-Env-Var-pmap-Set: y diff --git a/image/gpt/ab_userdata/pre-image.sh b/image/gpt/ab_userdata/pre-image.sh index 4ea4a2b9..20e26a53 100755 --- a/image/gpt/ab_userdata/pre-image.sh +++ b/image/gpt/ab_userdata/pre-image.sh @@ -49,7 +49,7 @@ cat genimage.cfg.in | sed \ -e "s||$IGconf_image_suffix|g" \ -e "s||$IGconf_image_boot_part_size|g" \ -e "s||$IGconf_image_system_part_size|g" \ - -e "s||$IGconf_image_data_part_size|g" \ + -e "s||$IGconf_image_data_part_size|g" \ -e "s||$IGconf_device_sector_size|g" \ -e "s||'$(readlink -ef slot-post-process.sh)'|g" \ -e "s||$BOOT_LABEL|g" \ @@ -58,3 +58,118 @@ cat genimage.cfg.in | sed \ -e "s||$MKE2FS_ARGS_SYSTEM|g" \ -e "s||$MKE2FS_ARGS_DATA|g" \ > ${genimg_in}/genimage.cfg + + +# Create persistent skeleton - must match part names in genimage.cfg.in +mkdir -p ${fs}/persistent/slots/system_a/var +mkdir -p ${fs}/persistent/slots/system_b/var +mkdir -p ${fs}/persistent/common/etc + +install -d -m 1777 ${fs}/persistent/slots/system_a/var/tmp +install -d -m 1777 ${fs}/persistent/slots/system_b/var/tmp + + +# machine-id(5) +wants_dir="${fs}/etc/systemd/system/sysinit.target.wants" +units=( + "machine-id-sync.service" +) +install -d -m 0755 "${wants_dir}" +for unit in "${units[@]}"; do + [ -f "${fs}/etc/systemd/system/${unit}" ] || die "missing ${unit}" + chmod 0644 "${fs}/etc/systemd/system/${unit}" + ln -sf "../${unit}" "${wants_dir}/${unit}" +done + +# Ship an empty file, add entry for legacy dbus +rm -f "${fs}/etc/machine-id" +rm -f "${fs}/var/lib/dbus/machine-id" +install -m0644 -o root -g root /dev/null "${fs}/etc/machine-id" +ln -s /etc/machine-id "${fs}/var/lib/dbus/machine-id" + + +# /var is per-slot, so synchronise +rsync -aHAXS --numeric-ids --delete "${fs}/var/" "${fs}/persistent/slots/system_a/var/" +rsync -aHAXS --numeric-ids --delete "${fs}/var/" "${fs}/persistent/slots/system_b/var/" + + +# Journal is retained across slot rotations +install -d -m 2755 -o root -g systemd-journal "${fs}/persistent/log/journal" +# set preferences +# https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html +install -d -m 0755 ${fs}/etc/systemd/journald.conf.d +cat > ${fs}/etc/systemd/journald.conf.d/persistent.conf <<'EOF' +[Journal] +Storage=persistent + +# Reduce space +Compress=yes + +# Lower disk budget +SystemMaxUse=512M +SystemKeepFree=15% +SystemMaxFileSize=20M + +# Retention +MaxRetentionSec=2w + +# Endurance profile +RuntimeMaxUse=128M +SyncIntervalSec=2m +RateLimitInterval=30s +RateLimitBurst=2000 + +# Integrity +Seal=yes +EOF + + +# /home is bind mounted to /persistent/home and retained across slot rotations +# Mirror it +mkdir -p "${fs}/persistent/home/" +rsync -aHAXS --numeric-ids --delete "${fs}/home/" "${fs}/persistent/home/" + +# Reclaim /home +find "${fs}/home" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + + +# Reclaim /var but ensure skeleton exists for services that need PrivateTmp +# otherwise namespace setup will fail on the immutable root. +find "${fs}/var" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + +install -d -m 1777 ${fs}/var/tmp +install -d -m 0755 ${fs}/var/log +install -d -m 0755 ${fs}/var/cache +install -d -m 0755 ${fs}/var/spool + + +# Older systemd (eg 252/Bookworm) sets up per‑service mount namespaces by +# bind mounting a read‑only snapshot of / under /run/systemd/unit-root then +# mounting APIVFS and PrivateTmp bits (eg, /dev, /proc, /sys) inside that. +# systemd is not able to create these on an immutable root if they don't +# exist. Even if we create them, namespace init fails (status=226/NAMESPACE). +# New systemd (eg 257/Trixie) does not have this problem. Only workaround is +# to relax the sandboxing for these services. See: +# https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html +ver=$(systemd-major "$fs") +[[ "$ver" =~ ^[0-9]+$ ]] || { echo "systemd-major error for $fs" >&2; exit 1; } +if [[ "$ver" -lt 257 ]]; then + for svc in systemd-resolved systemd-timesyncd; do + d="${fs}/etc/systemd/system/${svc}.service.d" + mkdir -p "$d" + cat > "${d}/immutable-root.conf" <<'EOF' +[Service] +PrivateDevices=no +EOF + done +fi + + +# Perms for bind mounts on the immutable root +chmod 755 "${fs}/home" +chmod 755 "${fs}/var" + + +# Generate the persistent skeleton suitable for on-device overlay +tar --xattrs --xattrs-include='*' --acls --numeric-owner \ + -C "${fs}/persistent" \ + -czf "${genimg_in}/persistent-skel.tar.gz" \ + --exclude='lost+found' . diff --git a/image/gpt/ab_userdata/slot-post-process.sh b/image/gpt/ab_userdata/slot-post-process.sh index 86c732c3..2269aa64 100755 --- a/image/gpt/ab_userdata/slot-post-process.sh +++ b/image/gpt/ab_userdata/slot-post-process.sh @@ -9,10 +9,18 @@ echo "pre-process $IMAGEMOUNTPATH for $COMP" 1>&2 case $COMP in SYSTEM) cat << EOF > $IMAGEMOUNTPATH/etc/fstab -/dev/disk/by-slot/active/system / ext4 rw,relatime,errors=remount-ro,commit=30 0 1 +/dev/disk/by-slot/active/system / ext4 ro,relatime,commit=30 0 1 /dev/disk/by-slot/active/boot /boot/firmware vfat defaults,rw,noatime,nofail 0 2 -LABEL=USERDATA /data ext4 rw,relatime,nofail,commit=30 0 2 LABEL=BOOTFS /bootfs vfat defaults,rw,noatime,errors=panic 0 2 + +# Bespoke systemd generators mount /persistent, /var and bind mount into it +# for per-slot storage. See slot-perst-generator. + +#tmpfs /var/tmp tmpfs mode=1777,nosuid,nodev,size=256M 0 0 + +# home and journal persist across slots +/persistent/home /home none bind,x-systemd.requires-mounts-for=/persistent/home,x-systemd.after=persistent.mount 0 0 +/persistent/log/journal /var/log/journal none bind,x-systemd.requires-mounts-for=/persistent/log/journal,x-systemd.after=persistent.mount 0 0 EOF ;; BOOT)