[SOLVED] SD mirror & ZRAM in openHABian: Test-bench findings and empirical fixes for live Raw Copy and Sync SD

**TL;DR**

The issues documented in this thread are now solved. See the final post for some final considerations.


Summary

  • On my live production setup with ZRAM active, running Raw Copy SD (openhabian-config 54) and Sync SD (openhabian-config 55) consistently fails to generate a bootable SD card with fully updated /var/log and persistence directories.
  • I set up a dedicated test-bench to systematically investigate this behaviour and reproduce the failure modes under controlled conditions.
  • I identified several reproducible cases in Raw Copy SD and in Sync SD in the backup.bash script. I developed tweaks that, on my setup, ensure that both Raw Copy and Sync SD produce a bootable and up-to-date mirror SD.
  • Disclaimer1: To analyse the script workflow and reason through potential fixes, I leveraged AI tools alongside standard debugging. I have manually verified and tested on my physical test-bench, and cross-checked against system logs every tweak.
  • Disclaimer2: Huge respect to the openHABian maintainers for such a solid project, that I always used. What follows are customised tweaks tested in my specific hardware/environment—they work for me, but I do not know if they are needed or recommended for a standard setup.

Background

I have been trying using Raw Copy SD (openhabian-config menu 54) and Sync SD (menu 55) for several years, but never managed to get a consistently working cloned SD on my setup. Initially, I suspected hardware issues— I bought new endurance SD cards, replaced the SD card reader and upgraded from a Pi 4 4Gb to a Pi 5 8Gb —before deciding to systematically debug the process.

The symptoms I experienced align with reports scattered across the forum:

  • The cloned SD card fails to boot due to a missing or empty PARTUUID in cmdline.txt and /etc/fstab.
  • When the cloned card does boot, persistence data and logs are outdated. In my setup, this is particularly critical since key runtime states rely on MapDB persistence, requiring manual restoration from backups.

Because my production system has been otherwise extremely stable thanks to ZRAM and to an uninterruptible power supply (UPS), investigating the impossibility to obtain a reliable mirror SD on my system was a low priority task. However, given that SD mirroring works reliably for other users, I finally set up a dedicated test-bench (the old Raspberry Pi 4 with 4GB RAM) to try isolating the reasons why it fails under my specific configuration.

Key Observations from Test-Bench Runs

  1. PARTUUID Assignment Failures (Issue 1):
    • Raw copy intermittently fails to produce a bootable SD because set-partuuid fails to mount the target partitions and therefore cannot assign a new partuuid.

    • The automated backup drive setup configured via backupdrive= in openhabian.conf during initial installation consistently fails to set the PARTUUID on my hardware.

    • Workaround: Manually running set-partuuid followed by the other commands defined in mirror_SD() always produce a bootable SD.

  2. ZRAM & Data Sync Inconsistencies (Issues 2-4):
    • Even when the cloned/synced SD successfully boots, ZRAM fails to initialise on the very first boot (Issue 2), requiring a second reboot to start properly (this is possibly related to github issue 142 as I discovered at the time of this posting).

    • Even when ZRAM properly start, live overlay directories (/var/log and /var/lib/openhab/persistence) contain outdated files both after Raw Copy (Issue 3) and after Sync SD (Issue 4).

Issue 1: Missing PARTUUID on Cloned SD

To analyze why the cloned SD fails to receive a valid PARTUUID, I tested the Raw Copy mechanism under two distinct scenarios.

Test 1: Automated Raw Copy at First Boot

To establish a fully reproducible baseline, I started by testing the initial unattended setup driven by openhabian.conf.

Hardware & Environment

  • SBC: Raspberry pi 4 with 4Gb RAM — validated once on production Pi 5 (8GB RAM)
  • Image: openhabian-pi-raspios64 (Release date: February 8, 2026) obtained through Raspberry pi Imager
  • OS: Raspberry Pi OS 13 (Trixie, 64-bit)
  • Internal SD (Source): Transcend High Endurance 16Gb
  • External SD (Target): Samsung PRO Endurance 32Gb
  • USB SD Reader: Prolific Technology, Inc. USB SD Card Reader
  • Configuration: backupdrive=/dev/sda set in openhabian.conf prior to first boot

Execution & Results

I performed this test 5 times in total (4 runs on the Pi 4 test-bench and 1 run on the Pi 5 production system which uses a different SD reader). In 100% of the runs, the first-boot installation failed to produce a bootable mirror SD. Below is the relevant section extracted from first_boot.log :

... (previous sections removed) ...

14892924928 bytes (15 GB, 14 GiB) copied, 1596 s, 9.3 MB/s
14905507840 bytes (15 GB, 14 GiB) copied, 1597 s, 9.3 MB/s
14226+0 records in
14226+0 records out
14917042176 bytes (15 GB, 14 GiB) copied, 1640.98 s, 9.1 MB/s
+ return 0
+ sfdisk -d /dev/mmcblk0
+ grep -q '^/dev/mmcblk0p3'
++ blkid /dev/mmcblk0p2
++ sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p'
++ sed -e s/-02//g
+ origPartUUID=ae8c47c2
++ yes
++ set-partuuid /dev/sda2 random
++ awk '/^PARTUUID/ { print substr($7,1,length($7) - 3) }'
mount: /tmp/set-partuuid-mnt: cannot mount; probably corrupted filesystem on /dev/sda2.
       dmesg(1) may have more information after failed mount system call.
yes: standard output: Broken pipe
+ partUUID=
+ cond_redirect e2fsck -f -y /dev/sda2
+ [[ -n '' ]]
+ echo -e '\n\033[90;01m$ e2fsck -f -y /dev/sda2 \033[39;49;00m'

e[90;01m$ e2fsck -f -y /dev/sda2 e[39;49;00m
+ e2fsck -f -y /dev/sda2
e2fsck 1.47.2 (1-Jan-2025)
rootfs: recovering journal
JBD2: Invalid checksum recovering data block 172 in log
JBD2: Invalid checksum recovering data block 524288 in log

... (truncated section) ...

rootfs: ***** FILE SYSTEM WAS MODIFIED *****
rootfs: 119492/879872 files (0.2% non-contiguous), 1488027/3641856 blocks
+ return 1
+ echo 'FAILED (e2fsck)'
FAILED (e2fsck)
+ dirty=yes
+ cond_redirect tune2fs /dev/sda2 -U random
+ [[ -n '' ]]
+ echo -e '\n\033[90;01m$ tune2fs /dev/sda2 -U random'

tune2fs /dev/sda2 -U random
+ tune2fs /dev/sda2 -U random
tune2fs 1.47.2 (1-Jan-2025)
+ return 0
+ umount -q /dev/sda1
+ mount /dev/sda1 /storage/syncmount
+ sed -i 's|ae8c47c2||g' /storage/syncmount/cmdline.txt
+ umount /storage/syncmount
+ umount -q /dev/sda2
+ mount /dev/sda2 /storage/syncmount
+ sed -i 's|ae8c47c2||g' /storage/syncmount/etc/fstab
+ [[ -f /storage/syncmount/etc/systemd/system//storage.mount ]]
+ sed -i 's|^What=.*|What=/dev/mmcblk0p3|g' /storage/syncmount/etc/systemd/system//storage.mount
+ umount /storage/syncmount
+ cond_redirect fsck -y -t ext4 /dev/sda2
+ [[ -n '' ]]
+ echo -e '\n\033[90;01m$ fsck -y -t ext4 /dev/sda2 \033[39;49;00m'

fsck -y -t ext4 /dev/sda2
+ fsck -y -t ext4 /dev/sda2
fsck from util-linux 2.41
e2fsck 1.47.2 (1-Jan-2025)
rootfs: clean, 119492/879872 files, 1488027/3641856 blocks
+ return 0

Key Finding

Inspecting the cloned target SD card confirms that PARTUUID was left empty in both /boot/firmware/cmdline.txt and /etc/fstab. As a direct result, the cloned SD fails to boot.

Test 2: Manual Raw Copy on a live system

Next, I tested manual execution via openhabian-config (menu 54: Raw Copy SD) under different system load conditions—ranging from a fresh, idle install to my full production environment.

1. Fresh / Idle System (Test-Bench Pi 4)

  • Setup: Immediately after the initial installation, with openHAB installed but sitting idle (no bindings or items configured).
  • Result: Menu 54 successfully set the PARTUUID and created a bootable SD card.
  • Repetition: I repeated the cycle (fresh install followed by menu 54) multiple times. While the first-boot automated copy consistently failed (as reported in Test 1), manually triggering menu 54 on an idle system reliably configured PARTUUID. I have not performed this test on the production system.

2. Moderately Loaded System (Test-Bench Pi 4)

  • Setup: I installed a few active bindings (Astro, Fine Offset Weather Station, MQTT, LG webOS) and configured 5 Things and 15 Items with RRD4J persistence.
  • Result: Menu 54 continued to properly assign PARTUUID and produce bootable mirrored SD cards.

3. Full Production System (Pi 5, 8GB RAM)

  • Setup: openHAB 5.2.0 (32GB internal SD and 64GB target SD). Active load: 20 bindings, 85 Things, 572 Items, 72 rules, and HABApp running 4 custom Python rules.
  • Result: Under these live production conditions, menu 54 consistently failed to assign PARTUUID, leaving the target SD unbootable.

Conclusion & Observation

On my hardware, menu 54 (Raw Copy) succeeds on lightweight or idle setups, but systematically fails on a fully loaded production system. Maybe, this points toward a timing issue, device busy state, or lock race condition when set-partuuid attempts to mount and modify the target block devices while openHAB and system services are actively writing to disk.

Manual Recovery & Tweak 1: Fixing the PARTUUID in Raw Copy

Manual Workaround (Step-by-Step Procedure)

When PARTUUID assignment fails during a Raw Copy, the cloned SD can be made bootable manually without re-running the full image copy. On my setup, issuing the standard commands sequence extracted from mirror_SD() resolves the issue (in my system the card reader is in /dev/sda):

# 1. Assign a new random PARTUUID to the root partition
sudo set-partuuid /dev/sda2 random

# 2. Update /cmdline.txt with the new PARTUUID
sudo mount /dev/sda1 /storage/syncmount
sudo nano /storage/syncmount/cmdline.txt
sudo umount /storage/syncmount

# 3. Update /etc/fstab on the root partition
sudo mount /dev/sda2 /storage/syncmount
sudo nano /storage/syncmount/etc/fstab
sudo umount /storage/syncmount

# 4. Final filesystem check and UUID reset
sudo tune2fs /dev/sda2 -U random
sudo fsck -y -t ext4 /dev/sda2

Tweak for mirror_SD() (Raw Copy)

Technical details

The intermittent failure of set-partuuid appears to be caused by I/O buffering and device lock state immediately following dd:

  • dd streams raw blocks to the target disk without forcing an immediate synchronous flush (the oflag=sync is not set). The Linux kernel caches these block writes in RAM.
  • If set-partuuid (or underlying block utilities) runs immediately after dd, it attempts to read or lock filesystem superblocks while disk flush operations are still in progress and before udev has re-evaluated the partition table.
  • Under heavy I/O loads (such as in my production system, and possibly during first-boot package installations), this race condition is amplified, causing set-partuuid to fail when attempting to mount or inspect /dev/sdb2. On an idle system, memory buffers flush much faster, hiding the race condition.

Code Patch

To ensure disk buffers are fully flushed and partition nodes are settled before set-partuuid is invoked, I inserted a cache flush, a udev event barrier.
Moreover I anticipated the explicit filesystem check (e2fsck) that in mirror_SD() was performed after set-partuuid :

# Ensure kernel write buffers are fully flushed to disk
sync

# Block until udev has processed all pending block device events
udevadm settle

# Force filesystem check to resolve dirty journal flags post-dd
if ! cond_redirect e2fsck -f -y "${dest}2"; then echo "FAILED (e2fsck)"; dirty="yes"; fi

# Assign random PARTUUID
if ! partUUID="$(yes | set-partuuid "${dest}2" random | awk '/^PARTUUID/ { print substr($7,1,length($7) - 3) }')"; then echo "FAILED (set random PARTUUID)"; dirty="yes"; fi

Verification

  • Production System (Pi 5, 8GB): With these additions, running menu 54 (Raw Copy SD) has maintained a 100% success rate across all subsequent tests.
  • First-Boot Note: I attempted to inject this fix into the base image pre-boot to re-test unattended installation, but the script is refreshed during execution. Consequently, first-boot verification remains untested.

Issue 2: ZRAM Initialisation Failure on First Boot of Cloned SD

Even when PARTUUID is properly set and the cloned SD boots successfully, ZRAM devices fail to initialize on the initial boot.

Symptom Analysis

Inspecting /usr/local/share/zram-config/log/zram-config.log reveals that zram-config skips the creation of all ZRAM devices:

	2026-08-11T11:19:17+0100 | WARN | main: Entry swap              /zram1          zram-config1 already exists as a zram device, skipping recreation of device.
	2026-08-11T11:19:17+0100 | WARN | main: Entry dir               /zram2          /var/lib/openhab/persistence            /persistence.bind already exists as a zram device, skipping recreation of device.
	2026-08-11T11:19:17+0100 | WARN | main: Entry log               /zram3          /var/log                /log.bind already exists as a zram device, skipping recreation of device.
	2026-08-11T11:19:17+0100 | INFO | serviceConfiguration: Restarting services that interfere with zram device configuration.

Checking active mounts confirms that ZRAM overlay mounts are not active.

Root Cause

This behaviour is driven by the presence of the file /usr/local/lib/zram-config/zram-device-list in the cloned card.

  1. When zram-config starts, it reads /etc/ztab.
  2. If an entry is already registered inside zram-device-list (which was Raw Copied from the live system by dd), zram-config assumes the devices are already provisioned and aborts setup (skipping creation of mount points, zram device allocation, and OverlayFS setups).
  3. Under normal operational cycles, zram-device-list is deleted when zram-config stop executes during system shutdown. However, a raw dd clone captures the system state while ZRAM is running, preserving this file on the target image.

Workaround & Resolution

  • Current Behaviour: A second reboot of the cloned SD card clears the file (since zram-config stop runs during the reboot), allowing ZRAM to start correctly on subsequent boots.
  • Tweak: Removing /usr/local/lib/zram-config/zram-device-list from the target filesystem immediately after the Raw Copy step allows ZRAM to initialise seamlessly on the very first boot of the cloned drive.

Issue 3: Stale State of /var/log and Persistence Data on Cloned SD

Even when the cloned SD card successfully boots and initialises ZRAM, the contents of /var/log and /var/lib/openhab/persistence revert to the state captured at the last system shutdown or reboot prior to running Raw Copy.

This behaviour occurs because dd operates directly at the physical block device level (/dev/mmcblk0), capturing only the underlying SD card storage (the lowerdir of OverlayFS) and completely bypassing live data held in RAM/ZRAM (the upperdir).

Architecture of ZRAM Overlay System

When zram-config initialises at boot:

  1. It creates bind mounts to the physical SD card directories (/opt/zram/log.bind and /opt/zram/persistence.bind), which serve as the static lower layer (lowerdir*).
  2. It provisions ZRAM devices and constructs an OverlayFS mount. From this point onward, /var/log and /var/lib/openhab/persistence write directly to volatile RAM (upper layer).
  3. Live changes to existing files are kept strictly within the volatile upper layer (ZRAM) and are not flushed to the underlying SD card until zram-config stop or zram-config sync executes. (Note: During testing I observed that newly created files (e.g. via touch) in the upper layer do appear on the cloned SD card, whereas subsequent modifications or appends to existing files remain isolated in RAM and are lost during a Raw Copy)
  4. When zram-config stop executes (e.g., during a graceful shutdown prior to reboot), all changes accumulated in the volatile upper layer are merged back into the base directories on the SD card. Once unmounted, the temporary /opt/zram/*.bind directory trees and files (such as /usr/local/lib/zram-config/zram-device-list) are explicitly removed by zram-config to clean up the filesystem state.

Empirical Verification Test

To systematically prove this behaviour, I conducted the following test sequence on a live system with ZRAM active:

Step 1: Create initial files and reboot to persist them to SD

echo "Content_before_rawcopy" | sudo tee -a /var/log/logfile_test.txt
echo "Content_before_rawcopy" | sudo tee -a  /var/lib/openhab/persistence/persistence_test.txt

# Reboot forces zram-config stop, flushing data to physical SD card
sudo reboot

Step 2: Append data to live ZRAM overlays after the main SD reboot

echo "Content_after_reboot" | sudo tee -a /var/log/logfile_test.txt
echo "Content_after_reboot" | sudo tee -a  /var/lib/openhab/persistence/persistence_test.txt

# Verify live ZRAM reflects the update, while physical lowerdir remains un-synced
echo "=== Live ZRAM content upperdir (updated) ==="
cat /var/log/logfile_test.txt
cat /var/lib/openhab/persistence/persistence_test.txt

echo "=== Physical SD card lowerdir (stale) ==="
cat /opt/zram/log.bind/logfile_test.txt
cat /opt/zram/persistence.bind/persistence_test.txt

Step 3: Execute Raw Copy and inspect offline target SD

# Execute Raw Copy (openhabian-config menu 54); do not reboot yet

# Mount root partition of the target clone (/dev/sda2)
sudo mount /dev/sda2 /storage/syncmount

echo "=== Target SD card content (stale) ==="
cat /storage/syncmount/var/log/logfile_test.txt
cat /storage/syncmount/var/lib/openhab/persistence/persistence_test.txt

echo "=== Target SD mount points are empty"
ls /storage/syncmount/opt/zram/log.bind
ls /storage/syncmount/opt/zram/persistence.bind

sudo umount /storage/syncmount

Step 4: Boot cloned SD and verify

# Check if PARTUUID has been properly set and fix it if required
# Clear /usr/local/lib/zram-config/zram-device-list on target or double-reboot.
# verify that ZRAM is active with df -h | grep zram

echo "=== Target system overlay content (stale) ==="
cat /var/log/logfile_test.txt
cat /var/lib/openhab/persistence/persistence_test.txt

echo "Target system lowerdir content (stale)"
cat /opt/zram/log.bind/logfile_test.txt
cat /opt/zram/persistence.bind/persistence_test.txt

Conclusion

A live dd Raw Copy captures solely the stale filesystem state present on the physical SD card. Any active runtime updates in /var/log or MapDB/rrd4j persistence stored inside volatile ZRAM overlays are lost on the cloned target.
A possible workaround would be to reboot right before Raw Copy. Another possibility would be to perform a Sync SD (Menu 55) step but here comes issue 4.

Issue 4 : Sync SD (Menu 55) does not update log and persistence

Behaviour of mirror_SD (SD Sync)

An inspection of mirror_SD() inside backup.bash shows that the Sync SD process executes three distinct rsync passes from the live internal system to the target SD card:

  1. Base Filesystem Pass: Copies the primary filesystem (excluding active mount points).
  2. Persistence Pass: Copies live /var/lib/openhab/persistence/ to ${syncMount}/opt/zram/persistence.bind/.
  3. Logs Pass: Copies live /var/log/ to ${syncMount}/opt/zram/log.bind/.

Analysis of Failure

  1. Inconsistent Target Paths:
    Synchronising live RAM data directly into ${syncMount}/opt/zram/*.bind/ renders the synced data inaccessible upon booting the target SD card. At boot time, zram-config initialises by reading the base system directories (/var/log and /var/lib/openhab/persistence) on the root filesystem as its lowerdir. It does not populate or read from /opt/zram/*.bind until after the overlay filesystem is constructed. Consequently, the target system boots using the data stored in /var/log and /var/lib/openhab/persistence on the physical SD card.
  2. Re-ransfer of temporary ZRAM files blocking first ZRAM start:
    The base rsync pass copies /usr/local/lib/zram-config/zram-device-list to the target card. As established earlier, this locks ZRAM from initialising on the target card’s first boot, requiring a manual file deletion or a second reboot.

Empirical Verification

I performed a test sequence identical to the one used for Raw Copy, substituting Raw Copy with Sync SD (openhabian-config menu 55).

Without modifications, the synced SD card first boots without ZRAM. Afterwards, or at first boot if the temporary ZRAM file is removed, it consistently boots into a stale state containing only log and persistence records from the last graceful shutdown of the primary system, completely ignoring the live data copied during the rsync run.

Tweak of mirror_SD() (SD sync)

By redirecting the second and third rsync targets directly to the base root filesystem paths on the target SD (/var/lib/openhab/persistence/ and /var/log/), the live ZRAM data is correctly written to the location where zram-config expects its lowerdir at boot. Additionally, explicitly removing zram-device-list ensures ZRAM starts on the first boot.

# Corrected rsync targets to update the target base filesystem directly
cond_redirect rsync --one-file-system --delete -aKh "/var/lib/openhab/persistence/" "${syncMount}/var/lib/openhab/persistence/"

cond_redirect rsync --one-file-system --delete -aKh "/var/log/" "${syncMount}/var/log/"

# Ensure ZRAM state file is removed on the target drive    
cond_redirect rm -f "${syncMount}/usr/local/lib/zram-config/zram-device-list"

Results

After applying these modifications to mirror_SD(), running Sync SD (menu 55) reliably yields a bootable backup SD with fully updated logs and persistence states updated at the exact time the sync was triggered.

Conclusion & Final thoughts

To be completely transparent, some of the technical explanations and proposed script modifications were developed with the assistance of Gemini AI. Every single tweak has been repeatedly tested and validated on my physical test-bench and on my production system. As I do not consider myself a raspberry kernel or openHABian core expert, these changes should be treated as empirical workarounds rather than production-ready Pull Requests—they warrant review by the core maintainers before being considered for the official repository.

Based on my findings I think that every user experiencing difficulties with mirrored SD can make its SD fully functional even without touching the openhabian scripts (i.e. by manually fixing the partuuid of cloned SD, by performing a reboot before rsync and by double booting the cloned SD).

There remain a couple of limitations in my testing.

  • Firstly, I was unable to verify the Raw Copy PARTUUID tweak under automated first-boot conditions on a fresh image, as first-boot installation scripts are pulled dynamically during setup.
  • Secondly, I did not test the theoretical risk of data corruption in persistence files if rsync or dd reads them while openHAB is actively writing to disk. I have not encountered corruptions on my systems, but hot mirroring always carries this inherent race condition. Guaranteeing absolute transactional consistency would require stopping openHAB prior to syncing, as is standard practice with openhab-cli backup --full. (Note: At the time of this posting, while looking for keywords in the forum, I found this recent post by @ehorvat1 that deals exactly with this specific topic. It seems to me that Issue #2 here may be the root cause of github issue 142).

That being said, applying these small adjustments on scripts in my setups (both the Pi 4 test-bench and the Pi 5 production system) has finally yielded fully functional, bootable mirrored SD cards with up-to-date log and persistence states, compatible with the automatic SD mirroring, without any need of manual fix.

Let me conclude by stating that over the last 10 years of using openHAB and openHABian, I’ve benefited immensely from the outstanding work of the developers and the support of this community, which together have helped me build a rock-solid automation system: I am very grateful for this. I hope sharing these findings can give a little something back and serve as a useful reference for others facing similar SD mirroring edge cases.

Thank you for this post,
I have also experienced similar issues on SD card sync. Until today I did blame it on my modifications (in /etc/fstab) after original OpenHabian setup. So I have a reason to stay more indoors at current extreme temperatures testing your findings on my OpenHab system.

Did you modify file /opt/openhabian/functions/backup.bash ? Just inserting your tweak code at end of mirror_SD() function (On my system this would be after line 462 …)?

I tweaked the backup.bash code in two places

  • after line 420 (for the raw copy part) adding the two commands and swapping e2fsck and set-partuuid lines
  • lines 456-457 (for the sync sd part) modifiying the two rsync targets and adding the rm command

Thanks for the thorough write-up and test bench work — this kind of empirical report is exactly what makes these issues fixable. I’ve gone through each finding against the openHABian code and the zram-config source. Results:

Sync SD target paths (your issue 4): confirmed, and it’s actually worse than you described. zram-config bind-mounts the target directory (e.g. /var/log) onto the .bind path and uses it as the overlay lowerdir, so the on-disk lower data lives at the plain path itself — /opt/zram/log.bind on the raw filesystem is just an empty mountpoint. Data rsynced there is permanently shadowed once the clone boots. On top of that, because /var/log and the persistence directory are overlay mounts on the running system, the main rsync’s --one-file-system + --delete combination actively empties those directories on the clone. So a synced card didn’t just carry stale logs/persistence — it carried none. Fixed by retargeting the two rsyncs to ${syncMount}/var/log/ and ${syncMount}/var/lib/openhab/persistence/, essentially as you proposed.

Stale zram-device-list (your issue 2): confirmed. The device list lives on the root filesystem, so both raw copies and syncs carried it over, and zram-config’s start logic then skips device creation on the clone’s first boot (“skipping recreation of device”). The mirror scripts now delete /usr/local/lib/zram-config/zram-device-list on the target in both the raw and sync paths.

PARTUUID race (your issue 1): plausible, hardening added. I couldn’t reproduce it deterministically, but running blkid/fdisk/mount immediately after two multi-GB dd writes is a textbook window for stale buffers and unsettled udev events, which matches your “intermittent under load” observation. sync and udevadm settle now run between the dd calls and set-partuuid. (The e2fsck you suggested already runs right after the UUID assignment, so I left that as is.)

Raw copy missing live ZRAM data (your issue 3): correct, but by design. dd reads the block device, and ZRAM upper-layer data only exists in RAM. We deliberately don’t force a zram-config sync before the copy because remounting the /var/log overlay mid-operation can break running log writers (this is the same reason the nightly zsync timer is currently disabled). With the sync-path fix above, the daily rsync now carries the live view of logs and persistence, so a mirror card is at most a day behind — the docs now state this explicitly.

Fixes are in openHABian PR, tracked in issue #2156. Testing on your bench setup before/after would be very welcome.

Thanks for your rapid answer. I checked the new version of openhabian-config with the modified backup.bash script.

  • I can confirm that Issue 4 : Sync SD (Menu 55) failure is now fixed.

  • Also Issue 2 : ZRAM initialisation failure is fixed. I added a suggestion comment on ZRAM Issue #142: some fix for the start after an anomalous shutdown could be useful in general:

    1. it may be useful to harden the zram-config start by checking if the devices in the device list temporary files are actually present and, if not, continue the start sequence of zram by ignoring the file.

    2. it may be also useful to add in the README.md Known Issues section of your github ZRAM page a warning of this anomalous behaviour, something like

       ## ZRAM devices won't start properly after a power failure
       
      Please pay attention that when ZRAM is installed, in case of power failure not only the RAM contents will be lost, as the system had no time to write them on the SD, but also the system at next reboot won't have ZRAM properly initialised. A second reboot will be needed.
      
  • I agree that Issue 3: stale log and persistence after Raw Copy is by design: the improvement of the docs is a good clarification on this. This is not a big issue given that SD sync now works reliably.

  • Concerning Issue 1 : intermittently missing partUUID your proposed hardening is not sufficient on my two systems. Moreover, I still cannot do all tests.

    1. a raw Copy on my production system with the up-to-date openhabian-config tools (i.e. with the addition of sync and udevm settle only) still fail to set the partUUID.
    2. raw Copy on the test-bench succeeds (but it also worked earlier)
    3. I still cannot test first-boot installation on the test-bench. For some reason, during first installation the openhabian-config does update itself but not to its most up to date version.

I am doing more tests on Issue 1, by swapping SD readers between the test-bench and the production system, and by checking if modifying usb-storage.quirks may improve reliability for my SD readers. I will report back later.

(EDIT: I also added a comment in the open openhabian github issue #2156)

Hi @ecdye,

Regarding Issue 1 (PARTUUID race & failure): I have isolated and deterministically reproduced why sync and udevadm settle alone are not sufficient on any active system, and why e2fsck must run strictly before set-partuuid (as empirically observed in my initial report).

The Mechanism of Failure

set-partuuid internally performs a temporary mount of the target root partition (/tmp/set-partuuid-mnt) to read/update boot files.

When a raw dd clone is performed on a live system, the operation takes anywhere from 10 to 30 minutes depending on card size and bus speed. Even with ZRAM handling /var/log and persistence, standard OS background activity (e.g., systemd timers, network state changes, apt caches, or auxiliary services) will write to the root filesystem during that window. As a result, the target ext4 journal (JBD2) is captured in an inconsistent/dirty state.

When set-partuuid immediately attempts to mount the partition immediately post-dd:

  1. The kernel attempts JBD2 journal replay and encounters block checksum mismatches.

  2. The kernel aborts the mount with:

    JBD2: journal recovery failed
    EXT4-fs (sda2): error loading journal
    mount: cannot mount; probably corrupted filesystem on /dev/sda2
    
  3. Because mount fails inside set-partuuid, the script cannot set the random UUID and aborts.

  4. Running e2fsck after set-partuuid is too late because the UUID assignment step has already failed.


Empirical Verification on Test Bench

To prove this deterministically, I focused on the Pi 4 test-bench system (which never fails raw-copy when idle). Once the target SD card was partitioned, I replicated only the dd step on /dev/sda2.

I set up a synthetic workload generator simulating periodic root filesystem writes (appending files every 60s) and ran a diagnostic script immediately after dd finished.

1. Baseline Run (Idle system during dd)

  • dumpe2fs reported clean status.
  • Direct mount succeeded without errors.

2. Loaded Run (Active filesystem writes during dd)

  • Immediate direct mount failed:

    [23224.494237] JBD2: journal recovery failed
    [23224.494255] EXT4-fs (sda2): error loading journal
    mount: /mnt/diag_check_mount: cannot mount; probably corrupted filesystem on /dev/sda2
    
  • Running e2fsck -fy immediately recovered the JBD2 journal and repaired allocation bitmaps.

  • Subsequent mount succeeded cleanly.


Proposed Sequence in mirror_SD()

To ensure deterministic behaviour under live system conditions, e2fsck needs to precede set-partuuid:

# 1. Flush kernel write buffers & settle udev
sync
udevadm settle

# 2. Repair dirty/inconsistent ext4 journal resulting from live dd
if ! cond_redirect e2fsck -f -y "${dest}2"; then echo "FAILED (e2fsck)"; dirty="yes"; fi

# 3. Assign random PARTUUID (mount now succeeds reliably)
if ! partUUID="$(yes | set-partuuid "${dest}2" random | awk '/^PARTUUID/ { print substr($7,1,length($7) - 3) }')"; then echo "FAILED (set random PARTUUID)"; dirty="yes"; fi

With this order (sync → udevadm settle → e2fsck → set-partuuid), live Raw Copy succeeds on both idle test bench and production setups.

Appendix: Test Scripts

  1. Workload Generator (simulate_write_activity.sh)
#!/usr/bin/env bash
# Simulation: 1 new file every minute + append timestamp on all previously created files
TEST_DIR="/var/tmp/oh_test_churn"
mkdir -p "$TEST_DIR"

cleanup() {
    echo -e "\n[*] Stopping filesystem load simulator..."
    exit 0
}
trap cleanup INT TERM

echo "[*] Starting filesystem load simulator in $TEST_DIR (interval: 60s)..."
count=1

while true; do
    NEW_FILE=$(printf "$TEST_DIR/test_%03d.log" "$count")
    NOW=$(date '+%Y-%m-%d %H:%M:%S')

    # 1. Create new file with initial timestamp
    echo "$NOW [INFO] Init log file #$count" > "$NEW_FILE"
    sync -d "$NEW_FILE" 2>/dev/null || true

    # 2. Append timestamp on ALL files created so far
    for f in "$TEST_DIR"/test_*.log; do
        if [ -f "$f" ]; then
            echo "$NOW [UPDATE] Status update event" >> "$f"
            sync -d "$f" 2>/dev/null || true
        fi
    done

    echo "[+] Cycle #$count completed on $NOW ($(ls -1 "$TEST_DIR"/test_*.log | wc -l) updated files)."
    count=$((count + 1))
    sleep 60
done

2. Diagnostic Script (analyze_clone.sh)

#!/usr/bin/env bash
# analyze_clone.sh - Post-clone diagnostic &  Markdown report generation
set -u

DEVICE="${1:-/dev/sda2}"
OUTPUT_MD="${2:-clone_diagnostic_report.md}"
MNT_TEST="/mnt/diag_check_mount"

if [ "$(id -u)" -ne 0 ]; then
    echo "Error: script must run as sudo." >&2
    exit 1
fi

mkdir -p "$MNT_TEST"

exec > "$OUTPUT_MD"

cat <<EOF
# Diagnostic Report on Filesystem Integrity Post-DD
- **Test Date/Time:** $(date '+%Y-%m-%d %H:%M:%S %Z')
- **Target Device:** \`$DEVICE\`
- **Kernel / Release:** \`$(uname -r)\` / \`$(cat /etc/debian_version 2>/dev/null || true)\`

---

## 1. Superblock state(\`dumpe2fs -h\`)

\`\`\`plaintext
$(dumpe2fs -h "$DEVICE" 2>&1)
\`\`\`

---

## 2. Bitmap & Allocations inspection (\`debugfs -R "stats"\`)

\`\`\`plaintext
$(debugfs -R "stats" "$DEVICE" 2>&1)
\`\`\`

---

## 3. Journal JBD2 Transaction dump (\`debugfs -c -R "logdump -c"\`)

\`\`\`plaintext
$(debugfs -c -R "logdump -c" "$DEVICE" 2>&1 | head -n 45)
\`\`\`

---

## 4. Dry-Run structural integrity check (\`e2fsck -nvf\`)

\`\`\`plaintext
$(e2fsck -nvf "$DEVICE" 2>&1 || true)
\`\`\`

---

## 5. Direct Mount test (without pre-emptive e2fsck )

EOF

MARK_TIME=$(date '+%Y-%m-%d %H:%M:%S')
sleep 0.5

echo "### Output of command \`mount\`"
echo '```plaintext'
if mount -t ext4 "$DEVICE" "$MNT_TEST" 2>&1; then
    echo "RESULT: Mount Successful (there may be further corruption though)."
    umount "$MNT_TEST"
else
    echo "RESULT: Mount failed."
fi
echo '```'

echo ""
echo "###  Kernel Log (\`dmesg\`) during mount"
echo '```plaintext'
DMESG_OUT=$(dmesg --since "$MARK_TIME" 2>/dev/null || dmesg | tail -n 25)
echo "$DMESG_OUT" | grep -iE "ext4|jbd2|sda2|buffer" || echo "No new messagge recorded in dmesg."
echo '```'

cat <<EOF

---

## 6. Repair with \`e2fsck -fy\` e& Post-Fix verification

### Corrections applied by \`e2fsck -fy\`
\`\`\`plaintext
$(e2fsck -fy "$DEVICE" 2>&1 || true)
\`\`\`

### Second attempt post-repair
\`\`\`plaintext
EOF

if mount -t ext4 "$DEVICE" "$MNT_TEST" 2>&1; then
    echo "RESULT: Mount SUCCESSFUL after e2fsck."
    umount "$MNT_TEST"
else
    echo "RESULT: Mount STILL FAILED."
fi

cat <<EOF
\`\`\`

---
EOF

sync
echo "Report generated in: $OUTPUT_MD" >&2

Thanks a lot for these insights and improvements. I updated with openhabian-config, is this enough? Or do I have to set up SD mirroring again by disabling and enabling it?

At present the SD Sync part have been corrected in the repository, therefore you should be ok, with no need to reconfigure anything.

On the other hand, the raw copy part has not been completely fixed yet. You have to check manually if your mirrored SD has a partuuid correctly set. Or you can simply try booting the cloned SD card and verify if it works

@Lionello_Marrelli

great work on this

Does the analyze clone script do this or can you guide on a way to confirm its configuration.

The analyze clone script does not check of the status of the SD mirror.

By combining the commands of the first post, a simple bash script can be written to check the presence of PARTUUID.

Script check_partuuid.sh(extracted from a Gemini AI generated script) , to be run as root with sudo.

bash
#!/usr/bin/env bash

# parameters to be adapted to the user situation
TARGET_DEV="/dev/sdb2"
MOUNT_POINT="/mnt/usb2"

# check if device is present
if [ -z "$TARGET_DEV" ]; then
    echo "CRITICAL: Backup SD not detected"
    exit 0
fi

mkdir -p "$MOUNT_POINT"

# check if fstab file in partition2 contains a valid partuuid

FSTAB_FILE="${MOUNT_POINT}/etc/fstab"
PARTUUID_OK=0

if ! mount "$TARGET_DEV" "$MOUNT_POINT" ; then
  echo "Error mounting SD mirror"
  exit 0
else
  echo "SD mirror partition ${TARGET_DEV} mounted on ${MOUNT_POINT}"
fi

if [ -f "$FSTAB_FILE" ]; then
    if grep -E 'PARTUUID=[a-f0-9-]+[[:space:]]+/[[:space:]]+ext4' "$FSTAB_FILE" >/dev/null 2>&1; then
        PARTUUID_OK=1
    fi
fi

umount "$MOUNT_POINT"

if [ "$PARTUUID_OK" -ne 1 ]; then
    echo "CRITICAL: PARTUUID missing or corrupted in ${TARGET_DEV}:/etc/fstab"
    exit 0
else
    echo "PARTUUID OK"
fi

Depending on what kind of persistence you use, you can write a similar script to check how old the persistence files on the SD are. I used gemini AI to write such a script.

I have opened this [PR]( perform e2fsck before set-partuuid by lionhe1966 · Pull Request #2165 · openhab/openhabian · GitHub ) with the fix of the empty PARTUUID after rawcopy.

EDIT. The PR has been merged.

Final Considerations.

The issues that affected raw copy and SD sync and has been documented in this thread has been solved thanks to two accepted PRs (see posts #4 and #11).

I think it is worth concluding by reiterating what is already stated in the documentation.

Raw copy and SD sync are inherently not as reliable as a cold, offline backup (openhabian-cli backup issue a warning when called without stopping openhab). Because mirroring operates continuously on a live system with actively running services, some open files (such as logs or active persistence databases) can end up in an inconsistent state during the copy process depending on write frequency.

This is an unavoidable trade-off of any live synchronization: attempting to prevent all potential inconsistencies by stopping services during sync would completely defeat the purpose of background mirroring, effectively turning it into a scheduled maintenance downtime.

Thanks for your efforts in analyzing and contributing, Lionello.

On this final comment however I believe you’re mistaken.

Actually, an SD mirroring run only happens A) initially when you set it up and B) repeats every some months thereafter. Way more important is what happens meanwhile as a diff sync every some hours or daily.
That diff sync now is not based on raw copy dd but on filesystem-level rsync which does take file system activity into account.
Yes there could be rare cases with syncing open files like persistence storage and logs that might corrupt when being written to and synced at the same time, but that’s pretty unlikely and actually corrupted persistence data never have been the issue in this context.

I think the key issue (which you now fixed - thanks) was that the set-partuuid part and as a consequence booting from the backup SD didn’t work at times because the mount didn’t work after an SD copy without prior fsck.

Now that that succeeds, I’m convinced rsync will work with openHAB running live.