From 5d0c674becfdaf19e7df360737a7fd4b7a01d9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20unbrot=20P=C3=A4tzold?= Date: Sun, 26 Apr 2026 21:14:41 +0200 Subject: [PATCH] Add install.sh bootstrap script to create OEMDRV partition Shrinks a selected ext4/btrfs partition by 4 GiB, creates a new BTRFS partition labeled OEMDRV, mounts it to /opt/sys_config and clones the repository into it. Co-Authored-By: Claude Sonnet 4.6 --- system_setup/install.sh | 339 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100755 system_setup/install.sh diff --git a/system_setup/install.sh b/system_setup/install.sh new file mode 100755 index 0000000..47397e8 --- /dev/null +++ b/system_setup/install.sh @@ -0,0 +1,339 @@ +#!/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. + +SHRINK_MIB=4096 +OEMDRV_LABEL="OEMDRV" +MOUNT_POINT="/opt/sys_config" +MOUNT_OPTS="compress=zstd:6" +REPO_URL="https://gitea.dtext.online/obel1x/fedora-OEMDRV.git" +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; } + +require_root() { + [[ "$EUID" -eq 0 ]] || die "This script must be run as root." +} + +check_tools() { + 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 ]] || die "Missing required tools: ${missing[*]}" +} + +# ── 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 + +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) +} + +# ── 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/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 + +info "Scanning partitions for shrinkable ext4 / btrfs volumes..." +collect_partitions + +[[ $PT_IDX -gt 0 ]] || die "No ext4 or btrfs partitions found on this system." + +show_table + +# Count shrinkable +shrink_count=0 +for (( i=0; 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 +done + +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 +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." +echo +read -r -p " Type YES (uppercase) to confirm, anything else to abort: " CONFIRM +[[ "$CONFIRM" == "YES" ]] || { echo "Aborted."; exit 0; } + +# ── Geometry ────────────────────────────────────────────────────────────────── + +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)" + +# ── Unmount ─────────────────────────────────────────────────────────────────── + +WAS_MOUNTED=0 +ORIG_MNT="" +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 + +# ── 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." + + info "Shrinking ext4 filesystem to ${NEW_FS_MIB} MiB..." + resize2fs "$S_DEV" "${NEW_FS_MIB}M" || die "resize2fs failed." + +elif [[ "$S_FS" == "btrfs" ]]; then + info "Shrinking btrfs filesystem on $S_DEV to ${NEW_FS_MIB} MiB..." + 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 + +# ── 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." + +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." + +partprobe "$S_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 \ + | awk -F: '/^[0-9]/{n=$1} END{print n}') +[[ -n "$NEW_PNUM" ]] || die "Could not determine new partition number on $S_DISK." + +OEMDRV_DEV=$(new_part_device "$S_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 ──────────────────────────────────────────────── + +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 ────────────────────────────────────────────────────────── + +info "Cloning $REPO_URL into $MOUNT_POINT..." +cd "$MOUNT_POINT" || die "Cannot cd to $MOUNT_POINT." +git clone --progress --depth 1 "$REPO_URL" . || die "git clone failed." + +# ── Done ────────────────────────────────────────────────────────────────────── + +info "Done." +echo +echo " OEMDRV device : $OEMDRV_DEV" +echo " Mounted at : $MOUNT_POINT" +echo +echo "Next steps:" +echo " 1. cp $MOUNT_POINT/config/setup_system.conf.dist \\" +echo " $MOUNT_POINT/config/setup_system.conf" +echo " 2. Edit setup_system.conf with your domain, IPA/Nextcloud FQDNs, and paths." +echo " 3. Boot the Kickstart installer — it will detect the OEMDRV partition automatically." +echo