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.bashscript. 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
PARTUUIDincmdline.txtand/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
PARTUUIDAssignment Failures (Issue 1):-
Raw copy intermittently fails to produce a bootable SD because
set-partuuidfails to mount the target partitions and therefore cannot assign a new partuuid. -
The automated backup drive setup configured via
backupdrive=inopenhabian.confduring initial installation consistently fails to set thePARTUUIDon my hardware. -
Workaround: Manually running
set-partuuidfollowed by the other commands defined inmirror_SD()always produce a bootable SD.
-
- 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/logand/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/sdaset inopenhabian.confprior 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
PARTUUIDand 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
PARTUUIDand 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:
ddstreams raw blocks to the target disk without forcing an immediate synchronous flush (theoflag=syncis not set). The Linux kernel caches these block writes in RAM.- If
set-partuuid(or underlying block utilities) runs immediately afterdd, it attempts to read or lock filesystem superblocks while disk flush operations are still in progress and beforeudevhas 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-partuuidto 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.
- When
zram-configstarts, it reads/etc/ztab. - If an entry is already registered inside
zram-device-list(which was Raw Copied from the live system bydd),zram-configassumes the devices are already provisioned and aborts setup (skipping creation of mount points, zram device allocation, and OverlayFS setups). - Under normal operational cycles,
zram-device-listis deleted whenzram-config stopexecutes during system shutdown. However, a rawddclone 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 stopruns during the reboot), allowing ZRAM to start correctly on subsequent boots. - Tweak: Removing
/usr/local/lib/zram-config/zram-device-listfrom 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:
- It creates bind mounts to the physical SD card directories (
/opt/zram/log.bindand/opt/zram/persistence.bind), which serve as the static lower layer (lowerdir*). - It provisions ZRAM devices and constructs an OverlayFS mount. From this point onward,
/var/logand/var/lib/openhab/persistencewrite directly to volatile RAM (upper layer). - 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 stoporzram-config syncexecutes. (Note: During testing I observed that newly created files (e.g. viatouch) 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) - When
zram-config stopexecutes (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/*.binddirectory trees and files (such as/usr/local/lib/zram-config/zram-device-list) are explicitly removed byzram-configto 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:
- Base Filesystem Pass: Copies the primary filesystem (excluding active mount points).
- Persistence Pass: Copies live
/var/lib/openhab/persistence/to${syncMount}/opt/zram/persistence.bind/. - Logs Pass: Copies live
/var/log/to${syncMount}/opt/zram/log.bind/.
Analysis of Failure
- 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-configinitialises by reading the base system directories (/var/logand/var/lib/openhab/persistence) on the root filesystem as its lowerdir. It does not populate or read from/opt/zram/*.binduntil after the overlay filesystem is constructed. Consequently, the target system boots using the data stored in/var/logand/var/lib/openhab/persistenceon the physical SD card. - Re-ransfer of temporary ZRAM files blocking first ZRAM start:
The basersyncpass copies/usr/local/lib/zram-config/zram-device-listto 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
PARTUUIDtweak 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
rsyncorddreads 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 withopenhab-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.