Add more timeouts (#69)

* Add timeout handling for dpkg queries

Updated run.sh to use 'timeout' for all dpkg -l queries to prevent script hangs if the dpkg database is locked. Enhanced test-script.sh with a new test_dpkg_hang function and integrated it into the bug fix test suite to verify proper timeout handling for dpkg database hangs.

* Update script version to 2025.11.1

Bump displayed version in casaos-fix-docker-api-version/run.sh from 2025.11.0 to 2025.11.1 for consistency across script output and help messages.

* Refactor variable assignment for POSIX compliance

Updated variable assignment in run.sh and test-script.sh to use separate declaration and assignment lines, improving POSIX shell compatibility and readability.

* Improve test-script.sh mock dpkg usage and variable handling

Refactored test_dpkg_hang to declare local variables upfront, avoid SC2155 issues, and use explicit paths for the mock dpkg command. Updated PATH handling and hash table clearing to ensure the mock is used, and improved the test script to override dpkg for more reliable testing.

* Remove local keyword from test_script variable

The 'local' keyword was removed from the 'test_script' variable declaration in test-script.sh to ensure compatibility and correct scoping outside of functions.
This commit is contained in:
Christopher
2025-11-28 09:42:08 -06:00
committed by GitHub
parent 72ac7a3811
commit ac0cbd09bf
2 changed files with 194 additions and 18 deletions

View File

@@ -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

View File

@@ -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
;;