forked from obel1x/fedora-OEMDRV
634 lines
26 KiB
Bash
Executable File
634 lines
26 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# SPDX-FileCopyrightText: Daniel Pätzold
|
||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||
#
|
||
# Bootstrap script: creates an OEMDRV partition by shrinking an existing
|
||
# ext4 or btrfs partition by 4 GiB, formats the freed space as BTRFS with
|
||
# label OEMDRV, mounts it to /opt/sys_config and clones the repository.
|
||
#
|
||
# Run as root on a target machine before any Kickstart installation.
|
||
|
||
[[ "$EUID" -eq 0 ]] || { echo "ERROR: This script must be run as root." >&2; exit 1; }
|
||
|
||
SHRINK_MIB=4096
|
||
OEMDRV_LABEL="OEMDRV"
|
||
MOUNT_POINT="/opt/sys_config"
|
||
MOUNT_OPTS="compress=zstd:6"
|
||
REPO_URL="${REPO_URL:-https://gitea.dtext.online/obel1x/fedora-OEMDRV.git}"
|
||
REPO_BRANCH="${REPO_BRANCH:-main}"
|
||
MIN_FREE_MIB=$(( SHRINK_MIB + 512 )) # require 512 MiB headroom above the shrink size
|
||
|
||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||
|
||
die() { echo; echo "ERROR: $*" >&2; exit 1; }
|
||
info() { echo; echo ">>> $*"; }
|
||
hr() { printf '%.0s─' {1..100}; echo; }
|
||
|
||
finish_install() {
|
||
local dev="$1"
|
||
|
||
chown root:root "$MOUNT_POINT" -R
|
||
chmod ug=rwX,o=rX "$MOUNT_POINT" -R
|
||
chmod o+w "$MOUNT_POINT/config" "$MOUNT_POINT/config.d"
|
||
|
||
# Create an empty ks.cfg at the OEMDRV root so non-root can overwrite it
|
||
# with configure.sh (the OEMDRV root itself is not world-writable).
|
||
touch "$MOUNT_POINT/ks.cfg"
|
||
chmod o+w "$MOUNT_POINT/ks.cfg"
|
||
|
||
info "Done."
|
||
echo
|
||
echo " OEMDRV device : $dev"
|
||
echo " Mounted at : $MOUNT_POINT"
|
||
echo
|
||
|
||
CONF_SCRIPT="$MOUNT_POINT/system_setup/configure.sh"
|
||
|
||
echo
|
||
read -r -p "Run configure.sh now to set up your environment? [y/N]: " RUN_CONF
|
||
if [[ "${RUN_CONF,,}" == "y" ]]; then
|
||
if [[ -n "$SUDO_USER" && "$SUDO_USER" != "root" ]]; then
|
||
info "Running configure.sh as user '$SUDO_USER'..."
|
||
su - "$SUDO_USER" -c "DISPLAY='${DISPLAY}' WAYLAND_DISPLAY='${WAYLAND_DISPLAY}' bash '$CONF_SCRIPT'"
|
||
else
|
||
info "Running configure.sh as root..."
|
||
bash "$CONF_SCRIPT"
|
||
fi
|
||
else
|
||
echo
|
||
echo "Next steps:"
|
||
echo " 1. Run: bash $CONF_SCRIPT"
|
||
echo " 2. Boot the Kickstart installer — it will detect the OEMDRV partition automatically."
|
||
echo
|
||
fi
|
||
}
|
||
|
||
do_clone_and_done() {
|
||
local dev="$1"
|
||
|
||
info "Cloning $REPO_URL into $MOUNT_POINT..."
|
||
cd "$MOUNT_POINT" || die "Cannot cd to $MOUNT_POINT."
|
||
git clone --progress --depth 1 -b $REPO_BRANCH "$REPO_URL" . || die "git clone failed."
|
||
${MOUNT_POINT}/system_setup/setup_system.inc.sh
|
||
finish_install "$dev"
|
||
}
|
||
|
||
require_root() {
|
||
[[ "$EUID" -eq 0 ]] || die "This script must be run as root."
|
||
}
|
||
|
||
check_tools() {
|
||
declare -A tool_pkg=(
|
||
[lsblk]="util-linux" [blkid]="util-linux"
|
||
[parted]="parted" [partprobe]="parted"
|
||
[mkfs.btrfs]="btrfs-progs" [git]="git"
|
||
[e2fsck]="e2fsprogs" [resize2fs]="e2fsprogs"
|
||
[tune2fs]="e2fsprogs"
|
||
)
|
||
local missing=()
|
||
for tool in lsblk blkid parted partprobe mkfs.btrfs git e2fsck resize2fs tune2fs; do
|
||
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
|
||
done
|
||
[[ ${#missing[@]} -eq 0 ]] && return 0
|
||
|
||
echo "Missing required tools: ${missing[*]}"
|
||
local pkgs=()
|
||
for tool in "${missing[@]}"; do
|
||
local pkg="${tool_pkg[$tool]}"
|
||
[[ " ${pkgs[*]} " != *" $pkg "* ]] && pkgs+=("$pkg")
|
||
done
|
||
|
||
read -r -p " Install missing packages (${pkgs[*]}) with dnf? [y/N]: " ans
|
||
if [[ "${ans,,}" == "y" ]]; then
|
||
dnf install -y "${pkgs[@]}" || die "Package installation failed."
|
||
local still_missing=()
|
||
for tool in "${missing[@]}"; do
|
||
command -v "$tool" >/dev/null 2>&1 || still_missing+=("$tool")
|
||
done
|
||
[[ ${#still_missing[@]} -eq 0 ]] || die "Still missing after install: ${still_missing[*]}"
|
||
else
|
||
die "Missing required tools: ${missing[*]}"
|
||
fi
|
||
}
|
||
|
||
# Returns 0 if the remote install.sh matches this script's checksum,
|
||
# 1 if the URL is unreachable or the file cannot be downloaded,
|
||
# 2 if the checksum does not match.
|
||
check_repo_url() {
|
||
local tmpdir sum_remote sum_local
|
||
|
||
tmpdir=$(mktemp -d /tmp/oemdrv_repocheck.XXXXXX)
|
||
|
||
if ! curl -fsSL "${REPO_URL%.git}/raw/branch/${REPO_BRANCH}/system_setup/install.sh" \
|
||
-o "$tmpdir/install.sh" 2>/dev/null; then
|
||
rm -rf "$tmpdir"
|
||
return 1
|
||
fi
|
||
|
||
sum_remote=$(sha256sum "$tmpdir/install.sh" | awk '{print $1}')
|
||
sum_local=$(sha256sum "$0" | awk '{print $1}')
|
||
rm -rf "$tmpdir"
|
||
|
||
[[ "$sum_remote" == "$sum_local" ]] || return 2
|
||
return 0
|
||
}
|
||
|
||
# ── Free-space helpers ────────────────────────────────────────────────────────
|
||
|
||
# Free MiB for a mounted device via df
|
||
mounted_free_mib() {
|
||
df --output=avail -BM "$1" 2>/dev/null | awk 'NR==2{gsub("M",""); print $1+0}'
|
||
}
|
||
|
||
# Free MiB for an unmounted ext4 device via tune2fs (no mount needed)
|
||
ext4_free_mib() {
|
||
local dev="$1" fb bs
|
||
fb=$(tune2fs -l "$dev" 2>/dev/null | awk '/^Free blocks:/{print $3}')
|
||
bs=$(tune2fs -l "$dev" 2>/dev/null | awk '/^Block size:/{print $3}')
|
||
[[ -n "$fb" && -n "$bs" ]] || { echo 0; return; }
|
||
echo $(( fb * bs / 1048576 ))
|
||
}
|
||
|
||
# Free MiB for a btrfs device – mounts temporarily if not already mounted
|
||
btrfs_free_mib() {
|
||
local dev="$1" mnt free tmp=0
|
||
mnt=$(lsblk -n -o MOUNTPOINT "$dev" 2>/dev/null | grep -v '^$' | head -1)
|
||
if [[ -z "$mnt" ]]; then
|
||
mnt=$(mktemp -d /tmp/oemdrv_check.XXXXXX)
|
||
mount -o ro "$dev" "$mnt" 2>/dev/null && tmp=1 || { rmdir "$mnt"; echo 0; return; }
|
||
fi
|
||
free=$(mounted_free_mib "$mnt")
|
||
[[ $tmp -eq 1 ]] && { umount "$mnt"; rmdir "$mnt"; }
|
||
echo "${free:-0}"
|
||
}
|
||
|
||
# ── Partition discovery ───────────────────────────────────────────────────────
|
||
|
||
# Parallel arrays, indexed 0..PT_IDX-1
|
||
PT_IDX=0
|
||
PT_DEV=() # /dev/sda2
|
||
PT_DISK=() # /dev/sda
|
||
PT_PNUM=() # 2
|
||
PT_SIZE=() # "100G"
|
||
PT_FS=() # ext4 | btrfs
|
||
PT_LABEL=() # home | -
|
||
PT_UUID=() # xxxxxxxx | -
|
||
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
|
||
|
||
while IFS= read -r line; do
|
||
NAME="" SIZE="" FSTYPE="" LABEL="" UUID="" MOUNTPOINT="" TYPE="" PKNAME=""
|
||
eval "$line"
|
||
|
||
[[ "$TYPE" != "part" ]] && continue
|
||
[[ "$FSTYPE" != "ext4" && "$FSTYPE" != "btrfs" ]] && continue
|
||
[[ "$LABEL" == "$OEMDRV_LABEL" ]] && continue
|
||
[[ -z "$PKNAME" ]] && continue
|
||
|
||
local disk="/dev/${PKNAME##*/}"
|
||
local pnum
|
||
pnum=$(cat /sys/class/block/"${NAME##/dev/}"/partition 2>/dev/null) || continue
|
||
|
||
# Free space check
|
||
local free_mib
|
||
if [[ -n "$MOUNTPOINT" ]]; then
|
||
free_mib=$(mounted_free_mib "$NAME")
|
||
elif [[ "$FSTYPE" == "ext4" ]]; then
|
||
free_mib=$(ext4_free_mib "$NAME")
|
||
else
|
||
free_mib=$(btrfs_free_mib "$NAME")
|
||
fi
|
||
free_mib=${free_mib:-0}
|
||
|
||
# Shrinkable: not /, enough free space
|
||
local ok=0
|
||
[[ "$MOUNTPOINT" != "/" && "$free_mib" -ge "$MIN_FREE_MIB" ]] && ok=1
|
||
|
||
PT_DEV[$PT_IDX]="$NAME"
|
||
PT_DISK[$PT_IDX]="$disk"
|
||
PT_PNUM[$PT_IDX]="$pnum"
|
||
PT_SIZE[$PT_IDX]="$SIZE"
|
||
PT_FS[$PT_IDX]="$FSTYPE"
|
||
PT_LABEL[$PT_IDX]="${LABEL:--}"
|
||
PT_UUID[$PT_IDX]="${UUID:--}"
|
||
PT_MNT[$PT_IDX]="${MOUNTPOINT:--}"
|
||
PT_FREE[$PT_IDX]="$free_mib"
|
||
PT_OK[$PT_IDX]="$ok"
|
||
(( PT_IDX++ )) || true
|
||
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") {
|
||
gsub(/MiB/,"",$2); gsub(/MiB/,"",$3);
|
||
e=int($3+0);
|
||
raw_s=$2+0;
|
||
s=int(raw_s)+(raw_s>int(raw_s)?1:0);
|
||
if (s < 1) s = 1;
|
||
sz=e-s;
|
||
if (sz >= min) print s " " e " " sz;
|
||
break
|
||
}
|
||
}
|
||
}'
|
||
)
|
||
done < <(lsblk -d -p -n -o NAME 2>/dev/null)
|
||
}
|
||
|
||
# ── Table display ─────────────────────────────────────────────────────────────
|
||
|
||
show_table() {
|
||
hr
|
||
printf " %-4s %-14s %-6s %-8s %-10s %-6s %-18s %-36s %s\n" \
|
||
"#" "Device" "Disk" "Size" "Free (MiB)" "FS" "Label" "UUID" "Mountpoint"
|
||
hr
|
||
local i mark note
|
||
for (( i=0; i<PT_IDX; i++ )); do
|
||
mark=" "
|
||
note=""
|
||
if [[ "${PT_OK[$i]}" == "1" ]]; then
|
||
mark="* "
|
||
elif [[ "${PT_MNT[$i]}" == "/" ]]; then
|
||
note=" (root — skip)"
|
||
else
|
||
note=" (not enough free space)"
|
||
fi
|
||
printf "%s%-4s %-14s %-6s %-8s %-10s %-6s %-18s %-36s %s%s\n" \
|
||
"$mark" \
|
||
"$((i+1))" \
|
||
"${PT_DEV[$i]}" \
|
||
"${PT_DISK[$i]##/dev/}" \
|
||
"${PT_SIZE[$i]}" \
|
||
"${PT_FREE[$i]}" \
|
||
"${PT_FS[$i]}" \
|
||
"${PT_LABEL[$i]}" \
|
||
"${PT_UUID[$i]}" \
|
||
"${PT_MNT[$i]}" \
|
||
"$note"
|
||
done
|
||
hr
|
||
printf " * = shrinkable: will be reduced by %s MiB to create OEMDRV\n" "$SHRINK_MIB"
|
||
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() {
|
||
parted -s "$1" -m unit MiB print 2>/dev/null \
|
||
| awk -F: -v p="$2" '$1==p{gsub("MiB","",$3); printf "%.0f\n",$3; exit}'
|
||
}
|
||
|
||
part_start_mib() {
|
||
parted -s "$1" -m unit MiB print 2>/dev/null \
|
||
| awk -F: -v p="$2" '$1==p{gsub("MiB","",$2); printf "%.0f\n",$2; exit}'
|
||
}
|
||
|
||
# Device name for a new partition on a given disk
|
||
new_part_device() {
|
||
local disk="$1" pnum="$2"
|
||
if [[ "$disk" =~ (nvme|mmcblk) ]]; then
|
||
echo "${disk}p${pnum}"
|
||
else
|
||
echo "${disk}${pnum}"
|
||
fi
|
||
}
|
||
|
||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||
|
||
require_root
|
||
check_tools
|
||
|
||
# ── Check for existing OEMDRV partition ───────────────────────────────────────
|
||
|
||
EXISTING_OEMDRV_DEV=$(blkid -L "$OEMDRV_LABEL" 2>/dev/null || true)
|
||
if [[ -n "$EXISTING_OEMDRV_DEV" ]]; then
|
||
echo
|
||
echo "Found existing '$OEMDRV_LABEL' partition: $EXISTING_OEMDRV_DEV"
|
||
read -r -p " Use this partition and overwrite its install files? [y/N]: " ans
|
||
if [[ "${ans,,}" == "y" ]]; then
|
||
EXISTING_MNT=$(lsblk -n -o MOUNTPOINT "$EXISTING_OEMDRV_DEV" 2>/dev/null | grep -v '^$' | head -1)
|
||
if [[ -n "$EXISTING_MNT" ]]; then
|
||
echo " Partition is already mounted at $EXISTING_MNT — using that mountpoint."
|
||
MOUNT_POINT="$EXISTING_MNT"
|
||
else
|
||
info "Mounting $EXISTING_OEMDRV_DEV to $MOUNT_POINT..."
|
||
[[ -d "$MOUNT_POINT" ]] || mkdir -p "$MOUNT_POINT"
|
||
mount -o "$MOUNT_OPTS" "$EXISTING_OEMDRV_DEV" "$MOUNT_POINT" || die "mount failed."
|
||
fi
|
||
|
||
if [[ -f "$MOUNT_POINT/system_setup/setup_system.inc.sh" && -f "$MOUNT_POINT/config/setup_system.conf" ]]; then
|
||
info "Reading existing configuration..."
|
||
$MOUNT_POINT/system_setup/setup_system.inc.sh
|
||
fi
|
||
|
||
# ── Check existing git repository origin ──────────────────────────────
|
||
if git -C "$MOUNT_POINT" rev-parse --git-dir >/dev/null 2>&1; then
|
||
EXIST_URL=$(git -C "$MOUNT_POINT" remote get-url origin 2>/dev/null || true)
|
||
EXIST_BRANCH=$(git -C "$MOUNT_POINT" symbolic-ref --short HEAD 2>/dev/null \
|
||
|| git -C "$MOUNT_POINT" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
||
if [[ -n "$EXIST_URL" && ( "$EXIST_URL" != "$REPO_URL" || "$EXIST_BRANCH" != "$REPO_BRANCH" ) ]]; then
|
||
echo
|
||
echo " The existing repository differs from the configured values:"
|
||
printf " %-12s %-55s %s\n" "" "Origin" "Branch"
|
||
printf " %-12s %-55s %s\n" "Existing:" "$EXIST_URL" "$EXIST_BRANCH"
|
||
printf " %-12s %-55s %s\n" "Configured:" "$REPO_URL" "$REPO_BRANCH"
|
||
echo
|
||
echo " How should this be resolved?"
|
||
echo " 1) Keep existing origin/branch — pull latest from $EXIST_URL / $EXIST_BRANCH"
|
||
echo " 2) Switch to configured origin — migrate to $REPO_URL / $REPO_BRANCH (preserves local files)"
|
||
echo " 3) Fresh clone from configured origin — clears all existing content"
|
||
read -r -p " Choice [1/2/3]: " GIT_CHOICE
|
||
case "${GIT_CHOICE}" in
|
||
1)
|
||
REPO_URL="$EXIST_URL"
|
||
REPO_BRANCH="$EXIST_BRANCH"
|
||
info "Fetching latest from $REPO_URL (branch: $REPO_BRANCH)..."
|
||
git -C "$MOUNT_POINT" fetch --depth 1 origin "$REPO_BRANCH" \
|
||
|| die "git fetch failed."
|
||
git -C "$MOUNT_POINT" checkout -B "$REPO_BRANCH" FETCH_HEAD \
|
||
|| die "git checkout failed."
|
||
$MOUNT_POINT/system_setup/setup_system.inc.sh
|
||
finish_install "$EXISTING_OEMDRV_DEV"
|
||
exit 0
|
||
;;
|
||
2)
|
||
info "Switching origin to $REPO_URL (branch: $REPO_BRANCH)..."
|
||
git -C "$MOUNT_POINT" remote set-url origin "$REPO_URL" \
|
||
|| die "git remote set-url failed."
|
||
git -C "$MOUNT_POINT" fetch --depth 1 origin "$REPO_BRANCH" \
|
||
|| die "git fetch failed."
|
||
git -C "$MOUNT_POINT" checkout -B "$REPO_BRANCH" FETCH_HEAD \
|
||
|| die "git checkout failed."
|
||
$MOUNT_POINT/system_setup/setup_system.inc.sh
|
||
finish_install "$EXISTING_OEMDRV_DEV"
|
||
exit 0
|
||
;;
|
||
*)
|
||
# Option 3 or anything else: fall through to clear + fresh clone
|
||
;;
|
||
esac
|
||
fi
|
||
fi
|
||
|
||
if [[ -n "$(ls -A "$MOUNT_POINT" 2>/dev/null)" ]]; then
|
||
info "Clearing existing content in $MOUNT_POINT before fresh clone..."
|
||
find "$MOUNT_POINT" -mindepth 1 -delete
|
||
fi
|
||
|
||
do_clone_and_done "$EXISTING_OEMDRV_DEV"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
info "Verifying repository URL..."
|
||
check_repo_url
|
||
case $? in
|
||
1) echo
|
||
echo "WARNING: '$REPO_URL' branch '${REPO_BRANCH}' is not a reachable git repository."
|
||
read -r -p " Continue anyway? [y/N]: " ans
|
||
[[ "${ans,,}" == "y" ]] || { echo "Aborted."; exit 0; }
|
||
;;
|
||
2) echo
|
||
echo "WARNING: The checksum of this script does not match 'system_setup/install.sh'"
|
||
echo " at '$REPO_URL' branch '${REPO_BRANCH}'."
|
||
echo " You may be running an outdated or modified version of install.sh."
|
||
read -r -p " Continue anyway? [y/N]: " ans
|
||
[[ "${ans,,}" == "y" ]] || { echo "Aborted."; exit 0; }
|
||
;;
|
||
esac
|
||
|
||
info "Scanning for shrinkable partitions and unpartitioned free space..."
|
||
collect_partitions
|
||
collect_free_space
|
||
|
||
[[ $PT_IDX -gt 0 || $FS_IDX -gt 0 ]] \
|
||
|| die "No ext4 or btrfs partitions and no free disk space found on this system."
|
||
|
||
# Count shrinkable partitions
|
||
shrink_count=0
|
||
for (( i=0; i<PT_IDX; i++ )); do [[ "${PT_OK[$i]}" == "1" ]] && (( shrink_count++ )) || true; done
|
||
|
||
[[ $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 || { echo; echo "Aborted."; exit 0; }
|
||
elif [[ $FS_IDX -gt 0 ]]; then
|
||
read -r -p "Enter number of free space region to use, or q to quit: " INPUT || { echo; echo "Aborted."; exit 0; }
|
||
[[ "$INPUT" =~ ^[0-9]+$ ]] && INPUT="f${INPUT}"
|
||
else
|
||
read -r -p "Enter number of partition to shrink, or q to quit: " INPUT || { echo; echo "Aborted."; exit 0; }
|
||
[[ "$INPUT" =~ ^[0-9]+$ ]] && INPUT="s${INPUT}"
|
||
fi
|
||
|
||
[[ "$INPUT" =~ ^[qQ]$ ]] && { echo "Aborted."; exit 0; }
|
||
|
||
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; }
|
||
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]}"
|
||
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"
|
||
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; }
|
||
|
||
# ── 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."
|
||
|
||
NEW_END=$(( CURR_END - SHRINK_MIB ))
|
||
NEW_FS_MIB=$(( NEW_END - CURR_START ))
|
||
OEMDRV_START=$NEW_END
|
||
OEMDRV_END=$CURR_END
|
||
|
||
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)"
|
||
|
||
if [[ "$S_FS" == "ext4" ]]; then
|
||
# ext4 requires offline resize — unmount first
|
||
if [[ "$S_MNT" != "-" && -n "$S_MNT" ]]; then
|
||
info "Unmounting $S_DEV from $S_MNT..."
|
||
ORIG_MNT="$S_MNT"
|
||
umount "$S_DEV" || die "Cannot unmount $S_DEV. Close all open files and try again."
|
||
WAS_MOUNTED=1
|
||
fi
|
||
info "Running e2fsck on $S_DEV..."
|
||
e2fsck -f "$S_DEV" || die "e2fsck found errors on $S_DEV. Fix them before retrying."
|
||
|
||
info "Shrinking ext4 filesystem to ${NEW_FS_MIB} MiB..."
|
||
resize2fs "$S_DEV" "${NEW_FS_MIB}M" || die "resize2fs failed."
|
||
|
||
elif [[ "$S_FS" == "btrfs" ]]; then
|
||
# btrfs supports online resize — use existing mount point if available,
|
||
# otherwise mount temporarily
|
||
info "Shrinking btrfs filesystem on $S_DEV to ${NEW_FS_MIB} MiB..."
|
||
if [[ "$S_MNT" != "-" && -n "$S_MNT" ]]; then
|
||
btrfs filesystem resize "${NEW_FS_MIB}m" "$S_MNT" \
|
||
|| die "btrfs filesystem resize failed."
|
||
else
|
||
TMP_MNT=$(mktemp -d /tmp/oemdrv_btrfs.XXXXXX)
|
||
mount "$S_DEV" "$TMP_MNT" || { rmdir "$TMP_MNT"; die "Cannot mount $S_DEV for btrfs resize."; }
|
||
btrfs filesystem resize "${NEW_FS_MIB}m" "$TMP_MNT" \
|
||
|| { umount "$TMP_MNT"; rmdir "$TMP_MNT"; die "btrfs filesystem resize failed."; }
|
||
sync
|
||
umount "$TMP_MNT" && rmdir "$TMP_MNT"
|
||
fi
|
||
fi
|
||
|
||
info "Shrinking partition $S_PNUM to ${NEW_END} MiB in partition table..."
|
||
SECTOR_SIZE=$(cat /sys/block/"${S_DISK##*/}"/queue/hw_sector_size 2>/dev/null || echo 512)
|
||
NEW_END_SEC=$(( NEW_END * 1048576 / SECTOR_SIZE ))
|
||
sfdisk -d "$S_DISK" | awk -v dev="$S_DEV" -v new_end="$NEW_END_SEC" '
|
||
$0 ~ "^" dev " : " {
|
||
match($0, /start= *([0-9]+)/, a)
|
||
sub(/size= *[0-9]+/, "size= " (new_end - a[1]+0))
|
||
}
|
||
{ print }
|
||
' | sfdisk --no-reread --force "$S_DISK" \
|
||
|| die "sfdisk partition resize failed."
|
||
fi
|
||
|
||
# ── Create OEMDRV partition ───────────────────────────────────────────────────
|
||
|
||
info "Creating new OEMDRV partition (${OEMDRV_START}–${OEMDRV_END} MiB) on $WORK_DISK..."
|
||
parted -s "$WORK_DISK" mkpart anacondainstall 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
|
||
|
||
# Find the partition whose start matches OEMDRV_START (±1 MiB for alignment)
|
||
NEW_PNUM=$(parted -s "$WORK_DISK" -m unit MiB print 2>/dev/null \
|
||
| awk -F: -v s="$OEMDRV_START" '
|
||
/^[0-9]/ { gsub(/MiB/,"",$2); if (int($2+0) >= s-1 && int($2+0) <= s+1) { print $1; exit } }')
|
||
[[ -n "$NEW_PNUM" ]] || die "Could not determine new partition number on $WORK_DISK."
|
||
|
||
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"
|
||
|
||
# ── Format ────────────────────────────────────────────────────────────────────
|
||
|
||
info "Formatting $OEMDRV_DEV as BTRFS (label: $OEMDRV_LABEL)..."
|
||
mkfs.btrfs -f -L "$OEMDRV_LABEL" "$OEMDRV_DEV" || die "mkfs.btrfs failed."
|
||
|
||
# ── Remount original partition (shrink path only) ─────────────────────────────
|
||
|
||
if [[ $WAS_MOUNTED -eq 1 && -n "$ORIG_MNT" ]]; then
|
||
info "Remounting $S_DEV to $ORIG_MNT..."
|
||
mount "$S_DEV" "$ORIG_MNT" \
|
||
|| echo "WARNING: Could not remount $S_DEV to $ORIG_MNT — remount manually."
|
||
fi
|
||
|
||
# ── Mount OEMDRV ──────────────────────────────────────────────────────────────
|
||
|
||
info "Mounting $OEMDRV_DEV to $MOUNT_POINT (options: $MOUNT_OPTS)..."
|
||
[[ -d "$MOUNT_POINT" ]] || mkdir -p "$MOUNT_POINT"
|
||
mount -o "$MOUNT_OPTS" "$OEMDRV_DEV" "$MOUNT_POINT" || die "mount failed."
|
||
|
||
# ── Clone repository + done ───────────────────────────────────────────────────
|
||
|
||
do_clone_and_done "$OEMDRV_DEV"
|