a09f9cfafa
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.
206 lines
8.5 KiB
Bash
206 lines
8.5 KiB
Bash
# lib/usb-lib.sh — shared USB-storage detection + pick flow. Used by
|
|
# pos-system-backup (post-verify USB copy) and pos-media-sync (Music sync).
|
|
# Requires common.sh helpers: log/warn/ok/section/confirm/run. Defines ONLY
|
|
# usb_* functions plus the USB_MOUNTED / USB_UNMOUNTED / USB_ROOT globals.
|
|
#
|
|
# Seams (env overrides; the BACKUP_* names are kept as aliases so existing
|
|
# system.env lines keep working):
|
|
# USB_MOUNT_BASE where an unmounted stick is offered to mount (default /media)
|
|
# USB_BYID /dev/disk/by-id dir consulted by usb_related_present
|
|
#
|
|
# API:
|
|
# 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>,
|
|
# fallback /media/usb-<devname>, umask=000);
|
|
# returns 0 = mounted (or user will do it
|
|
# manually), 1 = skipped/EOF
|
|
# usb_pick_root <confirm-prefix> <subfolder> <giveup-msg>
|
|
# full detect → mount-offer → pick loop; returns
|
|
# 0 with USB_ROOT set to the chosen mountpoint,
|
|
# 1 = skipped. Single-stick: confirm
|
|
# "<confirm-prefix> <root>/<subfolder>?" first.
|
|
|
|
USB_MOUNT_BASE="${USB_MOUNT_BASE:-${BACKUP_MOUNT_BASE:-/media}}"
|
|
USB_BYID="${USB_BYID:-${BACKUP_USB_BYID:-/dev/disk/by-id}}"
|
|
|
|
# ── USB detection ────────────────────────────────────────────────
|
|
# TRAN=="usb" is the per-device deciding signal; when TRAN is empty,
|
|
# 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="" fs="" parttype="" children=""
|
|
local warned=0
|
|
USB_MOUNTED=()
|
|
USB_UNMOUNTED=()
|
|
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 fs parttype children; do
|
|
[ -n "$path" ] || continue
|
|
if [ "$tran" = "usb" ]; then
|
|
:
|
|
elif [ -z "$tran" ] && usb_related_present; then
|
|
if [ "$warned" -eq 0 ]; then
|
|
warn "TRAN unavailable — assuming USB (lsusb/by-id corroboration)"
|
|
warned=1
|
|
fi
|
|
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|$label|$size|$model|$fs")
|
|
elif [ "$children" = "0" ]; then
|
|
USB_UNMOUNTED+=("$path|$label|$size|$model")
|
|
fi
|
|
done < <(printf '%s' "$out" | jq -r '
|
|
.. | objects
|
|
| select(.rm == true and (.type == "part" or .type == "disk"))
|
|
| [.path, (.mountpoint // ""), (.label // ""), (.size // ""),
|
|
(.model // ""), (.tran // ""), (.type // ""),
|
|
(.fstype // ""), (.parttypename // ""),
|
|
((.children // []) | length)]
|
|
| join("\u001f")')
|
|
}
|
|
|
|
# Whole-system "is any USB storage attached?" — udev by-id usb-* symlinks
|
|
# are definitive; lsusb text is a secondary hint for storage-ish devices.
|
|
usb_related_present() {
|
|
[ -n "$(ls "${USB_BYID}"/usb-* 2>/dev/null)" ] && return 0
|
|
command -v lsusb &>/dev/null \
|
|
&& lsusb 2>/dev/null | grep -qiE 'mass storage|card reader|flash disk|usb.*(disk|drive|storage)|reader|external'
|
|
}
|
|
|
|
# ── Mount an unmounted USB stick (CLI-box case) ──────────────────
|
|
# Returns 0 → caller re-scans (mounted now or the user will mount manually);
|
|
# 1 → skipped/EOF.
|
|
usb_mount_offer() {
|
|
local -a devs=("$@")
|
|
local i=0 dev="" label="" size="" model="" mp="" resp="" n=""
|
|
local -a chosen=()
|
|
|
|
if [ ${#devs[@]} -gt 1 ]; then
|
|
echo "Multiple unmounted USB storages found:"
|
|
for i in "${!devs[@]}"; do
|
|
IFS='|' read -r dev label size model <<< "${devs[$i]}"
|
|
printf "%2d) %s (%s, %s)\n" "$((i + 1))" "$dev" "$size" "${model:-no label}"
|
|
done
|
|
read -rp "Use which one? [1-${#devs[@]}] (0 = skip): " resp || return 1
|
|
if ! [[ "$resp" =~ ^[0-9]+$ ]] || (( resp < 1 || resp > ${#devs[@]} )); then
|
|
return 1
|
|
fi
|
|
chosen=("${devs[$((resp - 1))]}")
|
|
else
|
|
chosen=("${devs[0]}")
|
|
fi
|
|
|
|
IFS='|' read -r dev label size model <<< "${chosen[0]}"
|
|
label="${label//\//_}"
|
|
mp="${USB_MOUNT_BASE}/${label:-usb-$(basename "$dev")}"
|
|
if [ -d "$mp" ] && mountpoint -q "$mp" 2>/dev/null; then
|
|
n=2
|
|
while [ -d "${mp}-${n}" ] && mountpoint -q "${mp}-${n}" 2>/dev/null; do
|
|
n=$((n + 1))
|
|
done
|
|
mp="${mp}-${n}"
|
|
fi
|
|
|
|
echo "Found USB storage not mounted: $dev ($size, ${model:-no label})"
|
|
if confirm "Mount it at $mp (world-writable) so we can use it?" n; then
|
|
run sudo mkdir -p "$mp"
|
|
if ! run sudo mount -o umask=000 "$dev" "$mp"; then
|
|
run sudo mount "$dev" "$mp" || true
|
|
fi
|
|
if [ "${DRY_RUN:-0}" -eq 1 ] || mountpoint -q "$mp" 2>/dev/null; then
|
|
ok "Mounted $dev at $mp"
|
|
return 0
|
|
fi
|
|
warn "Mount failed — do it manually:"
|
|
log " sudo mkdir -p $mp && sudo mount $dev $mp"
|
|
else
|
|
warn "Mount it manually, then we'll continue:"
|
|
log " sudo mkdir -p $mp"
|
|
log " sudo mount $dev $mp"
|
|
fi
|
|
|
|
read -rp "Press Enter once mounted (or 's' to skip): " resp || return 1
|
|
case "$resp" in
|
|
s|S) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# ── Full pick flow ───────────────────────────────────────────────
|
|
# Loops detect → mount-offer (unmounted sticks) → "plug one in" re-scan until a
|
|
# mounted USB root is chosen or the user gives up. USB_ROOT is set on success.
|
|
usb_pick_root() {
|
|
local confirm_prefix="${1:-Use this storage for}"
|
|
local subfolder="${2:-}"
|
|
local giveup="${3:-skipping}"
|
|
local pass=0 resp="" i=0 root=""
|
|
local -a roots=()
|
|
local offered=0
|
|
|
|
while :; do
|
|
pass=$((pass + 1))
|
|
roots=()
|
|
|
|
usb_detect
|
|
roots=("${USB_MOUNTED[@]}")
|
|
|
|
if [ ${#roots[@]} -eq 0 ] && [ ${#USB_UNMOUNTED[@]} -gt 0 ]; then
|
|
if [ "$offered" -ge 1 ]; then
|
|
warn "USB stick still not mounted — ${giveup}"
|
|
return 1
|
|
fi
|
|
offered=1
|
|
usb_mount_offer "${USB_UNMOUNTED[@]}" || return 1
|
|
continue
|
|
fi
|
|
|
|
[ ${#roots[@]} -gt 0 ] && break
|
|
|
|
if [ "$pass" -ge 2 ]; then
|
|
warn "Still no USB storage detected — ${giveup}"
|
|
return 1
|
|
fi
|
|
warn "No USB storage detected"
|
|
read -rp "Plug a USB drive in now and press Enter to re-check (or 's' to skip): " resp || return 1
|
|
case "$resp" in
|
|
s|S) return 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [ ${#roots[@]} -eq 1 ]; then
|
|
root="${roots[0]}"
|
|
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
|
|
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="$mp"
|
|
return 0
|
|
}
|