claude-home/vm-management/scripts/fix-docker-apparmor.sh
Cal Corum 11b96bce2c CLAUDE: Add LXC migration guides and scripts
- Add LXC migration plan and quick-start guide
- Add wave 1 and wave 2 migration results
- Add lxc-docker-create.sh for container creation
- Add fix-docker-apparmor.sh for AppArmor issues
- Add comprehensive LXC migration guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 00:48:30 -06:00

276 lines
8.4 KiB
Bash
Executable File

#!/bin/bash
#
# Docker Compose AppArmor Fix Script
#
# Adds 'security_opt: ["apparmor=unconfined"]' to all services in docker-compose.yml files
# This is required for Docker containers running inside LXC containers.
#
# Usage: ./fix-docker-apparmor.sh <LXC_IP> [COMPOSE_DIR]
#
# Example: ./fix-docker-apparmor.sh 10.10.0.214
# Example: ./fix-docker-apparmor.sh 10.10.0.214 /home/cal/container-data
#
# Arguments:
# LXC_IP - IP address of the LXC container to SSH into
# COMPOSE_DIR - Optional directory containing docker-compose files (default: /home/cal/container-data)
#
# What this script does:
# 1. SSHs into the LXC container
# 2. Finds all docker-compose.yml files
# 3. Adds security_opt configuration to each service
# 4. Creates backups of original files
#
# Why this is needed:
# Docker containers in LXC need AppArmor disabled to function properly.
# Without this fix, containers may fail to start or have permission issues.
#
set -euo pipefail
# Color codes 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 messages
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# Parse arguments
if [[ $# -lt 1 ]]; then
log_error "Insufficient arguments"
echo "Usage: $0 <LXC_IP> [COMPOSE_DIR]"
echo ""
echo "Example: $0 10.10.0.214"
echo "Example: $0 10.10.0.214 /home/cal/container-data"
exit 1
fi
LXC_IP=$1
COMPOSE_DIR=${2:-/home/cal/container-data}
log_info "Starting AppArmor fix for Docker Compose files"
log_info "Target: root@$LXC_IP"
log_info "Directory: $COMPOSE_DIR"
echo ""
# Check SSH connectivity
log_info "Testing SSH connection to $LXC_IP..."
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes root@"$LXC_IP" "echo 'SSH OK'" &>/dev/null; then
log_error "Cannot connect to root@$LXC_IP via SSH"
log_error "Please ensure:"
echo " 1. SSH key is copied to the LXC container"
echo " 2. Container is running"
echo " 3. IP address is correct"
exit 1
fi
log_info "✅ SSH connection successful"
echo ""
# Create Python script on remote host
log_info "Creating AppArmor fix script on remote host..."
ssh root@"$LXC_IP" "cat > /tmp/fix_apparmor.py" <<'PYTHON_SCRIPT'
#!/usr/bin/env python3
"""
Fix Docker Compose files to work in LXC by adding AppArmor unconfined security option.
"""
import yaml
import glob
import sys
import os
from pathlib import Path
def add_apparmor_fix(compose_file):
"""Add security_opt to all services in a docker-compose file."""
print(f"\n📄 Processing: {compose_file}")
# Create backup
backup_file = f"{compose_file}.backup"
if not os.path.exists(backup_file):
os.system(f"cp '{compose_file}' '{backup_file}'")
print(f" ✅ Backup created: {backup_file}")
else:
print(f" ⏭️ Backup already exists: {backup_file}")
# Load compose file
try:
with open(compose_file, 'r') as f:
compose_data = yaml.safe_load(f)
except yaml.YAMLError as e:
print(f" ❌ Error parsing YAML: {e}")
return False
except Exception as e:
print(f" ❌ Error reading file: {e}")
return False
if not compose_data or 'services' not in compose_data:
print(f" ⚠️ No services found in compose file")
return False
# Track changes
services_modified = 0
services_skipped = 0
# Add security_opt to each service
for service_name, service_config in compose_data['services'].items():
if service_config is None:
service_config = {}
compose_data['services'][service_name] = service_config
# Check if security_opt already exists
existing_security = service_config.get('security_opt', [])
if 'apparmor=unconfined' in existing_security or 'apparmor:unconfined' in existing_security:
print(f" ⏭️ {service_name}: Already has AppArmor unconfined")
services_skipped += 1
else:
# Add apparmor=unconfined
if not existing_security:
service_config['security_opt'] = ['apparmor=unconfined']
else:
if 'apparmor=unconfined' not in existing_security:
existing_security.append('apparmor=unconfined')
service_config['security_opt'] = existing_security
print(f" ✅ {service_name}: Added AppArmor unconfined")
services_modified += 1
# Write updated compose file
try:
with open(compose_file, 'w') as f:
yaml.dump(compose_data, f, default_flow_style=False, sort_keys=False, indent=2)
if services_modified > 0:
print(f" 💾 Saved changes ({services_modified} services modified)")
return services_modified > 0
except Exception as e:
print(f" ❌ Error writing file: {e}")
# Restore backup
os.system(f"cp '{backup_file}' '{compose_file}'")
print(f" 🔄 Restored from backup")
return False
def main():
"""Main function to process all docker-compose files."""
compose_dir = sys.argv[1] if len(sys.argv) > 1 else "/home/cal/container-data"
print(f"🔍 Searching for docker-compose.yml files in {compose_dir}")
# Find all docker-compose files
patterns = [
f"{compose_dir}/**/docker-compose.yml",
f"{compose_dir}/**/docker-compose.yaml",
]
compose_files = []
for pattern in patterns:
compose_files.extend(glob.glob(pattern, recursive=True))
# Remove duplicates and sort
compose_files = sorted(set(compose_files))
if not compose_files:
print(f"⚠️ No docker-compose files found in {compose_dir}")
return 1
print(f"📋 Found {len(compose_files)} docker-compose file(s)")
# Process each file
total_modified = 0
total_errors = 0
for compose_file in compose_files:
try:
if add_apparmor_fix(compose_file):
total_modified += 1
except Exception as e:
print(f" ❌ Unexpected error: {e}")
total_errors += 1
# Summary
print("\n" + "="*60)
print("📊 SUMMARY")
print("="*60)
print(f"Total files found: {len(compose_files)}")
print(f"Files modified: {total_modified}")
print(f"Files with errors: {total_errors}")
print(f"Files unchanged: {len(compose_files) - total_modified - total_errors}")
print("="*60)
if total_modified > 0:
print("\n✅ AppArmor fix applied successfully!")
print("\n💡 Next steps:")
print(" 1. Review changes in modified files")
print(" 2. Start containers: docker compose up -d")
print(" 3. Check container status: docker compose ps")
print("\n📝 Note: Backups created with .backup extension")
return 0 if total_errors == 0 else 1
if __name__ == "__main__":
sys.exit(main())
PYTHON_SCRIPT
log_info "✅ Script uploaded to LXC container"
echo ""
# Install PyYAML if needed
log_info "Ensuring Python and PyYAML are installed..."
ssh root@"$LXC_IP" "apt-get update -qq && apt-get install -y -qq python3 python3-yaml > /dev/null 2>&1" || true
log_info "✅ Dependencies ready"
echo ""
# Run the fix script
log_info "Running AppArmor fix script..."
echo ""
ssh root@"$LXC_IP" "python3 /tmp/fix_apparmor.py '$COMPOSE_DIR'"
EXIT_CODE=$?
echo ""
# Cleanup
log_info "Cleaning up temporary files..."
ssh root@"$LXC_IP" "rm /tmp/fix_apparmor.py"
log_info "✅ Cleanup complete"
echo ""
if [[ $EXIT_CODE -eq 0 ]]; then
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "🎉 AppArmor Fix Complete!"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Your docker-compose files have been updated to work in LXC."
echo ""
echo "Next steps:"
echo " 1. SSH into container:"
echo " ssh root@$LXC_IP"
echo ""
echo " 2. Navigate to a service directory:"
echo " cd $COMPOSE_DIR/[service-name]"
echo ""
echo " 3. Start containers:"
echo " docker compose up -d"
echo ""
echo " 4. Check status:"
echo " docker compose ps"
echo ""
else
log_error "AppArmor fix encountered errors. Please review output above."
exit 1
fi