Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145#!/usr/bin/env zsh# phase0-inventory.sh v2 -- READ-ONLY inventory for MicroSD-512 migration.# ASCII-only, single-line awk programs. Writes ONLY to ~/msd512-audit.# Usage: phase0-inventory.sh [--force] (--force = rebuild manifests from scratch)emulate -L zshsetopt NO_NOMATCHtypeset -F SECONDSexport LC_ALL=C
FORCE=0[[ ${1:-} == --force ]] && FORCE=1
SRC="/Volumes/MicroSD-512"DST="/Volumes/photos library iCloud"BUNDLE="$DST/photos-restore.photoslibrary"AUDIT="$HOME/msd512-audit"mkdir -p "$AUDIT"TB=$'\t'FMT="%Sm${TB}%z${TB}%N"
[[ -d "$SRC" ]] || { print "FATAL: not mounted: $SRC"; exit 1; }[[ -d "$DST" ]] || { print "FATAL: not mounted: $DST"; exit 1; }
# ---------- 1. volume facts ----------{ diskutil info "$SRC"; print -- "-----"; diskutil info "$DST"; } > "$AUDIT/volumes.txt" 2>&1df -h "$SRC" "$DST" > "$AUDIT/df.txt" 2>&1grep -E 'Volume Name|File System Personality|Mount Point|Read-Only Volume|Protocol|Disk Size|Container Free Space|Device Node' "$AUDIT/volumes.txt" > "$AUDIT/volumes.digest.txt" 2>/dev/null
# ---------- 2. tools ----------: > "$AUDIT/tools.txt"for t in b3sum xxh128sum xxhsum shasum ditto rsync ddrescue exiftool sqlite3 tmux caffeinate; do if whence -p $t > /dev/null; then print "$t=OK" >> "$AUDIT/tools.txt"; else print "$t=MISSING" >> "$AUDIT/tools.txt"; fidone
# ---------- 3. manifests (metadata only, reuse if present) ----------manifest() { local root="$1" tag="$2" out="$AUDIT/$2.manifest.tsv" if [[ -s "$out" && $FORCE -eq 0 ]]; then print "[=] reusing $tag manifest ($(wc -l < "$out" | tr -d ' ') rows)" return fi print "[*] scanning $tag (this is the slow one for SRC)..." find "$root" -mindepth 1 \( -name '.Spotlight-V100' -o -name '.fseventsd' -o -name '.TemporaryItems' -o -name '.DocumentRevisions-V100' -o -name '.Trashes' \) -prune -o -type f -print0 2> "$AUDIT/$tag.find.err" | xargs -0 -n 300 stat -f "$FMT" -t '%Y-%m-%dT%H:%M:%S' > "$out" 2> "$AUDIT/$tag.stat.err"}manifest "$SRC" srcmanifest "$DST" dst
SM="$AUDIT/src.manifest.tsv"DM="$AUDIT/dst.manifest.tsv"[[ -s "$SM" ]] || { print "FATAL: empty src manifest"; exit 1; }
# ---------- 4. aggregates ----------top_dirs() { awk -F'\t' -v R="$1" '{rel=substr($3,length(R)+2); i=index(rel,"/"); d=(i>0)?substr(rel,1,i-1):"(root-files)"; n[d]++; b[d]+=$2} END{for(k in n) printf "%d\t%d\t%s\n", n[k], b[k], k}' "$2" | sort -t"$TB" -k2,2nr; }ext_prof() { awk -F'\t' '{m=split($3,q,"/"); f=q[m]; nd=split(f,e,"."); x=(nd>1)?tolower(e[nd]):"noext"; c[x]++; s[x]+=$2} END{for(k in c) printf "%d\t%d\t%s\n", c[k], s[k], k}' "$1" | sort -t"$TB" -k1,1nr; }
top_dirs "$SRC" "$SM" > "$AUDIT/src.by-topdir.tsv"top_dirs "$DST" "$DM" > "$AUDIT/dst.by-topdir.tsv"ext_prof "$SM" > "$AUDIT/src.by-ext.tsv"ext_prof "$DM" > "$AUDIT/dst.by-ext.tsv"
awk -F'\t' -v R="$SRC" '{rel=substr($3,length(R)+2); m=split(rel,a,"/"); d=(m>=3)?a[1]"/"a[2]:((m==2)?a[1]:"(root-files)"); n[d]++; b[d]+=$2; if(mn[d]==""||$1<mn[d])mn[d]=$1; if($1>mx[d])mx[d]=$1} END{for(k in n) printf "%d\t%d\t%s\t%s\t%s\n", n[k], b[k], substr(mn[k],1,7), substr(mx[k],1,7), k}' "$SM" | sort -t"$TB" -k2,2nr > "$AUDIT/src.by-dir2.tsv"
find "$SRC" -maxdepth 3 -type d -print > "$AUDIT/src.dirs3.txt" 2>/dev/nullfind "$DST" -maxdepth 2 -type d -print > "$AUDIT/dst.dirs2.txt" 2>/dev/null
# ---------- 5. bundles + junk (b6 validation) ----------: > "$AUDIT/bundles.txt"for V in "$SRC" "$DST"; do find "$V" -maxdepth 5 \( -name '*.photoslibrary' -o -name '*.photolibrary' -o -name '*.migratedphotolibrary' -o -name '*.aplibrary' -o -name '*.sparsebundle' -o -name '*.fcpbundle' -o -name '*.imovielibrary' -o -name '*.app' \) -print >> "$AUDIT/bundles.txt" 2>/dev/nulldoneawk -F'\t' '{m=split($3,q,"/"); f=q[m]; if (substr(f,1,2)=="._") a++; else if (f==".DS_Store") s++; else if (tolower(f)=="thumbs.db") t++} END{printf "AppleDouble=%d DS_Store=%d Thumbs=%d\n", a+0, s+0, t+0}' "$SM" > "$AUDIT/src.junk.txt"awk -F'\t' -v B="$BUNDLE" '{if (index($3,B)==1) {n1++; b1+=$2} else {n2++; b2+=$2}} END{printf "DST in-bundle: %d files %.1f GiB | DST loose: %d files %.1f GiB\n", n1+0, b1/1073741824, n2+0, b2/1073741824}' "$DM" > "$AUDIT/dst.split.txt"
# ---------- 6. overlap estimate (a4, no byte reads) ----------cut -f2 "$DM" | sort -u > "$AUDIT/dst.sizes.txt"awk -F'\t' 'NR==FNR{z[$1]=1; next} {tn++; tb+=$2; if ($2 in z){n++; b+=$2}} END{printf "size-only match: %d/%d files, %.1f/%.1f GiB (upper bound already-present)\n", n+0, tn+0, b/1073741824, tb/1073741824}' "$AUDIT/dst.sizes.txt" "$SM" > "$AUDIT/overlap.txt"awk -F'\t' '{m=split($3,q,"/"); print tolower(q[m]) "\t" $2}' "$DM" | sort -u > "$AUDIT/dst.namesize.txt"awk -F'\t' 'NR==FNR{k[$1 FS $2]=1; next} {m=split($3,q,"/"); key=tolower(q[m]) FS $2; if (key in k){n++; b+=$2}} END{printf "name+size match: %d files, %.1f GiB (strong already-present signal)\n", n+0, b/1073741824}' "$AUDIT/dst.namesize.txt" "$SM" >> "$AUDIT/overlap.txt"
# ---------- 7. benchmarks ----------seqmbs=0BIG=$(sort -t"$TB" -k2,2nr "$SM" | head -1 | cut -f3)if [[ -n "$BIG" && -f "$BIG" ]]; then sz=$(stat -f %z "$BIG") rd=$(( sz > 268435456 ? 268435456 : sz )) t0=$SECONDS dd if="$BIG" of=/dev/null bs=1m count=256 > /dev/null 2> "$AUDIT/dd.out" el=$(( SECONDS - t0 + 0.001 )) seqmbs=$(( rd/1048576.0/el )) printf "sequential: %.1f MiB in %.1f s => %.2f MiB/s\n" $(( rd/1048576.0 )) $el $seqmbs > "$AUDIT/bench.seq.txt"else print "sequential: SKIPPED (no candidate file)" > "$AUDIT/bench.seq.txt"fi
awk -F'\t' '$2>102400 && $2<8388608 {print $3}' "$SM" | awk 'NR%29==0' | head -120 > "$AUDIT/bench.list"nf=$(wc -l < "$AUDIT/bench.list" | tr -d ' ')if [[ $nf -gt 0 ]]; then t0=$SECONDS while IFS= read -r f; do cat -- "$f" > /dev/null 2>&1; done < "$AUDIT/bench.list" el=$(( SECONDS - t0 + 0.001 )) nb=$(awk -F'\t' 'NR==FNR{w[$0]=1; next} ($3 in w){s+=$2} END{print s+0}' "$AUDIT/bench.list" "$SM") printf "small-file: %d files %.1f MiB in %.1f s => %.2f MiB/s, %.1f files/s\n" $nf $(( nb/1048576.0 )) $el $(( nb/1048576.0/el )) $(( nf/el )) > "$AUDIT/bench.small.txt"else print "small-file: SKIPPED" > "$AUDIT/bench.small.txt"fi
SB=$(awk -F'\t' '{b+=$2} END{print b+0}' "$SM")printf "PROJECTION: SRC %.1f GiB at %.2f MiB/s => %.1f hours for one full read pass\n" $(( SB/1073741824.0 )) $(( seqmbs+0.001 )) $(( SB/1048576.0/(seqmbs+0.001)/3600 )) > "$AUDIT/projection.txt"
# ---------- 8. Jellyfin ----------{ print "process:"; pgrep -fl -i jellyfin || print " (none - server stopped)" print -n "app version: "; /usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' /Applications/Jellyfin.app/Contents/Info.plist 2>/dev/null || print "unknown" for d in "$HOME/Library/Application Support/jellyfin" "$HOME/Library/Application Support/Jellyfin" "$HOME/.config/jellyfin" "$HOME/.local/share/jellyfin"; do [[ -d "$d" ]] && { print "DATADIR: $d"; du -sh "$d" 2>/dev/null; } done print "--- refs to MicroSD ---" for d in "$HOME/Library/Application Support/jellyfin" "$HOME/Library/Application Support/Jellyfin" "$HOME/.config/jellyfin" "$HOME/.local/share/jellyfin"; do [[ -d "$d" ]] || continue find "$d" -name '*.mblink' -print0 2>/dev/null | xargs -0 -I@ sh -c 'printf "%s => " "@"; cat "@"; echo' grep -rl -i 'MicroSD' "$d" --include='*.xml' 2>/dev/null | head -20 done} > "$AUDIT/jellyfin.txt" 2>&1
# ================= DIGEST =================print "===== DF ====="; cat "$AUDIT/df.txt"print "===== FS ====="; cat "$AUDIT/volumes.digest.txt"print "===== TOOLS ====="; tr '\n' ' ' < "$AUDIT/tools.txt"; printprint "===== TOTALS ====="for t in src dst; do awk -F'\t' -v T="$t" '{n++; b+=$2; if(mn==""||$1<mn)mn=$1; if($1>mx)mx=$1} END{printf "%s: files=%d bytes=%d (%.1f GiB) mtime %s .. %s\n", T, n, b, b/1073741824, mn, mx}' "$AUDIT/$t.manifest.tsv"donecat "$AUDIT/dst.split.txt"print "===== SRC TOP-LEVEL (files, bytes, dir) ====="; head -25 "$AUDIT/src.by-topdir.tsv"print "===== SRC 2ND-LEVEL top 25 (files, bytes, first, last, dir) ====="; head -25 "$AUDIT/src.by-dir2.tsv"print "===== DST TOP-LEVEL ====="; head -20 "$AUDIT/dst.by-topdir.tsv"print "===== SRC EXT top 20 ====="; head -20 "$AUDIT/src.by-ext.tsv"print "===== BUNDLES ====="; cat "$AUDIT/bundles.txt"print "===== JUNK ====="; cat "$AUDIT/src.junk.txt"print "===== OVERLAP ====="; cat "$AUDIT/overlap.txt"print "===== BENCH ====="; cat "$AUDIT/bench.seq.txt" "$AUDIT/bench.small.txt" "$AUDIT/projection.txt"print "===== JELLYFIN ====="; cat "$AUDIT/jellyfin.txt"print "===== ERRORS ====="; head -6 "$AUDIT/src.find.err" "$AUDIT/dst.find.err" "$AUDIT/src.stat.err" 2>/dev/nullprint "===== END DIGEST ====="