Files
compose-backup/src/backup.py
2025-07-20 13:17:20 +02:00

100 lines
3.1 KiB
Python

import os
import subprocess
import tarfile
import tempfile
import shutil
import yaml
from datetime import datetime
from pathlib import Path
def is_valid_compose_project():
result = subprocess.run(
["docker", "compose", "config"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
return result.returncode == 0
def run_cmd(cmd, capture_output=False):
result = subprocess.run(cmd, shell=True, text=True, capture_output=capture_output)
if result.returncode != 0:
print(f"[!] Error while running command: {cmd}")
print(result.stderr)
raise RuntimeError("Command failed with return code:", result.returncode)
return result.stdout if capture_output else None
def get_project_name():
return Path(os.getcwd()).name
def get_volumes():
print("[*] Determine Volumes with `docker compose config`...")
output = run_cmd("docker compose config", capture_output=True)
output = yaml.safe_load(output)
volumes = []
for logical_name, attrs in output.get("volumes", {}).items():
actual_name = attrs.get("name", logical_name)
volumes.append(actual_name)
return volumes
def archive_project(output_dir):
print("[*] Archiving Project folder")
with tarfile.open(output_dir / "project_files.tar.gz", "w:gz") as tar:
for item in Path(".").iterdir():
if item.name in [output_dir.name, ".git", "__pycache__"]:
continue
tar.add(item, arcname=item.name)
def archive_volume(volume_name, backup_path):
print(f"[*] Archiving Volume: {volume_name}")
archive_file = backup_path / f"{volume_name}.tar.gz"
cmd = (
f"docker run --rm "
f"-v {volume_name}:/volume "
f"-v {backup_path.absolute()}:/backup "
f"alpine tar czf /backup/{volume_name}.tar.gz -C /volume ."
)
run_cmd(cmd)
return archive_file
def main():
if is_valid_compose_project():
print("[*] Valid Compose Project Found")
else:
print("[*] No Valid Compose Project Found")
return
return
project_name = get_project_name()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
final_archive_name = f"{project_name}_backup_{timestamp}.tar.gz"
# Step 1: Define and create backup target directory
backup_root = Path.home() / ".compose-backup"
backup_root.mkdir(parents=True, exist_ok=True)
final_archive_path = backup_root / final_archive_name
with tempfile.TemporaryDirectory() as tmpdir:
print("[*] Running docker compose up to create volumes...")
run_cmd("docker compose down")
tmp_path = Path(tmpdir)
archive_project(tmp_path)
volumes = get_volumes()
for vol in volumes:
archive_volume(vol, tmp_path)
print(f"[*] Creating full archive: {final_archive_path}")
with tarfile.open(final_archive_path, "w:gz") as tar:
for file in tmp_path.iterdir():
tar.add(file, arcname=file.name)
print(f"[✔] Backup completed successfully. Archive saved as: {final_archive_path}")
if __name__ == "__main__":
main()