Rebuilding a RAID-1 Array After ZimaOS Reinstall — The Safe, UI-Native Way
Tested on ZimaOS v1.6.1 running on a TerraMaster 4-bay (i3 / 32 GB RAM) with a 2-disk 14 TB RAID-1 array. The procedure applies to any ZimaOS install where you’ve reinstalled the OS and your data array is no longer recognized by the UI.
This writeup was compiled with the assistance of Claude (by Anthropic), based on a real migration I worked through step-by-step. Every command below was executed on a live ZimaOS system and verified end-to-end. I’m sharing it because the forum has several threads from people stuck in the same spot with no clear path forward.
The scenario
You reinstalled ZimaOS (system drive died, corrupted eMMC, upgrade went sideways, whatever). After reinstall, the data disks from your RAID-1 are visible in the Storage panel, but ZimaOS treats them as “unused.” The only button the UI offers you is “Create RAID,” which formats the disks and destroys your data.
This happens because ZimaOS stores pool/RAID configuration in /ZimaOS-HD/.casaos/db/local-storage.db on the system drive. When the system drive is lost, that database goes with it. The mdadm superblocks on the data disks themselves are perfectly intact — ZimaOS just doesn’t know they’re there.
IceWhale has an official “restore local-storage.db” procedure (Zimaspace docs) that works if you backed up that file before the reinstall. If you didn’t (most people didn’t), you’re where I was.
What this writeup is NOT
-
It’s not a “just use mdadm from the CLI and move on” guide. That works, but leaves the array invisible to the ZimaOS UI, which means no pool-aware features, brittle behavior across ZimaOS updates, and a setup that’s confusing to maintain long-term. I wanted the array managed natively by ZimaOS, not held together by a systemd unit I’d have to remember existed.
-
It’s not risk-free. We physically destroy the old array partway through. If your backup drive fails at the wrong moment, you lose data. Read all the way through before starting.
-
It’s not for everyone. If you can’t get an offload drive large enough to hold all your RAID data, stop reading and open a support ticket with IceWhale — they can sometimes edit
local-storage.dbmanually to re-adopt an existing array.
Why this path
Given the choice between:
-
CLI-mount the array and live with it outside the UI
-
Pay IceWhale support to edit
local-storage.dbmanually -
Offload data, destroy the old array, rebuild via UI, restore data
…I chose option 3. Reasons:
-
The result is a completely clean, UI-managed RAID indistinguishable from one created on a fresh install
-
No hidden state to trip me up during future updates
-
It’s fully documented here so anyone can reproduce it
-
The work is proportional to “days of wall-clock time, mostly unattended” rather than “wizardry”
Prerequisites
-
A single external drive larger than the used space on your RAID (not larger than the RAID itself — just larger than the data you actually have on it)
-
SSH access enabled (Settings → Developer → Enable SSH in the ZimaOS UI)
-
Enough free time to let two multi-hour copies finish without interruption
-
A stable power situation (UPS if you have one — don’t start this during a thunderstorm)
Assumptions in this writeup
-
Your RAID data disks are
/dev/sdaand/dev/sdb(adjust to your setup) -
Your offload drive shows up as
/dev/sdd(adjust to your setup) -
You’re logged in as root (
sudo -iif not) -
You have ~2 TB of data on the array — adjust time estimates proportionally
Phase 0 — Verify RAID superblocks are intact (read-only, zero risk)
Before committing to anything, confirm the disks actually still have a recoverable RAID. Identify your disks:
bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL
Your RAID disks should show linux_raid_member in the FSTYPE column. Note which physical disks those are by size/model/serial — be absolutely certain which devices are the RAID and which are system/offload. Every destructive command below references specific /dev/sdX paths; a typo here is how people lose data.
Then examine the superblocks:
bash
mdadm --examine /dev/sda
mdadm --examine /dev/sdb
What to check on both outputs:
-
Magic : a92b4efc -
Raid Level : raid1 -
Array UUIDmatches on both disks (critical) -
Eventscounter roughly equal on both (large gap means one disk fell out of sync earlier) -
State : clean -
Array State : AA(both active)
If all that looks good, your RAID metadata is healthy and this procedure will work. If the Array UUID values differ or one disk shows .. / A. state, stop here and open a support ticket — you’re in a more complicated recovery scenario.
Phase 1 — Prepare the offload drive
Plug in the external drive and identify it:
bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL
TRIPLE-CHECK which device is the new offload drive before running any of the following. If ZimaOS auto-mounted it:
bash
umount /dev/sdd1 2>/dev/null
umount /dev/sdd 2>/dev/null
Wipe any existing filesystem signatures:
bash
wipefs -a /dev/sdd
Format as ext4 (not exFAT, not NTFS — those don’t preserve Linux permissions, which matters if your RAID contains AppData, Docker volumes, or anything with meaningful ownership):
bash
mkfs.ext4 -L offload /dev/sdd
Why ext4 and not exFAT
A lot of external drives come preformatted exFAT for cross-platform use. Tempting to leave as-is. Don’t. ExFAT doesn’t preserve Linux UIDs/GIDs, file modes, or symlinks. If you restore from an exFAT backup, every file in AppData that Docker cares about needs its ownership manually reset, every symlink is broken, every permission bit is wrong. ext4 preserves all of it transparently.
Note: ZimaOS’s /mnt is read-only
ZimaOS uses an immutable system partition with an overlay. /mnt is mounted read-only and you can’t create directories there. All scratch mount points go under /DATA (which is the writable part of the system SSD):
bash
mkdir -p /DATA/offload
mount /dev/sdd /DATA/offload
df -h /DATA/offload # confirm it mounted
Phase 2 — Assemble the RAID read-only and verify data
This is the most important safety property of the whole procedure: we assemble the array read-only so nothing can accidentally modify it before we’ve offloaded the data.
bash
mdadm --assemble --readonly /dev/md127 /dev/sda /dev/sdb
Expected: mdadm: /dev/md127 has been started with 2 drives.
bash
cat /proc/mdstat
You want [UU] (both mirrors active) and State : clean.
Make a mount point and mount the array read-only as a second layer of protection:
bash
mkdir -p /DATA/oldraid
mount -o ro /dev/md127 /DATA/oldraid
Verify your data:
bash
df -h /DATA/oldraid
ls -la /DATA/oldraid
du -sh /DATA/oldraid/*
The du will take several minutes on a large array — let it finish. Add up the folder sizes and confirm it’s less than the free space on your offload drive. Confirm every top-level folder you expect is present. This is your only easy visual check that the array is intact before we start the destructive part.
Phase 3 — Offload to the external drive
Use tmux (or screen) so your copy survives an SSH disconnect:
bash
tmux new -s copy
Inside the tmux session, fire the rsync:
bash
rsync -aHAX --info=progress2 \
--exclude='.DS_Store' \
/DATA/oldraid/ \
/DATA/offload/
Flag explanations
-
-a— archive mode: recursive, preserves symlinks, permissions, timestamps, ownership, groups -
-H— preserves hardlinks (important for Docker overlay filesystems inside AppData) -
-A— preserves ACLs -
-X— preserves extended attributes -
--info=progress2— single-line progress instead of one line per file -
--exclude='.DS_Store'— optional; drops macOS metadata files if you have any
Detach from tmux without killing rsync: Ctrl+B then D.
Check progress without attaching:
bash
tmux capture-pane -pt copy | tail -5
df -h /DATA/offload # used column should be growing
Expected speed on USB 3: ~100–130 MB/s sustained, slower through folders with many small files, faster through large media files. A ~2 TB copy typically finishes in 5–6 hours.
Phase 3.5 — Verify the offload before destroying the source
This is the checkpoint that earns its keep. Do not skip it.
bash
find /DATA/oldraid -xdev -type f | wc -l
find /DATA/offload -xdev -type f | wc -l
The two numbers should match within a handful (the .DS_Store excludes plus ext4’s lost+found directory on the destination account for tiny differences).
Also verify folder sizes match:
bash
du -sh /DATA/oldraid/* > /tmp/source-sizes.txt
du -sh /DATA/offload/* > /tmp/dest-sizes.txt
diff /tmp/source-sizes.txt /tmp/dest-sizes.txt
Small differences in directory-entry sizes are normal; file content sizes should be identical.
If file counts don’t match or you see missing folders, STOP. Do not proceed to Phase 4. Re-run rsync to pick up whatever was missed, then re-verify.
Phase 4 — Destroy the old array (point of no return)
Read this section twice before running any commands. After Phase 4, your only copy of the data is on the offload drive. If that drive fails or gets unplugged during Phase 6, data is lost.
Confirm one more time:
-
Offload drive is physically secure and plugged in
-
No Docker containers are actively writing to
/DATA/oldraid -
You have uninterrupted time ahead of you
Unmount the RAID:
bash
umount /DATA/oldraid
Stop the array:
bash
mdadm --stop /dev/md127
Zero the mdadm superblocks — this is the irreversible step:
bash
mdadm --zero-superblock /dev/sda /dev/sdb
Verify:
bash
mdadm --examine /dev/sda
mdadm --examine /dev/sdb
lsblk -o NAME,SIZE,TYPE,FSTYPE /dev/sda /dev/sdb
Expected: “No md superblock detected” on both, and empty FSTYPE column. The disks now look factory-fresh to ZimaOS.
Phase 5 — Create the new RAID via the ZimaOS UI
This is the whole point of the procedure — do the RAID creation the native, supported way.
In the ZimaOS web UI:
-
Settings → Storage — confirm both data disks now appear as unused/available
-
Click Create RAID (or the current equivalent — the wording shifts between versions)
-
Select RAID 1 (Safe)
-
Select both data disks
-
Pick filesystem (ZimaOS 1.6.x defaulted mine to btrfs — that’s fine; btrfs adds data checksumming and snapshots, which are legitimately useful. ext4 may also be available depending on your version.)
-
Name the pool (e.g.,
Storage) and confirm
ZimaOS will start an initial sync (for RAID-1: byte-for-byte mirror from disk 0 to disk 1). For a 14 TB mirror, expect ~18–30 hours. The array is usable during sync — it just isn’t fully redundant yet, and sync runs slower if you’re actively writing to it.
Verify the pool is mounted and healthy:
bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE
cat /proc/mdstat
df -h | grep -E "(md|Storage)"
You should see the new md0 (or similar) device mounted at /media/<poolname> (and bind-mounted to /var/lib/casaos_data/.media/<poolname> for Docker access).
Phase 6 — Restore data from the offload drive
Start a new tmux session:
bash
tmux new -s restore
Run rsync from the offload drive to the new pool:
bash
rsync -aHAX --info=progress2 \
/DATA/offload/ \
/media/Storage/
(Adjust the destination to match your actual pool path.)
Detach with Ctrl+B then D. Expect 5–6 hours for ~2 TB; somewhat slower than Phase 3 if the initial RAID sync is still running (I/O contention).
Phase 7 — Verify the restore
Same verification pattern as Phase 3.5:
bash
find /DATA/offload -xdev -type f | wc -l
find /media/Storage -xdev -type f | wc -l
du -sh /media/Storage/*
ps aux | grep rsync | grep -v grep # should return nothing
Expectations:
-
File counts should match within 1–2 (the new pool will have its own
.zimaos_storage.jsonmarker file, which accounts for the +1) -
Per-folder sizes should match the offload drive exactly
-
No rsync processes still running
Check /media/Storage in the Files app via the ZimaOS web UI. Browse into your folders. Confirm you can read files natively through the UI.
Phase 8 — Cleanup (wait until initial sync is 100% complete)
Do not unmount the offload drive until the initial RAID sync hits 100%. Until then, you don’t have full mirror redundancy, and the offload drive is still your insurance policy.
Check sync status:
bash
grep -E "resync|recovery" /proc/mdstat && echo "STILL SYNCING" || echo "SYNC COMPLETE"
Or the full view:
bash
cat /proc/mdstat
When sync is complete (no resync = line, just [UU]), unmount the offload drive:
bash
umount /DATA/offload
lsblk /dev/sdd # confirm MOUNTPOINT is empty
What to do with the offload drive
My recommendation: keep it as-is for at least 30 days as a cold backup. Label it with the date and contents. If no missing-file issues surface in a month of normal use, reformat and put it back into regular service.
Prevent this next time — back up local-storage.db
The reason this procedure takes a day instead of 15 minutes is that local-storage.db wasn’t backed up before the reinstall. Fix that now:
bash
mkdir -p /media/Storage/Backup/zimaos-config
cp /DATA/.casaos/db/local-storage.db \
/media/Storage/Backup/zimaos-config/local-storage.db.$(date +%Y%m%d)
Schedule it weekly via cron, or include it in whatever backup workflow you use. If you ever need to reinstall again, you’ll have it and can use IceWhale’s official one-click recovery instead of this whole procedure.
Summary of key decisions and why
| Decision | Why |
|---|---|
| Don’t CLI-mount permanently | Leaves array invisible to UI, fragile across updates |
| Don’t ask IceWhale to edit the db | Only works if they’re available; undocumented |
| Read-only assemble + read-only mount | Two layers of protection for source data during offload |
| ext4 on offload drive (not exFAT) | Preserves Linux permissions, ownership, symlinks |
| Verify file counts before destroying source | Cheap check that rsync didn’t silently miss anything |
| Create new RAID via UI, not CLI mdadm | Produces a fully UI-managed, update-safe result |
| Keep offload drive 30 days post-migration | Catches problems that only surface with rarely-accessed files |
Closing note
This procedure is conservative by design. Every destructive step is preceded by verification. Every command was written to have a clear, recoverable state if interrupted. That said: do not run it half-asleep or half-distracted. The one real failure mode is a typo on a device path. Paste carefully, read before pressing enter, and confirm device identity at each step.
Good luck. If you hit a step where your output doesn’t match what’s described here, stop and ask in the forum rather than improvising — most ZimaOS RAID data loss I’ve seen on the forum came from people improvising under pressure.
Compiled with the assistance of Claude (Anthropic). The commands and sequence were developed and verified during a real migration on ZimaOS v1.6.1. If you spot an error or have a suggestion to improve the procedure, reply to the thread — I’d rather update this than have someone hit a snag that could be prevented.