Tasks: NAS Storage Directory Layout¶
Design: ./design.md Implementation: ./implementation.md Status: pending Created: 2026-08-22 Not Doing: SMB shares, documents-service selection (Nextcloud/Copyparty/OpenCloud), Plex/second media service, automated photo promotion, vault reorganization, ZFS migration planning, Gitea code-archive extraction, restic policy values, NFS health monitoring
Task 1: Tree skeleton via storage role¶
- Status: pending
- Depends on: —
- Size: M
- Can run in parallel with: Task 2, Task 3
- Docs: implementation.md#tree-skeleton
Subtasks¶
- 1.1 Replace
storage_dirsinmetal/roles/storage/vars/main.ymlwith the designed tree as a flat path list (top-level dirs,NN.MMsubdirs,30_media/rotation/{movies,shows},30_media/rotation/downloads/{complete,incomplete}— downloads nests INSIDE rotation,10_documents/inbox) - 1.2 Ensure the
storage_dirsloop intasks/nfs.ymlsets owner/group1000:1000, mode0775 - 1.3 Add
templates/storage-readme.j2(legend: grammar rules, working-set test, machine-vs-human convention) rendered to00_meta/README.mdfrom the same variable; create00_meta/RETIRED.mdwithforce: false - 1.4 Grep the role for references to old dirs (
Temp,Videosin validation tasks) and repoint to99_tmp - 1.5 Run the role against the NAS twice: first run creates, second run reports zero changes; manually remove empty legacy dirs (
Videos,Backups, …) and verifyfind /mnt/storage -maxdepth 1shows only the new tree
Task 2: snapraid exclude rules¶
- Status: pending
- Depends on: —
- Size: S
- Can run in parallel with: Task 1, Task 3, Task 4, Task 5, Task 6, Task 7, Task 9
- Docs: implementation.md#snapraid-excludes
Subtasks¶
- 2.1 Add root-anchored
exclude /30_media/rotation/,exclude /10_documents/inbox/,exclude /99_tmp/with why-comments tometal/roles/storage/templates/snapraid.conf.j2; remove the name-anchoredexclude downloads/(superseded; deep-matches inside 70_backups dumps) and reviewexclude appdata/for the same hazard - 2.2 Apply via the storage role; verify rendered
/etc/snapraid.confcontains the lines andsnapraid statusparses the config
Task 3: b3sum in the dev shell¶
- Status: pending
- Depends on: —
- Size: S
- Can run in parallel with: Task 1, Task 2, Task 4, Task 5, Task 9
- Docs: implementation.md#devshell-b3sum
Subtasks¶
- 3.1 Add
b3sumtodevShells.defaultpackages inflake.nix - 3.2 Verify
nix develop -c b3sum --version
Task 4: NFS media PV (csi-driver-nfs)¶
- Status: pending
- Depends on: Task 1
- Size: S
- Can run in parallel with: Task 2, Task 3, Task 6, Task 7, Task 8, Task 9
- Docs: implementation.md#nfs-media-share
- Note: land Tasks 4 and 5 in ONE commit/sync window — Task 4 alone leaves the deployed chart claiming the deleted
pv-nfs-videos(transient degradation)
Subtasks¶
- 4.1 In
system/csi-driver-nfs/values.yamlreplace thevideosvolume entry withmedia/ share30_media/ capacity 4Ti - 4.2 Delete stale
pv-nfs-videosfrom the cluster if present (Retain policy — NAS data untouched) - 4.3 Verify:
helm templaterenderspv-nfs-mediawith volumeHandle…/mnt/storage/30_media; after ArgoCD synckubectl get pv pv-nfs-mediaexists
Task 5: Jellyfin stack remount end-to-end¶
- Status: pending
- Depends on: Task 4
- Size: M
- Can run in parallel with: Task 2, Task 3, Task 6, Task 7, Task 8, Task 9
- Docs: implementation.md#jellyfin-remount
Subtasks¶
- 5.1 Rename
apps/jellyfin/templates/pvc-videos.yaml→pvc-media.yamlbindingpvc-nfs-media→pv-nfs-media - 5.2 Rework
persistenceinapps/jellyfin/values.yaml: single NFS claim; ONE volumeMount per linking container (radarr/data→subPathrotation; sonarr/data→subPathrotation; transmission/data/downloads→subPathrotation/downloads; Jellyfin all-readOnlyincl./media/music→30.03_music); strip media subPaths from the CephdataPVC (configs only) - 5.3 Add
PUID: "1000"/PGID: "1000"env to all lscr.io containers (transmission, radarr, sonarr, prowlarr); norunAsUseron them - 5.4 Update transmission download-dir config to
/data/downloads/complete+/data/downloads/incomplete; *arr root folders/data/movies,/data/shows - 5.5 Update
docs/guides/how_to_for_media_management.md(root folders, Jellyfin library paths for both tiers) - 5.6 Verify: helm template shows single-claim mounts, exactly one media mount on radarr/sonarr, RO flags, PUID/PGID env; in-cluster UID proof (write to
/data/downloadssucceeds as 1000); hardlink proof (ln/data/downloads/complete/…→/data/movies/…,stat -c %h= 2); EROFS in Jellyfin's/media/movies;./tests/metal.shpasses
Task 6: Migration — copy & cross-drive verification (manual ops)¶
- Status: pending
- Depends on: Task 1, Task 3
- Size: M
- Can run in parallel with: Task 2, Task 4, Task 5, Task 9
- Docs: implementation.md#migration-runbook
Subtasks¶
- 6.1 rsync drive A →
99_tmp/driveA/(-aHAX, no--delete) - 6.2 Generate b3sum manifests: NAS copy (hash locally on the NAS — one-off
apt install b3sum, recorded in00_meta/migration/README), drive B, drive C →00_meta/migration/*.b3 - 6.3 Hash-set diff; copy unique B/C content into
99_tmp/drive{B,C}_delta/; iterate until diff empty or discards logged indiscarded.txt
Task 7: Migration — sort into numbered homes (manual ops)¶
- Status: pending
- Depends on: Task 6
- Size: M
- Can run in parallel with: Task 2, Task 4, Task 5, Task 9
- Docs: implementation.md#migration-runbook
Subtasks¶
- 7.1
mvcontent from99_tmpper design.md § Migration mapping, appending to00_meta/migration/moves.log - 7.2 Delete cruft (
.@__thumb,.streams,Thumbs.db, superseded archives) →discarded.txt - 7.3 Exit check:
99_tmpempty; sampledb3sum -caudit — derive each sampled file's destination via the longest-prefix-matchingmoves.logentry applied to its manifest path
Task 8: Parity enablement & drive release (manual ops)¶
- Status: pending
- Depends on: Task 2, Task 7
- Size: M
- Can run in parallel with: Task 4, Task 5, Task 9
- Docs: implementation.md#parity-enablement
Subtasks¶
- 8.1 Parity-size gate: compare
lsblk -bsizes of candidate parity drive vs every data branch (all 18TB Seagates, confirmed — abort wipe if candidate smaller); then wipe drive B, add toparity_drivesin sops inventory, run storage role; initial sync completes (journalctl -u snapraid-initial-sync) - 8.2 Repeat for drive C (2-parity);
snapraid statushealthy - 8.3 Full scrub, zero errors
- 8.4 Wipe drive A last; record its new role (parity/data/cold spare) in
00_meta/migration/
Task 9: Legend & docs¶
- Status: pending
- Depends on: Task 1
- Size: S
- Can run in parallel with: Task 2, Task 3, Task 4, Task 5, Task 6, Task 7, Task 8
- Docs: implementation.md#legend-docs
Subtasks¶
- 9.1 Write the vault legend note (manual, in Obsidian vault) mirroring
00_meta/README.md - 9.2 Point
docs/info/todo.mdstorage section atdocs/wip/nas-storage-layout/(supersedes "Future shares (Pictures, Documents, Music, Backups)" naming) - 9.3 Verify
make docsbuilds clean
Task 10: Final verification¶
- Status: pending
- Depends on: Task 1, Task 2, Task 3, Task 4, Task 5, Task 6, Task 7, Task 8, Task 9
- Size: S
- Can run in parallel with: —
Subtasks¶
- 10.1 Run
/kk:test— role idempotency,./tests/metal.sh, in-cluster mount/hardlink/RO proofs - 10.2 Run
/kk:document— finalize docs, move feature out of wip when done - 10.3 Run
/kk:review-code— review the ansible/helm/nix changes - 10.4 Run
/kk:review-spec— verify implementation matches design/implementation docs