Last active
September 8, 2025 16:52
-
-
Save HackingGate/98f80db3645a3c383ea4fa179aaa4e25 to your computer and use it in GitHub Desktop.
Expand Root Filesystem for NanoPi R6S OpenWrt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # ============================================================================= | |
| # OpenWrt Root Filesystem Expander | |
| # | |
| # This script automates resizing the main data partition and filesystem | |
| # on an OpenWrt device to use all available eMMC/SD card space. | |
| # | |
| # It now auto-detects the correct start sector and the correct device | |
| # to target for filesystem resizing (e.g., /dev/loop0). | |
| # | |
| # USAGE: | |
| # 1. Run the script: ./expand_rootfs.sh | |
| # 2. Reboot the device when prompted. | |
| # 3. Run the SAME script again after rebooting to resize the filesystem. | |
| # | |
| # ============================================================================= | |
| # --- Configuration --- | |
| # The target disk device. On NanoPi R6S, this is typically mmcblk1. | |
| DEVICE="/dev/mmcblk1" | |
| # The partition number to expand. This is almost always the 2nd partition. | |
| PARTITION_NUM="2" | |
| # The full partition device path. | |
| PARTITION_DEVICE="${DEVICE}p${PARTITION_NUM}" | |
| # CLI flags | |
| DEBUG=0 | |
| USE_PARTED=0 | |
| while [ "$#" -gt 0 ]; do | |
| case "$1" in | |
| -v|--debug) | |
| DEBUG=1; shift;; | |
| -p|--parted) | |
| USE_PARTED=1; shift;; | |
| --) | |
| shift; break;; | |
| -*) | |
| echo "Unknown option: $1"; exit 1;; | |
| *) | |
| break;; | |
| esac | |
| done | |
| # Helpers for logging | |
| log() { | |
| echo "$@" | |
| } | |
| debug() { | |
| [ "$DEBUG" -eq 1 ] && echo "[DEBUG] $@" | |
| } | |
| # Run a command verbosely when DEBUG=1 | |
| run_if_available() { | |
| # $1: command name, rest: args | |
| cmd="$1"; shift | |
| if command -v "$cmd" >/dev/null 2>&1; then | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[DEBUG] $cmd $*" | |
| fi | |
| "$cmd" "$@" | |
| return $? | |
| fi | |
| return 127 | |
| } | |
| # --- Functions --- | |
| check_filesystem_size() { | |
| # Get the size of the overlay filesystem in KB | |
| df_output=$(df -k /overlay | tail -n 1) | |
| fs_size_kb=$(echo "$df_output" | awk '{print $2}') | |
| debug "df -k /overlay => $df_output (size_kb=$fs_size_kb)" | |
| # If filesystem is larger than 1GB (1048576 KB), we assume it's already expanded. | |
| if [ "$fs_size_kb" -gt 1048576 ]; then | |
| echo "✅ Filesystem is already expanded to $(echo "$df_output" | awk '{print int($2/1024/1024)}') GB. No action needed." | |
| exit 0 | |
| fi | |
| } | |
| resize_partition_table() { | |
| echo "============================= WARNING ==================================" | |
| echo "This script will modify the partition table on ${DEVICE}." | |
| echo "It will delete partition #${PARTITION_NUM} and recreate it with the maximum" | |
| echo "available size, using the auto-detected start sector (${START_SECTOR})." | |
| echo "" | |
| echo "This is a potentially dangerous operation. Please ensure you have" | |
| echo "backed up your configuration." | |
| echo "========================================================================" | |
| printf "Do you want to continue? (yes/no): " | |
| read -r confirmation | |
| if [ "$confirmation" != "yes" ]; then | |
| echo "Aborted by user." | |
| exit 1 | |
| fi | |
| echo "▶️ Proceeding with fdisk..." | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[DEBUG] Current partition table before change (fdisk -l $DEVICE):" | |
| fdisk -l "$DEVICE" 2>&1 | sed 's/^/[DEBUG] /' | |
| fi | |
| # Use a "here document" to feed commands directly to fdisk | |
| fdisk_output=$(mktemp 2>/dev/null || echo "/tmp/fdisk.out.$$") | |
| fdisk "$DEVICE" <<EOF >"$fdisk_output" 2>&1 | |
| d | |
| $PARTITION_NUM | |
| n | |
| p | |
| $PARTITION_NUM | |
| $START_SECTOR | |
| N | |
| w | |
| EOF | |
| fdisk_rc=$? | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[DEBUG] fdisk scripted session output:" | |
| sed 's/^/[DEBUG] /' "$fdisk_output" | |
| fi | |
| rm -f "$fdisk_output" 2>/dev/null | |
| if [ $fdisk_rc -eq 0 ]; then | |
| echo "✅ Partition table successfully rewritten." | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[DEBUG] Partition table after change (fdisk -l $DEVICE):" | |
| fdisk -l "$DEVICE" 2>&1 | sed 's/^/[DEBUG] /' | |
| run_if_available partx -l "$DEVICE" 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| fi | |
| echo "A reboot is required for the system to see the new partition size." | |
| echo "" | |
| echo "➡️ Please run 'reboot' now, then run this script again after the" | |
| echo " device comes back online to complete the process." | |
| else | |
| echo "❌ ERROR: fdisk failed to rewrite the partition table." | |
| exit 1 | |
| fi | |
| } | |
| resize_partition_with_parted() { | |
| echo "▶️ Proceeding with parted to resize partition ${PARTITION_NUM} on ${DEVICE}..." | |
| if ! command -v parted >/dev/null 2>&1; then | |
| echo "❌ ERROR: 'parted' not found. Install it with: opkg update && opkg install parted" | |
| exit 1 | |
| fi | |
| echo "[INFO] Running: parted -f -s ${DEVICE} resizepart ${PARTITION_NUM} 100%" | |
| parted -f -s "$DEVICE" resizepart "$PARTITION_NUM" 100% | |
| if [ $? -ne 0 ]; then | |
| echo "❌ ERROR: parted failed to resize partition." | |
| exit 1 | |
| fi | |
| echo "✅ parted resized the partition. A reboot is recommended to ensure the kernel rereads the partition table." | |
| echo "➡️ Please run 'reboot' now, then run this script again after the device comes back online to complete the process." | |
| } | |
| resize_the_filesystem() { | |
| echo "▶️ Detecting device mounted at /overlay..." | |
| # Get the device mounted at /overlay (e.g., /dev/loop0 or /dev/mmcblk1p2) | |
| OVERLAY_DEVICE=$(df /overlay | tail -n 1 | awk '{print $1}') | |
| debug "df /overlay device => ${OVERLAY_DEVICE}" | |
| if [ "$DEBUG" -eq 1 ]; then | |
| run_if_available lsblk -f 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available losetup -l 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available losetup -j "$OVERLAY_DEVICE" 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available block info 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| echo "[DEBUG] mount entries for overlay:" | |
| grep -E "/overlay|overlayfs" /proc/mounts | sed 's/^/[DEBUG] /' || true | |
| fi | |
| # Check if overlay is mounted on a loop device | |
| RESIZE_CMD="" | |
| RESIZE_TARGET="" | |
| if echo "$OVERLAY_DEVICE" | grep -q "^/dev/loop"; then | |
| LOOP_DEV="$OVERLAY_DEVICE" | |
| echo "▶️ Overlay is mounted on loop device: ${LOOP_DEV}" | |
| # Prefer resizing the loop device filesystem (the writable area inside squashfs) | |
| LOOP_FSTYPE=$(lsblk -no FSTYPE "$LOOP_DEV" 2>/dev/null || echo "") | |
| debug "Loop device fs type: ${LOOP_FSTYPE}" | |
| if [ "${LOOP_FSTYPE}" = "f2fs" ]; then | |
| RESIZE_CMD="resize.f2fs" | |
| RESIZE_TARGET="${LOOP_DEV}" | |
| elif [ "${LOOP_FSTYPE}" = "ext4" ]; then | |
| RESIZE_CMD="resize2fs" | |
| RESIZE_TARGET="${LOOP_DEV}" | |
| else | |
| # Unknown fs on loop device; fall back to inspecting the partition | |
| echo "▶️ Unknown loop fs type ('${LOOP_FSTYPE}'), falling back to partition ${PARTITION_DEVICE}" | |
| RESIZE_TARGET="${PARTITION_DEVICE}" | |
| PART_FSTYPE=$(lsblk -no FSTYPE "$PARTITION_DEVICE" 2>/dev/null || echo "") | |
| if [ "${PART_FSTYPE}" = "f2fs" ]; then | |
| RESIZE_CMD="resize.f2fs" | |
| else | |
| RESIZE_CMD="resize2fs" | |
| fi | |
| fi | |
| else | |
| echo "▶️ Overlay is mounted directly on: ${OVERLAY_DEVICE}" | |
| if [ -z "$OVERLAY_DEVICE" ] || ! [ -b "$OVERLAY_DEVICE" ]; then | |
| echo "❌ WARNING: Could not automatically detect a valid block device for /overlay." | |
| echo " Attempting to fall back to the raw partition: ${PARTITION_DEVICE}" | |
| RESIZE_TARGET="${PARTITION_DEVICE}" | |
| else | |
| RESIZE_TARGET="${OVERLAY_DEVICE}" | |
| fi | |
| # detect fs type | |
| PART_FSTYPE=$(lsblk -no FSTYPE "$RESIZE_TARGET" 2>/dev/null || echo "") | |
| if [ "${PART_FSTYPE}" = "f2fs" ]; then | |
| RESIZE_CMD="resize.f2fs" | |
| else | |
| RESIZE_CMD="resize2fs" | |
| fi | |
| fi | |
| if [ -z "$RESIZE_CMD" ] || [ -z "$RESIZE_TARGET" ]; then | |
| echo "❌ ERROR: Could not determine resize command or target." | |
| exit 1 | |
| fi | |
| if ! command -v "$RESIZE_CMD" >/dev/null 2>&1; then | |
| echo "❌ ERROR: Required command '$RESIZE_CMD' not found." | |
| if [ "$RESIZE_CMD" = "resize.f2fs" ]; then | |
| echo " Install with: opkg update && opkg install f2fs-tools" | |
| else | |
| echo " Install with: opkg update && opkg install e2fsprogs" | |
| fi | |
| exit 1 | |
| fi | |
| echo "▶️ Attempting to resize the filesystem on ${RESIZE_TARGET} using ${RESIZE_CMD}..." | |
| debug "Invoking: ${RESIZE_CMD} ${RESIZE_TARGET}" | |
| ${RESIZE_CMD} "${RESIZE_TARGET}" | |
| rc=$? | |
| if [ $rc -eq 0 ]; then | |
| echo "✅ Filesystem resized successfully!" | |
| df -h /overlay | |
| return 0 | |
| fi | |
| # If resize failed, provide targeted advice | |
| echo "❌ ERROR: ${RESIZE_CMD} failed (exit ${rc})." | |
| if [ "$RESIZE_CMD" = "resize.f2fs" ] && echo "$RESIZE_TARGET" | grep -q "^/dev/loop"; then | |
| echo " The f2fs filesystem on the loop device may be in use." | |
| echo " You can try forcing online resize (if supported) or run the resize from a recovery/live environment." | |
| echo " Try: resize.f2fs -f ${RESIZE_TARGET} (only if you understand the risks)" | |
| echo " Or: boot a live image / initramfs, detach the loop, and run resize.f2fs on the loop device file." | |
| else | |
| echo " Please check the device is not busy and that the correct filesystem tool is used." | |
| fi | |
| echo "[DEBUG] Helpful info:" | |
| run_if_available losetup -l 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| exit $rc | |
| } | |
| # --- Main Logic --- | |
| echo "--- OpenWrt Filesystem Expander ---" | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[DEBUG] Script invoked with DEBUG=1" | |
| echo "[DEBUG] DEVICE=$DEVICE PARTITION_NUM=$PARTITION_NUM PARTITION_DEVICE=$PARTITION_DEVICE" | |
| run_if_available uname -a 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available df -h 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| run_if_available lsblk 2>/dev/null | sed 's/^/[DEBUG] /' || true | |
| fi | |
| # Dependency Check | |
| for cmd in fdisk resize2fs awk grep df; do | |
| if ! command -v $cmd >/dev/null 2>&1; then | |
| echo "❌ ERROR: Required command '$cmd' not found." | |
| case $cmd in | |
| fdisk) echo " Please install 'util-linux': opkg update && opkg install util-linux";; | |
| resize2fs) echo " Please install 'e2fsprogs': opkg update && opkg install e2fsprogs";; | |
| esac | |
| exit 1 | |
| fi | |
| done | |
| # Check if the partition exists | |
| if ! [ -b "$PARTITION_DEVICE" ]; then | |
| echo "❌ ERROR: Device ${PARTITION_DEVICE} not found." | |
| exit 1 | |
| fi | |
| # Get partition info from fdisk and parse its start and end sectors | |
| partition_info=$(fdisk -l "$DEVICE" 2>/dev/null | grep "^$PARTITION_DEVICE") | |
| if [ -z "$partition_info" ]; then | |
| echo "❌ ERROR: Could not get partition info for $PARTITION_DEVICE." | |
| exit 1 | |
| fi | |
| START_SECTOR=$(echo "$partition_info" | awk '{print $2}') | |
| partition_end_sector=$(echo "$partition_info" | awk '{print $3}') | |
| debug "Partition info line => '$partition_info'" | |
| debug "Parsed START_SECTOR=$START_SECTOR PARTITION_END=$partition_end_sector" | |
| # Get total disk info from fdisk and parse its end sector | |
| disk_info=$(fdisk -l "$DEVICE" 2>/dev/null | grep "sectors$" | head -n 1) | |
| if [ -z "$disk_info" ]; then | |
| echo "❌ ERROR: Could not get disk info for $DEVICE." | |
| exit 1 | |
| fi | |
| disk_end_sector=$(echo "$disk_info" | awk '{print $7}') | |
| debug "Disk info line => '$disk_info'" | |
| debug "Parsed DISK_END=$disk_end_sector" | |
| # Validate that the extracted values are numbers before doing math | |
| if ! [ "$disk_end_sector" -eq "$disk_end_sector" ] 2>/dev/null || \ | |
| ! [ "$partition_end_sector" -eq "$partition_end_sector" ] 2>/dev/null || \ | |
| ! [ "$START_SECTOR" -eq "$START_SECTOR" ] 2>/dev/null; then | |
| echo "❌ ERROR: Could not parse sector counts from fdisk output." | |
| echo " - Parsed Disk End Sector: '$disk_end_sector'" | |
| echo " - Parsed Partition Start Sector: '$START_SECTOR'" | |
| echo " - Parsed Partition End Sector: '$partition_end_sector'" | |
| exit 1 | |
| fi | |
| # Stage 1: The partition is small. We need to run fdisk. | |
| # We check if the partition's end is far from the disk's end (margin of 1000 sectors). | |
| if [ $(($disk_end_sector - $partition_end_sector)) -gt 1000 ]; then | |
| echo "Phase 1: Partition table needs resizing." | |
| if [ "$USE_PARTED" -eq 1 ]; then | |
| resize_partition_with_parted | |
| else | |
| resize_partition_table | |
| fi | |
| # Stage 2: The partition is large, but the filesystem might be small. | |
| else | |
| echo "Phase 2: Partition table looks correct. Checking filesystem." | |
| check_filesystem_size | |
| resize_the_filesystem | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment