WIUNIX ACADEMY
Grow an LVM logical volume and XFS/ext4 filesystem online
Page 2 of 3. Review the earlier steps and prerequisites before continuing.
YOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment3 editable values
LET’S BUILD
Recheck the mapping and calculate an absolute extent target
Keep this Bash shell for the remaining panels. The function below only reads identity and capacity. It rejects a wrong mount/LV match, a non-root filesystem subtree, multiple mounts of the same device, read-only mounts, unsupported filesystem/LV layouts and a shared-lock VG. It is a scope guard, not a proof of hardware health or a lock against concurrent administrators.
The final GiB value becomes a rounded absolute extent count. Subtracting the existing extent count gives the additional allocation; a repeated run at the same completed LV target requires zero additional extents. Freeze concurrent mount/storage reconfiguration operationally with the storage owner. If you open a new shell, load this function again; later calls pass the current form values explicitly.
Same Bash shell on the approved storage host
export LC_ALL=C
set -o pipefail
plan_growth() {
mount=$1
lv=$2
target_gib=$3
test -d "$mount" && test -b "$lv" || return 1
source=$(findmnt -rn --mountpoint "$mount" -o SOURCE) || return 1
test "$(readlink -f -- "$source")" = "$(readlink -f -- "$lv")" || return 1
test "$(findmnt -rn --mountpoint "$mount" -o FSROOT)" = / || return 1
fs=$(findmnt -rn --mountpoint "$mount" -o FSTYPE) || return 1
case "$fs" in xfs|ext4) ;; *) return 1 ;; esac
options=$(findmnt -rn --mountpoint "$mount" -o OPTIONS) || return 1
case ",$options," in *,rw,*) ;; *) return 1 ;; esac
devno=$(findmnt -rn --mountpoint "$mount" -o MAJ:MIN) || return 1
mount_count=$(findmnt -rn -o MAJ:MIN | awk -v d="$devno" '$1==d {n++} END {print n+0}') || return 1
test "$mount_count" -eq 1 || return 1
attr=$(sudo lvs --noheadings -o lv_attr "$lv" | tr -d '[:space:]') || return 1
case "$attr" in -w??a*) ;; *) return 1 ;; esac
types=$(sudo lvs --segments --noheadings -o segtype "$lv" | awk 'NF {print $1}' | sort -u) || return 1
test "$types" = linear || return 1
vg=$(sudo lvs --noheadings -o vg_name "$lv" | tr -d '[:space:]') || return 1
test -n "$vg" || return 1
locktype=$(sudo vgs --noheadings -o vg_locktype "$vg" | tr -d '[:space:]') || return 1
case "$locktype" in ''|none) ;; *) return 1 ;; esac
vgattr=$(sudo vgs --noheadings -o vg_attr "$vg" | tr -d '[:space:]') || return 1
case "$vgattr" in ??x*|???p*|?????[cs]*) return 1 ;; esac
current_b=$(sudo lvs --noheadings --units b --nosuffix -o lv_size "$lv" | awk 'NF {printf "%.0f\n",$1}') || return 1
extent_b=$(sudo vgs --noheadings --units b --nosuffix -o vg_extent_size "$vg" | awk 'NF {printf "%.0f\n",$1}') || return 1
free_extents=$(sudo vgs --noheadings -o vg_free_count "$vg" | tr -d '[:space:]') || return 1
case "$current_b:$extent_b:$free_extents:$target_gib" in *[!0-9:]*|:*|*::*) return 1 ;; esac
test "$extent_b" -gt 0 && test "$target_gib" -ge 1 && test "$target_gib" -le 1024 || return 1
test $((current_b % extent_b)) -eq 0 || return 1
target_gib=$((10#$target_gib))
requested_b=$((target_gib * 1024 * 1024 * 1024))
target_extents=$(((requested_b + extent_b - 1) / extent_b))
current_extents=$((current_b / extent_b))
test "$target_extents" -ge "$current_extents" || return 1
needed_extents=$((target_extents - current_extents))
test "$free_extents" -ge "$needed_extents" || return 1
printf 'mount=%s LV=%s filesystem=%s VG=%s\n' "$mount" "$lv" "$fs" "$vg"
printf 'current_bytes=%s extent_bytes=%s target_extents=%s additional_extents=%s free_extents=%s\n' "$current_b" "$extent_b" "$target_extents" "$needed_extents" "$free_extents"
}
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || {
printf '%s\n' 'STOP: mapping, supported layout, target size or free extents did not pass.'
exit 1
}The printed mount, device, filesystem, VG and extent calculation match the approved change; a failed guard means stop and investigate, not remove the check.
LET’S BUILD
Keep a private before-record and the VG metadata
After confirming the separate data backup, save the current VG metadata and a few focused reports in a new root-owned private directory. The command names the one VG discovered from the selected LV; it does not export all volume groups. Retain the printed directory with the change record and copy the needed evidence to the team’s existing protected storage.
The temporary record location must itself have room. If it shares the affected filesystem, it is especially not a disaster-recovery copy. Do not proceed merely because vgcfgbackup succeeded: that file describes LV mappings and cannot restore application data or reverse a grown filesystem.
Same Bash shell on the approved storage host
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
record=$(sudo mktemp -d /var/tmp/academy-lvm-growth.XXXXXXXX) || exit 1
sudo vgcfgbackup --file "$record/vg-before.conf" "$vg" || exit 1
sudo lvs --segments -o lv_path,lv_uuid,lv_attr,segtype,lv_size,vg_name,devices "$lv" | sudo tee "$record/lvs-before.txt" >/dev/null || exit 1
findmnt --mountpoint "$mount" -o TARGET,SOURCE,FSTYPE,FSROOT,OPTIONS | sudo tee "$record/mount-before.txt" >/dev/null || exit 1
df -B1 -- "$mount" | sudo tee "$record/df-before.txt" >/dev/null || exit 1
printf 'Private change record: %s\n' "$record"A private before-record exists and the separate application-data restore path has been checked; an error stops before capacity changes.
LET’S BUILD
Check the allocation plan without confusing it with execution
Re-read identity and free extents, then ask LVM to test the requested absolute target. Test mode disables metadata writes; a success is not proof that the real allocation or subsequent filesystem growth will succeed. Another storage operation can consume extents after this check.
Read the complete report. If the LV already has the target size, do not add another increment: continue by comparing the filesystem layer with that LV. If the current size exceeds this form’s target, reconcile the plan with the owner rather than attempting a reduction.
Same Bash shell on the approved storage host
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
if test "$needed_extents" -eq 0; then
printf '%s\n' 'LV already has the reviewed target size; inspect the filesystem layer next.'
else
sudo lvextend --test --extents "$target_extents" "$lv" || exit 1
fiThe test reports the intended LV/extent target or explicitly skips an already-sized LV; no filesystem has grown yet.
LET’S BUILD
Extend only the logical-volume layer
This is the first capacity-changing panel. It repeats the identity/capacity guard immediately before requesting the absolute extent count. The command deliberately does not combine filesystem resizing, so a partial result has a clear boundary. LVM also owns its allocation locking; the earlier read-only check does not reserve extents.
Keep the terminal and error output. After a successful return, compare LVM’s bytes with the device-mapper block-device size. The filesystem can still have its old size; that is an unfinished second step, not a reason to extend the LV again.
Same Bash shell on the approved storage host
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
if test "$needed_extents" -eq 0; then
printf '%s\n' 'LV already has the reviewed target size; no repeated extension.'
else
sudo lvextend --extents "$target_extents" "$lv" || exit 1
fi
sudo lvs --units b --nosuffix -o lv_path,lv_size,vg_name "$lv"
sudo blockdev --getsize64 "$lv"The LV and its block device expose the approved rounded target; the filesystem action remains to be completed.
LET’S BUILD
For XFS only: grow through the mounted filesystem
Execute this branch only when the mapping probe reports xfs. The guard requires the LV to have reached the reviewed target, then xfs_growfs grows the data section through the existing mountpoint. The filesystem must remain mounted; an ordinary LV path is not a replacement for identifying its actual mount.
Read geometry and filesystem capacity after the operation. No external log/realtime-device topology is handled here. If you have ext4, skip this entire branch and use the next one instead.
Same Bash shell on the approved storage host
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
test "$needed_extents" -eq 0 && test "$fs" = xfs || exit 1
sudo xfs_growfs -d "$mount" || exit 1
sudo xfs_info "$mount"
df -B1 -- "$mount"The mounted XFS data section uses the newly available LV capacity, or the command reports the exact reason growth did not complete.
LET’S BUILD
For ext4 only: grow to the existing LV size
Execute this branch only when the probe reports ext4. With no size argument, resize2fs grows to the underlying device’s current size. The guard requires that device to be the approved LV and already at the target. Mounted online growth depends on the installed kernel and filesystem features; a refusal is not permission to add force flags.
If the tool requests an offline check or reports unsupported growth/features, stop and arrange the distribution’s offline maintenance procedure with the application owner. Do not run a filesystem checker or repair tool against this mounted filesystem, and do not unmount a live application blindly. Skip this branch for XFS.
Same Bash shell on the approved storage host
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
test "$needed_extents" -eq 0 && test "$fs" = ext4 || exit 1
sudo resize2fs "$lv" || exit 1
df -B1 -- "$mount"resize2fs completes the ext4 growth and the mounted filesystem reports the new capacity, or a specific unsupported/error state remains open.