fix: usb target picking excludes EFI partitions and pre-flights space
gates / consistency-and-conventions (push) Successful in 59s
gates / consistency-and-conventions (push) Successful in 59s
usb_detect offered a Ventoy stick's 32M VTOYEFI ESP as a sync/backup target: with the data partition unmounted it was the only mounted candidate, and cp died mid-copy with 'No space left on device'. Detection now reads FSTYPE/PARTTYPENAME and drops EFI system partitions from both the mounted list and the mount-offer list; USB_MOUNTED entries carry mp|label|size|model|fs and usb_pick_root shows that in the single-stick confirm and the multi-stick/partition picker (USB_ROOT stays a bare mountpoint). pos-media-sync pre-flights the exact payload size vs df free space (err, or warn under --dry-run) before any copy.
This commit is contained in:
@@ -42,6 +42,7 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **2026-08-15** — Fix `pos media sync` offering a Ventoy stick's **EFI partition** as the sync target: with the data partition unmounted, the 32 MB `VTOYEFI` ESP was the only mounted USB partition, `usb_detect` offered it with no context, and `cp` died mid-copy with `No space left on device` (live-box report). `usb_detect` now fetches `FSTYPE`/`PARTTYPENAME` and excludes EFI system partitions (Ventoy `VTOYEFI`, `/boot/efi`) from **both** the mounted list and the mount-offer list; `USB_MOUNTED` entries carry `mp|label|size|model|fs` and `usb_pick_root` shows that in the single-stick confirm and the multi-stick/partition picker (`1) /media/Ventoy (1.1T, Ventoy, exfat)`), while `USB_ROOT` stays a bare mountpoint (`${root%|*}`) so `pos system backup` (`${root%/}/backups`) is unaffected. `pos-media-sync` gained a pre-flight space check (measures exactly what `needs_copy` would copy vs `df -Pk`, `err`/`warn` before any copy) — no more mid-copy ENOSPC. Docs: howto/media.md target-picking note, SCRIPTS.md usb-lib paragraph, AGENT_Context hand-maintained lib row (194→205). Verified: new stub harness `/tmp/opencode/vtoyefi-run.sh` (ESP filtered from mounted + mount-offer, multi-pick shows only the data partition, space fit/too-small/dry-run-warn) green; `/tmp/opencode/backup-test` still green; live check `printf 'n\ns\n' | bash bin/pos-media-sync --mp3` no longer offers VTOYEFI (offers unmounted `sda1` Ventoy instead); `make gen && make check`, `make lint` 0 FAIL / 0 WARN.
|
||||
- **2026-08-15** — Fix `pos media sync` reporting success with 0 files when the source is a symlink: it enumerated with plain `find "$SRC"`, and GNU find (default `-P`) does not descend a command-line symlink to a directory — `~/Music -> /mnt/hdd/…/music` therefore yielded zero matches, the loop never ran, and the tool printed `0 added, 0 updated, 0 unchanged` without creating the target dir (live-box report). Switched to `find -H "$SRC"` (follows only command-line symlinks; inner-symlink semantics unchanged). howto/media.md sync section notes symlinked sources are followed. Caught live, not by the 46-case stub suite (which used a real temp dir source — lesson: add a symlink-root fixture). Verified: `printf 'y\n' | bash bin/pos-media-sync --mp3 --dry-run` now lists all 31 mp3s as "would copy"; `make gen && make check` green.
|
||||
- **2026-08-14** — `pos system backup` — smart USB detection: lsblk TRAN (lsusb/by-id cross-check), mount offer for plugged-in-but-unmounted sticks, sha256-verified copy (stub-suite 54/54).
|
||||
- **2026-08-05** — `pos communication telegram` — `--parse-mode` (plain/markdown/html).
|
||||
|
||||
@@ -574,7 +574,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `lib/entertainment-plugin-lib.sh` | 67 | Message-safe helpers for plugins (config load, require, fetch+retry) — plugins MAY source it |
|
||||
| `lib/scheduler-lib.sh` | 760 | Scheduler lib (job parsing, notify policies, per-job user timers via user-timers-lib, legacy migrate) |
|
||||
| `lib/user-timers-lib.sh` | 112 | Shared systemd **user** timer machinery (interval→OnCalendar, unit pair writer, linger) |
|
||||
| `lib/usb-lib.sh` | 194 | Shared USB-storage detection + pick flow (detect/mount-offer/`usb_pick_root`) — used by `pos system backup` + `pos media sync` |
|
||||
| `lib/usb-lib.sh` | 205 | Shared USB-storage detection + pick flow (detect/mount-offer/`usb_pick_root`; EFI system partitions excluded; picker shows size/label/fs) — used by `pos system backup` + `pos media sync` |
|
||||
| `bin/flag-reader` | 58 | Inspect flags (list/status/`--raw`) |
|
||||
| `bin/flag-set` | 21 | Set a flag (optionally with a value) |
|
||||
| `bin/flag-clear` | 21 | Unset a flag |
|
||||
@@ -599,7 +599,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-entertainment-status` | 62 | Show enabled plugins and scheduler state |
|
||||
| `bin/pos-media-mp3` | 86 | Download audio as MP3 (yt-dlp) |
|
||||
| `bin/pos-media-mp4` | 132 | Download video as MP4 (smart/interactive format select) |
|
||||
| `bin/pos-media-sync` | 138 | Incremental Music → USB sync (mp3/mp4, add/update only) |
|
||||
| `bin/pos-media-sync` | 164 | Incremental Music → USB sync (mp3/mp4, add/update only) |
|
||||
| `bin/pos-network-checkport` | 496 | Check TCP/UDP port reachability (nmap, or bash/nc fallback) + local interface view |
|
||||
| `bin/pos-network-download` | 951 | aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits) |
|
||||
| `bin/pos-network-hotspot` | 93 | Wi-Fi hotspot via create_ap + wihotspot-gui |
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ Sourced by `bin/pos-entertainment-send|config|enable|disable|status` (after `lib
|
||||
## lib/usb-lib.sh — shared USB-storage detection
|
||||
|
||||
**File:** `lib/usb-lib.sh` (installed to `/usr/local/bin/usb-lib.sh`)
|
||||
**Purpose:** the one copy of the USB-storage machinery shared by `pos system backup`'s post-verify USB copy and `pos media sync` — `usb_detect` (lsblk JSON, TRAN + lsusb/by-id cross-check → `USB_MOUNTED`/`USB_UNMOUNTED`), `usb_related_present`, `usb_mount_offer` (mount an unmounted stick at `/media/<label>`, `usb-automount` scheme), and `usb_pick_root` (detect → mount-offer → single/multi picker → `USB_ROOT`). Defines only `usb_*`; seams `USB_MOUNT_BASE` (default `/media`, alias `BACKUP_MOUNT_BASE`) and `USB_BYID` (default `/dev/disk/by-id`, alias `BACKUP_USB_BYID`) keep existing config lines working.
|
||||
**Purpose:** the one copy of the USB-storage machinery shared by `pos system backup`'s post-verify USB copy and `pos media sync` — `usb_detect` (lsblk JSON, TRAN + lsusb/by-id cross-check → `USB_MOUNTED` as `mp|label|size|model|fs` entries and `USB_UNMOUNTED` as `path|label|size|model`; EFI system partitions — Ventoy `VTOYEFI`, `/boot/efi` — are excluded from both), `usb_related_present`, `usb_mount_offer` (mount an unmounted stick at `/media/<label>`, `usb-automount` scheme), and `usb_pick_root` (detect → mount-offer → single-confirm or multi-picker, rows showing size/label/fs → `USB_ROOT` set to the bare mountpoint). Defines only `usb_*`; seams `USB_MOUNT_BASE` (default `/media`, alias `BACKUP_MOUNT_BASE`) and `USB_BYID` (default `/dev/disk/by-id`, alias `BACKUP_USB_BYID`) keep existing config lines working.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -116,6 +116,14 @@ The source folder may be a symlink to a library elsewhere
|
||||
(`~/Music -> /mnt/data/music`) — it is followed, the artist/album tree is
|
||||
mirrored under the symlink's target.
|
||||
|
||||
**Target picking.** Mounted USB partitions are listed with size, label and
|
||||
filesystem (single candidate → confirm prompt; several → numbered picker, one
|
||||
row per partition). EFI system partitions (e.g. a Ventoy stick's `VTOYEFI`,
|
||||
32 MB) are **never** offered as a target — they are boot machinery, not
|
||||
storage. Before any copy the tool verifies the payload fits (`df` vs the
|
||||
exact bytes to copy) and fails fast with `Not enough free space on …` instead
|
||||
of dying mid-copy with `No space left on device`.
|
||||
|
||||
| Flag | Meaning |
|
||||
|------|---------|
|
||||
| `--mp3` | Sync only `*.mp3` (neither flag = both) |
|
||||
|
||||
@@ -105,6 +105,32 @@ needs_copy() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Pre-flight: fail early on "won't fit" instead of a mid-copy ENOSPC that
|
||||
# leaves a half-written target. Measures exactly what needs_copy would copy.
|
||||
space_path="$USB_ROOT"
|
||||
if [ "$DRY_RUN" -eq 0 ]; then
|
||||
mkdir -p "$dest_root"
|
||||
space_path="$dest_root"
|
||||
fi
|
||||
need_kb=0
|
||||
while IFS= read -r f; do
|
||||
rel="${f#"$SRC/"}"
|
||||
if needs_copy "$f" "$dest_root/$rel"; then
|
||||
sz="$(stat -c %s "$f" 2>/dev/null || printf 0)"
|
||||
need_kb=$((need_kb + (sz + 1023) / 1024))
|
||||
fi
|
||||
done < <(find -H "$SRC" "${find_expr[@]}")
|
||||
have_kb="$(df -Pk "$space_path" 2>/dev/null | awk 'NR==2 {print $4}')"
|
||||
have_kb="${have_kb:-0}"
|
||||
if [ "$need_kb" -gt "$have_kb" ]; then
|
||||
msg="Not enough free space on ${USB_ROOT%/}: need ~${need_kb}K, have ${have_kb}K"
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
warn "$msg"
|
||||
else
|
||||
err "$msg"
|
||||
fi
|
||||
fi
|
||||
|
||||
added=0
|
||||
updated=0
|
||||
unchanged=0
|
||||
|
||||
+21
-10
@@ -9,9 +9,10 @@
|
||||
# USB_BYID /dev/disk/by-id dir consulted by usb_related_present
|
||||
#
|
||||
# API:
|
||||
# usb_detect fill USB_MOUNTED (mountpoints, one per
|
||||
# removable USB storage) and USB_UNMOUNTED
|
||||
# ("path|label|size|model" entries)
|
||||
# usb_detect fill USB_MOUNTED ("mp|label|size|model|fs",
|
||||
# one per removable USB storage partition;
|
||||
# EFI system partitions are excluded) and
|
||||
# USB_UNMOUNTED ("path|label|size|model")
|
||||
# usb_related_present whole-system "is any USB storage attached?"
|
||||
# usb_mount_offer <devs...> offer to mount detected-but-unmounted sticks
|
||||
# (sudo, mirrors usb-automount: /media/<label>,
|
||||
@@ -32,13 +33,13 @@ USB_BYID="${USB_BYID:-${BACKUP_USB_BYID:-/dev/disk/by-id}}"
|
||||
# usb_related_present() corroborates. Non-USB removables are skipped. The
|
||||
# "TRAN unavailable" warning fires once per scan, not per device.
|
||||
usb_detect() {
|
||||
local out path="" mp="" label="" size="" model="" tran="" type="" children=""
|
||||
local out path="" mp="" label="" size="" model="" tran="" type="" fs="" parttype="" children=""
|
||||
local warned=0
|
||||
USB_MOUNTED=()
|
||||
USB_UNMOUNTED=()
|
||||
out="$(lsblk -J -o NAME,PATH,LABEL,MOUNTPOINT,RM,TYPE,TRAN,SIZE,MODEL 2>/dev/null)" || return 0
|
||||
out="$(lsblk -J -o NAME,PATH,LABEL,MOUNTPOINT,RM,TYPE,TRAN,SIZE,MODEL,FSTYPE,PARTTYPENAME 2>/dev/null)" || return 0
|
||||
|
||||
while IFS=$'\x1f' read -r path mp label size model tran type children; do
|
||||
while IFS=$'\x1f' read -r path mp label size model tran type fs parttype children; do
|
||||
[ -n "$path" ] || continue
|
||||
if [ "$tran" = "usb" ]; then
|
||||
:
|
||||
@@ -50,8 +51,14 @@ usb_detect() {
|
||||
else
|
||||
continue
|
||||
fi
|
||||
# EFI system partitions (Ventoy VTOYEFI, /boot/efi) are boot
|
||||
# machinery, never a storage target — skip them entirely, mounted or
|
||||
# not (else a 32M ESP can be offered as a sync/backup target).
|
||||
if [[ "${parttype^^}" == *EFI* ]] || [[ "${label^^}" == *EFI* ]] || [[ "${mp^^}" == */EFI ]]; then
|
||||
continue
|
||||
fi
|
||||
if [ -n "$mp" ]; then
|
||||
USB_MOUNTED+=("$mp")
|
||||
USB_MOUNTED+=("$mp|$label|$size|$model|$fs")
|
||||
elif [ "$children" = "0" ]; then
|
||||
USB_UNMOUNTED+=("$path|$label|$size|$model")
|
||||
fi
|
||||
@@ -60,6 +67,7 @@ usb_detect() {
|
||||
| select(.rm == true and (.type == "part" or .type == "disk"))
|
||||
| [.path, (.mountpoint // ""), (.label // ""), (.size // ""),
|
||||
(.model // ""), (.tran // ""), (.type // ""),
|
||||
(.fstype // ""), (.parttypename // ""),
|
||||
((.children // []) | length)]
|
||||
| join("\u001f")')
|
||||
}
|
||||
@@ -174,21 +182,24 @@ usb_pick_root() {
|
||||
|
||||
if [ ${#roots[@]} -eq 1 ]; then
|
||||
root="${roots[0]}"
|
||||
if ! confirm "${confirm_prefix} ${root%/}/${subfolder}?" n; then
|
||||
IFS='|' read -r mp label size model fs <<< "$root"
|
||||
if ! confirm "${confirm_prefix} ${mp%/}/${subfolder}? (${size}, ${label:-no label}, ${fs})" n; then
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "Multiple USB storages found:"
|
||||
for i in "${!roots[@]}"; do
|
||||
printf "%2d) %s\n" "$((i + 1))" "${roots[$i]}"
|
||||
IFS='|' read -r mp label size model fs <<< "${roots[$i]}"
|
||||
printf "%2d) %s (%s, %s, %s)\n" "$((i + 1))" "$mp" "$size" "${label:-no label}" "$fs"
|
||||
done
|
||||
read -rp "Use which one? [1-${#roots[@]}] (0 = skip): " resp || return 1
|
||||
if ! [[ "$resp" =~ ^[0-9]+$ ]] || (( resp < 1 || resp > ${#roots[@]} )); then
|
||||
return 1
|
||||
fi
|
||||
root="${roots[$((resp - 1))]}"
|
||||
IFS='|' read -r mp label size model fs <<< "$root"
|
||||
fi
|
||||
|
||||
USB_ROOT="$root"
|
||||
USB_ROOT="$mp"
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user