Storage service creates a duplicate raids row in local-storage.db after unclean shutdown — pool appears twice as read-only/obsolete although the RAID6 array is healthy
Summary
After every unclean shutdown (power cut), zimaos-local-storage inserts a
second row for the same RAID pool into /DATA/.casaos/db/local-storage.db
(table raids): identical uuid and mount_point, but empty status.
The web UI then renders the pool twice, and the ghost copy shows as
read-only / obsolete — even though the underlying md array and btrfs
mount are completely healthy (rw, all disks up).
Deleting the duplicate row and restarting zimaos-local-storage.service
fixes the UI immediately. The bug returns on the next unclean shutdown.
Verified on ZimaOS 1.7.1-beta2, x86_64.
Environment
- ZimaOS 1.7.1-beta2 (x86_64)
- Pool ZeroByte:
/dev/md0, RAID6, 4 disks, btrfs — the affected one
- Pool JBOD:
/dev/md1, RAID0, 2 disks, btrfs — never affected
- The affected pool uses the md write-intent bitmap (visible in
/proc/mdstat)
- Trigger: unclean shutdown / power loss, then boot
Steps to reproduce
- Have a RAID6 pool (btrfs) mounted.
- Cut power / unclean shutdown while the pool is mounted.
- Boot the machine.
- Open the Storage panel → the pool appears twice; the extra entry shows
a read-only / obsolete state. The Files UI also lists the pool twice.
- Inspect the DB:
SELECT id,name,uuid,mount_point,status FROM raids ORDER BY id;
Actual state captured right after the 2026-09-16 power cut
(2026-09-17, before manual cleanup):
id name uuid mount_point status
2 JBOD 0a26d655:7aa36efc:2a76863a:f32e0bd0 /media/JBOD ok
3 ZeroByte aea3561d:5822a58b:4d32ccfa:e27970d4 /media/ZeroByte ok
4 ZeroByte aea3561d:5822a58b:4d32ccfa:e27970d4 /media/ZeroByte (empty)
Duplicate check:
uuid c
aea3561d:5822a58b:4d32ccfa:e27970d4 2
Note the second row: same uuid, same mount_point, empty status.
Expected behavior
On the boot-time storage scan, the service should reconcile pools by
uuid: update the existing row (and recompute its real status) instead of
blind-inserting a second row for a pool that is already registered. A pool
reappearing after an unclean shutdown must never duplicate in raids.
Actual behavior
- A second
raids row is inserted for the same uuid → UI shows the pool
twice and falls back to a defensive "read-only" presentation for the ghost.
- The filesystem is NOT read-only at any point:
mount shows
/dev/md0 on /media/ZeroByte type btrfs (rw,...), /proc/mdstat shows
[4/4] [UUUU]. Only the DB row is wrong.
Why it only affects the RAID6 pool (analysis)
The RAID0 pool (md1) assembles instantly and never enters a non-clean
state (no parity, no bitmap, no resync). The RAID6 pool (md0) does:
after a power cut it assembles "non-clean" and replays its write-intent
bitmap. Kernel log from the affected boot (2026-09-16 22:15):
Sep 16 22:15:33 kernel: md/raid:md127: raid level 6 active with 4 out of 4 devices
Sep 16 22:15:33 kernel: md127: detected capacity change from 0 to 7813529600
Sep 16 22:15:40 kernel: md127: detected capacity change from 7813529600 to 0
Sep 16 22:15:40 kernel: md: md127 stopped.
Sep 16 22:15:40 kernel: md: md0 stopped.
Sep 16 22:15:41 kernel: md/raid:md0: raid level 6 active with 4 out of 4 devices
Sep 16 22:15:41 kernel: md0: detected capacity change from 0 to 7813529600
The array is assembled as md127, stopped, and re-assembled as md0
within ~8 seconds. If the storage service's boot scan runs inside that
window, the pool briefly disappears / reappears with a different md node
name — plausibly the moment it treats the existing pool as a new one and
inserts the duplicate row.
Supporting service log (GetRaids failing to read df after boot):
Sep 16 22:24:21 zimaos-local-storage[1995]: error error when getting df info
{"error": "exit status 1", "func": "route.(*LocalStorage).GetRaids.func5",
"file": ".../route/raid.go", "line": 452}
Historical evidence: the local-storage.db.preclean.* backups taken before
each manual fix (Sep 10 and Sep 17 incidents) always contain exactly one
JBOD row (status ok) and the ZeroByte duplicate only in the snapshots
taken after an unclean shutdown. In the Sep 10 incident, both ZeroByte
rows had an empty status.
Workaround that works (for users, until this is fixed)
# 1) Backup
sudo cp /DATA/.casaos/db/local-storage.db \
/DATA/.casaos/db/local-storage.db.preclean.$(date +%s)
# 2) Delete duplicate rows (keep the oldest per uuid) and fix empty status
sudo sqlite3 /DATA/.casaos/db/local-storage.db \
"DELETE FROM raids WHERE id NOT IN (SELECT MIN(id) FROM raids GROUP BY uuid);
UPDATE raids SET status='ok' WHERE status IS NULL OR status='';"
# 3) Restart the service that owns this DB (NOT the whole CasaOS stack)
sudo systemctl restart zimaos-local-storage.service
Reload the web UI ~10 s later: duplicate entry and the read-only label are
gone.
Things that do not work (don't waste time like I did):
sudo systemctl restart casaos.service — unit does not exist
sudo systemctl restart zimaos.service — restarts everything, same rows
- UI "Restart CasaOS" — restarts the frontend, the daemon re-reads the same DB
btrfs property set /media/<pool> ro false — the FS was never read-only
Suggested fix directions
- On the boot scan, upsert by
uuid (e.g. INSERT ... ON CONFLICT(uuid) DO UPDATE) instead of inserting; or delete-then-insert keyed by uuid.
- Delay the initial storage scan until md arrays are fully assembled and
stable (e.g. systemd ordering after mdadm/btrfs mounts, or re-check that
the pool still exists before inserting).
- Never persist an empty
status: compute it from the real md state at
insert time.
- Consider a migration adding
UNIQUE(uuid, mount_point) on raids so the
duplicate state becomes impossible.
Happy to provide more logs or DB dumps if useful.
Storage service creates a duplicate
raidsrow inlocal-storage.dbafter unclean shutdown — pool appears twice as read-only/obsolete although the RAID6 array is healthySummary
After every unclean shutdown (power cut),
zimaos-local-storageinserts asecond row for the same RAID pool into
/DATA/.casaos/db/local-storage.db(table
raids): identicaluuidandmount_point, but emptystatus.The web UI then renders the pool twice, and the ghost copy shows as
read-only / obsolete — even though the underlying md array and btrfs
mount are completely healthy (
rw, all disks up).Deleting the duplicate row and restarting
zimaos-local-storage.servicefixes the UI immediately. The bug returns on the next unclean shutdown.
Verified on ZimaOS 1.7.1-beta2, x86_64.
Environment
/dev/md0, RAID6, 4 disks, btrfs — the affected one/dev/md1, RAID0, 2 disks, btrfs — never affected/proc/mdstat)Steps to reproduce
a read-only / obsolete state. The Files UI also lists the pool twice.
Actual state captured right after the 2026-09-16 power cut
(2026-09-17, before manual cleanup):
Duplicate check:
Note the second row: same uuid, same mount_point, empty
status.Expected behavior
On the boot-time storage scan, the service should reconcile pools by
uuid: update the existing row (and recompute its real status) instead ofblind-inserting a second row for a pool that is already registered. A pool
reappearing after an unclean shutdown must never duplicate in
raids.Actual behavior
raidsrow is inserted for the same uuid → UI shows the pooltwice and falls back to a defensive "read-only" presentation for the ghost.
mountshows/dev/md0 on /media/ZeroByte type btrfs (rw,...),/proc/mdstatshows[4/4] [UUUU]. Only the DB row is wrong.Why it only affects the RAID6 pool (analysis)
The RAID0 pool (
md1) assembles instantly and never enters a non-cleanstate (no parity, no bitmap, no resync). The RAID6 pool (
md0) does:after a power cut it assembles "non-clean" and replays its write-intent
bitmap. Kernel log from the affected boot (2026-09-16 22:15):
The array is assembled as
md127, stopped, and re-assembled asmd0within ~8 seconds. If the storage service's boot scan runs inside that
window, the pool briefly disappears / reappears with a different md node
name — plausibly the moment it treats the existing pool as a new one and
inserts the duplicate row.
Supporting service log (
GetRaidsfailing to read df after boot):Historical evidence: the
local-storage.db.preclean.*backups taken beforeeach manual fix (Sep 10 and Sep 17 incidents) always contain exactly one
JBOD row (status
ok) and the ZeroByte duplicate only in the snapshotstaken after an unclean shutdown. In the Sep 10 incident, both ZeroByte
rows had an empty
status.Workaround that works (for users, until this is fixed)
Reload the web UI ~10 s later: duplicate entry and the read-only label are
gone.
Things that do not work (don't waste time like I did):
sudo systemctl restart casaos.service— unit does not existsudo systemctl restart zimaos.service— restarts everything, same rowsbtrfs property set /media/<pool> ro false— the FS was never read-onlySuggested fix directions
uuid(e.g.INSERT ... ON CONFLICT(uuid) DO UPDATE) instead of inserting; or delete-then-insert keyed by uuid.stable (e.g. systemd ordering after mdadm/btrfs mounts, or re-check that
the pool still exists before inserting).
status: compute it from the real md state atinsert time.
UNIQUE(uuid, mount_point)onraidsso theduplicate state becomes impossible.
Happy to provide more logs or DB dumps if useful.