Add free space detection to install.sh as alternative to partition shrinking

If a disk has unpartitioned space >= 4096 MiB, it is offered as a direct
target for the OEMDRV partition, avoiding any filesystem resize.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 14:59:14 +02:00
parent 9974facd45
commit 9e16162077
+122 -30
View File
@@ -79,6 +79,13 @@ PT_MNT=() # /home | -
PT_FREE=() # free MiB (integer)
PT_OK=() # 1 = shrinkable
# Parallel arrays for unpartitioned free space regions, indexed 0..FS_IDX-1
FS_IDX=0
FS_DISK=() # /dev/sda
FS_START=() # region start, MiB
FS_END=() # region end, MiB
FS_SIZE=() # region size, MiB
collect_partitions() {
local NAME SIZE FSTYPE LABEL UUID MOUNTPOINT TYPE PKNAME
@@ -124,6 +131,34 @@ collect_partitions() {
done < <(lsblk -o NAME,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINT,TYPE,PKNAME -p -P -n 2>/dev/null)
}
collect_free_space() {
local disk
while IFS= read -r disk; do
[[ -b "$disk" ]] || continue
while read -r s e sz; do
FS_DISK[$FS_IDX]="$disk"
FS_START[$FS_IDX]="$s"
FS_END[$FS_IDX]="$e"
FS_SIZE[$FS_IDX]="$sz"
(( FS_IDX++ )) || true
done < <(
parted -s "$disk" -m unit MiB print free 2>/dev/null \
| awk -F'[;:]' -v min="$SHRINK_MIB" '
$1+0 > 0 {
for (i = 1; i <= NF; i++) {
if ($i == "free") {
start=$2; end=$3; size=$4;
gsub(/MiB/,"",start); gsub(/MiB/,"",end); gsub(/MiB/,"",size);
s=int(start+0); e=int(end+0); sz=int(size+0);
if (sz >= min) print s " " e " " sz;
break
}
}
}'
)
done < <(lsblk -d -p -n -o NAME 2>/dev/null)
}
# ── Table display ─────────────────────────────────────────────────────────────
show_table() {
@@ -160,6 +195,25 @@ show_table() {
echo
}
show_free_table() {
[[ $FS_IDX -gt 0 ]] || return
echo
echo "Unpartitioned free space regions (>= ${SHRINK_MIB} MiB):"
hr
printf " %-4s %-14s %-12s %-12s %s\n" "#" "Disk" "Start (MiB)" "End (MiB)" "Size (MiB)"
hr
for (( i=0; i<FS_IDX; i++ )); do
printf " %-4s %-14s %-12s %-12s %s\n" \
"$((i+1))" \
"${FS_DISK[$i]}" \
"${FS_START[$i]}" \
"${FS_END[$i]}" \
"${FS_SIZE[$i]}"
done
hr
echo
}
# ── Partition geometry via parted -m ─────────────────────────────────────────
part_end_mib() {
@@ -187,39 +241,76 @@ new_part_device() {
require_root
check_tools
info "Scanning partitions for shrinkable ext4 / btrfs volumes..."
info "Scanning for shrinkable partitions and unpartitioned free space..."
collect_partitions
collect_free_space
[[ $PT_IDX -gt 0 ]] || die "No ext4 or btrfs partitions found on this system."
[[ $PT_IDX -gt 0 || $FS_IDX -gt 0 ]] \
|| die "No ext4 or btrfs partitions and no free disk space found on this system."
show_table
# Count shrinkable
# Count shrinkable partitions
shrink_count=0
for (( i=0; i<PT_IDX; i++ )); do [[ "${PT_OK[$i]}" == "1" ]] && (( shrink_count++ )) || true; done
[[ $shrink_count -gt 0 ]] || die "No shrinkable partitions found. A non-root ext4 or btrfs partition needs at least ${MIN_FREE_MIB} MiB free."
[[ $PT_IDX -gt 0 ]] && show_table
show_free_table
[[ $shrink_count -gt 0 || $FS_IDX -gt 0 ]] \
|| die "No shrinkable partitions and no free space found. A non-root ext4 or btrfs partition needs at least ${MIN_FREE_MIB} MiB free."
# ── Selection ─────────────────────────────────────────────────────────────────
MODE="" # "freespace" | "shrink"
SEL=-1
while true; do
echo
if [[ $FS_IDX -gt 0 && $shrink_count -gt 0 ]]; then
read -r -p "Enter f<n> to use free space, s<n> to shrink a partition, or q to quit: " INPUT
elif [[ $FS_IDX -gt 0 ]]; then
read -r -p "Enter number of free space region to use, or q to quit: " INPUT
[[ "$INPUT" =~ ^[0-9]+$ ]] && INPUT="f${INPUT}"
else
read -r -p "Enter number of partition to shrink, or q to quit: " INPUT
[[ "$INPUT" =~ ^[0-9]+$ ]] && INPUT="s${INPUT}"
fi
[[ "$INPUT" =~ ^[qQ]$ ]] && { echo "Aborted."; exit 0; }
[[ "$INPUT" =~ ^[0-9]+$ ]] || { echo " Please enter a number."; continue; }
local_i=$(( INPUT - 1 ))
if [[ "$INPUT" =~ ^[fF]([0-9]+)$ ]]; then
local_i=$(( ${BASH_REMATCH[1]} - 1 ))
(( local_i >= 0 && local_i < FS_IDX )) || { echo " Number out of range."; continue; }
MODE="freespace"; SEL=$local_i; break
elif [[ "$INPUT" =~ ^[sS]([0-9]+)$ ]]; then
local_i=$(( ${BASH_REMATCH[1]} - 1 ))
(( local_i >= 0 && local_i < PT_IDX )) || { echo " Number out of range."; continue; }
[[ "${PT_OK[$local_i]}" == "1" ]] || { echo " That partition cannot be shrunk (see marks above)."; continue; }
SEL=$local_i
break
MODE="shrink"; SEL=$local_i; break
else
echo " Invalid input."
fi
done
# ── Confirm ───────────────────────────────────────────────────────────────────
echo
if [[ "$MODE" == "freespace" ]]; then
WORK_DISK="${FS_DISK[$SEL]}"
OEMDRV_START="${FS_START[$SEL]}"
OEMDRV_END=$(( FS_START[$SEL] + SHRINK_MIB ))
echo " Disk : $WORK_DISK"
echo " Free region : ${FS_START[$SEL]} MiB ${FS_END[$SEL]} MiB (${FS_SIZE[$SEL]} MiB available)"
echo " New OEMDRV : ${OEMDRV_START} MiB ${OEMDRV_END} MiB (${SHRINK_MIB} MiB)"
echo " It will be mounted at $MOUNT_POINT and the repository cloned into it."
else
S_DEV="${PT_DEV[$SEL]}"
S_DISK="${PT_DISK[$SEL]}"
S_PNUM="${PT_PNUM[$SEL]}"
S_FS="${PT_FS[$SEL]}"
S_MNT="${PT_MNT[$SEL]}"
echo
WORK_DISK="$S_DISK"
echo " Partition : $S_DEV (${PT_FS[$SEL]}, ${PT_SIZE[$SEL]}, ${PT_FREE[$SEL]} MiB free)"
echo " Disk : $S_DISK Partition number: $S_PNUM"
echo " Mountpoint: $S_MNT"
@@ -227,17 +318,23 @@ echo
echo " $S_DEV will be shrunk by ${SHRINK_MIB} MiB."
echo " A new ${SHRINK_MIB} MiB BTRFS partition labeled OEMDRV will be created."
echo " It will be mounted at $MOUNT_POINT and the repository cloned into it."
fi
echo
read -r -p " Type YES (uppercase) to confirm, anything else to abort: " CONFIRM
[[ "$CONFIRM" == "YES" ]] || { echo "Aborted."; exit 0; }
# ── Geometry ──────────────────────────────────────────────────────────────────
# ── Shrink path: geometry + filesystem resize + partition table update ─────────
WAS_MOUNTED=0
ORIG_MNT=""
if [[ "$MODE" == "shrink" ]]; then
info "Reading partition geometry on $S_DISK..."
CURR_END=$(part_end_mib "$S_DISK" "$S_PNUM")
CURR_START=$(part_start_mib "$S_DISK" "$S_PNUM")
[[ -n "$CURR_END" && -n "$CURR_START" ]] || die "Could not read geometry for partition $S_PNUM on $S_DISK."
[[ -n "$CURR_END" && -n "$CURR_START" ]] \
|| die "Could not read geometry for partition $S_PNUM on $S_DISK."
NEW_END=$(( CURR_END - SHRINK_MIB ))
NEW_FS_MIB=$(( NEW_END - CURR_START ))
@@ -248,10 +345,6 @@ echo " Current partition : ${CURR_START} MiB ${CURR_END} MiB"
echo " After shrink : ${CURR_START} MiB ${NEW_END} MiB (filesystem: ${NEW_FS_MIB} MiB)"
echo " New OEMDRV : ${OEMDRV_START} MiB ${OEMDRV_END} MiB (${SHRINK_MIB} MiB)"
# ── Unmount ───────────────────────────────────────────────────────────────────
WAS_MOUNTED=0
ORIG_MNT=""
if [[ "$S_MNT" != "-" && -n "$S_MNT" ]]; then
info "Unmounting $S_DEV from $S_MNT..."
ORIG_MNT="$S_MNT"
@@ -259,8 +352,6 @@ if [[ "$S_MNT" != "-" && -n "$S_MNT" ]]; then
WAS_MOUNTED=1
fi
# ── Filesystem resize ─────────────────────────────────────────────────────────
if [[ "$S_FS" == "ext4" ]]; then
info "Running e2fsck on $S_DEV..."
e2fsck -f "$S_DEV" || die "e2fsck found errors on $S_DEV. Fix them before retrying."
@@ -278,25 +369,26 @@ elif [[ "$S_FS" == "btrfs" ]]; then
umount "$TMP_MNT" && rmdir "$TMP_MNT"
fi
# ── Partition table update ────────────────────────────────────────────────────
info "Shrinking partition $S_PNUM to ${NEW_END} MiB in partition table..."
parted -s "$S_DISK" resizepart "$S_PNUM" "${NEW_END}MiB" \
|| die "parted resizepart failed."
fi
info "Creating new OEMDRV partition (${OEMDRV_START}${OEMDRV_END} MiB)..."
parted -s "$S_DISK" mkpart primary btrfs "${OEMDRV_START}MiB" "${OEMDRV_END}MiB" \
|| die "parted mkpart failed. Check that the target area is free space on $S_DISK."
# ── Create OEMDRV partition ───────────────────────────────────────────────────
partprobe "$S_DISK"
info "Creating new OEMDRV partition (${OEMDRV_START}${OEMDRV_END} MiB) on $WORK_DISK..."
parted -s "$WORK_DISK" mkpart primary btrfs "${OEMDRV_START}MiB" "${OEMDRV_END}MiB" \
|| die "parted mkpart failed. Check that the target area is free space on $WORK_DISK."
partprobe "$WORK_DISK"
sleep 1
# Determine new partition number (highest on the disk after partprobe)
NEW_PNUM=$(parted -s "$S_DISK" -m unit MiB print 2>/dev/null \
NEW_PNUM=$(parted -s "$WORK_DISK" -m unit MiB print 2>/dev/null \
| awk -F: '/^[0-9]/{n=$1} END{print n}')
[[ -n "$NEW_PNUM" ]] || die "Could not determine new partition number on $S_DISK."
[[ -n "$NEW_PNUM" ]] || die "Could not determine new partition number on $WORK_DISK."
OEMDRV_DEV=$(new_part_device "$S_DISK" "$NEW_PNUM")
OEMDRV_DEV=$(new_part_device "$WORK_DISK" "$NEW_PNUM")
[[ -b "$OEMDRV_DEV" ]] || die "New partition device $OEMDRV_DEV not found after partprobe."
info "New partition device: $OEMDRV_DEV"
@@ -306,7 +398,7 @@ info "New partition device: $OEMDRV_DEV"
info "Formatting $OEMDRV_DEV as BTRFS (label: $OEMDRV_LABEL)..."
mkfs.btrfs -f -L "$OEMDRV_LABEL" "$OEMDRV_DEV" || die "mkfs.btrfs failed."
# ── Remount original partition ────────────────────────────────────────────────
# ── Remount original partition (shrink path only) ─────────────────────────────
if [[ $WAS_MOUNTED -eq 1 && -n "$ORIG_MNT" ]]; then
info "Remounting $S_DEV to $ORIG_MNT..."