diff --git a/nixos/modules/services/system/hot-resize.nix b/nixos/modules/services/system/hot-resize.nix new file mode 100644 index 00000000000000..ef43a152f3149b --- /dev/null +++ b/nixos/modules/services/system/hot-resize.nix @@ -0,0 +1,140 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.hotResize; +in +{ + options.services.hotResize = { + enable = lib.mkEnableOption "the hot resize service for filesystems"; + + devices = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + device = lib.mkOption { + type = lib.types.str; + example = "/dev/sda1"; + description = "Block device path to resize"; + }; + + fsType = lib.mkOption { + type = lib.types.enum [ + "ext4" + "xfs" + "btrfs" + ]; + default = "ext4"; + description = "Filesystem type (supported: ext4, xfs, btrfs)"; + }; + + mountPoint = lib.mkOption { + type = lib.types.str; + example = "/"; + description = "Mount point of the filesystem to resize"; + }; + }; + } + ); + default = [ ]; + example = lib.literalExpression '' + [ + { + device = "/dev/sda1"; + fsType = "ext4"; + mountPoint = "/"; + } + { + device = "/dev/sdb1"; + fsType = "xfs"; + mountPoint = "/data"; + } + ] + ''; + description = "List of devices to monitor for resizing"; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.hotResize = { + description = "Hot resize service for filesystems"; + wantedBy = [ "multi-user.target" ]; + + path = [ + pkgs.parted + pkgs.cloud-utils + pkgs.coreutils + pkgs.util-linux + pkgs.e2fsprogs + pkgs.xfsprogs + pkgs.btrfs-progs + ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = false; + ExecStart = pkgs.writeShellScript "resize-fs" '' + set -euo pipefail + + process_device() { + local device="$1" + local fstype="$2" + local mountpoint="$3" + + echo "Processing $device ($fstype) mounted at $mountpoint..." + + REAL_DEVICE=$(readlink -f "$device") + DISK=$(lsblk -ndo pkname $REAL_DEVICE) + PART_NUM=$(lsblk -ndo part $REAL_DEVICE) + + if [ -z "$DISK" ] || [ -z "$PART_NUM" ]; then + echo "Failed to determine disk and partition number for $device" + return 1 + fi + + echo "Growing partition /dev/$DISK partition $PART_NUM..." + GROWPART_OUTPUT=$(growpart "/dev/$DISK" "$PART_NUM" 2>&1) || { + EXIT_CODE=$? + if [ $EXIT_CODE -eq 2 ]; then + echo "Partition is already at maximum size, continuing with filesystem resize..." + else + echo "Failed to grow partition: $GROWPART_OUTPUT" + return $EXIT_CODE + fi + } + + echo "Resizing filesystem..." + case "$fstype" in + "ext4") + resize2fs "$device" + ;; + "xfs") + xfs_growfs "$mountpoint" + ;; + "btrfs") + btrfs filesystem resize max "$mountpoint" + ;; + *) + echo "Unsupported filesystem type: $fstype" + return 1 + ;; + esac + + echo "Current size:" + df -h "$mountpoint" + echo "---" + } + + # Process each device in sequence + ${lib.concatMapStrings (dev: '' + process_device "${dev.device}" "${dev.fsType}" "${dev.mountPoint}" + '') cfg.devices} + ''; + }; + }; + }; +}