diff --git a/casaos-fix-docker-api-version/run.sh b/casaos-fix-docker-api-version/run.sh index 70a1fa6..2846dc1 100755 --- a/casaos-fix-docker-api-version/run.sh +++ b/casaos-fix-docker-api-version/run.sh @@ -59,7 +59,7 @@ print_info() { } echo "==========================================" -echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" +echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" echo "Here are some links:" @@ -90,7 +90,7 @@ display_versions() { # Also show installed package versions for clarity echo "Installed Docker packages:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print $2, $3}' 2>/dev/null || echo "Unable to query package versions" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print $2, $3}' || echo "Unable to query package versions" echo "" else echo "Docker command not found" @@ -317,8 +317,9 @@ check_containerd_version() { return 1 fi - # Get installed version - local version=$(dpkg -l | grep containerd.io | awk '{print $3}' | head -n1) + # Get installed version (use timeout to prevent hanging if dpkg is locked) + local version + version=$(timeout 10 dpkg -l 2>/dev/null | grep containerd.io | awk '{print $3}' | head -n1) echo "$version" return 0 } @@ -961,8 +962,8 @@ remove_standalone_docker_compose() { if command -v docker-compose &>/dev/null; then echo "Standalone docker-compose found. Checking installation method..." - # Check if docker-compose was installed via package manager - if dpkg -l | grep -qw docker-compose 2>/dev/null; then + # Check if docker-compose was installed via package manager (use timeout to prevent hang) + if timeout 10 dpkg -l 2>/dev/null | grep -qw docker-compose; then echo "Removing docker-compose installed via package manager..." $SUDO apt-get remove -y docker-compose else @@ -1085,7 +1086,7 @@ downgrade_docker() { echo "" # Check if Docker is already installed and remove it to ensure clean downgrade - if dpkg -l | grep -qE "docker-ce|containerd.io"; then + if timeout 10 dpkg -l 2>/dev/null | grep -qE "docker-ce|containerd.io"; then echo "Removing existing Docker packages to ensure clean installation..." # Stop Docker services before removal @@ -1572,7 +1573,7 @@ main() { case "$1" in apply-override|override) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" apply_docker_api_override @@ -1580,7 +1581,7 @@ main() { ;; remove-override|no-override) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" remove_docker_api_override @@ -1588,7 +1589,7 @@ main() { ;; help|--help|-h) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" show_usage @@ -2021,7 +2022,7 @@ main() { echo "==========================================" echo "" echo "Installed Docker Package Versions:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' 2>/dev/null || echo "Unable to query package versions" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' || echo "Unable to query package versions" echo "" echo "Docker Version Information:" timeout 10 $SUDO docker version 2>&1 || echo "Unable to get Docker version" @@ -2077,7 +2078,7 @@ main() { # Check installed packages echo "4. Installed Docker packages:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' 2>/dev/null || echo " Unable to query packages" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' || echo " Unable to query packages" echo "" # Check Docker service status diff --git a/casaos-fix-docker-api-version/test-script.sh b/casaos-fix-docker-api-version/test-script.sh index 50976e9..60c8235 100755 --- a/casaos-fix-docker-api-version/test-script.sh +++ b/casaos-fix-docker-api-version/test-script.sh @@ -1291,6 +1291,7 @@ show_usage() { echo " test-snap - Test Snap daemon hang handling (timeout check)" echo " test-netns - Test network namespace cleanup (device busy error)" echo " test-casaos - Test CasaOS version check hang handling (timeout check)" + echo " test-dpkg - Test dpkg database hang handling (timeout check)" echo " test-bugfixes - Run all bug fix tests" echo "" echo "Alternative fix for newer distros (Ubuntu 24.04+, Debian trixie):" @@ -1316,6 +1317,7 @@ show_usage() { echo " $0 test-snap # Test Snap hang handling" echo " $0 test-netns # Test network namespace cleanup" echo " $0 test-casaos # Test CasaOS version check hang handling" + echo " $0 test-dpkg # Test dpkg database hang handling" echo " $0 test-bugfixes # Run all bug fix tests" echo "" echo "Alternative fix (for distros without Docker 28.x):" @@ -1592,6 +1594,159 @@ TESTEOF return 0 } +# Function to test dpkg database hang handling +test_dpkg_hang() { + print_header "Testing dpkg Database Hang Handling" + print_info "This test verifies that the script handles a hanging 'dpkg -l' command" + print_info "It should timeout in 10 seconds and not hang forever" + echo "" + + # Declare local variables upfront to avoid SC2155 issues with $? + local mock_dir old_path which_dpkg start_time exit_code end_time duration output test_script + + # Create mock dpkg command + mock_dir="/tmp/mock-dpkg-$$" + mkdir -p "$mock_dir" + + cat > "$mock_dir/dpkg" << 'EOF' +#!/bin/sh +# Simulate dpkg database lock (hangs waiting for lock) +sleep 20 +echo "docker-ce 28.0.0 amd64 Docker Engine" +exit 0 +EOF + chmod +x "$mock_dir/dpkg" + + # Save PATH and update it + old_path="$PATH" + export PATH="$mock_dir:$PATH" + # Clear bash's command hash table so it finds our mock + hash -r + + print_info "Testing: timeout 10 dpkg -l (using mock at $mock_dir/dpkg)" + + # Verify mock is being used + which_dpkg=$(which dpkg 2>/dev/null) + print_info "Using dpkg at: $which_dpkg" + + # Test the timeout command directly using explicit path to mock + start_time=$(date +%s) + timeout 10 "$mock_dir/dpkg" -l >/dev/null 2>&1 + exit_code=$? + end_time=$(date +%s) + duration=$((end_time - start_time)) + + # Restore PATH + export PATH="$old_path" + hash -r + + print_info "Command completed in $duration seconds with exit code $exit_code" + + # Analyze results + if [ $exit_code -eq 124 ]; then + print_success "✓ Timeout occurred (exit code 124)" + else + print_error "✗ Expected exit code 124 (timeout), got $exit_code" + rm -rf "$mock_dir" + return 1 + fi + + if [ $duration -le 12 ]; then + print_success "✓ Timeout triggered in $duration seconds (expected ~10s)" + else + print_error "✗ Timeout took $duration seconds (expected ~10s)" + rm -rf "$mock_dir" + return 1 + fi + + # Now test the actual function behavior by sourcing and calling it + print_info "Testing actual display_versions function..." + + # Extract just the function from run.sh - but use explicit mock path + test_script="/tmp/test-dpkg-func-$$.sh" + cat > "$test_script" << TESTEOF +#!/bin/bash +set -o pipefail + +SUDO="" +if [ "\$EUID" -ne 0 ]; then + SUDO="sudo" +fi + +# Override dpkg to use our mock +dpkg() { + "$mock_dir/dpkg" "\$@" +} + +# Function to display current versions (simplified for testing) +display_versions() { + echo "Current Docker versions:" + if command -v docker &>/dev/null; then + echo "(docker version output skipped for test)" + echo "" + + # Also show installed package versions for clarity + echo "Installed Docker packages:" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print \$2, \$3}' || echo "Unable to query package versions" + echo "" + else + echo "Docker command not found" + echo "" + fi +} + +echo "Calling display_versions..." +display_versions +TESTEOF + + chmod +x "$test_script" + + # Create a mock docker command too (just to pass the command -v check) + cat > "$mock_dir/docker" << 'EOF' +#!/bin/sh +echo "Docker version 28.0.0" +exit 0 +EOF + chmod +x "$mock_dir/docker" + + # Run with mocked dpkg (and docker for the command -v check) + export PATH="$mock_dir:$PATH" + hash -r + start_time=$(date +%s) + output=$($test_script 2>&1) + end_time=$(date +%s) + duration=$((end_time - start_time)) + export PATH="$old_path" + hash -r + + print_info "Function test completed in $duration seconds" + + if [ $duration -le 12 ]; then + print_success "✓ Function completed in $duration seconds (expected ~10s)" + else + print_error "✗ Function took $duration seconds (expected ~10s)" + echo "Output: $output" + rm -rf "$mock_dir" "$test_script" + return 1 + fi + + if echo "$output" | grep -q "Unable to query package versions"; then + print_success "✓ Function detected timeout and printed fallback message" + else + print_error "✗ Function did not handle timeout correctly" + echo "Output: $output" + rm -rf "$mock_dir" "$test_script" + return 1 + fi + + # Clean up + rm -rf "$mock_dir" "$test_script" + + print_header "Test Result: PASSED" + print_success "dpkg database hang handling works correctly" + return 0 +} + # Function to run all bug fix tests test_all_bugfixes() { print_header "Running All Bug Fix Tests" @@ -1602,13 +1757,14 @@ test_all_bugfixes() { print_info " 4. Network namespace cleanup" print_info " 5. Unresponsive Docker daemon handling" print_info " 6. CasaOS version check hang handling" + print_info " 7. dpkg database hang handling" echo "" local results=() local failed=0 # Test GPG conflicts - print_info "=== Test 1 of 6: GPG Key Conflicts ===" + print_info "=== Test 1 of 7: GPG Key Conflicts ===" echo "" if test_gpg_key_conflicts; then results+=("✓ GPG key conflict handling: PASSED") @@ -1624,7 +1780,7 @@ test_all_bugfixes() { fi # Test GPG download resilience - print_info "=== Test 2 of 6: GPG Download Resilience ===" + print_info "=== Test 2 of 7: GPG Download Resilience ===" echo "" if test_gpg_download_resilience; then results+=("✓ GPG download resilience: PASSED") @@ -1640,7 +1796,7 @@ test_all_bugfixes() { fi # Test Snap hang - print_info "=== Test 3 of 6: Snap Daemon Hang ===" + print_info "=== Test 3 of 7: Snap Daemon Hang ===" echo "" if test_snap_hang; then results+=("✓ Snap daemon hang handling: PASSED") @@ -1656,7 +1812,7 @@ test_all_bugfixes() { fi # Test netns cleanup - print_info "=== Test 4 of 6: Network Namespace Cleanup ===" + print_info "=== Test 4 of 7: Network Namespace Cleanup ===" echo "" if test_netns_cleanup; then results+=("✓ Network namespace cleanup: PASSED") @@ -1672,7 +1828,7 @@ test_all_bugfixes() { fi # Test unresponsive daemon - print_info "=== Test 5 of 6: Unresponsive Docker Daemon ===" + print_info "=== Test 5 of 7: Unresponsive Docker Daemon ===" echo "" if test_unresponsive_daemon; then results+=("✓ Unresponsive daemon handling: PASSED") @@ -1688,7 +1844,7 @@ test_all_bugfixes() { fi # Test CasaOS hang - print_info "=== Test 6 of 6: CasaOS Version Check Hang ===" + print_info "=== Test 6 of 7: CasaOS Version Check Hang ===" echo "" if test_casaos_hang; then results+=("✓ CasaOS version check hang handling: PASSED") @@ -1697,6 +1853,22 @@ test_all_bugfixes() { failed=$((failed + 1)) fi + echo "" + if [ "$NON_INTERACTIVE" != "true" ]; then + read -p "Press Enter to continue to next test..." -r + echo "" + fi + + # Test dpkg hang + print_info "=== Test 7 of 7: dpkg Database Hang ===" + echo "" + if test_dpkg_hang; then + results+=("✓ dpkg database hang handling: PASSED") + else + results+=("✗ dpkg database hang handling: FAILED") + failed=$((failed + 1)) + fi + # Print summary print_header "Bug Fix Test Summary" echo "Results:" @@ -1761,6 +1933,9 @@ main() { test-casaos|casaos|test-casaos-hang) test_casaos_hang ;; + test-dpkg|dpkg|test-dpkg-hang) + test_dpkg_hang + ;; test-bugfixes|bugfixes|bug-fixes) test_all_bugfixes ;;