Skip to content

Storage service creates a duplicate raids row in local-storage.db after unclean shutdown - pool appears twice as read-only/obsolete (RAID6 healthy) #601

Description

@Meliodas-96

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

  1. Have a RAID6 pool (btrfs) mounted.
  2. Cut power / unclean shutdown while the pool is mounted.
  3. Boot the machine.
  4. 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.
  5. 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

  1. 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.
  2. 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).
  3. Never persist an empty status: compute it from the real md state at
    insert time.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions