|
|
|
@ -16,7 +16,10 @@ TMP_BACKUP_FOLDER = "/mnt/Datenspeicher/snap-backup-dataset/temporary-backups"
@@ -16,7 +16,10 @@ TMP_BACKUP_FOLDER = "/mnt/Datenspeicher/snap-backup-dataset/temporary-backups"
|
|
|
|
|
|
|
|
|
|
def call(arguments, as_text=False): |
|
|
|
|
result = subprocess.run( |
|
|
|
|
arguments, check=True, stdout=subprocess.PIPE, universal_newlines=as_text |
|
|
|
|
arguments, |
|
|
|
|
check=True, |
|
|
|
|
stdout=subprocess.PIPE, |
|
|
|
|
universal_newlines=as_text, |
|
|
|
|
) |
|
|
|
|
return result.stdout |
|
|
|
|
|
|
|
|
@ -50,13 +53,27 @@ def list_elab_snapshots():
@@ -50,13 +53,27 @@ def list_elab_snapshots():
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gzip_filename(snapshot_name): |
|
|
|
|
pool, name = snapshot_name.split("/", 1) |
|
|
|
|
return f"{name}.gz" |
|
|
|
|
def snapshot_short_name(full_snapshot_name): |
|
|
|
|
pool, name = full_snapshot_name.split("/", 1) |
|
|
|
|
return name |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gzip_filepath(snapshot_name): |
|
|
|
|
return pathlib.Path(TMP_BACKUP_FOLDER) / gzip_filename(snapshot_name) |
|
|
|
|
def gzip_filename(current, last=None): |
|
|
|
|
current_name = snapshot_short_name(current) |
|
|
|
|
if last is None: |
|
|
|
|
return f"{current_name}.gz" |
|
|
|
|
else: |
|
|
|
|
last_name = snapshot_name(last) |
|
|
|
|
return f"{last_name}.to.{current_name}.gz" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_snapshot_name(filename): |
|
|
|
|
parts = filename.split(".") |
|
|
|
|
if len(parts) in (2, 4): |
|
|
|
|
# snapshot1.gz or snapshot1.to.snapshot2.gz |
|
|
|
|
return parts[-2] |
|
|
|
|
else: |
|
|
|
|
raise ValueError(f"Unknown Filename Format: {filename}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def list_remote_backups(members): |
|
|
|
@ -65,7 +82,7 @@ def list_remote_backups(members):
@@ -65,7 +82,7 @@ def list_remote_backups(members):
|
|
|
|
|
remote_sub_dir = f"{REMOTE_PATH}/{member}" |
|
|
|
|
try: |
|
|
|
|
backups = clean_split(remote_call(["ls", remote_sub_dir])) |
|
|
|
|
result[member] = set(backups) |
|
|
|
|
result[member] = set((extract_snapshot_name(i) for i in backups)) |
|
|
|
|
except subprocess.CalledProcessError: |
|
|
|
|
remote_call(["mkdir", remote_sub_dir]) |
|
|
|
|
result[member] = set() |
|
|
|
@ -78,7 +95,7 @@ def backup_latest_snapshot(member, elab_snapshots, existing_backups):
@@ -78,7 +95,7 @@ def backup_latest_snapshot(member, elab_snapshots, existing_backups):
|
|
|
|
|
current_snapshot = snapshots[0] |
|
|
|
|
latest_backup = None |
|
|
|
|
for snapshot in snapshots: |
|
|
|
|
if gzip_filename(snapshot) in existing_backups: |
|
|
|
|
if snapshot_short_name(snapshot) in existing_backups: |
|
|
|
|
latest_backup = snapshot |
|
|
|
|
break |
|
|
|
|
if current_snapshot == latest_backup: |
|
|
|
@ -88,16 +105,20 @@ def backup_latest_snapshot(member, elab_snapshots, existing_backups):
@@ -88,16 +105,20 @@ def backup_latest_snapshot(member, elab_snapshots, existing_backups):
|
|
|
|
|
elif latest_backup is None: |
|
|
|
|
# no snapshot was found in backups, make a full backup for consistency |
|
|
|
|
send_cmd = ["zfs", "send", current_snapshot] |
|
|
|
|
gzip_tmp_filename = gzip_filename(current_snapshot) |
|
|
|
|
print(f" - full backup, latest snapshot: {current_snapshot}") |
|
|
|
|
else: |
|
|
|
|
# make an incremental backup |
|
|
|
|
print( |
|
|
|
|
f" - incremental backup, from: {latest_backup} to: {current_snapshot}" |
|
|
|
|
) |
|
|
|
|
gzip_tmp_filename = gzip_filename( |
|
|
|
|
current=current_snapshot, last=latest_backup |
|
|
|
|
) |
|
|
|
|
send_cmd = ["zfs", "send", "-I", latest_backup, current_snapshot] |
|
|
|
|
|
|
|
|
|
# create the backup |
|
|
|
|
tmp_gzip_filepath = gzip_filepath(current_snapshot) |
|
|
|
|
tmp_gzip_filepath = pathlib.Path(TMP_BACKUP_FOLDER) / gzip_tmp_filename |
|
|
|
|
print(f" - generating temporary backup file {tmp_gzip_filepath.name}") |
|
|
|
|
with open(tmp_gzip_filepath, "wb") as file_handle: |
|
|
|
|
gzip_in = subprocess.Popen( |
|
|
|
|