Skip to content

Instantly share code, notes, and snippets.

@HackingGate
Last active September 8, 2025 16:52
Show Gist options
  • Select an option

  • Save HackingGate/98f80db3645a3c383ea4fa179aaa4e25 to your computer and use it in GitHub Desktop.

Select an option

Save HackingGate/98f80db3645a3c383ea4fa179aaa4e25 to your computer and use it in GitHub Desktop.
Expand Root Filesystem for NanoPi R6S OpenWrt
#!/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