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
45 changes: 34 additions & 11 deletions .check/VerifySourceCalls.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,26 +340,49 @@ def fix_script(
lines_to_insert.append(shellcheck_line)
lines_to_insert.append(source_line)

# 3) Rebuild lines, skipping the ones to remove (including their shellcheck lines)
# 3) Rebuild lines, skipping the ones to remove (including their shellcheck lines and error handlers)
new_lines = []
skip_next = False
skip_mode = None # Can be None, 'source', or 'error_handler'
brace_depth = 0

for i, line in enumerate(lines):
# Check if we should skip this line because it's a shellcheck directive for a removed source
if skip_next:
skip_next = False
stripped = line.strip()

# Handle skip modes
if skip_mode == 'source':
# We're skipping a source line - check if it has error handler
if '||' in line and '{' in line:
# Multi-line source with error handler - enter error handler skip mode
skip_mode = 'error_handler'
brace_depth = line.count('{') - line.count('}')
continue
else:
# Single-line source - done skipping
skip_mode = None
continue

elif skip_mode == 'error_handler':
# Count braces to find end of error handler block
brace_depth += line.count('{') - line.count('}')
if brace_depth <= 0:
# Error handler block complete
skip_mode = None
continue

# Check if this is a shellcheck directive for a source we're removing
stripped = line.strip()
if stripped in remove_shellcheck:
skip_next = True # Skip the next line (the actual source statement)
skip_mode = 'source' # Skip the next line (and possibly error handler)
continue

# Check if line is a source to remove
# Check if line is a source to remove (fallback for sources without shellcheck directive)
if should_remove_source_line(line, remove_lines, remove_lines_dot):
# Also check if previous line was a shellcheck directive
# Check if it has error handler
if '||' in line and '{' in line:
skip_mode = 'error_handler'
brace_depth = line.count('{') - line.count('}')
# Also remove previous shellcheck directive if present
if i > 0 and new_lines and new_lines[-1].strip().startswith('# shellcheck source='):
new_lines.pop() # Remove the shellcheck directive too
new_lines.pop()
continue

# We'll insert new sources at the insertion index
Expand Down
4 changes: 2 additions & 2 deletions .check/_RunChecks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ if [ "$NO_FIX" = true ]; then
cat "$TEMP_OUTPUT"
else
# Show summary only
sed -n '/^====/p; /^Scripts found:/p; /^Summary/,/^====/p' "$TEMP_OUTPUT"
sed -n '/^Scripts found:/p; /^Summary/,/^/p' "$TEMP_OUTPUT"
fi

if [ $EXIT_CODE -eq 0 ]; then
Expand All @@ -216,7 +216,7 @@ else
cat "$TEMP_OUTPUT"
else
# Show fixed files and summary
grep -E "^✓|^===|^Scripts found:|^Summary|^ ✓|^ ✗" "$TEMP_OUTPUT" || true
grep -E "^✓|^Scripts found:|^Summary|^ ✓|^ ✗" "$TEMP_OUTPUT" || true
fi

echo "- Script notes validated and fixed"
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ nodes.json
TestConnectionInfo.json
.d/
.nfs*
*/__pycache__/
*/__pycache__/*
__pycache__/
__pycache__/*

# Downloaded Proxmox documentation (keep scripts, ignore generated content)
.docs/*.html
Expand Down
55 changes: 54 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,62 @@ 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.1.8] - 2026-01-08

Critical bug fixes for bulk operations and performance optimizations

### Fixed
- **CRITICAL** - Fixed Bash dynamic scoping bug causing infinite recursion in bulk operations
- **CRITICAL** - Fixed potential infinite polling in status wait functions
- Fixed wasteful waiting when VM stop fails
- Fixed temp file naming conflict in GUI.sh sessions
- Added support for custom SSH ports
- Fixed random node ordering in GUI
- Cleaned up error handling in RemoteExecutor
- Fixed TRACE logs appearing when LOG_LEVEL=INFO

### Changed
- Reduced redundant queries in bulk operations
- Added comprehensive logging to bulk operations framework

### Added
- Added connection testing before script execution

### Known Issues
- **Script Cancellation** - Cannot cancel scripts when connection is dropped
- Possibly add method to automatically stop scripts when the SSH connection breaks

### Technical Details
- Added TRACE log level support with priority 0
- Modified `__vm_exists__`/`__ct_exists__` to accept `--get-node` flag, refactored 13 operation functions, hardened `__vm_wait_for_status__` and `__ct_wait_for_status__` with timeout protection
- Fixed callback parameter passing, added trace logging, updated tmpdir naming
- Added `__test_remote_connection__` function, port parameter to SSH/SCP functions, updated log file naming
- Added `NODE_ORDER` array and `__get_node_port__` function
- Added password validation in all remote configuration flows
- Added port field in `nodes.json.template`

## [2.1.7] - 2025-11-25

Bug fix for VerifySourceCalls.py validation tool,

### Fixed
- **Bug: VerifySourceCalls.py Multi-line Source Removal Bug**
- Fixed logic that caused orphaned error handler blocks when removing unused sources
- Tool previously only skipped ONE line after shellcheck directive
- Now properly handles multi-line source statements with error handlers (`|| { ... }`)
- Implements brace-depth tracking to remove entire error handler blocks
- This bug was the root cause of the HostInfo.sh orphaned code issue discovered in v2.1.6

### Changed
- **Startup Dependency Check** - Added informational dependency warning at GUI.sh startup
- Shows friendly warning if `jq` is not installed
- Provides installation commands for all major distributions
- Non-blocking - allows local execution without optional dependencies
- Complements the existing remote execution dependency check

## [2.1.6] - 2025-11-25

Bug fixes, username support, and validation improvements
Critical bug fixes, username support, and validation improvements

### Added
- **Username Configuration** - Support for specifying SSH usernames per node
Expand Down
93 changes: 93 additions & 0 deletions GUI.sh
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,18 @@ configure_single_remote() {
echo "SSH keys not detected, password required."
read -rsp "Enter password: " manual_pass
echo

# Test connection before proceeding
echo "Testing connection..."
if ! __test_remote_connection__ "$manual_ip" "$manual_pass" "$manual_user" "22"; then
echo
__line_rgb__ "✗ Connection failed! Please check IP, username, and password." 255 0 0
echo "Press Enter to try again..."
read -r
continue
fi
echo "✓ Connection successful!"
sleep 1

__clear_remote_targets__
__add_remote_target__ "$manual_name" "$manual_ip" "$manual_pass" "$manual_user"
Expand Down Expand Up @@ -571,6 +583,18 @@ configure_single_remote() {
echo "SSH keys not detected, password required."
read -rsp "Enter password: " manual_pass
echo

# Test connection before proceeding
echo "Testing connection..."
if ! __test_remote_connection__ "$manual_ip" "$manual_pass" "$manual_user" "22"; then
echo
__line_rgb__ "✗ Connection failed! Please check IP, username, and password." 255 0 0
echo "Press Enter to try again..."
read -r
continue
fi
echo "✓ Connection successful!"
sleep 1

__clear_remote_targets__
__add_remote_target__ "$manual_name" "$manual_ip" "$manual_pass" "$manual_user"
Expand Down Expand Up @@ -599,6 +623,22 @@ configure_single_remote() {
echo "SSH keys not detected, password required."
read -rsp "Enter password for $selected_name: " node_pass
echo

# Get port for this node
local selected_port
selected_port=$(__get_node_port__ "$selected_name")

# Test connection before proceeding
echo "Testing connection to $selected_name..."
if ! __test_remote_connection__ "$selected_ip" "$node_pass" "$selected_username" "$selected_port"; then
echo
__line_rgb__ "✗ Connection failed! Please check password." 255 0 0
echo "Press Enter to try again..."
read -r
continue
fi
echo "✓ Connection successful!"
sleep 1

__clear_remote_targets__
__add_remote_target__ "$selected_name" "$selected_ip" "$node_pass" "$selected_username"
Expand Down Expand Up @@ -806,6 +846,18 @@ configure_multi_saved() {
if ! ssh -o BatchMode=yes -o ConnectTimeout=2 "${node_username}@$node_ip" echo "test" &>/dev/null 2>&1; then
read -rsp "Enter password for $node_name: " node_pass
echo

# Test connection
local node_port
node_port=$(__get_node_port__ "$node_name")
echo "Testing connection to $node_name..."
if ! __test_remote_connection__ "$node_ip" "$node_pass" "$node_username" "$node_port"; then
echo
__line_rgb__ "✗ Connection to $node_name failed! Skipping this node." 255 0 0
continue
fi
echo "✓ Connection to $node_name successful!"

NODE_PASSWORDS["$node_name"]="$node_pass"
else
NODE_PASSWORDS["$node_name"]="" # Empty = use SSH keys
Expand All @@ -823,15 +875,56 @@ configure_multi_saved() {
if [[ "$same_pass" =~ ^[Yy]$ ]]; then
read -rsp "Enter password for all nodes: " shared_pass
echo

# Test password on first node
local first_target="${REMOTE_TARGETS[0]}"
IFS=':' read -r first_name first_ip <<<"$first_target"
local first_username="${NODE_USERNAMES[$first_name]:-$DEFAULT_USERNAME}"
local first_port
first_port=$(__get_node_port__ "$first_name")

echo "Testing password on first node ($first_name)..."
if ! __test_remote_connection__ "$first_ip" "$shared_pass" "$first_username" "$first_port"; then
echo
__line_rgb__ "✗ Password test failed on $first_name!" 255 0 0
echo "Please try again..."
sleep 2
continue
fi
echo "✓ Password works on $first_name"
sleep 1

for target in "${REMOTE_TARGETS[@]}"; do
IFS=':' read -r node_name node_ip <<<"$target"
NODE_PASSWORDS["$node_name"]="$shared_pass"
done
else
for target in "${REMOTE_TARGETS[@]}"; do
IFS=':' read -r node_name node_ip <<<"$target"
local node_username="${NODE_USERNAMES[$node_name]:-$DEFAULT_USERNAME}"
local node_port
node_port=$(__get_node_port__ "$node_name")

read -rsp "Enter password for $node_name ($node_ip): " node_pass
echo

# Test connection
echo "Testing connection to $node_name..."
if ! __test_remote_connection__ "$node_ip" "$node_pass" "$node_username" "$node_port"; then
echo
__line_rgb__ "✗ Connection to $node_name failed! Please try again." 255 0 0
# Loop back to retry this specific node
read -rsp "Enter password for $node_name ($node_ip): " node_pass
echo
echo "Testing connection to $node_name..."
if ! __test_remote_connection__ "$node_ip" "$node_pass" "$node_username" "$node_port"; then
echo
__line_rgb__ "✗ Still failed! Skipping $node_name." 255 0 0
continue
fi
fi
echo "✓ Connection to $node_name successful!"

NODE_PASSWORDS["$node_name"]="$node_pass"
done
fi
Expand Down
Loading