183 lines
7.0 KiB
Markdown
183 lines
7.0 KiB
Markdown
# Rebuilding the empty directory tree after LVM recovery
|
||
|
||
These scripts are **independent** of the file-salvage scripts. Their job is to
|
||
save the names (and, when available, numeric UID/GID/mode) of directories
|
||
**before you wipe the damaged array**, then recreate those empty directories
|
||
on the new `/storage` filesystem.
|
||
|
||
## Before rebuilding: export the surviving tree
|
||
|
||
The current damaged ext4 volume cannot mount normally. `capture-tree.py` uses
|
||
`debugfs -c` instead, just as the file-salvage scripts do. It enumerates
|
||
**directory metadata only**. It does not read or recover the file contents.
|
||
|
||
Keep the damaged LV read-only. Check:
|
||
|
||
```bash
|
||
blockdev --getro /dev/mapper/plex--new-plex--new # must print 1
|
||
findmnt /mnt/recovery # Passport must be mounted
|
||
```
|
||
|
||
Capture all *reachable* top-level directories and their descendants:
|
||
|
||
```bash
|
||
sudo python3 capture-tree.py \
|
||
--source / \
|
||
--include /Backups \
|
||
--output /mnt/recovery/plex-directory-tree.jsonl
|
||
```
|
||
|
||
`--include /Backups` adds that root manually because, in this incident, its
|
||
inode metadata is zeroed out. **It will not restore unknown children beneath
|
||
it.** Repeat `--include` for any additional known directory path you need to
|
||
exist later, including the share roots that have missing metadata.
|
||
|
||
To scan only selected trees (less time, but incomplete):
|
||
|
||
```bash
|
||
sudo python3 capture-tree.py \
|
||
--source /Media \
|
||
--source /Warez \
|
||
--include /Backups \
|
||
--output /mnt/recovery/plex-directory-tree.jsonl
|
||
```
|
||
|
||
This writes three files **to the Passport**:
|
||
|
||
- `plex-directory-tree.jsonl` — directory paths with owner/mode metadata (when
|
||
available); preferred input for the restore script.
|
||
- `plex-directory-tree.txt` — one absolute filesystem path per line, easy to
|
||
inspect or edit, and also valid restore input (without ownership/modes).
|
||
- `plex-directory-tree.warnings.txt` — unreadable directories and ambiguous
|
||
entries. **Read this** before deciding the capture is complete.
|
||
|
||
You can manually append known missing directory paths to the `.txt` file, or
|
||
re-run the capture with additional `--include` arguments. Save the resulting
|
||
three files somewhere safe besides the array you're about to rebuild.
|
||
|
||
**Important:** If a directory's inode or parent directory block was lost, no
|
||
metadata-only tool can reconstruct the unknown descendants. Keep a list of
|
||
missing Samba share roots and any especially important subdirectories to add
|
||
manually. If a directory entry's type is destroyed, `capture-tree.py` reports
|
||
it as ambiguous in the warnings file; it does *not* invent a directory.
|
||
|
||
## Alternative: use an existing full-path folder list
|
||
|
||
`restore-tree.py` also accepts a plain UTF-8 text file with **one full directory
|
||
path per line**, for example:
|
||
|
||
```text
|
||
/Media
|
||
/Media/Videos
|
||
/Media/Videos/Television
|
||
/Media/Videos/Television/Bob's Burgers
|
||
/Media/Videos/Movies
|
||
/Warez
|
||
/Warez/Books
|
||
/Backups
|
||
```
|
||
|
||
Folder paths may contain spaces and apostrophes; don't quote individual lines
|
||
in the text file. Empty directory parents are created automatically. An
|
||
indented/box-drawing `tree` diagram is **not** valid input; use `tree -dfi
|
||
--noreport` or `find ... -type d` output that prints a full path on each line.
|
||
|
||
If a working source had been mounted under `/storage`, a text export might
|
||
contain `/storage/Media/...` rather than `/Media/...`; see `--strip-prefix` below.
|
||
|
||
## After rebuilding: validate, dry-run, then apply
|
||
|
||
**Do not run `restore-tree.py --apply` until the newly rebuilt filesystem is
|
||
mounted at `/storage`.** The script refuses to apply if `/storage` is just an
|
||
unmounted directory on the OS disk. It also refuses symlinks/non-directories
|
||
along the destination path.
|
||
|
||
Verify that the *correct* filesystem is mounted:
|
||
|
||
```bash
|
||
findmnt /storage
|
||
lsblk -f
|
||
```
|
||
|
||
Dry run using the captured JSONL:
|
||
|
||
```bash
|
||
python3 restore-tree.py /mnt/recovery/plex-directory-tree.jsonl \
|
||
--target /storage
|
||
```
|
||
|
||
If it looks correct, create the empty directories:
|
||
|
||
```bash
|
||
sudo python3 restore-tree.py /mnt/recovery/plex-directory-tree.jsonl \
|
||
--target /storage --apply
|
||
```
|
||
|
||
**Optional:** Restore original numeric owners and permission modes where the
|
||
capture has valid metadata. Check that the same service accounts/UIDs exist on
|
||
the rebuilt host first. Files on the exFAT Passport do not themselves preserve
|
||
Unix owner/mode metadata; the JSONL does.
|
||
|
||
```bash
|
||
sudo python3 restore-tree.py /mnt/recovery/plex-directory-tree.jsonl \
|
||
--target /storage --apply --restore-metadata
|
||
```
|
||
|
||
Use `--expect-source /dev/mapper/<your-new-lv>` for an extra safety check if
|
||
you know the exact `findmnt` SOURCE string for the rebuilt array.
|
||
|
||
For an existing text list whose paths include the old mountpoint:
|
||
|
||
```bash
|
||
python3 restore-tree.py old-tree.txt --target /storage \
|
||
--strip-prefix /storage
|
||
|
||
sudo python3 restore-tree.py old-tree.txt --target /storage \
|
||
--strip-prefix /storage --apply
|
||
```
|
||
|
||
Re-running the restore is supported. Existing directories are retained.
|
||
`--restore-metadata` **does** change mode and owner of existing directories
|
||
listed in the snapshot, so use it deliberately.
|
||
|
||
## What this does **not** restore
|
||
|
||
- File contents, missing/unreachable directory names, symlinks, or hardlinks.
|
||
- Samba definitions (`smb.conf` or included shares), user accounts, ACLs, and
|
||
extended attributes. Those must already exist or be separately restored.
|
||
- Docker bind-mount configuration and application databases. Creating the
|
||
directory is not sufficient if a container also needs its files or metadata.
|
||
|
||
After restoration, verify the expected share root directories exist and have
|
||
appropriate permissions before starting services. For Samba, `testparm` can
|
||
validate the existing share configuration; directory creation alone cannot
|
||
recreate the share definitions.
|
||
|
||
## Recovery-incident particulars (Banshee)
|
||
|
||
- Damaged logical volume: `/dev/mapper/plex--new-plex--new`.
|
||
- Filesystem: ext4, 4,096-byte blocks.
|
||
- Previously calculated missing data-block range: `3418636288–4395389951`.
|
||
(The directory-tree scripts do **not** use this range; they simply traverse
|
||
readable directory metadata. See the main salvage README for dead-block
|
||
calculations.)
|
||
- `/Backups` root entry survives, but inode `496369665` is zeroed and cannot
|
||
be traversed. Use `--include /Backups` plus any known child directories.
|
||
- `/mnt/recovery` is the removable Passport; export onto that or another
|
||
device, **not** onto the array being rebuilt.
|
||
|
||
**Checkpoint:** Copy all three capture files off the recovery system and
|
||
review the warnings and share roots *before* formatting or recreating the LV.
|
||
Also make sure your existing Samba configuration and share definitions are
|
||
backed up independently. Useful commands before the rebuild:
|
||
|
||
```bash
|
||
testparm -s # inspect configured Samba shares and paths
|
||
cp -a /etc/samba /mnt/recovery/samba-config-pre-rebuild
|
||
```
|
||
|
||
If the new storage will be served by containers instead, independently save
|
||
their Compose files and bind-mount declarations. If the operating-system disk
|
||
isn't being rebuilt, those configurations may remain intact, but it's worth
|
||
having copies before changing the storage layout.
|