mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 04:08:15 +00:00 
			
		
		
		
	- Unified service templates into generic systemctl templates - Introduced reusable filter plugins for script path handling - Updated path variables and service/timer definitions - Migrated roles (backup, cleanup, repair, etc.) to use systemctl role - Added sys-daemon role for core systemd cleanup - Simplified timer handling via sys-timer role Note: This is a large refactor and some errors may still exist. Further testing and adjustments will be needed.
		
			
				
	
	
		
			146 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import subprocess
 | 
						|
import os
 | 
						|
import time
 | 
						|
import sys
 | 
						|
import shutil
 | 
						|
import argparse
 | 
						|
 | 
						|
 | 
						|
def run_command(command):
 | 
						|
    """ Run a shell command and return its output """
 | 
						|
    print(command)
 | 
						|
    output = subprocess.check_output(command, shell=True).decode('utf-8').strip()
 | 
						|
    print(output)
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
def stop_containers(containers):
 | 
						|
    """Stop a list of containers."""
 | 
						|
    container_list = ' '.join(containers)
 | 
						|
    print(f"Stopping containers {container_list}...")
 | 
						|
    run_command(f"docker stop {container_list}")
 | 
						|
 | 
						|
 | 
						|
def start_containers(containers):
 | 
						|
    """Start a list of containers."""
 | 
						|
    container_list = ' '.join(containers)
 | 
						|
    print(f"Starting containers {container_list}...")
 | 
						|
    run_command(f"docker start {container_list}")
 | 
						|
 | 
						|
 | 
						|
def is_database(image):
 | 
						|
    databases = {"postgres", "mariadb", "redis", "memcached", "mongo"}
 | 
						|
    prefix = image.split(':')[0]
 | 
						|
    return prefix in databases
 | 
						|
 | 
						|
 | 
						|
def is_symbolic_link(file_path):
 | 
						|
    return os.path.islink(file_path)
 | 
						|
 | 
						|
 | 
						|
def get_volume_path(volume):
 | 
						|
    return run_command(f"docker volume inspect --format '{{{{ .Mountpoint }}}}' {volume}")
 | 
						|
 | 
						|
 | 
						|
def get_image(container):
 | 
						|
    return run_command(f"docker inspect --format='{{{{.Config.Image}}}}' {container}")
 | 
						|
 | 
						|
 | 
						|
def has_healthcheck(container):
 | 
						|
    """Check if a container has a HEALTHCHECK defined."""
 | 
						|
    result = run_command(
 | 
						|
        f"docker inspect --format='{{{{json .State.Health}}}}' {container}"
 | 
						|
    )
 | 
						|
    return result not in ("null", "")
 | 
						|
 | 
						|
 | 
						|
def get_health_status(container):
 | 
						|
    """Return the health status."""
 | 
						|
    status = run_command(
 | 
						|
        f"docker inspect --format='{{{{.State.Health.Status}}}}' {container}"
 | 
						|
    )
 | 
						|
    return status
 | 
						|
 | 
						|
 | 
						|
def run_rsync(src, dest):
 | 
						|
    run_command(f"rsync -aP --remove-source-files {src} {dest}")
 | 
						|
 | 
						|
 | 
						|
def delete_directory(path):
 | 
						|
    """Deletes a directory and all its contents."""
 | 
						|
    try:
 | 
						|
        shutil.rmtree(path)
 | 
						|
        print(f"Directory {path} was successfully deleted.")
 | 
						|
    except OSError as e:
 | 
						|
        print(f"Error deleting directory {path}: {e}")
 | 
						|
 | 
						|
 | 
						|
def pause_and_move(storage_path, volume, volume_path, containers):
 | 
						|
    stop_containers(containers)
 | 
						|
    storage_volume_path = os.path.join(storage_path, 'data', 'docker', 'volumes', volume)
 | 
						|
    os.makedirs(storage_volume_path, exist_ok=False)
 | 
						|
    run_rsync(f"{volume_path}/", f"{storage_volume_path}/")
 | 
						|
    delete_directory(volume_path)
 | 
						|
    os.symlink(storage_volume_path, volume_path)
 | 
						|
    start_containers(containers)
 | 
						|
 | 
						|
 | 
						|
def has_container_with_database(containers):
 | 
						|
    for container in containers:
 | 
						|
        image = get_image(container)
 | 
						|
        if is_database(image):
 | 
						|
            return True
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    parser = argparse.ArgumentParser(
 | 
						|
        description='Migrate Docker volumes to SSD or HDD based on container image.'
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        '--rapid-storage-path', type=str, required=True,
 | 
						|
        help='Path to the SSD storage'
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        '--mass-storage-path', type=str, required=True,
 | 
						|
        help='Path to the HDD storage'
 | 
						|
    )
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    rapid_storage_path = args.rapid_storage_path
 | 
						|
    mass_storage_path = args.mass_storage_path
 | 
						|
 | 
						|
    volumes = run_command("docker volume ls -q").splitlines()
 | 
						|
 | 
						|
    for volume in volumes:
 | 
						|
        volume_path = get_volume_path(volume)
 | 
						|
        containers = run_command(
 | 
						|
            f"docker ps -q --filter volume={volume}"
 | 
						|
        ).splitlines()
 | 
						|
 | 
						|
        if not containers:
 | 
						|
            print(f"Skipped Volume {volume}. It does not belong to a running container.")
 | 
						|
            continue
 | 
						|
        if is_symbolic_link(volume_path):
 | 
						|
            print(f"Skipped Volume {volume}. The storage path {volume_path} is a symbolic link.")
 | 
						|
            continue
 | 
						|
 | 
						|
        # Wait until containers with a healthcheck are healthy (not starting or unhealthy)
 | 
						|
        for container in containers:
 | 
						|
            if has_healthcheck(container):
 | 
						|
                status = get_health_status(container)
 | 
						|
                while status != 'healthy':
 | 
						|
                    print(f"Warte auf Container {container}, Status '{status}'...")
 | 
						|
                    time.sleep(1)
 | 
						|
                    status = get_health_status(container)
 | 
						|
 | 
						|
        # Proceed with migration
 | 
						|
        if has_container_with_database(containers):
 | 
						|
            print(f"Safing volume {volume} on SSD.")
 | 
						|
            pause_and_move(rapid_storage_path, volume, volume_path, containers)
 | 
						|
        else:
 | 
						|
            print(f"Safing volume {volume} on HDD.")
 | 
						|
            pause_and_move(mass_storage_path, volume, volume_path, containers)
 | 
						|
 | 
						|
    print("Operation completed.")
 |