--- # roles/disk_grow/tasks/main.yml # # Idempotently grows the root partition and filesystem to fill the allocated # virtual disk. Safe to re-run on already-expanded nodes (no-op). # # Prerequisites: # The Proxmox virtual disk must already be resized via `qm resize` before # this role runs. This role only handles the in-guest partition/fs layer. # # Idempotency contract: # - growpart: exits 0 + prints CHANGED when it grew; exits 1 + prints NOCHANGE # when already at disk boundary. failed_when excludes NOCHANGE. # - resize2fs: prints "Nothing to do!" when filesystem already fills partition. # Always exits 0. changed_when detects that message. # -------------------------------------------------- # STEP 1: Install growpart (ships in cloud-guest-utils) # WHY check first: on a 100%-full root disk, apt-get update writes to # /var/cache/apt and will fail before installing anything. If growpart # is already present (cloud images ship it), skip the apt step entirely. # This avoids a chicken-and-egg failure where we need disk space to # install the tool that creates disk space. # -------------------------------------------------- - name: Check if growpart is already present ansible.builtin.command: which growpart register: disk_grow_growpart_check changed_when: false failed_when: false tags: [disk_grow, packages] - name: Install cloud-guest-utils if growpart is missing ansible.builtin.apt: name: cloud-guest-utils state: present when: disk_grow_growpart_check.rc != 0 tags: [disk_grow, packages] # -------------------------------------------------- # STEP 2: Grow partition to fill allocated disk # WHY growpart not parted module: growpart is the canonical safe tool for # live root partition expansion without unmounting. # -------------------------------------------------- - name: Grow root partition to fill disk boundary ansible.builtin.command: > growpart {{ disk_grow_device }} {{ disk_grow_partition_number }} register: disk_grow_growpart_result changed_when: "'CHANGED' in disk_grow_growpart_result.stdout" failed_when: > disk_grow_growpart_result.rc != 0 and 'NOCHANGE' not in disk_grow_growpart_result.stdout tags: [disk_grow, partition] # -------------------------------------------------- # STEP 3: Grow filesystem to fill the now-expanded partition # WHY always run: resize2fs is safe on a live ext4 root filesystem and # does nothing ("Nothing to do!") when already at full size. # -------------------------------------------------- - name: Grow ext4 filesystem to fill partition ansible.builtin.command: resize2fs {{ disk_grow_filesystem }} register: disk_grow_resize2fs_result changed_when: "'Nothing to do' not in disk_grow_resize2fs_result.stderr" tags: [disk_grow, filesystem] # -------------------------------------------------- # STEP 4: Assert result meets minimum size threshold # WHY assert not debug: a still-small root means qm resize was skipped; # hard fail prevents downstream tasks from hitting disk-full errors. # -------------------------------------------------- - name: Get root filesystem total size ansible.builtin.command: df -BG {{ disk_grow_mount_point }} --output=size register: disk_grow_df_result changed_when: false tags: [disk_grow, verify] - name: Assert root filesystem meets minimum size ({{ disk_grow_min_gb }}G) ansible.builtin.assert: that: - disk_grow_df_result.stdout_lines[1] | regex_replace('[^0-9]', '') | int >= disk_grow_min_gb fail_msg: >- Root filesystem at {{ disk_grow_mount_point }} is {{ disk_grow_df_result.stdout_lines[1] | trim }} — below the minimum {{ disk_grow_min_gb }}G. Run 'qm resize scsi0 32G' on the Proxmox host first, then re-run this playbook. success_msg: >- Root filesystem: {{ disk_grow_df_result.stdout_lines[1] | trim }} ✓ tags: [disk_grow, verify]