Files
ps5-linux-image/distros/arch/grow-rootfs
2026-07-11 13:55:37 +02:00

92 lines
3.9 KiB
Bash

#!/bin/bash
# Grows the root partition + filesystem to fill the disk on first boot.
# Self-disables after success.
#
# Uses sfdisk + partx + resize2fs (all in util-linux + e2fsprogs, both
# present in the minimal Arch/SteamOS base). Earlier versions called
# growpart from cloud-utils — but that package isn't in the SteamOS
# recovery image, so the unit failed with "growpart: command not found"
# and rootfs stayed at the dd'd image size (~14 GB) on any disk larger
# than that.
set -e
ROOT_DEV=$(findmnt -no SOURCE /)
DISK="/dev/$(lsblk -ndo PKNAME "$ROOT_DEV")"
# PS5 M.2 drive appear as /dev/nvme*. Do not modify partition table,
# because it contains m2_init PS5 offset. Only USB drive (/dev/sd*)
# are safe for automatic grow root partition.
if [[ "$(basename "$DISK")" == nvme* ]]; then
echo "grow-rootfs: M.2 detected, skipping"
systemctl disable grow-rootfs.service
exit 0
fi
PART_NUM=$(cat /sys/class/block/$(basename "$ROOT_DEV")/partition)
# Our images dd at their built size (~14 GB) onto a much larger USB. The GPT
# backup header is therefore at byte ~built-size, not at end of disk. Move
# the backup header to end-of-disk first so partition table growth works.
sgdisk -e "$DISK"
# If a previous flash left behind partitions BEYOND our rootfs + boot pair
# (e.g. a stale `linux-home` partset at part 3 from a prior SteamOS install
# on the same drive), the partition extend refuses to grow into the space
# already claimed by the leftover partition. Delete every partition with a
# number higher than 2 — we only ever create partitions 1 (rootfs) + 2 (boot).
# Important: sgdisk only rewrites the on-disk GPT; we must ALSO drop those
# stale entries from the kernel's in-memory partition table via partx -d,
# or BLKPG_RESIZE_PARTITION below returns EBUSY because the kernel still
# thinks sda1 would have to grow into space owned by phantom sda3.
for n in $(parted -ms "$DISK" print 2>/dev/null \
| awk -F: 'NR>2 && $1+0>2 {print $1}'); do
sgdisk -d "$n" "$DISK" 2>/dev/null || true
partx -d --nr "$n" "$DISK" 2>/dev/null || true
done
# sfdisk `,+` extends partition $PART_NUM to consume all available free
# space. Works on a mounted/live partition because sfdisk only rewrites
# the GPT entry (kernel re-read of the partition table comes next).
echo ",+" | sfdisk --no-reread -N "$PART_NUM" "$DISK"
# Make the kernel pick up the new partition size without unmounting via
# BLKPG_RESIZE_PARTITION. partx -u also uses BLKPG, but on some images
# (notably SteamOS recovery loop-mounted btrfs) partx returns an error
# updating the mounted partition; the direct ioctl is more reliable.
python3 - "$DISK" "$PART_NUM" <<'PY'
import sys, os, fcntl, ctypes, subprocess
disk, pno = sys.argv[1], int(sys.argv[2])
out = subprocess.run(["sfdisk","--bytes","-q","-l",disk],
capture_output=True, text=True, check=True).stdout
start = sectors = None
for line in out.splitlines():
if line.startswith(f"{disk}{pno} ") or line.startswith(f"{disk}p{pno} "):
f = line.split()
start, sectors = int(f[1]), int(f[3])
break
if start is None:
sys.exit(f"could not parse partition {pno} from sfdisk -l {disk}")
class P(ctypes.Structure):
_fields_=[("start",ctypes.c_longlong),("length",ctypes.c_longlong),
("pno",ctypes.c_int),("devname",ctypes.c_char*64),
("volname",ctypes.c_char*64)]
class A(ctypes.Structure):
_fields_=[("op",ctypes.c_int),("flags",ctypes.c_int),
("datalen",ctypes.c_int),("data",ctypes.c_void_p)]
p = P(start=start*512, length=sectors*512, pno=pno)
a = A(op=3, flags=0, datalen=ctypes.sizeof(p), data=ctypes.addressof(p))
fd = os.open(disk, os.O_RDONLY)
try:
fcntl.ioctl(fd, 0x1269, a) # BLKPG, BLKPG_RESIZE_PARTITION
finally:
os.close(fd)
PY
# Belt-and-suspenders: also try partx in case BLKPG via python failed.
partx -u --nr "$PART_NUM" "$DISK" 2>/dev/null || true
# Online ext4 grow.
resize2fs "$ROOT_DEV"
systemctl disable grow-rootfs.service