first commit
This commit is contained in:
83
src/backup.py
Normal file
83
src/backup.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
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"[!] Fehler bei Befehl: {cmd}")
|
||||
print(result.stderr)
|
||||
raise RuntimeError("Befehl fehlgeschlagen")
|
||||
return result.stdout if capture_output else None
|
||||
|
||||
def get_project_name():
|
||||
return Path(os.getcwd()).name
|
||||
|
||||
def get_volumes():
|
||||
print("[*] Ermittele Volumes mit `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) # fallback if "name" not set
|
||||
volumes.append(actual_name)
|
||||
|
||||
return volumes
|
||||
|
||||
def archive_project(output_dir):
|
||||
print("[*] Archiviere Projektordner...")
|
||||
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"[*] Archiviere 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():
|
||||
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"[*] Erstelle Gesamtarchiv: {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 abgeschlossen: {final_archive_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
86
src/restore.py
Normal file
86
src/restore.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import tarfile
|
||||
import tempfile
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
BACKUP_DIR = Path.home() / ".compose-backup"
|
||||
|
||||
def run_cmd(cmd):
|
||||
result = subprocess.run(cmd, shell=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Fehler beim Befehl: {cmd}")
|
||||
|
||||
def list_projects():
|
||||
backups = BACKUP_DIR.glob("*.tar.gz")
|
||||
projects = {}
|
||||
|
||||
for archive in backups:
|
||||
name = archive.name.split("_backup_")[0]
|
||||
projects.setdefault(name, []).append(archive)
|
||||
|
||||
return projects
|
||||
|
||||
def select_from_list(prompt, items):
|
||||
for i, item in enumerate(items, 1):
|
||||
print(f"{i}) {item}")
|
||||
while True:
|
||||
choice = input(f"{prompt} [1-{len(items)}]: ")
|
||||
if choice.isdigit() and 1 <= int(choice) <= len(items):
|
||||
return items[int(choice) - 1]
|
||||
|
||||
def restore_project_files(project, tempdir):
|
||||
print("[*] Entpacke Projektdateien...")
|
||||
with tarfile.open(tempdir / "project_files.tar.gz", "r:gz") as tar:
|
||||
tar.extractall(f"./{project}/") # extract to current dir
|
||||
|
||||
def restore_volume(volume_name, archive_path):
|
||||
print(f"[*] Stelle Volume wieder her: {volume_name}")
|
||||
cmd = (
|
||||
f"docker run --rm "
|
||||
f"-v {volume_name}:/volume "
|
||||
f"-v {archive_path.parent.absolute()}:/backup "
|
||||
f"alpine sh -c \"cd /volume && tar xzf /backup/{archive_path.name}\""
|
||||
)
|
||||
run_cmd(cmd)
|
||||
|
||||
def main():
|
||||
print("📦 Verfügbare Projekte:")
|
||||
projects = list_projects()
|
||||
if not projects:
|
||||
print("Keine Backups gefunden.")
|
||||
return
|
||||
|
||||
project = select_from_list("Projekt auswählen", list(projects.keys()))
|
||||
versions = sorted(projects[project], reverse=True)
|
||||
backup_file = select_from_list("Version auswählen", [p.name for p in versions])
|
||||
backup_path = BACKUP_DIR / backup_file
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp = Path(tmpdir)
|
||||
print(f"[*] Entpacke Backup: {backup_path}")
|
||||
with tarfile.open(backup_path, "r:gz") as tar:
|
||||
tar.extractall(tmp)
|
||||
|
||||
restore_project_files(project, tmp)
|
||||
|
||||
# change into project folder and run the compose project a single time
|
||||
os.chdir(f"{Path.cwd()}/{project}")
|
||||
print("[*] Running docker compose up to create volumes...")
|
||||
run_cmd("docker compose up -d")
|
||||
|
||||
print("[*] Stopping containers after volume creation...")
|
||||
run_cmd("docker compose down")
|
||||
|
||||
|
||||
for file in tmp.iterdir():
|
||||
if file.name == "project_files.tar.gz":
|
||||
continue
|
||||
if file.suffix == ".gz" and file.name.endswith(".tar.gz"):
|
||||
volume_name = file.name.replace(".tar.gz", "")
|
||||
restore_volume(volume_name, file)
|
||||
|
||||
print("✅ Wiederherstellung abgeschlossen.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user