image-rota

image v5.5.0

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

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 point Backing Type Notes

/

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

erofs or ext4

ro System root (active slot A or B)

/boot/firmware

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

vfat

ro Boot files (active slot A or B)

/bootfs

BOOTFS

vfat

rw Boot metadata

/persistent

PERSISTENT

ext4

rw Shared persistent storage

/home

/persistent/home

bind

User data shared across slots

/var

/persistent/slots/<slot>/var

bind

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

/var/log/journal

/persistent/log/journal

bind

Single log directory used by both slots

<slot-shared paths>

/persistent/shared/<path>

bind

Application data shared across slots (layer-declared, see Slot-shared application data)

  • 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-shared application data

Any layer can declare filesystem paths whose application data should be shared across slots. At boot, each declared path is bind-mounted from a common location on the persistent partition so the same data is visible regardless of which slot is active.

How it works

  1. slot-shared-generator runs at early boot, reads every /etc/rpi-image-gen/slot-shared.d/*.conf file and emits a .mount systemd unit for each declared path, mounting /persistent/shared/<path> over <path> as a bind mount.

  2. persistent-shared-init.service runs before the bind mounts activate. It creates any missing /persistent/shared/<path> directories on the persistent partition, handling first boot and disaster-recovery scenarios where the partition has been re-initialised.

  3. Bind mounts are guarded by ConditionPathIsDirectory on the source. If the directory is absent the mount is skipped and the service falls back to its local path - safe degradation with no boot failure.

Declaring a shared path

Drop a .conf file into /etc/rpi-image-gen/slot-shared.d/, eg via the layer’s rootfs overlay or script:

Version=1
Path=/var/lib/myapp
Path=/etc/myapp/state
Version=1

Required. A generator that does not recognise the version skips the file with a warning rather than misprocessing it.

Path=

One or more absolute paths to share. Multiple Path= entries are supported in a single file. A missing leading / is corrected automatically. Only directory paths are supported.

Unknown keys are silently ignored, providing forward compatibility within version 1. A version increment signals a breaking change. The files are inert in non-AB configurations.

Persistent partition layout

Shared data is stored under /persistent/shared/, mirroring the full path hierarchy:

/persistent/
└── shared/
    └── var/
        └── lib/
            └── myapp/    <-- bind-mounted over /var/lib/myapp

Because the immutable root retains a copy of each declared path, files baked into the image are pushed into the shared location by rpi-persistent-shared-init, before the bind mounts activate. Files are only pushed if missing from or different to the destination. This means an OTA update can deliver new or updated shared application data simply by including it in the rootfs - no separate data partition update is required.

Slot Selection (run-time)

Handled entirely by layer rpi-ab-slot-mapper.

Slot Pairing (build-time)

Definition

This layer generates an image which has bit-for-bit identical A/B slot members:

  • Both a.boot and b.boot contain the exact same vfat filesystem.

  • Both a.system and b.system contain the exact same ext4 filesystem.

  • Filesystem UUIDs are intentionally identical across slots.

  • GPT partition GUIDs (PARTUUID) are unique per partition.

  • GPT partition labels (PARTLABEL) are unique per partition.

Rationale and benefits

  • Faster image creation: only a single slot pair is produced.

  • Simpler updates: only a single slot pair is built, tested, and distributed.

  • Update generation: operating on a single slot pair is unambiguous.

  • Smaller CI/storage footprint: fewer images to store.

Requirements and constraints

  • Does not use UUID= anywhere. Duplicate filesystem UUIDs make /dev/disk/by-uuid/* ambiguous.

  • Uses stable identifiers instead, e.g. /dev/disk/by-slot/{active,other}/{boot,system}.

Encrypted (LUKS) Provisioning Map layouts

  • Identical content inside LUKS is supported. Slot mapping can use GPT labels or static map triplets to identify mapper:<name>:<part> slot components.

Relationships

Depends on:

image-base device-base rpi-ab-slot-mapper systemd-min

Provides: image

Configuration Variables

References: IGconf_device_storage_type, IGconf_device_class

Declares (prefix: image):

Variable Description Default Validation Policy
IGconf_image_boot_part_size Boot partition size per-slot. 96M Size value with optional unit (bytes, k/m/g/s) or percentage immediate
IGconf_image_system_part_size System partition size per-slot. 512M Size value with optional unit (bytes, k/m/g/s) or percentage immediate
IGconf_image_data_part_size Writable data partition retained across slot rotations. 1G Size value with optional unit (bytes, k/m/g/s) or percentage immediate
IGconf_image_rootfs_type Root filesystem type for each system partition. erofs Must be one of: ext4, erofs immediate
IGconf_image_assetdir Image specific asset directory ${DIRECTORY} Non-empty string value immediate
IGconf_image_pmap Provisioning Map type for this image layout. clear: All partitions will be provisioned unencrypted. crypt: All non-boot partitions will be provisioned encrypted. cryptslots: Only system OS partitions will be provisioned encrypted. cryptdata: Only the data partition will be provisioned encrypted. clear Must be one of: clear, crypt, cryptslots, cryptdata immediate
IGconf_image_ptable_protect Enable eMMC Power-On Write Protect of the partition table. If enabled, the first 8MB of the boot storage device will be protected. Applies to eMMC only. n Boolean value - accepts: true/false, 1/0, yes/no, y/n (case insensitive) lazy
IGconf_image_compression Compression scheme used for update payloads. zstd Non-empty string value immediate

mmdebstrap

Packages

Installs:

Attributes

File: gpt/ab_userdata/image.yaml

Type: static