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
2 changes: 1 addition & 1 deletion .docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
### Specialized Hardware/System Scripts
- [ ] Host/FanControl/DellIPMIFanControl.sh - IPMI-specific, hardware dependent
- [ ] Host/FanControl/EnablePWMFanControl.sh - PWM control, hardware dependent
- [ ] Host/Hardware/EnableCPUScalingGoverner.sh - Kernel parameter manipulation
- [ ] Host/Hardware/EnableCPUScalingGovernor.sh - Kernel parameter manipulation
- [ ] Host/Bulk/FirstTimeProxmoxSetup.sh - Complex initial setup wizard

### Interactive Menu Scripts
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ jobs:
run: |
echo "Running repository checks..."
./.check/_RunChecks.sh

- name: Run unit tests
run: |
echo "Running unit test suite..."
chmod +x Utilities/RunAllTests.sh
cd Utilities && bash RunAllTests.sh --unit-only

- name: Check results
run: |
Expand Down
72 changes: 71 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,76 @@ 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.0] - 2026-03-02

Security hardening, performance optimizations, and GUI improvements

### Security
- **SSH Password Exposure** - Switched all `sshpass -p` calls to `sshpass -e` (environment variable)
- Passwords no longer visible in `ps aux` process listing
- Applied to all 4 sites in SSH.sh (`__wait_for_ssh__`, `__ssh_exec__`, `__scp_send__`, `__scp_fetch__`)
- SSHPASS environment variable is unset immediately after each command
- **Container Password Exposure** - Changed `__ct_change_password__` to pipe credentials via stdin
- Previously embedded password in `bash -c` command string (visible in /proc)
- Now pipes directly to `pct exec -- chpasswd`
- **Guacamole Token Security** - Token file now created with restricted permissions
- Directory created with `mkdir -p -m 700`, token file set to `chmod 600`
- Prevents other system users from reading authentication tokens
- **Guacamole API Credentials** - Switched to `--data-urlencode` for curl authentication
- Prevents special characters in passwords (e.g., `&`, `=`) from breaking API calls
- **Eval Removal** - Replaced `eval` with safer alternatives across 10 sites in 6 files
- Command execution contexts now use `bash -c` instead of `eval "$cmd"`
- ArgumentParser.sh uses `declare -g` instead of `eval` for variable assignment
- **ArgumentParser Blocklist** - Extended reserved variable name list
- Added high-risk names (HOSTNAME, RANDOM, SECONDS, GROUPS, etc.) to prevent overwrites

### Fixed
- **Filename Typo** - Renamed `EnableCPUScalingGoverner.sh` to `EnableCPUScalingGovernor.sh`
- Updated all references in CHANGELOG.md, .docs/TODO.md, and internal SCRIPT_NAME
- **CreateFromISO Structure** - Moved `set -euo pipefail` after header comment block
- Added shellcheck source directive for sourced utility files
- **RemoveStorage Race Condition** - Cached VM/CT config per iteration
- Added `|| continue` to skip VMs/CTs deleted between list and config check
- **Locale-Dependent Parsing** - Fixed AWK decimal parsing in CreateFromISO.sh
- Added `LC_NUMERIC=C` and comma-to-dot conversion for European locale compatibility
- **GUI Unicode Symbols** - Replaced all Unicode checkmarks/crosses with plain text

### Changed
- **GUI Breadcrumb Navigation** - Path display now shows `cc_pve > Storage > Ceph` style
- **GUI Script Descriptions** - Menu listings show inline description extracted from script headers
- **GUI Log Level Hint** - "Type 'l' to change log level" only shown in remote execution mode
- **SSH Error Context** - Connection failures now display the SSH error reason at all 7 failure sites
- **SSH Keepalive** - Added `ServerAliveInterval=5` and `ServerAliveCountMax=3` to SSH and SCP
- **Multi-Node Recovery** - Execution summary now lists per-node results with retry option
- Shows `OK: node1 node2` and `FAIL: node3` after multi-remote execution
- Prompts to retry only the failed nodes
- **CreateFromISO ArgumentParser Migration** - Replaced `getopts` with `__parse_args__`
- Arguments now use `--vm-name`, `--iso-url`, `--vm-storage` style flags
- All 8 arguments optional with interactive fallback preserved

### Added
- **CI Unit Tests** - Added unit test stage to `.github/workflows/checks.yml`
- Runs `Utilities/RunAllTests.sh` after static analysis checks
- **BulkOperations Source Guards** - Defensive guards on source calls in BulkOperations.sh
- **GUI Update Safety Guard** - Validates BASE_DIR before cleanup in `update_scripts()`
- **Documentation** - Added `Manuals/README.md` table of contents and Documentation section in main README

### Performance
- **FindVMIDFromIP Caching** - Config fetched once per VMID instead of 3 times (~67% fewer API calls)
- **Double-Sed Consolidation** - Merged 9 paired `sed | sed` calls into single `sed -e ... -e ...`
- Applied to BulkConfigureNetworkBandwidth, BulkConfigureDiskIOPS, BulkConfigureDiskBandwidth
- **Bash Builtins** - Replaced `echo | tr` subprocesses with native `${var^^}` case conversion
- Applied to FindVMIDFromIP, BulkCloneSetIP_Proxmox, BulkReconfigureMacAddresses, Conversion.sh, ChangeAllMACPrefix.sh
- **Carriage Return Removal** - Replaced `echo | tr -d '\r'` with `${var//$'\r'/}` in GUI.sh

### Technical Details
- `sshpass -e` reads from `SSHPASS` environment variable; inline assignment (`SSHPASS=x cmd`) used where possible
- `declare -g` requires Bash 4.2+
- `eval` retained in TestFramework.sh (dynamic function stubs) and RemoteExecutor.sh (SSH parameter expansion) - both legitimate uses
- Multi-node retry uses recursive `__execute_remote_script__` call with filtered target list
- FindVMIDFromIP caches both JSON and plain-text config formats per VMID for reuse
- `--data-urlencode` sends each parameter separately, preventing URL parameter injection

## [2.1.9] - 2026-02-24

Remote execution cancellation, live output streaming, and custom port support improvements
Expand Down Expand Up @@ -315,7 +385,7 @@ Implementation of ArgumentParser across the codebase and improved scripting stan
- Now use `__parse_args__` declarative parsing
- Fixed `__prompt_yes_no__` -> `__prompt_user_yn__` calls
- **ArgumentParser integration**
- Host/Hardware/EnableCPUScalingGoverner.sh
- Host/Hardware/EnableCPUScalingGovernor.sh
- RemoteManagement/ConfigureOverSSH/Proxmox/BulkDisableAutoStart.sh
- RemoteManagement/ConfigureOverSSH/Proxmox/BulkUnmountISOs.sh
- Storage/Ceph/SetScrubInterval.sh: (quality improvement with proper documentation/standards)
Expand Down
96 changes: 76 additions & 20 deletions GUI.sh
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,13 @@ configure_single_remote() {
echo "Testing connection..."
if ! __test_remote_connection__ "$manual_ip" "$manual_pass" "$manual_user" "$manual_port"; then
echo
__line_rgb__ "✗ Connection failed! Please check IP, username, port, and password." 255 0 0
__line_rgb__ "Connection failed! Please check IP, username, port, and password." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
echo "Press Enter to try again..."
read -r
continue
fi
echo "Connection successful!"
echo "Connection successful!"
sleep 1

__clear_remote_targets__
Expand Down Expand Up @@ -597,12 +598,13 @@ configure_single_remote() {
echo "Testing connection..."
if ! __test_remote_connection__ "$manual_ip" "$manual_pass" "$manual_user" "$manual_port"; then
echo
__line_rgb__ "✗ Connection failed! Please check IP, username, and password." 255 0 0
__line_rgb__ "Connection failed! Please check IP, username, and password." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
echo "Press Enter to try again..."
read -r
continue
fi
echo "Connection successful!"
echo "Connection successful!"
sleep 1

__clear_remote_targets__
Expand Down Expand Up @@ -639,12 +641,13 @@ configure_single_remote() {
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
__line_rgb__ "Connection failed! Please check password." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
echo "Press Enter to try again..."
read -r
continue
fi
echo "Connection successful!"
echo "Connection successful!"
sleep 1

__clear_remote_targets__
Expand Down Expand Up @@ -860,10 +863,11 @@ configure_multi_saved() {
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
__line_rgb__ "Connection to $node_name failed! Skipping this node." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
continue
fi
echo "Connection to $node_name successful!"
echo "Connection to $node_name successful!"

NODE_PASSWORDS["$node_name"]="$node_pass"
else
Expand Down Expand Up @@ -893,12 +897,13 @@ configure_multi_saved() {
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
__line_rgb__ "Password test failed on $first_name!" 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
echo "Please try again..."
sleep 2
continue
fi
echo "Password works on $first_name"
echo "Password works on $first_name"
sleep 1

for target in "${REMOTE_TARGETS[@]}"; do
Expand All @@ -919,18 +924,20 @@ configure_multi_saved() {
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
__line_rgb__ "Connection to $node_name failed! Please try again." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
# 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
__line_rgb__ "Still failed! Skipping $node_name." 255 0 0
[[ -n "${LAST_SSH_ERROR:-}" ]] && echo "Reason: $LAST_SSH_ERROR"
continue
fi
fi
echo "Connection to $node_name successful!"
echo "Connection to $node_name successful!"

NODE_PASSWORDS["$node_name"]="$node_pass"
done
Expand Down Expand Up @@ -1213,7 +1220,7 @@ run_script_local() {
__line_rgb__ "=== Running: $(display_path "$script_path") $param_line ===" 200 200 0

IFS=' ' read -r -a param_array <<<"$param_line"
param_line=$(echo "$param_line" | tr -d '\r')
param_line="${param_line//$'\r'/}"

mkdir -p .log
touch .log/out.log
Expand Down Expand Up @@ -1413,6 +1420,15 @@ update_scripts() {

echo "Updating files..."

# Safety check: verify BASE_DIR looks like the repo before deleting
if [[ ! -f "$BASE_DIR/GUI.sh" || ! -d "$BASE_DIR/Utilities" ]]; then
echo "Error: BASE_DIR ($BASE_DIR) does not appear to be the ProxmoxScripts repository"
echo "Aborting update to prevent accidental file deletion"
rm -rf "$TEMP_DIR"
sleep 3
return
fi

# Remove old files but preserve .current_branch
find "$BASE_DIR" -mindepth 1 ! -name ".current_branch" ! -name ".git" -delete 2>/dev/null || true

Expand Down Expand Up @@ -1524,8 +1540,13 @@ navigate() {
while true; do
clear
show_ascii_art
echo -n "CURRENT DIRECTORY: "
__line_rgb__ "./$(display_path "$current_dir")" 0 255 0

# Show breadcrumb-style path
local rel_path
rel_path="$(display_path "$current_dir")"
local breadcrumb="${rel_path//\// > }"
echo -n "PATH: "
__line_rgb__ "$breadcrumb" 0 255 0
echo
echo "Folders and scripts:"
echo "----------------------------------------"
Expand All @@ -1544,7 +1565,7 @@ navigate() {
((index += 1))
done

# List scripts
# List scripts with one-line descriptions
for s in "${scripts[@]}"; do
local sname
sname="$(basename "$s")"
Expand All @@ -1555,8 +1576,41 @@ navigate() {
continue
fi
fi

__line_rgb__ "$index) $sname" 100 200 100

# Extract first description line from script header
local desc=""
local past_name=false
while IFS= read -r line; do
[[ "$line" =~ ^#!/ ]] && continue
if [[ "$line" =~ ^#[[:space:]]*$ ]]; then
continue
elif [[ "$line" =~ ^#[[:space:]]+(.+)$ ]]; then
local content="${BASH_REMATCH[1]}"
# Skip the script filename line
if [[ "$content" == *.sh ]]; then
past_name=true
continue
fi
if [[ "$past_name" == true ]]; then
desc="$content"
break
fi
else
break
fi
done <"$s"

if [[ -n "$desc" ]]; then
# Truncate long descriptions
if [[ ${#desc} -gt 60 ]]; then
desc="${desc:0:57}..."
fi
echo -ne "\033[38;2;100;200;100m$index) $sname\033[0m"
echo " - $desc"
else
__line_rgb__ "$index) $sname" 100 200 100
fi

menu_map[$index]="$s"
((index += 1))
done
Expand All @@ -1565,7 +1619,9 @@ navigate() {
echo "----------------------------------------"
echo
echo "Type 'h<number>' to show script comments."
echo "Type 'l' to change log level (remote execution only)."
if [[ "$EXECUTION_MODE" != "local" ]]; then
echo "Type 'l' to change log level."
fi
show_common_footer "b" "e"
echo
echo "----------------------------------------"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
#
# EnableCPUScalingGoverner.sh
# EnableCPUScalingGovernor.sh
#
# A script to manage CPU frequency scaling governor on a Proxmox (or general Linux) system.
# Supports three major actions:
Expand All @@ -9,11 +9,11 @@
# 3. configure - Adjust CPU governor ("performance", "balanced", or "powersave") with optional min/max frequencies.
#
# Usage:
# EnableCPUScalingGoverner.sh install
# EnableCPUScalingGoverner.sh install performance -m 1.2GHz -M 3.0GHz
# EnableCPUScalingGoverner.sh remove
# EnableCPUScalingGoverner.sh configure balanced
# EnableCPUScalingGoverner.sh configure powersave --min 800MHz
# EnableCPUScalingGovernor.sh install
# EnableCPUScalingGovernor.sh install performance -m 1.2GHz -M 3.0GHz
# EnableCPUScalingGovernor.sh remove
# EnableCPUScalingGovernor.sh configure balanced
# EnableCPUScalingGovernor.sh configure powersave --min 800MHz
#
# Arguments:
# action - Action to perform: install, remove, or configure
Expand Down Expand Up @@ -53,7 +53,7 @@ trap '__handle_err__ $LINENO "$BASH_COMMAND"' ERR
# Globals / Defaults
###############################################################################

SCRIPT_NAME="EnableCPUScalingGoverner.sh"
SCRIPT_NAME="EnableCPUScalingGovernor.sh"
TARGET_PATH="/usr/local/bin/${SCRIPT_NAME}"
BALANCED_FALLBACK="ondemand"

Expand Down
Loading