Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# EditorConfig - https://editorconfig.org
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8

[*.sh]
indent_style = space
indent_size = 4

[*.py]
indent_style = space
indent_size = 4

[*.{yml,yaml}]
indent_style = space
indent_size = 2

[*.{json,json.template}]
indent_style = tab

[*.md]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
13 changes: 13 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Default owner for all files
* @coelacant1

# CI and repository checks
.github/ @coelacant1
.check/ @coelacant1

# Utility framework
Utilities/ @coelacant1

# GUI entry point
GUI.sh @coelacant1
CCPVE.sh @coelacant1
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
labels:
- "dependencies"
commit-message:
prefix: "ci"
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
python-version: '3.12'

- name: Install shellcheck
run: |
Expand Down
27 changes: 22 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
#Custom
# IDE and editor
.vscode/
*.code-workspace
.idea/
*.swp
*.swo
*~

# OS artifacts
.DS_Store
Thumbs.db

# Environment and secrets
.env
.env.local
nodes.json
TestConnectionInfo.json
.d/

# Temporary and log files
*.log
*.tmp

# NFS and stale file handles
.nfs*
*/__pycache__/
*/__pycache__/*

# Python caches
__pycache__/
__pycache__/*
*/__pycache__/

# Downloaded Proxmox documentation (keep scripts, ignore generated content)
.docs/*.html
Expand Down
10 changes: 10 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# ShellCheck configuration
# https://www.shellcheck.net/wiki/

# Allow sourcing from paths that can't be resolved at analysis time
# (UTILITYPATH is set at runtime)
external-sources=true

# Disabled checks:
# SC1091 - Not following sourced files (resolved via external-sources)
disable=SC1091
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.2.1] - 2026-04-29

Online ext4 partition expansion fix

### Fixed
- **ExpandEXT4Partition** - Resize now succeeds on mounted filesystems
- Previously parted's heredoc fed `Yes` to a `Fix/Ignore?` GPT-recovery prompt,
producing `parted: invalid token: Yes` and silently leaving the partition
unchanged while the script reported success
- Replaced parted heredoc with `sgdisk` delete + recreate at the same start
sector, preserving partition GUID, type code and name (mountpoint stays valid)
- Removed the `umount` step entirely; uses `partx -u` to refresh the kernel
view online and `resize2fs` to grow ext4 while mounted
- Added a `blockdev --getsize64` verification step that fails loudly if the
kernel does not see the new partition size before `resize2fs` runs
- Dropped the `parted` install dependency (no longer used)

## [2.2.0] - 2026-03-02

Security hardening, performance optimizations, and GUI improvements
Expand Down
14 changes: 12 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ This repository contains Bash scripts (`.sh` files) that help automate and manag
- Fork the repository to your own GitHub account.
- Clone your fork locally.

2. **Make Your Changes**
2. **Create a Branch**
- Create a feature branch from `main` for your changes.
- Use a descriptive name (e.g., `fix/bulk-migrate-timeout`, `feature/add-serial-port`).

3. **Make Your Changes**
- Follow the coding guidelines below.
- **Use the [Script Compliance Checklist](Utilities/_ScriptComplianceChecklist.md)** to ensure your script follows all conventions.
- Test your changes thoroughly in a development/test environment.

3. **Submit a Pull Request**
4. **Submit a Pull Request**
- Open a pull request (PR) against the repository’s `main` branch.
- Provide a clear title and description, referencing any related issues.
- All PRs are reviewed by maintainers before merging.

### Branching Model

- **`main`** — Stable release branch. All PRs target this branch.
- **`testing`** — Active development and integration testing before release.
- **Feature branches** — Created from `main` for individual changes.
---

## 3. Shell Script Style Guide
Expand Down
160 changes: 114 additions & 46 deletions Host/Storage/ExpandEXT4Partition.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# ExpandEXT4Partition.sh
#
# Expands a GPT partition and ext4 filesystem to use available disk space.
# Uses parted for safe in-place partition resizing without destroying metadata.
# Uses sgdisk to recreate the partition non-interactively (preserving start
# sector, UUID, type and name) so the operation works on mounted filesystems
# without unmounting.
#
# Usage:
# ExpandEXT4Partition.sh <device>
Expand Down Expand Up @@ -43,7 +45,6 @@ main() {
__check_proxmox__

__install_or_prompt__ "gdisk"
__install_or_prompt__ "parted"
__install_or_prompt__ "util-linux"
__install_or_prompt__ "e2fsprogs"

Expand All @@ -63,10 +64,13 @@ main() {
exit 0
fi

# Fix GPT if needed
# Fix GPT if needed (relocate secondary header to end of disk).
# When partitions on the device are mounted, partprobe cannot re-read the
# in-kernel partition table; refresh per-partition state with partx -u so
# parted sees a consistent view on the next invocation.
__info__ "Fixing GPT table if needed"
sgdisk -e "$DEVICE" 2>&1 || true
partprobe "$DEVICE" 2>&1 || true
partprobe "$DEVICE" 2>&1 || partx -u "$DEVICE" 2>&1 || true
sleep 2

# Verify exactly one partition exists
Expand All @@ -78,44 +82,110 @@ main() {
exit 1
fi

# Unmount if mounted
# Detect mount state - prefer online resize so we don't have to unmount
local mountpoint
mountpoint=$(lsblk -no MOUNTPOINT "$partition" 2>/dev/null || true)
mountpoint=$(lsblk -no MOUNTPOINT "$partition" 2>/dev/null | head -n1 || true)
local was_mounted=0
local online_resize=0
if [[ -n "$mountpoint" ]]; then
__info__ "Unmounting ${partition} from ${mountpoint}"
if umount "$partition" 2>&1; then
__ok__ "Unmounted successfully"
else
__err__ "Could not unmount ${partition}"
exit 1
fi
was_mounted=1
__info__ "${partition} is mounted at ${mountpoint}"
__info__ "Attempting online expansion (no unmount required)"
online_resize=1
fi

# Use parted to resize partition in-place (safer than recreating table)
__info__ "Resizing partition to use all available space"
if parted "$DEVICE" ---pretend-input-tty <<EOF 2>&1
resizepart 1 100%
Yes
EOF
then
__ok__ "Partition resized successfully"
else
__err__ "Failed to resize partition"
# Capture pre-resize partition size so we can verify the resize actually
# changed something
local size_before
size_before=$(blockdev --getsize64 "$partition" 2>/dev/null || echo 0)

# Resize partition non-interactively using sgdisk. parted's interactive
# heredoc approach is brittle: the "Fix/Ignore?" GPT-recovery prompt and
# the "partition in use" Yes/No prompt both consume our input lines and
# cause "invalid token" errors. sgdisk has no prompts and works fine on
# mounted devices because it only writes the on-disk GPT - the kernel
# picks up the change later via partx -u.
#
# Read the current partition's first sector, type code, and
# unique GUID, then delete partition 1 and recreate it at the same start
# spanning to the end of the disk (0 = end). This is what
# `parted resizepart 1 100%` does, just without the prompts.
__info__ "Resizing partition to use all available space (sgdisk)"

local part_info first_sector part_type part_uuid part_name
part_info=$(sgdisk -i 1 "$DEVICE" 2>&1) || {
__err__ "Failed to read partition 1 info"
echo "$part_info"
exit 1
}

first_sector=$(echo "$part_info" | awk -F': ' '/First sector/ {print $2}' | awk '{print $1}')
part_type=$(echo "$part_info" | awk -F': ' '/Partition GUID code/ {print $2}' | awk '{print $1}')
part_uuid=$(echo "$part_info" | awk -F': ' '/Partition unique GUID/ {print $2}' | awk '{print $1}')
part_name=$(echo "$part_info" | awk -F"'" '/Partition name/ {print $2}')

if [[ -z "$first_sector" || -z "$part_type" || -z "$part_uuid" ]]; then
__err__ "Could not parse partition metadata from sgdisk:"
echo "$part_info"
exit 1
fi

partprobe "$DEVICE" 2>&1 || true
__info__ "Recreating partition 1 (start=${first_sector}, type=${part_type}, uuid=${part_uuid})"

local sgdisk_args=(
-d 1
-n "1:${first_sector}:0"
-t "1:${part_type}"
-u "1:${part_uuid}"
)
if [[ -n "$part_name" ]]; then
sgdisk_args+=( -c "1:${part_name}" )
fi

local sgdisk_out
if ! sgdisk_out=$(sgdisk "${sgdisk_args[@]}" "$DEVICE" 2>&1); then
__err__ "sgdisk failed to recreate partition:"
echo "$sgdisk_out"
exit 1
fi
__ok__ "Partition table updated on disk"

# Inform the kernel of the new partition size. partprobe will fail to
# re-read the table while a partition is mounted, so fall back to
# partx --update which can update a single partition online
if [[ "$was_mounted" -eq 1 ]]; then
__info__ "Updating kernel partition table online (partx)"
partx -u "$partition" 2>&1 || partx -u "$DEVICE" 2>&1 || true
else
partprobe "$DEVICE" 2>&1 || true
fi
sleep 2

# Run e2fsck
__info__ "Checking filesystem"
if e2fsck -f -y "$partition" 2>&1; then
__ok__ "Filesystem check completed"
# Verify the kernel sees the new (larger) partition size.
local size_after
size_after=$(blockdev --getsize64 "$partition" 2>/dev/null || echo 0)
if [[ "$size_after" -le "$size_before" ]]; then
__err__ "Partition size did not increase (before=${size_before} after=${size_after})"
__err__ "sgdisk output:"
echo "$sgdisk_out"
exit 1
fi
__ok__ "Kernel sees new partition size: ${size_after} bytes (was ${size_before})"

# e2fsck cannot run on a mounted filesystem; skip when online.
if [[ "$online_resize" -eq 0 ]]; then
__info__ "Checking filesystem"
if e2fsck -f -y "$partition" 2>&1; then
__ok__ "Filesystem check completed"
else
__warn__ "Filesystem check reported issues"
fi
else
__warn__ "Filesystem check reported issues"
__info__ "Skipping e2fsck (filesystem is mounted)"
fi

# Resize filesystem
# Resize the ext4 filesystem. resize2fs supports online growth on
# mounted ext4 filesystems.
__info__ "Resizing ext4 filesystem"
if resize2fs "$partition" 2>&1; then
__ok__ "Filesystem resized"
Expand All @@ -124,17 +194,6 @@ EOF
exit 1
fi

# Remount if it was mounted
if [[ -n "$mountpoint" ]]; then
__info__ "Remounting at $mountpoint"
mkdir -p "$mountpoint" 2>/dev/null || true
if mount "$partition" "$mountpoint" 2>&1; then
__ok__ "Remounted successfully"
else
__warn__ "Could not remount partition"
fi
fi

echo
__ok__ "Partition expansion completed successfully!"
__info__ "Verify with: lsblk or df -h"
Expand All @@ -147,21 +206,30 @@ main "$@"
###############################################################################
# Script notes:
###############################################################################
# Last checked: 2025-11-20
# Last checked: 2026-04-29
#
# Changes:
# - 2026-04-29: Switched from parted heredoc to sgdisk for non-interactive
# resize - parted's Fix/Ignore + in-use Yes/No prompts could not be
# answered reliably and left the partition unchanged
# - 2026-04-29: Online expansion - no unmount required for mounted ext4
# - 2026-04-29: Verify kernel sees new partition size before resize2fs
# - 2025-11-20: Updated to use utility functions
# - 2025-11-20: Pending validation
# - 2025-11-20: Updated to use ArgumentParser.sh
# - 2025-11-20: Added proper nvme/mmcblk partition naming support
# - 2025-11-20: Validated against CONTRIBUTING.md
# - Non-interactive operation using sgdisk and sfdisk (note: now uses parted)
#
# Fixes:
# - 2026-04-29: FIXED: parted heredoc sent "Yes" to a Fix/Ignore prompt,
# causing "invalid token" errors and leaving the partition at its original
# size while the script reported success. Replaced with sgdisk delete +
# recreate at the same start sector preserving GUID/type/name, and added a
# blockdev size verification step before calling resize2fs.
# - 2026-04-29: FIXED: umount failed when the partition was in use
# (e.g. /mnt/pve/storage). Online resize is now the default path.
# - 2025-11-20: Fixed partition count logic (was counting device + partitions)
# - 2025-11-20: Fixed to use parted resizepart instead of recreating partition table
#
# Known issues:
# - Pending validation
# -
#

Loading
Loading