mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 12:18:17 +00:00 
			
		
		
		
	Refactor systemctl services and timers
- 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.
This commit is contained in:
		
							
								
								
									
										145
									
								
								roles/svc-opt-ssd-hdd/files/script.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								roles/svc-opt-ssd-hdd/files/script.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
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.")
 | 
			
		||||
		Reference in New Issue
	
	Block a user