From 5a76107f8fd9f04369bef7d5bad4619887502aa2 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Thu, 18 Jun 2026 07:54:42 -0400 Subject: [PATCH] actually --- .../terminal/scripts/bin/plan-disk-burns.sh | 116 +++++++++++++++++- .../programs/terminal/scripts/default.nix | 2 +- 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/modules/home/programs/terminal/scripts/bin/plan-disk-burns.sh b/modules/home/programs/terminal/scripts/bin/plan-disk-burns.sh index 7401d6b..02ffefd 100644 --- a/modules/home/programs/terminal/scripts/bin/plan-disk-burns.sh +++ b/modules/home/programs/terminal/scripts/bin/plan-disk-burns.sh @@ -1,5 +1,117 @@ #!/usr/bin/env bash +# +# plan-disk-burns.sh - Split a set of files into per-disc manifests that each +# fit on a Blu-ray (BD-R). Packs files greedily by accumulated size and writes +# one manifest per disc, then prints a summary you can review before burning. +# +# It does NOT burn anything. It only produces discNN_files.txt manifests. + set -euo pipefail -echo "plan-disk-burns: placeholder" -echo "runtime tools available: smartctl, lsblk" +usage() { + cat <<'EOF' +Usage: plan-disk-burns.sh [options] + +Options: + -s DIR Source directory to scan (default: current directory) + -p GLOB Filename pattern, case-insensitive (default: *.raf) + -o DIR Output directory for manifests (default: ./br-manifests) + -l LABEL Volume label prefix for the burn commands; disc number is + appended, e.g. -l PHOTOS yields PHOTOS_1 (default: DISK) + -c GiB Per-disc capacity in GiB, may be fractional e.g. 22.5 (default: 23) + -h Show this help + +Output: + DIR/disc01_files.txt, disc02_files.txt, ... absolute file paths (one per line) + +Manifests use absolute paths so burning works from any directory. The printed +xorriso command strips the source root and roots everything at the disc root. +EOF +} + +src="." +pattern="*.raf" +outdir="./br-manifests" +label="DISK" +cap_gib=23 + +while getopts ":s:p:o:l:c:h" opt; do + case "$opt" in + s) src="$OPTARG" ;; + p) pattern="$OPTARG" ;; + o) outdir="$OPTARG" ;; + l) label="$OPTARG" ;; + c) cap_gib="$OPTARG" ;; + h) usage; exit 0 ;; + \?) echo "Unknown option: -$OPTARG" >&2; usage; exit 2 ;; + :) echo "Option -$OPTARG requires an argument" >&2; exit 2 ;; + esac +done + +[[ -d "$src" ]] || { echo "Source directory not found: $src" >&2; exit 1; } +[[ "$cap_gib" =~ ^[0-9]+(\.[0-9]+)?$ ]] || { echo "Capacity must be a number (GiB), e.g. 23 or 22.5: $cap_gib" >&2; exit 1; } + +# Resolve to an absolute path so manifests and the burn command are CWD-independent. +src=$(realpath "$src") + +limit=$(awk -v c="$cap_gib" 'BEGIN { printf "%d", c * 1024 * 1024 * 1024 }') +mkdir -p "$outdir" +rm -f "$outdir"/disc*_files.txt + +# Collect files sorted by path so discs group naturally by folder/date. +mapfile -d '' entries < <(find "$src" -type f -iname "$pattern" -printf '%s\t%p\0' | sort -z -t$'\t' -k2) + +if [[ ${#entries[@]} -eq 0 ]]; then + echo "No files matching '$pattern' under '$src'." >&2 + exit 1 +fi + +disc=1 +disc_bytes=0 +total_bytes=0 +total_files=0 +declare -A disc_bytes_map disc_files_map + +manifest() { printf '%s/disc%02d_files.txt' "$outdir" "$1"; } + +for entry in "${entries[@]}"; do + size="${entry%%$'\t'*}" + path="${entry#*$'\t'}" + + if (( size > limit )); then + echo "WARNING: '$path' ($((size/1024/1024)) MiB) exceeds disc capacity (${cap_gib} GiB); skipping." >&2 + continue + fi + + if (( disc_bytes + size > limit )); then + disc=$(( disc + 1 )) + disc_bytes=0 + fi + + printf '%s\n' "$path" >> "$(manifest "$disc")" + disc_bytes=$(( disc_bytes + size )) + disc_bytes_map[$disc]=$disc_bytes + disc_files_map[$disc]=$(( ${disc_files_map[$disc]:-0} + 1 )) + total_bytes=$(( total_bytes + size )) + total_files=$(( total_files + 1 )) +done + +echo "=== Manifest summary (cap ${cap_gib} GiB/disc) ===" +for ((d=1; d<=disc; d++)); do + [[ -f "$(manifest "$d")" ]] || continue + mib=$(( disc_bytes_map[$d] / 1024 / 1024 )) + printf ' disc%02d: %5d files, %6d MiB -> %s\n' \ + "$d" "${disc_files_map[$d]}" "$mib" "$(manifest "$d")" +done +printf ' TOTAL : %5d files, %6d MiB across %d disc(s)\n' \ + "$total_files" "$(( total_bytes / 1024 / 1024 ))" "$disc" + +echo +echo "=== Burn commands (review, then run one per disc) ===" +for ((d=1; d<=disc; d++)); do + [[ -f "$(manifest "$d")" ]] || continue + # SC2016 - The $(cat ...) is literal text printed for the user to run later, not meant to expand here. + # shellcheck disable=SC2016 + printf 'xorriso -outdev /dev/sr0 -volid "%s_%d" \\\n -map_l %q / $(cat %q) -- -commit -eject\n\n' \ + "$label" "$d" "$src" "$(manifest "$d")" +done diff --git a/modules/home/programs/terminal/scripts/default.nix b/modules/home/programs/terminal/scripts/default.nix index 29e3546..54ee853 100644 --- a/modules/home/programs/terminal/scripts/default.nix +++ b/modules/home/programs/terminal/scripts/default.nix @@ -16,7 +16,7 @@ let # my-new-thing = { runtimeInputs = with pkgs; [ jq curl ]; }; # }; scriptDefs = { - plan-disk-burns = { runtimeInputs = with pkgs; [ smartmontools util-linux ]; }; + plan-disk-burns = { runtimeInputs = with pkgs; [ coreutils findutils gawk xorriso ]; }; }; mkScript = name: def: