diff --git a/ansible/playbooks/day1_deploy_honcho.yml b/ansible/playbooks/day1_deploy_honcho.yml index 059c608..3768199 100644 --- a/ansible/playbooks/day1_deploy_honcho.yml +++ b/ansible/playbooks/day1_deploy_honcho.yml @@ -5,6 +5,9 @@ # Deploys Honcho + pgvector PostgreSQL on the `lincoln` host. Run AFTER # day0_linux_baseline.yml has been applied to the target. # +# expand_root_lv runs first to reclaim any unallocated space left by the +# Ubuntu autoinstall template (half-disk LV pattern). +# # Usage: # ansible-playbook -i inventory.yml playbooks/day1_deploy_honcho.yml # ============================================================================ @@ -14,4 +17,5 @@ become: true gather_facts: true roles: + - expand_root_lv - honcho diff --git a/ansible/roles/expand_root_lv/README.md b/ansible/roles/expand_root_lv/README.md new file mode 100644 index 0000000..284ea6f --- /dev/null +++ b/ansible/roles/expand_root_lv/README.md @@ -0,0 +1,63 @@ +# expand-root-lv role + +Idempotent role that extends the root LVM logical volume to fill its +volume group and grows the underlying filesystem (ext4 or xfs). + +## Why this role exists + +The Ubuntu Server autoinstall template (used by the mk-labs `wed`-baked +VM templates) provisions the root LV at roughly half the available disk +size — a longstanding installer default that surprises everyone who +hasn't been bitten by it before. Every freshly-provisioned VM in mk-labs +needs this fix-up before it's fully useful. + +## Idempotency + +- If `vg_free_count == 0`, the `lvextend` step is skipped and the + filesystem-grow step is also skipped (nothing to resize against). +- If the target volume group doesn't exist on the host (e.g. a non-LVM + layout), the role exits cleanly via `meta: end_play`. +- Safe to leave in a day1 deploy playbook so future disk expansions + (Proxmox-side disk grow → reboot → run role) are picked up + automatically. + +## Usage + +Standalone: + +```yaml +- hosts: lincoln + become: true + roles: + - expand_root_lv +``` + +Or chained ahead of an application role in a day1 playbook: + +```yaml +- hosts: honcho_server + become: true + roles: + - expand_root_lv + - honcho +``` + +## Defaults + +| Variable | Default | Purpose | +|-----------------------------------|---------------|---------------------------------------------------| +| `expand_root_lv_vg_name` | `ubuntu-vg` | LVM volume group name (Ubuntu installer default). | +| `expand_root_lv_lv_name` | `ubuntu-lv` | LVM logical volume name (Ubuntu installer default). | +| `expand_root_lv_mountpoint` | `/` | Mountpoint of the filesystem to grow. | + +Override these in `host_vars/.yml` for hosts that use a different +LVM layout. Hosts without LVM are silently no-op'd. + +## Limitations + +- Does not extend the underlying partition. If the operator grows the + Proxmox disk and the partition itself needs to grow before lvextend + can claim the new space, run `growpart /dev/sda 3` (or equivalent) + first. A future enhancement could automate this via `cloud-utils`' + `growpart` package, but it's out of scope for the initial template + fix-up case where the partition already covers the whole disk. diff --git a/ansible/roles/expand_root_lv/defaults/main.yml b/ansible/roles/expand_root_lv/defaults/main.yml new file mode 100644 index 0000000..222a91d --- /dev/null +++ b/ansible/roles/expand_root_lv/defaults/main.yml @@ -0,0 +1,26 @@ +--- +# ============================================================================ +# expand-root-lv role defaults +# ============================================================================ +# Extends the root LVM logical volume to fill its volume group, then grows +# the underlying filesystem to match. Idempotent: when there's no free PE +# in the VG (i.e. the LV already fills the VG), the lvextend step is a +# no-op and resize2fs/xfs_growfs simply confirms the filesystem is at +# capacity. +# +# Designed for Ubuntu cloud-image-style installations where the autoinstall +# template provisions an LV at half the disk size (the Ubuntu Server +# installer's longstanding default). Run once after VM provisioning to +# reclaim the unallocated PE; safe to leave in a day1 playbook so future +# disk expansions are picked up automatically. +# ============================================================================ + +# The LV and VG names follow the Ubuntu Server installer's convention. +# Override per-host if your template differs. +expand_root_lv_vg_name: ubuntu-vg +expand_root_lv_lv_name: ubuntu-lv + +# Mount point we expect to be backed by the target LV. Used purely for +# the resize2fs / xfs_growfs decision — the role inspects this path's +# filesystem type and dispatches to the correct grow command. +expand_root_lv_mountpoint: / diff --git a/ansible/roles/expand_root_lv/meta/main.yml b/ansible/roles/expand_root_lv/meta/main.yml new file mode 100644 index 0000000..e425d7b --- /dev/null +++ b/ansible/roles/expand_root_lv/meta/main.yml @@ -0,0 +1,23 @@ +--- +galaxy_info: + role_name: expand_root_lv + author: JARVIS + description: >- + Idempotent role that extends the root LVM logical volume to fill its + volume group and grows the underlying filesystem (ext4 or xfs). Fixes + the half-disk LV that the Ubuntu Server autoinstall template ships + with by default. + license: MIT + min_ansible_version: "2.14" + platforms: + - name: Ubuntu + versions: + - noble + - jammy + galaxy_tags: + - lvm + - cloud-init + - homelab + - storage + +dependencies: [] diff --git a/ansible/roles/expand_root_lv/tasks/main.yml b/ansible/roles/expand_root_lv/tasks/main.yml new file mode 100644 index 0000000..65d5858 --- /dev/null +++ b/ansible/roles/expand_root_lv/tasks/main.yml @@ -0,0 +1,70 @@ +--- +# ============================================================================ +# expand-root-lv / main +# ---------------------------------------------------------------------------- +# 1. Confirm the target VG exists (skip role cleanly on non-LVM hosts). +# 2. Read free physical-extent count for the VG. +# 3. Extend the LV to +100%FREE only when free_pe > 0. +# 4. Grow the filesystem on the mountpoint (ext4 -> resize2fs, xfs -> xfs_growfs). +# Each step is idempotent and skips when there's nothing to do. +# ============================================================================ + +- name: Gather LVM facts + ansible.builtin.command: + cmd: "vgs --noheadings --nosuffix --units b -o vg_name,vg_free_count {{ expand_root_lv_vg_name }}" + register: vg_info + changed_when: false + failed_when: false + +- name: Skip role when target VG is absent + ansible.builtin.meta: end_play + when: vg_info.rc != 0 + +- name: Parse free PE count + ansible.builtin.set_fact: + expand_root_lv_free_pe: "{{ (vg_info.stdout.split() | last | int) if vg_info.stdout | length > 0 else 0 }}" + +- name: Extend LV to fill VG (only if free PE > 0) + ansible.builtin.command: + cmd: "lvextend -l +100%FREE /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}" + register: lvextend_result + when: expand_root_lv_free_pe | int > 0 + changed_when: lvextend_result.rc == 0 + +- name: Detect filesystem type at mountpoint + ansible.builtin.command: + cmd: "findmnt {{ expand_root_lv_mountpoint }} -no FSTYPE" + register: fstype_result + changed_when: false + +- name: Set filesystem type fact + ansible.builtin.set_fact: + expand_root_lv_fstype: "{{ fstype_result.stdout | trim }}" + +- name: Grow ext4 filesystem + ansible.builtin.command: + cmd: "resize2fs /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}" + register: resize_result + when: + - expand_root_lv_fstype == "ext4" + - lvextend_result.changed | default(false) + changed_when: resize_result.rc == 0 + +- name: Grow xfs filesystem + ansible.builtin.command: + cmd: "xfs_growfs {{ expand_root_lv_mountpoint }}" + register: xfs_result + when: + - expand_root_lv_fstype == "xfs" + - lvextend_result.changed | default(false) + changed_when: xfs_result.rc == 0 + +- name: Report current root size + ansible.builtin.command: + cmd: "df -h {{ expand_root_lv_mountpoint }}" + register: df_result + changed_when: false + +- name: Show post-resize disk usage + ansible.builtin.debug: + msg: "{{ df_result.stdout_lines }}"