mirror of
https://github.com/bigbeartechworld/big-bear-scripts.git
synced 2026-08-10 12:05:21 -04:00
* Improve Docker downgrade robustness and version reporting Enhances the downgrade_docker function to retry apt-get installs, verify package installation after errors, and provide more detailed status and version reporting for containerd. Also updates package hold/unhold logic to include docker-ce-rootless-extras and improves logging in both run.sh and test-script.sh for better diagnostics and validation. * Refactor variable assignment and improve containerd version check Refactored variable assignments in run.sh and test-script.sh to use separate declaration and assignment for better readability and shell compatibility. Updated the containerd version check in test-script.sh to accept both '1.7.28' and 'v1.7.28' formats.
2224 lines
63 KiB
Bash
Executable File
2224 lines
63 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Test script for casaos-fix-docker-api-version
|
|
# This script simulates upgrading Docker to a newer version and then tests
|
|
# that the fix script can successfully downgrade it back to 24.0.7
|
|
#
|
|
# Usage:
|
|
# ./test-script.sh upgrade - Upgrade Docker to latest version (simulates the problem)
|
|
# ./test-script.sh test - Run the fix script and verify it works
|
|
# ./test-script.sh full - Do both: upgrade then test the fix
|
|
# ./test-script.sh status - Show current Docker version and API
|
|
#
|
|
|
|
set -o pipefail
|
|
|
|
# Initialize SUDO variable
|
|
if [ "$EUID" -eq 0 ]; then
|
|
SUDO=""
|
|
else
|
|
SUDO="sudo"
|
|
fi
|
|
|
|
# Check for non-interactive mode
|
|
if [ "$NON_INTERACTIVE" = "true" ]; then
|
|
echo "Running in NON-INTERACTIVE mode"
|
|
fi
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Function to print colored output
|
|
print_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
print_header() {
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "$1"
|
|
echo "=========================================="
|
|
echo ""
|
|
}
|
|
|
|
# Function to get Docker version
|
|
get_docker_version() {
|
|
if command -v docker &>/dev/null; then
|
|
local version=$(timeout 10 $SUDO docker version --format '{{.Server.Version}}' 2>/dev/null || echo "")
|
|
echo "$version"
|
|
else
|
|
echo "not_installed"
|
|
fi
|
|
}
|
|
|
|
# Function to get Docker API version
|
|
get_docker_api_version() {
|
|
if command -v docker &>/dev/null; then
|
|
local api_version=$(timeout 10 $SUDO docker version --format '{{.Server.APIVersion}}' 2>/dev/null || echo "")
|
|
if [ -z "$api_version" ]; then
|
|
# Fallback method
|
|
api_version=$(timeout 10 $SUDO docker version 2>/dev/null | grep -A 5 "Server:" | grep "API version:" | awk '{print $3}' | head -n1 || echo "")
|
|
fi
|
|
echo "$api_version"
|
|
else
|
|
echo "not_installed"
|
|
fi
|
|
}
|
|
|
|
# Function to get dockerd binary version
|
|
get_dockerd_binary_version() {
|
|
if command -v dockerd &>/dev/null; then
|
|
dockerd --version 2>/dev/null | head -n1 | awk '{print $3}' | sed 's/,//'
|
|
else
|
|
echo "not_installed"
|
|
fi
|
|
}
|
|
|
|
# Function to show current status
|
|
show_status() {
|
|
print_header "Current Docker Status"
|
|
|
|
local docker_version=$(get_docker_version)
|
|
local api_version=$(get_docker_api_version)
|
|
local dockerd_version=$(get_dockerd_binary_version)
|
|
local containerd_pkg
|
|
containerd_pkg=$(dpkg -l 2>/dev/null | grep containerd.io | awk 'NR==1 {print $3}')
|
|
local containerd_bin
|
|
containerd_bin=$(containerd --version 2>/dev/null | awk '{print $3}')
|
|
|
|
echo "Docker daemon version: $docker_version"
|
|
echo "Docker API version: $api_version"
|
|
echo "dockerd binary version: $dockerd_version"
|
|
echo "containerd package: ${containerd_pkg:-unknown}"
|
|
echo "containerd binary: ${containerd_bin:-unknown}"
|
|
echo ""
|
|
|
|
# Show package versions
|
|
echo "Installed packages:"
|
|
dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' 2>/dev/null || echo " No Docker packages found"
|
|
echo ""
|
|
|
|
# Show which docker binary
|
|
if command -v docker &>/dev/null; then
|
|
echo "Docker binary location: $(which docker)"
|
|
fi
|
|
|
|
if command -v dockerd &>/dev/null; then
|
|
echo "dockerd binary location: $(which dockerd)"
|
|
fi
|
|
echo ""
|
|
|
|
# Check if Docker is running
|
|
if $SUDO systemctl is-active --quiet docker 2>/dev/null; then
|
|
print_success "Docker service is running"
|
|
else
|
|
print_warning "Docker service is NOT running"
|
|
fi
|
|
|
|
# Check running processes
|
|
if pgrep -x dockerd >/dev/null 2>&1; then
|
|
echo ""
|
|
echo "Running dockerd process(es):"
|
|
pgrep -a dockerd
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# Function to detect OS
|
|
detect_os() {
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
OS=$ID
|
|
VERSION_CODENAME=${VERSION_CODENAME:-$(lsb_release -cs 2>/dev/null)}
|
|
else
|
|
print_error "Cannot detect OS. This script supports Debian/Ubuntu-based systems."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to check if CasaOS is installed
|
|
check_casaos_installed() {
|
|
if command -v casaos &>/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to restart CasaOS services
|
|
restart_casaos() {
|
|
print_info "Restarting CasaOS services..."
|
|
|
|
local CASA_SERVICES=(
|
|
"casaos-gateway.service"
|
|
"casaos-message-bus.service"
|
|
"casaos-user-service.service"
|
|
"casaos-local-storage.service"
|
|
"casaos-app-management.service"
|
|
"casaos.service"
|
|
)
|
|
|
|
# Stop services
|
|
for SERVICE in "${CASA_SERVICES[@]}"; do
|
|
if $SUDO systemctl is-active --quiet "$SERVICE" 2>/dev/null; then
|
|
$SUDO systemctl stop "$SERVICE" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
sleep 2
|
|
|
|
# Start services
|
|
for SERVICE in "${CASA_SERVICES[@]}"; do
|
|
if $SUDO systemctl is-enabled --quiet "$SERVICE" 2>/dev/null; then
|
|
$SUDO systemctl start "$SERVICE" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
sleep 2
|
|
print_success "CasaOS services restarted"
|
|
echo ""
|
|
}
|
|
|
|
# Function to apply Docker API override (for newer distros without Docker 24.0.x)
|
|
apply_docker_api_override() {
|
|
print_header "Applying Docker API Override Configuration"
|
|
print_info "This sets DOCKER_MIN_API_VERSION=1.24 to allow older CasaOS versions"
|
|
print_info "to work with newer Docker versions (27.x, 28.x, 29.x)"
|
|
echo ""
|
|
|
|
local override_dir="/etc/systemd/system/docker.service.d"
|
|
local override_file="$override_dir/override.conf"
|
|
|
|
# Create directory if it doesn't exist
|
|
if [ ! -d "$override_dir" ]; then
|
|
print_info "Creating directory: $override_dir"
|
|
$SUDO mkdir -p "$override_dir"
|
|
fi
|
|
|
|
# Create or update override.conf
|
|
print_info "Creating Docker service override configuration..."
|
|
$SUDO tee "$override_file" > /dev/null <<'EOF'
|
|
[Service]
|
|
Environment=DOCKER_MIN_API_VERSION=1.24
|
|
EOF
|
|
|
|
if [ -f "$override_file" ]; then
|
|
print_success "Created: $override_file"
|
|
echo ""
|
|
echo "Contents:"
|
|
$SUDO cat "$override_file" | sed 's/^/ /'
|
|
echo ""
|
|
else
|
|
print_error "Failed to create override file"
|
|
return 1
|
|
fi
|
|
|
|
# Reload systemd daemon
|
|
print_info "Reloading systemd daemon..."
|
|
$SUDO systemctl daemon-reload
|
|
echo ""
|
|
|
|
# Restart Docker
|
|
print_info "Restarting Docker service..."
|
|
$SUDO systemctl restart docker
|
|
sleep 3
|
|
echo ""
|
|
|
|
# Verify Docker is running
|
|
if $SUDO systemctl is-active --quiet docker; then
|
|
print_success "Docker service restarted successfully"
|
|
|
|
# Show current Docker info
|
|
local docker_version=$(get_docker_version)
|
|
local api_version=$(get_docker_api_version)
|
|
|
|
echo ""
|
|
echo "Current configuration:"
|
|
echo " Docker version: $docker_version"
|
|
echo " API version: $api_version"
|
|
echo ""
|
|
|
|
print_success "Docker API override applied successfully!"
|
|
print_info "CasaOS should now work with this Docker version"
|
|
else
|
|
print_error "Docker service failed to start after applying override"
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
return 0
|
|
}
|
|
|
|
# Function to remove Docker API override
|
|
remove_docker_api_override() {
|
|
print_header "Removing Docker API Override Configuration"
|
|
|
|
local override_file="/etc/systemd/system/docker.service.d/override.conf"
|
|
|
|
if [ ! -f "$override_file" ]; then
|
|
print_info "No override configuration found"
|
|
return 0
|
|
fi
|
|
|
|
print_info "Removing: $override_file"
|
|
$SUDO rm -f "$override_file"
|
|
|
|
# Reload systemd daemon
|
|
print_info "Reloading systemd daemon..."
|
|
$SUDO systemctl daemon-reload
|
|
|
|
# Restart Docker
|
|
print_info "Restarting Docker service..."
|
|
$SUDO systemctl restart docker
|
|
sleep 3
|
|
|
|
if $SUDO systemctl is-active --quiet docker; then
|
|
print_success "Docker API override removed successfully"
|
|
else
|
|
print_warning "Docker service may have issues after removing override"
|
|
fi
|
|
|
|
echo ""
|
|
return 0
|
|
}
|
|
|
|
# Function to upgrade Docker to latest version (simulate the problem)
|
|
upgrade_docker_to_latest() {
|
|
print_header "Upgrading Docker to Latest Version"
|
|
print_info "This simulates the problem where Docker gets upgraded to a newer version"
|
|
print_info "that is incompatible with CasaOS (API 1.45+)"
|
|
echo ""
|
|
|
|
detect_os
|
|
|
|
# Unhold packages if they're held
|
|
print_info "Unholding Docker packages..."
|
|
$SUDO apt-mark unhold docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 2>/dev/null || true
|
|
echo ""
|
|
|
|
# Setup Docker repository if not already present
|
|
print_info "Setting up Docker repository..."
|
|
$SUDO apt-get update
|
|
|
|
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_info "Adding Docker GPG key..."
|
|
$SUDO install -m 0755 -d /etc/apt/keyrings
|
|
$SUDO curl -fsSL https://download.docker.com/linux/${OS}/gpg -o /etc/apt/keyrings/docker.asc
|
|
$SUDO chmod a+r /etc/apt/keyrings/docker.asc
|
|
fi
|
|
|
|
if [ ! -f /etc/apt/sources.list.d/docker.list ]; then
|
|
print_info "Adding Docker repository..."
|
|
echo \
|
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${OS} \
|
|
${VERSION_CODENAME} stable" | \
|
|
$SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
$SUDO apt-get update
|
|
fi
|
|
echo ""
|
|
|
|
# Install latest Docker
|
|
print_info "Installing latest Docker version..."
|
|
print_warning "This will upgrade Docker to the latest version (API 1.45+)"
|
|
echo ""
|
|
|
|
if $SUDO apt-get install -y --allow-downgrades \
|
|
docker-ce \
|
|
docker-ce-cli \
|
|
containerd.io \
|
|
docker-buildx-plugin \
|
|
docker-compose-plugin \
|
|
docker-ce-rootless-extras; then
|
|
print_success "Docker upgraded to latest version"
|
|
else
|
|
print_error "Failed to upgrade Docker"
|
|
return 1
|
|
fi
|
|
echo ""
|
|
|
|
# Restart Docker
|
|
print_info "Restarting Docker to ensure new version loads..."
|
|
$SUDO systemctl daemon-reload
|
|
$SUDO systemctl stop docker 2>/dev/null || true
|
|
$SUDO pkill -9 dockerd 2>/dev/null || true
|
|
sleep 2
|
|
$SUDO systemctl start docker
|
|
sleep 3
|
|
echo ""
|
|
|
|
# Verify upgrade
|
|
local new_version=$(get_docker_version)
|
|
local new_api=$(get_docker_api_version)
|
|
|
|
print_header "Upgrade Complete"
|
|
echo "New Docker version: $new_version"
|
|
echo "New API version: $new_api"
|
|
echo ""
|
|
|
|
if awk -v ver="$new_api" 'BEGIN {exit !(ver >= 1.45)}'; then
|
|
print_success "Successfully upgraded to newer Docker (API $new_api)"
|
|
print_warning "This version is incompatible with older CasaOS versions"
|
|
return 0
|
|
else
|
|
print_warning "Upgrade may not have worked as expected (API: $new_api)"
|
|
print_warning "Expected API 1.45 or higher"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to test the fix script
|
|
test_fix_script() {
|
|
print_header "Testing the Fix Script"
|
|
|
|
# Get current version before fix
|
|
local before_version=$(get_docker_version)
|
|
local before_api=$(get_docker_api_version)
|
|
|
|
print_info "Docker version before fix: $before_version"
|
|
print_info "API version before fix: $before_api"
|
|
echo ""
|
|
|
|
# Check if the fix script exists
|
|
if [ ! -f "./run.sh" ]; then
|
|
print_error "Fix script (run.sh) not found in current directory"
|
|
print_info "Please run this test script from the casaos-fix-docker-api-version directory"
|
|
return 1
|
|
fi
|
|
|
|
print_info "Running the fix script..."
|
|
echo ""
|
|
echo "=========================================="
|
|
|
|
# Run the fix script
|
|
if bash ./run.sh; then
|
|
echo "=========================================="
|
|
echo ""
|
|
print_success "Fix script completed"
|
|
else
|
|
local exit_code=$?
|
|
echo "=========================================="
|
|
echo ""
|
|
print_error "Fix script failed with exit code $exit_code"
|
|
return $exit_code
|
|
fi
|
|
|
|
# Wait a moment for Docker to stabilize
|
|
sleep 3
|
|
|
|
# Verify the fix
|
|
print_header "Verifying the Fix"
|
|
|
|
local after_version=$(get_docker_version)
|
|
local after_api=$(get_docker_api_version)
|
|
local dockerd_binary=$(get_dockerd_binary_version)
|
|
local containerd_pkg
|
|
containerd_pkg=$(dpkg -l 2>/dev/null | grep containerd.io | awk 'NR==1 {print $3}')
|
|
local containerd_bin
|
|
containerd_bin=$(containerd --version 2>/dev/null | awk '{print $3}')
|
|
|
|
echo "Docker version after fix: $after_version"
|
|
echo "API version after fix: $after_api"
|
|
echo "dockerd binary version: $dockerd_binary"
|
|
echo "containerd package: ${containerd_pkg:-unknown}"
|
|
echo "containerd binary: ${containerd_bin:-unknown}"
|
|
echo ""
|
|
|
|
# Check package versions
|
|
echo "Package versions:"
|
|
dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}'
|
|
echo ""
|
|
|
|
# Verify results
|
|
local success=true
|
|
|
|
# Check if version contains 28.x (28.0, 28.1, 28.5, etc.)
|
|
if echo "$after_version" | grep -q "^28\."; then
|
|
print_success "✓ Docker version is 28.x ($after_version)"
|
|
else
|
|
print_error "✗ Docker version is NOT 28.x (got: $after_version)"
|
|
success=false
|
|
fi
|
|
|
|
# Check if API is below 1.52 (Docker 28.x series, before breaking change)
|
|
# Use awk for decimal comparison (doesn't require bc)
|
|
if awk -v ver="$after_api" 'BEGIN {exit !(ver >= 1.47 && ver < 1.52)}'; then
|
|
print_success "✓ API version is compatible ($after_api - Docker 28.x, below 1.52 breaking change)"
|
|
else
|
|
print_error "✗ API version is NOT compatible (got: $after_api, expected: 1.47-1.51, below 1.52 breaking change)"
|
|
success=false
|
|
fi
|
|
|
|
# Check if dockerd binary is correct
|
|
if echo "$dockerd_binary" | grep -q "^28\."; then
|
|
print_success "✓ dockerd binary is version 28.x ($dockerd_binary)"
|
|
else
|
|
print_error "✗ dockerd binary is NOT 28.x (got: $dockerd_binary)"
|
|
success=false
|
|
fi
|
|
|
|
# Check containerd package/bin match expected 1.7.28
|
|
if echo "$containerd_pkg" | grep -q "1.7.28"; then
|
|
print_success "✓ containerd package is 1.7.28 (${containerd_pkg})"
|
|
else
|
|
print_error "✗ containerd package is not 1.7.28 (got: ${containerd_pkg:-unknown})"
|
|
success=false
|
|
fi
|
|
if echo "$containerd_bin" | grep -q "^v\?1\.7\.28"; then
|
|
print_success "✓ containerd binary is 1.7.28 (${containerd_bin})"
|
|
else
|
|
print_error "✗ containerd binary is not 1.7.28 (got: ${containerd_bin:-unknown})"
|
|
success=false
|
|
fi
|
|
|
|
# Check if Docker is running
|
|
if $SUDO systemctl is-active --quiet docker; then
|
|
print_success "✓ Docker service is running"
|
|
else
|
|
print_error "✗ Docker service is NOT running"
|
|
success=false
|
|
fi
|
|
|
|
# Check if packages are held
|
|
echo ""
|
|
echo "Checking if packages are held:"
|
|
local held_packages=$($SUDO apt-mark showhold | grep -E "docker-ce|containerd.io")
|
|
if [ -n "$held_packages" ]; then
|
|
print_success "✓ Docker packages are held:"
|
|
echo "$held_packages" | sed 's/^/ /'
|
|
else
|
|
print_warning "⚠ Docker packages are NOT held (they may auto-upgrade)"
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$success" = true ]; then
|
|
print_header "Test Result: PASSED"
|
|
print_success "The fix script successfully installed Docker 28.x"
|
|
print_success "API version is compatible with CasaOS"
|
|
return 0
|
|
else
|
|
print_header "Test Result: FAILED"
|
|
print_error "The fix script did not successfully install Docker 28.x"
|
|
print_error "Please review the errors above"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to run full test
|
|
run_full_test() {
|
|
print_header "Full Test: Upgrade then Fix"
|
|
print_info "This will:"
|
|
print_info " 1. Upgrade Docker to latest version (simulate the problem)"
|
|
print_info " 2. Run the fix script to install Docker 28.x"
|
|
print_info " 3. Verify everything works correctly"
|
|
echo ""
|
|
|
|
if [ "$NON_INTERACTIVE" = "true" ]; then
|
|
REPLY="yes"
|
|
else
|
|
read -p "Do you want to continue? (yes/no): " -r REPLY
|
|
echo ""
|
|
fi
|
|
|
|
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]] && [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
print_info "Test cancelled"
|
|
return 0
|
|
fi
|
|
|
|
# Step 1: Upgrade
|
|
if ! upgrade_docker_to_latest; then
|
|
print_error "Upgrade step failed"
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue with the fix script test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Step 2: Test fix
|
|
if ! test_fix_script; then
|
|
print_error "Fix script test failed"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Function to install specific Docker version
|
|
install_docker_version() {
|
|
local version=$1
|
|
local description=$2
|
|
|
|
print_header "Installing Docker $version"
|
|
print_info "$description"
|
|
echo ""
|
|
|
|
detect_os
|
|
|
|
# Unhold packages if they're held
|
|
print_info "Unholding Docker packages..."
|
|
$SUDO apt-mark unhold docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 2>/dev/null || true
|
|
echo ""
|
|
|
|
# Setup Docker repository if not already present
|
|
print_info "Setting up Docker repository..."
|
|
$SUDO apt-get update -qq
|
|
|
|
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_info "Adding Docker GPG key..."
|
|
$SUDO install -m 0755 -d /etc/apt/keyrings
|
|
$SUDO curl -fsSL https://download.docker.com/linux/${OS}/gpg -o /etc/apt/keyrings/docker.asc
|
|
$SUDO chmod a+r /etc/apt/keyrings/docker.asc
|
|
fi
|
|
|
|
if [ ! -f /etc/apt/sources.list.d/docker.list ]; then
|
|
print_info "Adding Docker repository..."
|
|
echo \
|
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${OS} \
|
|
${VERSION_CODENAME} stable" | \
|
|
$SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
$SUDO apt-get update -qq
|
|
fi
|
|
echo ""
|
|
|
|
# Query available versions
|
|
print_info "Looking for Docker $version in repository..."
|
|
local full_version=$(apt-cache madison docker-ce 2>/dev/null | grep "$version" | head -n1 | awk '{print $3}')
|
|
|
|
if [ -z "$full_version" ]; then
|
|
print_error "Could not find Docker version $version in repository"
|
|
print_info "Available versions:"
|
|
apt-cache madison docker-ce 2>/dev/null | head -n 10
|
|
return 1
|
|
fi
|
|
|
|
print_info "Found: $full_version"
|
|
echo ""
|
|
|
|
# Install specific version
|
|
print_info "Installing Docker $full_version..."
|
|
if $SUDO apt-get install -y --allow-downgrades \
|
|
docker-ce=$full_version \
|
|
docker-ce-cli=$full_version \
|
|
containerd.io \
|
|
docker-buildx-plugin \
|
|
docker-compose-plugin \
|
|
docker-ce-rootless-extras; then
|
|
print_success "Docker $version installed successfully"
|
|
else
|
|
print_error "Failed to install Docker $version"
|
|
return 1
|
|
fi
|
|
echo ""
|
|
|
|
# Restart Docker
|
|
print_info "Restarting Docker to ensure new version loads..."
|
|
$SUDO systemctl daemon-reload
|
|
$SUDO systemctl stop docker 2>/dev/null || true
|
|
$SUDO pkill -9 dockerd 2>/dev/null || true
|
|
sleep 2
|
|
$SUDO systemctl start docker
|
|
sleep 3
|
|
echo ""
|
|
|
|
# Verify installation
|
|
local installed_version=$(get_docker_version)
|
|
local installed_api=$(get_docker_api_version)
|
|
|
|
print_header "Installation Complete"
|
|
echo "Installed Docker version: $installed_version"
|
|
echo "Installed API version: $installed_api"
|
|
echo ""
|
|
|
|
if echo "$installed_version" | grep -q "$version"; then
|
|
print_success "Successfully installed Docker $version (API $installed_api)"
|
|
else
|
|
print_warning "Version mismatch: expected $version, got $installed_version"
|
|
fi
|
|
|
|
# Check if CasaOS is installed and offer to restart it
|
|
if check_casaos_installed; then
|
|
echo ""
|
|
print_info "CasaOS is installed on this system"
|
|
if [ "$NON_INTERACTIVE" = "true" ]; then
|
|
RESTART_REPLY="yes"
|
|
else
|
|
read -p "Do you want to restart CasaOS services? (yes/no): " -r RESTART_REPLY
|
|
echo ""
|
|
fi
|
|
|
|
if [[ $RESTART_REPLY =~ ^[Yy][Ee][Ss]$ ]] || [[ $RESTART_REPLY =~ ^[Yy]$ ]]; then
|
|
restart_casaos
|
|
else
|
|
print_info "Skipping CasaOS restart"
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
if echo "$installed_version" | grep -q "$version"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to test with API 1.45 (Docker 27.x)
|
|
test_api_1_45() {
|
|
print_header "Testing with API 1.45 (Docker 27.x)"
|
|
|
|
if ! install_docker_version "27." "API 1.45 - Docker 27.x series"; then
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Docker 27.x installed. You can now test CasaOS behavior with this version."
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to run the fix script..." -r
|
|
echo ""
|
|
fi
|
|
|
|
test_fix_script
|
|
}
|
|
|
|
# Function to test with API 1.46 (Docker 28.x)
|
|
test_api_1_46() {
|
|
print_header "Testing with API 1.46 (Docker 28.x)"
|
|
|
|
if ! install_docker_version "28." "API 1.46 - Docker 28.x series"; then
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Docker 28.x installed. You can now test CasaOS behavior with this version."
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to run the fix script..." -r
|
|
echo ""
|
|
fi
|
|
|
|
test_fix_script
|
|
}
|
|
|
|
# Function to test with API 1.47 (Docker 29.x)
|
|
test_api_1_47() {
|
|
print_header "Testing with API 1.47 (Docker 29.x)"
|
|
|
|
if ! install_docker_version "29." "API 1.47 - Docker 29.x series"; then
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Docker 29.x installed. You can now test CasaOS behavior with this version."
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to run the fix script..." -r
|
|
echo ""
|
|
fi
|
|
|
|
test_fix_script
|
|
}
|
|
|
|
# Function to test GPG key conflict handling
|
|
test_gpg_key_conflicts() {
|
|
print_header "Testing GPG Key Conflict Handling"
|
|
print_info "This test simulates the GPG key conflict issue reported in Debian/OMV"
|
|
print_info "where multiple Docker GPG keys exist and cause repository conflicts"
|
|
echo ""
|
|
|
|
detect_os
|
|
|
|
# Create conflicting GPG keys
|
|
print_info "Creating conflicting Docker GPG keys..."
|
|
|
|
# Backup existing keys if present
|
|
local backup_dir="/tmp/docker-gpg-backup-$(date +%s)"
|
|
mkdir -p "$backup_dir"
|
|
|
|
if [ -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_info "Backing up /etc/apt/keyrings/docker.asc"
|
|
$SUDO cp /etc/apt/keyrings/docker.asc "$backup_dir/"
|
|
fi
|
|
|
|
if [ -f /usr/share/keyrings/docker.gpg ]; then
|
|
print_info "Backing up /usr/share/keyrings/docker.gpg"
|
|
$SUDO cp /usr/share/keyrings/docker.gpg "$backup_dir/"
|
|
fi
|
|
|
|
# Download Docker GPG key
|
|
print_info "Downloading Docker GPG key..."
|
|
$SUDO install -m 0755 -d /etc/apt/keyrings
|
|
$SUDO install -m 0755 -d /usr/share/keyrings
|
|
$SUDO curl -fsSL https://download.docker.com/linux/${OS}/gpg -o /tmp/docker.gpg
|
|
|
|
# Create the conflict: place the same key in two locations
|
|
print_info "Creating conflicting GPG key locations..."
|
|
$SUDO cp /tmp/docker.gpg /etc/apt/keyrings/docker.asc
|
|
$SUDO cp /tmp/docker.gpg /usr/share/keyrings/docker.gpg
|
|
$SUDO chmod a+r /etc/apt/keyrings/docker.asc
|
|
$SUDO chmod a+r /usr/share/keyrings/docker.gpg
|
|
|
|
# Create a conflicting docker.list that references the old location
|
|
print_info "Creating conflicting Docker repository configuration..."
|
|
echo \
|
|
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/${OS} \
|
|
${VERSION_CODENAME} stable" | \
|
|
$SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
|
|
# Try to update - this should fail with the conflict error
|
|
print_info "Testing that apt update fails with conflict..."
|
|
if $SUDO apt-get update 2>&1 | grep -q "Conflicting values set for option Signed-By"; then
|
|
print_success "✓ Confirmed: GPG key conflict error reproduced"
|
|
else
|
|
print_warning "⚠ Could not reproduce GPG key conflict (might already be fixed)"
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Running fix script to resolve GPG key conflicts..."
|
|
echo ""
|
|
echo "=========================================="
|
|
|
|
# Run the fix script - it should handle the conflict
|
|
if bash ./run.sh; then
|
|
echo "=========================================="
|
|
echo ""
|
|
print_success "Fix script completed"
|
|
else
|
|
echo "=========================================="
|
|
echo ""
|
|
print_error "Fix script failed"
|
|
|
|
# Restore backups
|
|
print_info "Restoring GPG key backups..."
|
|
if [ -f "$backup_dir/docker.asc" ]; then
|
|
$SUDO cp "$backup_dir/docker.asc" /etc/apt/keyrings/
|
|
fi
|
|
if [ -f "$backup_dir/docker.gpg" ]; then
|
|
$SUDO cp "$backup_dir/docker.gpg" /usr/share/keyrings/
|
|
fi
|
|
|
|
return 1
|
|
fi
|
|
|
|
# Verify the fix
|
|
print_header "Verifying GPG Key Conflict Resolution"
|
|
|
|
# Check that only docker.asc exists in the correct location
|
|
local gpg_success=true
|
|
|
|
if [ -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_success "✓ /etc/apt/keyrings/docker.asc exists"
|
|
else
|
|
print_error "✗ /etc/apt/keyrings/docker.asc missing"
|
|
gpg_success=false
|
|
fi
|
|
|
|
if [ ! -f /usr/share/keyrings/docker.gpg ]; then
|
|
print_success "✓ /usr/share/keyrings/docker.gpg removed (conflict resolved)"
|
|
else
|
|
print_error "✗ /usr/share/keyrings/docker.gpg still exists (conflict not resolved)"
|
|
gpg_success=false
|
|
fi
|
|
|
|
# Check that apt update works now
|
|
print_info "Testing apt update after fix..."
|
|
if $SUDO apt-get update >/dev/null 2>&1; then
|
|
print_success "✓ apt update works (no conflicts)"
|
|
else
|
|
print_error "✗ apt update still fails"
|
|
$SUDO apt-get update 2>&1 | tail -n 5
|
|
gpg_success=false
|
|
fi
|
|
|
|
# Check Docker installation
|
|
local docker_version=$(get_docker_version)
|
|
local api_version=$(get_docker_api_version)
|
|
|
|
echo ""
|
|
echo "Docker status after fix:"
|
|
echo " Version: $docker_version"
|
|
echo " API: $api_version"
|
|
echo ""
|
|
|
|
if echo "$docker_version" | grep -q "^28\."; then
|
|
print_success "✓ Docker 28.x installed successfully ($docker_version)"
|
|
else
|
|
print_error "✗ Docker version incorrect: $docker_version (expected 28.x)"
|
|
gpg_success=false
|
|
fi
|
|
|
|
if awk -v ver="$api_version" 'BEGIN {exit !(ver >= 1.47 && ver < 1.52)}'; then
|
|
print_success "✓ Docker API $api_version confirmed (Docker 28.x series, below 1.52 breaking change)"
|
|
else
|
|
print_error "✗ Docker API incorrect: $api_version (expected 1.47-1.51, below 1.52 breaking change)"
|
|
gpg_success=false
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -rf "$backup_dir"
|
|
$SUDO rm -f /tmp/docker.gpg
|
|
|
|
echo ""
|
|
if [ "$gpg_success" = true ]; then
|
|
print_header "Test Result: PASSED"
|
|
print_success "GPG key conflict handling works correctly"
|
|
return 0
|
|
else
|
|
print_header "Test Result: FAILED"
|
|
print_error "GPG key conflict handling has issues"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to test GPG download resilience
|
|
test_gpg_download_resilience() {
|
|
print_header "Testing GPG Download Resilience"
|
|
print_info "This test verifies the GPG key download logic (retries + HTTP/1.1 fallback)"
|
|
echo ""
|
|
|
|
# Backup existing key
|
|
if [ -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_info "Backing up existing GPG key..."
|
|
$SUDO cp /etc/apt/keyrings/docker.asc /tmp/docker.asc.backup
|
|
fi
|
|
|
|
# Remove key to force download
|
|
print_info "Removing GPG key to force download..."
|
|
$SUDO rm -f /etc/apt/keyrings/docker.asc
|
|
|
|
# Run the script
|
|
print_info "Running fix script..."
|
|
if bash ./run.sh; then
|
|
print_success "Script finished successfully"
|
|
else
|
|
print_error "Script failed"
|
|
# Restore backup if script failed
|
|
if [ -f /tmp/docker.asc.backup ]; then
|
|
$SUDO cp /tmp/docker.asc.backup /etc/apt/keyrings/docker.asc
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
# Verify key exists
|
|
if [ -f /etc/apt/keyrings/docker.asc ]; then
|
|
print_success "✓ GPG key downloaded successfully"
|
|
|
|
# Verify it's a valid key
|
|
if file /etc/apt/keyrings/docker.asc | grep -q "PGP public key"; then
|
|
print_success "✓ File is a valid PGP public key"
|
|
else
|
|
print_error "✗ File is not a valid PGP key"
|
|
return 1
|
|
fi
|
|
else
|
|
print_error "✗ GPG key failed to download"
|
|
# Restore backup
|
|
if [ -f /tmp/docker.asc.backup ]; then
|
|
$SUDO cp /tmp/docker.asc.backup /etc/apt/keyrings/docker.asc
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
# Clean up backup
|
|
rm -f /tmp/docker.asc.backup
|
|
|
|
return 0
|
|
}
|
|
|
|
# Function to test Snap daemon hang handling
|
|
test_snap_hang() {
|
|
print_header "Testing Snap Daemon Hang Handling"
|
|
print_info "This test directly tests the check_and_remove_snap_docker function"
|
|
print_info "It should timeout in 3 seconds and not hang forever"
|
|
echo ""
|
|
|
|
# Create mock snap command
|
|
local mock_dir="/tmp/mock-snap-$$"
|
|
mkdir -p "$mock_dir"
|
|
|
|
cat > "$mock_dir/snap" << 'EOF'
|
|
#!/bin/sh
|
|
# Simulate hang
|
|
sleep 10
|
|
exit 0
|
|
EOF
|
|
chmod +x "$mock_dir/snap"
|
|
|
|
# Save PATH
|
|
local old_path="$PATH"
|
|
export PATH="$mock_dir:$PATH"
|
|
|
|
print_info "Testing: timeout 3 snap list docker"
|
|
|
|
# Test the timeout command directly
|
|
local start_time=$(date +%s)
|
|
timeout 3 snap list docker >/dev/null 2>&1
|
|
local exit_code=$?
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - start_time))
|
|
|
|
# Restore PATH
|
|
export PATH="$old_path"
|
|
|
|
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 5 ]; then
|
|
print_success "✓ Timeout triggered in $duration seconds (expected ~3s)"
|
|
else
|
|
print_error "✗ Timeout took $duration seconds (expected ~3s)"
|
|
rm -rf "$mock_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Now test the actual function behavior by sourcing and calling it
|
|
print_info "Testing actual check_and_remove_snap_docker function..."
|
|
|
|
# Extract just the function from run.sh
|
|
local test_script="/tmp/test-snap-func-$$.sh"
|
|
cat > "$test_script" << 'TESTEOF'
|
|
#!/bin/bash
|
|
set -o pipefail
|
|
|
|
SUDO=""
|
|
if [ "$EUID" -ne 0 ]; then
|
|
SUDO="sudo"
|
|
fi
|
|
|
|
check_and_remove_snap_docker() {
|
|
echo "Checking for Docker installed via Snap..."
|
|
|
|
if ! command -v snap &>/dev/null; then
|
|
echo "Snap is not installed on this system"
|
|
echo ""
|
|
return 0
|
|
fi
|
|
|
|
# Check if Docker is installed via snap
|
|
# Use timeout to prevent hanging if snapd is unresponsive
|
|
if timeout 3 snap list docker >/dev/null 2>&1; then
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "WARNING: Docker installed via Snap detected!"
|
|
echo "=========================================="
|
|
echo ""
|
|
return 0
|
|
else
|
|
# Check if it was a timeout or just not found
|
|
local exit_code=$?
|
|
if [ $exit_code -eq 124 ]; then
|
|
echo "WARNING: 'snap list' timed out. Snap daemon might be unresponsive."
|
|
echo "Skipping Snap Docker check to prevent hanging."
|
|
echo ""
|
|
return 0
|
|
fi
|
|
|
|
echo "No Docker Snap package found"
|
|
echo ""
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
check_and_remove_snap_docker
|
|
TESTEOF
|
|
|
|
chmod +x "$test_script"
|
|
|
|
# Run with mocked snap
|
|
export PATH="$mock_dir:$PATH"
|
|
start_time=$(date +%s)
|
|
local output
|
|
output=$($test_script 2>&1)
|
|
end_time=$(date +%s)
|
|
duration=$((end_time - start_time))
|
|
export PATH="$old_path"
|
|
|
|
print_info "Function test completed in $duration seconds"
|
|
|
|
if [ $duration -le 5 ]; then
|
|
print_success "✓ Function completed in $duration seconds (expected ~3s)"
|
|
else
|
|
print_error "✗ Function took $duration seconds (expected ~3s)"
|
|
echo "Output: $output"
|
|
rm -rf "$mock_dir" "$test_script"
|
|
return 1
|
|
fi
|
|
|
|
if echo "$output" | grep -q "WARNING: 'snap list' timed out"; then
|
|
print_success "✓ Function detected timeout and printed correct warning"
|
|
else
|
|
print_error "✗ Function did not detect 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 "Snap timeout handling works correctly"
|
|
return 0
|
|
}
|
|
|
|
# Function to test network namespace cleanup
|
|
test_netns_cleanup() {
|
|
print_header "Testing Network Namespace Cleanup"
|
|
print_info "This test verifies that the script handles 'device busy' errors"
|
|
print_info "when cleaning up Docker network namespaces"
|
|
echo ""
|
|
|
|
# Check if Docker is running
|
|
if ! $SUDO systemctl is-active --quiet docker; then
|
|
print_warning "Docker is not running, starting it..."
|
|
$SUDO systemctl start docker
|
|
sleep 3
|
|
fi
|
|
|
|
# Create some network namespaces by running containers
|
|
print_info "Creating Docker containers with network namespaces..."
|
|
|
|
local containers=()
|
|
for i in 1 2 3; do
|
|
local container_id=$(timeout 10 $SUDO docker run -d --rm --name test-netns-$i busybox sleep 300 2>/dev/null || echo "")
|
|
if [ -n "$container_id" ]; then
|
|
containers+=("test-netns-$i")
|
|
print_success "Created container: test-netns-$i"
|
|
else
|
|
print_warning "Could not create test container $i (busybox image may need to be pulled)"
|
|
fi
|
|
done
|
|
|
|
if [ ${#containers[@]} -eq 0 ]; then
|
|
print_warning "No containers created, trying to pull busybox..."
|
|
if timeout 60 $SUDO docker pull busybox >/dev/null 2>&1; then
|
|
print_success "Pulled busybox image"
|
|
# Try again
|
|
for i in 1 2 3; do
|
|
local container_id=$(timeout 10 $SUDO docker run -d --rm --name test-netns-$i busybox sleep 300 2>/dev/null || echo "")
|
|
if [ -n "$container_id" ]; then
|
|
containers+=("test-netns-$i")
|
|
print_success "Created container: test-netns-$i"
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Containers created: ${#containers[@]}"
|
|
|
|
if [ ${#containers[@]} -eq 0 ]; then
|
|
print_warning "Could not create test containers, skipping netns cleanup test"
|
|
return 0
|
|
fi
|
|
|
|
# Check that network namespaces exist
|
|
if [ -d /var/run/docker/netns ]; then
|
|
local netns_count=$(find /var/run/docker/netns -type f 2>/dev/null | wc -l)
|
|
print_info "Network namespaces found: $netns_count"
|
|
fi
|
|
|
|
# Now try to clean Docker state while containers are running
|
|
# This should trigger the "device busy" error on /var/run/docker/netns/default
|
|
print_info "Stopping Docker while containers are running (simulates cleanup scenario)..."
|
|
$SUDO systemctl stop docker 2>/dev/null || true
|
|
sleep 2
|
|
|
|
# Try to clean up - this is what the script does
|
|
print_info "Attempting to clean /var/run/docker (should handle 'device busy' gracefully)..."
|
|
|
|
if [ -d /var/run/docker ]; then
|
|
# This should NOT fail even if netns is busy
|
|
if $SUDO find /var/run/docker -mindepth 1 -maxdepth 1 ! -path '/var/run/docker/netns' -delete 2>/dev/null; then
|
|
print_success "✓ Cleaned /var/run/docker (excluding netns)"
|
|
else
|
|
print_warning "⚠ Some files could not be cleaned"
|
|
fi
|
|
|
|
# Try to clean netns, but ignore "device busy" errors
|
|
if [ -d /var/run/docker/netns ]; then
|
|
$SUDO find /var/run/docker/netns -type f -delete 2>/dev/null || true
|
|
print_success "✓ Attempted netns cleanup (errors ignored)"
|
|
fi
|
|
fi
|
|
|
|
# Start Docker again
|
|
print_info "Starting Docker..."
|
|
$SUDO systemctl start docker
|
|
sleep 3
|
|
|
|
# Clean up test containers
|
|
print_info "Cleaning up test containers..."
|
|
for container in "${containers[@]}"; do
|
|
timeout 10 $SUDO docker stop "$container" 2>/dev/null || true
|
|
timeout 10 $SUDO docker rm -f "$container" 2>/dev/null || true
|
|
done
|
|
|
|
# Verify Docker is working
|
|
if $SUDO systemctl is-active --quiet docker; then
|
|
print_success "✓ Docker service is running after cleanup test"
|
|
else
|
|
print_error "✗ Docker service failed to start after cleanup test"
|
|
return 1
|
|
fi
|
|
|
|
# Try to run a simple container to verify functionality
|
|
print_info "Testing Docker functionality with hello-world..."
|
|
if timeout 60 $SUDO docker run --rm hello-world >/dev/null 2>&1; then
|
|
print_success "✓ Docker is fully functional after netns cleanup test"
|
|
else
|
|
print_warning "⚠ Docker may have issues after netns cleanup test"
|
|
fi
|
|
|
|
print_header "Test Result: PASSED"
|
|
print_success "Network namespace cleanup handled correctly"
|
|
print_info "The script properly handles 'device busy' errors during cleanup"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Function to test all API versions
|
|
test_all_apis() {
|
|
print_header "Testing All API Versions Above 1.44"
|
|
print_info "This will test the fix script against multiple Docker versions:"
|
|
print_info " • API 1.45 (Docker 27.x)"
|
|
print_info " • API 1.46 (Docker 28.x)"
|
|
print_info " • API 1.47 (Docker 29.x)"
|
|
echo ""
|
|
|
|
if [ "$NON_INTERACTIVE" = "true" ]; then
|
|
REPLY="yes"
|
|
else
|
|
read -p "Do you want to continue? (yes/no): " -r REPLY
|
|
echo ""
|
|
fi
|
|
|
|
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]] && [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
print_info "Test cancelled"
|
|
return 0
|
|
fi
|
|
|
|
local results=()
|
|
local failed=0
|
|
|
|
# Test API 1.45
|
|
print_info "=== Test 1 of 3: API 1.45 ==="
|
|
echo ""
|
|
if install_docker_version "27." "API 1.45 - Docker 27.x series"; then
|
|
if test_fix_script; then
|
|
results+=("✓ API 1.45 (Docker 27.x): PASSED")
|
|
else
|
|
results+=("✗ API 1.45 (Docker 27.x): FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
else
|
|
results+=("✗ API 1.45 (Docker 27.x): Installation FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test API 1.46
|
|
print_info "=== Test 2 of 3: API 1.46 ==="
|
|
echo ""
|
|
if install_docker_version "28." "API 1.46 - Docker 28.x series"; then
|
|
if test_fix_script; then
|
|
results+=("✓ API 1.46 (Docker 28.x): PASSED")
|
|
else
|
|
results+=("✗ API 1.46 (Docker 28.x): FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
else
|
|
results+=("✗ API 1.46 (Docker 28.x): Installation FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test API 1.47
|
|
print_info "=== Test 3 of 3: API 1.47 ==="
|
|
echo ""
|
|
if install_docker_version "29." "API 1.47 - Docker 29.x series"; then
|
|
if test_fix_script; then
|
|
results+=("✓ API 1.47 (Docker 29.x): PASSED")
|
|
else
|
|
results+=("✗ API 1.47 (Docker 29.x): FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
else
|
|
results+=("✗ API 1.47 (Docker 29.x): Installation FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
# Print summary
|
|
print_header "Test Summary: All API Versions"
|
|
echo "Results:"
|
|
for result in "${results[@]}"; do
|
|
echo " $result"
|
|
done
|
|
echo ""
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
print_success "All tests PASSED!"
|
|
return 0
|
|
else
|
|
print_error "$failed test(s) FAILED"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to show usage
|
|
show_usage() {
|
|
echo "Usage: $0 [command]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " status - Show current Docker version and API"
|
|
echo " upgrade - Upgrade Docker to latest (simulates the problem)"
|
|
echo " test - Run the fix script and verify it works"
|
|
echo " full - Run full test (upgrade + test fix)"
|
|
echo ""
|
|
echo "Test specific API versions:"
|
|
echo " api-1.45 - Test with API 1.45 (Docker 27.x)"
|
|
echo " api-1.46 - Test with API 1.46 (Docker 28.x)"
|
|
echo " api-1.47 - Test with API 1.47 (Docker 29.x)"
|
|
echo " test-all - Test all API versions above 1.44"
|
|
echo ""
|
|
echo "Bug fix tests (for reported issues):"
|
|
echo " test-gpg - Test GPG key conflict handling (Debian/OMV issue)"
|
|
echo " test-download - Test GPG download resilience (retries + HTTP/1.1)"
|
|
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-polling - Test Docker startup polling (two runs fix)"
|
|
echo " test-bugfixes - Run all bug fix tests"
|
|
echo ""
|
|
echo "Alternative fix for newer distros (Ubuntu 24.04+, Debian trixie):"
|
|
echo " apply-override - Apply Docker API override (DOCKER_MIN_API_VERSION=1.24)"
|
|
echo " remove-override - Remove Docker API override configuration"
|
|
echo ""
|
|
echo " help - Show this help message"
|
|
echo ""
|
|
echo "Example workflow:"
|
|
echo " 1. $0 status # Check current state"
|
|
echo " 2. $0 upgrade # Simulate the problem"
|
|
echo " 3. $0 status # Verify Docker was upgraded"
|
|
echo " 4. $0 test # Test the fix script"
|
|
echo ""
|
|
echo "Test specific API versions:"
|
|
echo " $0 api-1.45 # Install Docker 27.x and test"
|
|
echo " $0 api-1.46 # Install Docker 28.x and test"
|
|
echo " $0 api-1.47 # Install Docker 29.x and test"
|
|
echo ""
|
|
echo "Test bug fixes:"
|
|
echo " $0 test-gpg # Test GPG key conflict resolution (Debian/OMV)"
|
|
echo " $0 test-download # Test GPG download resilience"
|
|
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-polling # Test Docker startup polling (two runs fix)"
|
|
echo " $0 test-bugfixes # Run all bug fix tests"
|
|
echo ""
|
|
echo "Alternative fix (for distros without Docker 28.x):"
|
|
echo " $0 apply-override # Apply API override to work with newer Docker"
|
|
echo " $0 remove-override # Remove API override"
|
|
echo ""
|
|
echo "Or run all tests:"
|
|
echo " $0 test-all # Test against all API versions"
|
|
echo " $0 full # Standard full test (upgrade + fix)"
|
|
echo ""
|
|
}
|
|
|
|
# Function to test CasaOS version check hang
|
|
test_casaos_hang() {
|
|
print_header "Testing CasaOS Version Check Hang Handling"
|
|
print_info "This test verifies that the script handles a hanging 'casaos -v' command"
|
|
print_info "It should timeout in 5 seconds and not hang forever"
|
|
echo ""
|
|
|
|
# Create mock casaos command
|
|
local mock_dir="/tmp/mock-casaos-$$"
|
|
mkdir -p "$mock_dir"
|
|
|
|
cat > "$mock_dir/casaos" << 'EOF'
|
|
#!/bin/sh
|
|
# Simulate hang (like when CasaOS is in a broken state)
|
|
sleep 15
|
|
echo "v0.4.4"
|
|
exit 0
|
|
EOF
|
|
chmod +x "$mock_dir/casaos"
|
|
|
|
# Save PATH
|
|
local old_path="$PATH"
|
|
export PATH="$mock_dir:$PATH"
|
|
|
|
print_info "Testing: timeout 5 casaos -v"
|
|
|
|
# Test the timeout command directly
|
|
local start_time=$(date +%s)
|
|
timeout 5 casaos -v >/dev/null 2>&1
|
|
local exit_code=$?
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - start_time))
|
|
|
|
# Restore PATH
|
|
export PATH="$old_path"
|
|
|
|
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 7 ]; then
|
|
print_success "✓ Timeout triggered in $duration seconds (expected ~5s)"
|
|
else
|
|
print_error "✗ Timeout took $duration seconds (expected ~5s)"
|
|
rm -rf "$mock_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Now test the actual function behavior by sourcing and calling it
|
|
print_info "Testing actual check_casaos function..."
|
|
|
|
# Extract just the function from run.sh
|
|
local test_script="/tmp/test-casaos-func-$$.sh"
|
|
cat > "$test_script" << 'TESTEOF'
|
|
#!/bin/bash
|
|
set -o pipefail
|
|
|
|
# Function to check if CasaOS is installed
|
|
check_casaos() {
|
|
if command -v casaos &>/dev/null; then
|
|
# Use timeout to prevent hanging if CasaOS is in a broken state
|
|
local version
|
|
version=$(timeout 5 casaos -v 2>/dev/null) || version="version unknown"
|
|
echo "CasaOS is installed: $version"
|
|
return 0
|
|
else
|
|
echo "CasaOS not detected."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
echo "Calling check_casaos..."
|
|
check_casaos
|
|
TESTEOF
|
|
|
|
chmod +x "$test_script"
|
|
|
|
# Run with mocked casaos
|
|
export PATH="$mock_dir:$PATH"
|
|
start_time=$(date +%s)
|
|
local output
|
|
output=$($test_script 2>&1)
|
|
end_time=$(date +%s)
|
|
duration=$((end_time - start_time))
|
|
export PATH="$old_path"
|
|
|
|
print_info "Function test completed in $duration seconds"
|
|
|
|
if [ $duration -le 7 ]; then
|
|
print_success "✓ Function completed in $duration seconds (expected ~5s)"
|
|
else
|
|
print_error "✗ Function took $duration seconds (expected ~5s)"
|
|
echo "Output: $output"
|
|
rm -rf "$mock_dir" "$test_script"
|
|
return 1
|
|
fi
|
|
|
|
if echo "$output" | grep -q "version unknown"; then
|
|
print_success "✓ Function detected timeout and set version to 'version unknown'"
|
|
else
|
|
print_error "✗ Function did not handle timeout correctly"
|
|
echo "Output: $output"
|
|
rm -rf "$mock_dir" "$test_script"
|
|
return 1
|
|
fi
|
|
|
|
if echo "$output" | grep -q "CasaOS is installed:"; then
|
|
print_success "✓ Function correctly identified CasaOS as installed"
|
|
else
|
|
print_error "✗ Function did not identify CasaOS as installed"
|
|
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 "CasaOS version check timeout handling works correctly"
|
|
return 0
|
|
}
|
|
|
|
# Function to test unresponsive Docker daemon
|
|
test_unresponsive_daemon() {
|
|
print_header "Testing Unresponsive Docker Daemon Handling"
|
|
print_info "This test verifies that the script handles an unresponsive Docker daemon"
|
|
print_info "It should timeout in 10 seconds and not hang forever"
|
|
echo ""
|
|
|
|
# Create mock docker command
|
|
local mock_dir="/tmp/mock-docker-$$"
|
|
mkdir -p "$mock_dir"
|
|
|
|
cat > "$mock_dir/docker" << 'EOF'
|
|
#!/bin/sh
|
|
# Simulate hang
|
|
sleep 20
|
|
exit 0
|
|
EOF
|
|
chmod +x "$mock_dir/docker"
|
|
|
|
# Save PATH
|
|
local old_path="$PATH"
|
|
export PATH="$mock_dir:$PATH"
|
|
|
|
print_info "Testing: timeout 10 docker version"
|
|
|
|
# Test the timeout command directly
|
|
local start_time=$(date +%s)
|
|
timeout 10 docker version >/dev/null 2>&1
|
|
local exit_code=$?
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - start_time))
|
|
|
|
# Restore PATH
|
|
export PATH="$old_path"
|
|
|
|
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 get_docker_api_version function..."
|
|
|
|
# Extract just the function from run.sh
|
|
local test_script="/tmp/test-docker-func-$$.sh"
|
|
cat > "$test_script" << 'TESTEOF'
|
|
#!/bin/bash
|
|
set -o pipefail
|
|
|
|
SUDO=""
|
|
if [ "$EUID" -ne 0 ]; then
|
|
SUDO="sudo"
|
|
fi
|
|
|
|
# Function to get Docker API version
|
|
get_docker_api_version() {
|
|
local api_version=""
|
|
|
|
# Try to get the API version from docker version command
|
|
if command -v docker &>/dev/null; then
|
|
# Get server API version
|
|
# Use timeout to prevent hanging if Docker daemon is unresponsive
|
|
local output
|
|
output=$(timeout 10 $SUDO docker version --format '{{.Server.APIVersion}}' 2>/dev/null)
|
|
local exit_code=$?
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
api_version="$output"
|
|
elif [ $exit_code -eq 124 ]; then
|
|
# Timed out - do not try fallback as it will likely hang too
|
|
api_version=""
|
|
else
|
|
# Other error (e.g. format not supported), try fallback
|
|
api_version=""
|
|
fi
|
|
|
|
if [ -z "$api_version" ] && [ $exit_code -ne 124 ]; then
|
|
# Fallback: try to parse from docker version output
|
|
api_version=$(timeout 10 $SUDO docker version 2>/dev/null | grep -A 5 "Server:" | grep "API version:" | awk '{print $3}' | head -n1 || echo "")
|
|
fi
|
|
fi
|
|
|
|
echo "$api_version"
|
|
return 0
|
|
}
|
|
|
|
echo "Calling get_docker_api_version..."
|
|
get_docker_api_version
|
|
TESTEOF
|
|
|
|
chmod +x "$test_script"
|
|
|
|
# Run with mocked docker
|
|
export PATH="$mock_dir:$PATH"
|
|
start_time=$(date +%s)
|
|
local output
|
|
output=$($test_script 2>&1)
|
|
end_time=$(date +%s)
|
|
duration=$((end_time - start_time))
|
|
export PATH="$old_path"
|
|
|
|
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
|
|
|
|
# Clean up
|
|
rm -rf "$mock_dir" "$test_script"
|
|
|
|
print_header "Test Result: PASSED"
|
|
print_success "Docker daemon timeout handling works correctly"
|
|
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 test Docker startup polling (fixes the "two runs needed" issue)
|
|
test_docker_startup_polling() {
|
|
print_header "Testing Docker Startup Polling"
|
|
print_info "This test verifies the fix for the 'two runs needed' bug"
|
|
print_info "The script now uses active polling to wait for Docker to be ready"
|
|
print_info "instead of fixed sleep times that were often too short"
|
|
echo ""
|
|
|
|
# Test 1: Verify the polling logic exists in run.sh
|
|
print_info "Test 1: Checking for active polling loops in run.sh..."
|
|
|
|
local run_script="./run.sh"
|
|
if [ ! -f "$run_script" ]; then
|
|
print_error "run.sh not found in current directory"
|
|
return 1
|
|
fi
|
|
|
|
# Check for the containerd polling loop
|
|
if grep -q "Waiting for containerd to be fully ready" "$run_script"; then
|
|
print_success "✓ Containerd polling loop found"
|
|
else
|
|
print_error "✗ Containerd polling loop NOT found"
|
|
print_info "Expected: 'Waiting for containerd to be fully ready'"
|
|
return 1
|
|
fi
|
|
|
|
# Check for the Docker polling loop with docker info verification
|
|
if grep -q "Waiting for Docker to be fully ready" "$run_script" && grep -q 'docker info' "$run_script"; then
|
|
print_success "✓ Docker polling loop with 'docker info' verification found"
|
|
else
|
|
print_error "✗ Docker polling loop with 'docker info' verification NOT found"
|
|
print_info "Expected: 'Waiting for Docker to be fully ready' and 'docker info' check"
|
|
return 1
|
|
fi
|
|
|
|
# Check for sleep after daemon-reload
|
|
if grep -A1 'systemctl daemon-reload' "$run_script" | grep -q 'sleep 2'; then
|
|
print_success "✓ Sleep after daemon-reload found (prevents race condition)"
|
|
else
|
|
print_warning "⚠ Sleep after daemon-reload may be missing or different"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Test 2: Simulate slow Docker startup and verify polling works
|
|
print_info "Test 2: Testing polling behavior with simulated slow startup..."
|
|
|
|
local mock_dir="/tmp/mock-docker-polling-$$"
|
|
mkdir -p "$mock_dir"
|
|
|
|
# Create a state file to track docker info calls
|
|
local state_file="$mock_dir/docker_state"
|
|
echo "0" > "$state_file"
|
|
|
|
# Create mock docker that fails first few times, then succeeds
|
|
# Note: We use double quotes for the heredoc delimiter to allow variable expansion
|
|
cat > "$mock_dir/docker" << EOF
|
|
#!/bin/bash
|
|
STATE_FILE="$state_file"
|
|
if [ ! -f "\$STATE_FILE" ]; then
|
|
echo "0" > "\$STATE_FILE"
|
|
fi
|
|
|
|
# Read current call count
|
|
count=\$(cat "\$STATE_FILE")
|
|
count=\$((count + 1))
|
|
echo "\$count" > "\$STATE_FILE"
|
|
|
|
# For 'info' command: fail first 2 times, succeed after
|
|
if [ "\$1" = "info" ]; then
|
|
if [ \$count -le 2 ]; then
|
|
# Simulate Docker not ready yet
|
|
exit 1
|
|
else
|
|
# Docker is ready now
|
|
echo "Docker is ready"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# For 'version' command: always succeed with mock version
|
|
if [ "\$1" = "version" ]; then
|
|
echo "Docker version 28.0.4, build 2a55bb0"
|
|
exit 0
|
|
fi
|
|
|
|
exit 0
|
|
EOF
|
|
chmod +x "$mock_dir/docker"
|
|
|
|
# Create mock systemctl
|
|
cat > "$mock_dir/systemctl" << 'EOF'
|
|
#!/bin/bash
|
|
# Always report docker as active
|
|
if [ "$1" = "is-active" ] && [ "$2" = "--quiet" ] && [ "$3" = "docker" ]; then
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "--quiet" ] && [ "$3" = "containerd" ]; then
|
|
exit 0
|
|
fi
|
|
# Pass through other commands
|
|
exit 0
|
|
EOF
|
|
chmod +x "$mock_dir/systemctl"
|
|
|
|
# Create test script that simulates the polling logic from run.sh
|
|
local test_script="$mock_dir/test_polling.sh"
|
|
cat > "$test_script" << 'TESTEOF'
|
|
#!/bin/bash
|
|
set -o pipefail
|
|
|
|
SUDO=""
|
|
|
|
echo "Testing Docker startup polling..."
|
|
echo ""
|
|
|
|
# Simulate the polling logic from run.sh
|
|
echo "Waiting for Docker to be fully ready..."
|
|
docker_ready=false
|
|
for i in {1..15}; do
|
|
if $SUDO systemctl is-active --quiet docker 2>/dev/null; then
|
|
if timeout 5 $SUDO docker info >/dev/null 2>&1; then
|
|
docker_ready=true
|
|
echo "✓ Docker is ready and responding (attempt $i)"
|
|
break
|
|
fi
|
|
fi
|
|
echo " Waiting for Docker... ($i/15)"
|
|
sleep 0.1 # Use short sleep for test speed
|
|
done
|
|
|
|
if [ "$docker_ready" = true ]; then
|
|
echo ""
|
|
echo "SUCCESS: Docker became ready after polling"
|
|
exit 0
|
|
else
|
|
echo ""
|
|
echo "FAILURE: Docker did not become ready"
|
|
exit 1
|
|
fi
|
|
TESTEOF
|
|
chmod +x "$test_script"
|
|
|
|
# Save PATH and run with mocks
|
|
local old_path="$PATH"
|
|
export PATH="$mock_dir:$PATH"
|
|
hash -r
|
|
|
|
local start_time=$(date +%s)
|
|
local output
|
|
output=$("$test_script" 2>&1)
|
|
local exit_code=$?
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - start_time))
|
|
|
|
# Restore PATH
|
|
export PATH="$old_path"
|
|
hash -r
|
|
|
|
echo "$output"
|
|
echo ""
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
print_success "✓ Polling detected Docker became ready after retries"
|
|
else
|
|
print_error "✗ Polling failed to detect Docker readiness"
|
|
rm -rf "$mock_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Verify multiple attempts were made
|
|
local final_count=$(cat "$state_file" 2>/dev/null || echo "0")
|
|
if [ "$final_count" -ge 3 ]; then
|
|
print_success "✓ Multiple polling attempts were made ($final_count calls to docker)"
|
|
else
|
|
print_warning "⚠ Expected at least 3 docker calls, got $final_count"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Test 3: Verify that old fixed sleep times are replaced with polling
|
|
print_info "Test 3: Verifying fixed 'sleep 5' for Docker startup is removed..."
|
|
|
|
# The problematic pattern was "sleep 5 # Give Docker more time"
|
|
# This should be replaced with the polling loop
|
|
if grep -n 'sleep 5.*Give Docker more time' "$run_script" 2>/dev/null; then
|
|
print_error "✗ Old fixed 'sleep 5' pattern still exists"
|
|
print_info "This should be replaced with active polling"
|
|
rm -rf "$mock_dir"
|
|
return 1
|
|
else
|
|
print_success "✓ Old fixed 'sleep 5' pattern has been removed"
|
|
fi
|
|
|
|
# Check that polling loop exists in docker startup section
|
|
if grep -A30 "Enabling and starting Docker service" "$run_script" | grep -q "Waiting for Docker to be fully ready"; then
|
|
print_success "✓ Polling loop is in the correct location (after Docker service start)"
|
|
else
|
|
print_error "✗ Polling loop not found after Docker service start"
|
|
rm -rf "$mock_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -rf "$mock_dir"
|
|
|
|
print_header "Test Result: PASSED"
|
|
print_success "Docker startup polling fix is correctly implemented"
|
|
print_info "The 'two runs needed' bug should now be fixed"
|
|
return 0
|
|
}
|
|
|
|
# Function to run all bug fix tests
|
|
test_all_bugfixes() {
|
|
print_header "Running All Bug Fix Tests"
|
|
print_info "This will test all bug fixes:"
|
|
print_info " 1. GPG key conflict handling"
|
|
print_info " 2. GPG download resilience"
|
|
print_info " 3. Snap daemon hang handling"
|
|
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"
|
|
print_info " 8. Docker startup polling (two runs fix)"
|
|
echo ""
|
|
|
|
local results=()
|
|
local failed=0
|
|
|
|
# Test GPG conflicts
|
|
print_info "=== Test 1 of 8: GPG Key Conflicts ==="
|
|
echo ""
|
|
if test_gpg_key_conflicts; then
|
|
results+=("✓ GPG key conflict handling: PASSED")
|
|
else
|
|
results+=("✗ GPG key conflict handling: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test GPG download resilience
|
|
print_info "=== Test 2 of 8: GPG Download Resilience ==="
|
|
echo ""
|
|
if test_gpg_download_resilience; then
|
|
results+=("✓ GPG download resilience: PASSED")
|
|
else
|
|
results+=("✗ GPG download resilience: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test Snap hang
|
|
print_info "=== Test 3 of 8: Snap Daemon Hang ==="
|
|
echo ""
|
|
if test_snap_hang; then
|
|
results+=("✓ Snap daemon hang handling: PASSED")
|
|
else
|
|
results+=("✗ Snap daemon hang handling: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test netns cleanup
|
|
print_info "=== Test 4 of 8: Network Namespace Cleanup ==="
|
|
echo ""
|
|
if test_netns_cleanup; then
|
|
results+=("✓ Network namespace cleanup: PASSED")
|
|
else
|
|
results+=("✗ Network namespace cleanup: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test unresponsive daemon
|
|
print_info "=== Test 5 of 8: Unresponsive Docker Daemon ==="
|
|
echo ""
|
|
if test_unresponsive_daemon; then
|
|
results+=("✓ Unresponsive daemon handling: PASSED")
|
|
else
|
|
results+=("✗ Unresponsive daemon handling: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test CasaOS hang
|
|
print_info "=== Test 6 of 8: CasaOS Version Check Hang ==="
|
|
echo ""
|
|
if test_casaos_hang; then
|
|
results+=("✓ CasaOS version check hang handling: PASSED")
|
|
else
|
|
results+=("✗ CasaOS version check hang handling: FAILED")
|
|
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 8: 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
|
|
|
|
echo ""
|
|
if [ "$NON_INTERACTIVE" != "true" ]; then
|
|
read -p "Press Enter to continue to next test..." -r
|
|
echo ""
|
|
fi
|
|
|
|
# Test Docker startup polling (two runs fix)
|
|
print_info "=== Test 8 of 8: Docker Startup Polling (Two Runs Fix) ==="
|
|
echo ""
|
|
if test_docker_startup_polling; then
|
|
results+=("✓ Docker startup polling: PASSED")
|
|
else
|
|
results+=("✗ Docker startup polling: FAILED")
|
|
failed=$((failed + 1))
|
|
fi
|
|
|
|
# Print summary
|
|
print_header "Bug Fix Test Summary"
|
|
echo "Results:"
|
|
for result in "${results[@]}"; do
|
|
echo " $result"
|
|
done
|
|
echo ""
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
print_success "All bug fix tests PASSED!"
|
|
return 0
|
|
else
|
|
print_error "$failed bug fix test(s) FAILED"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Main script
|
|
main() {
|
|
if [ $# -eq 0 ]; then
|
|
show_usage
|
|
exit 0
|
|
fi
|
|
|
|
case "$1" in
|
|
status)
|
|
show_status
|
|
;;
|
|
upgrade)
|
|
upgrade_docker_to_latest
|
|
;;
|
|
test)
|
|
test_fix_script
|
|
;;
|
|
full)
|
|
run_full_test
|
|
;;
|
|
api-1.45|api-1-45|api145)
|
|
test_api_1_45
|
|
;;
|
|
api-1.46|api-1-46|api146)
|
|
test_api_1_46
|
|
;;
|
|
api-1.47|api-1-47|api147)
|
|
test_api_1_47
|
|
;;
|
|
test-all|all)
|
|
test_all_apis
|
|
;;
|
|
test-gpg|gpg|test-gpg-conflicts)
|
|
test_gpg_key_conflicts
|
|
;;
|
|
test-download|download|test-gpg-download)
|
|
test_gpg_download_resilience
|
|
;;
|
|
test-snap|snap|test-snap-hang)
|
|
test_snap_hang
|
|
;;
|
|
test-netns|netns|test-network-namespace)
|
|
test_netns_cleanup
|
|
;;
|
|
test-casaos|casaos|test-casaos-hang)
|
|
test_casaos_hang
|
|
;;
|
|
test-dpkg|dpkg|test-dpkg-hang)
|
|
test_dpkg_hang
|
|
;;
|
|
test-polling|polling|test-docker-polling|two-runs)
|
|
test_docker_startup_polling
|
|
;;
|
|
test-bugfixes|bugfixes|bug-fixes)
|
|
test_all_bugfixes
|
|
;;
|
|
apply-override|override)
|
|
apply_docker_api_override
|
|
;;
|
|
remove-override|no-override)
|
|
remove_docker_api_override
|
|
;;
|
|
help|--help|-h)
|
|
show_usage
|
|
;;
|
|
*)
|
|
print_error "Unknown command: $1"
|
|
echo ""
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|