67 lines
2.9 KiB
Python
67 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
|
|
|
|
def get_volume_datasets(root_dataset):
|
|
zfs_list = subprocess.getoutput(
|
|
f"zfs list -H -r {root_dataset} -o name,mountpoint"
|
|
)
|
|
zfs_list_lines = zfs_list.split('\n')
|
|
zfs_list_lines_items = map(lambda l: l.split(), zfs_list_lines)
|
|
return {
|
|
os.path.relpath(dataset, root_dataset).replace("/", "---"): {
|
|
"dataset": dataset,
|
|
"mountpoint": mountpoint,
|
|
} for dataset, mountpoint in zfs_list_lines_items if ((dataset != root_dataset) and
|
|
os.path.ismount(mountpoint))
|
|
}
|
|
|
|
def get_last_daily_snapshot(dataset):
|
|
snapshots = subprocess.getoutput(
|
|
f"zfs list -t snapshot -H -r {dataset} -o name -s creation"
|
|
)
|
|
daily_snapshots = filter(lambda s: s.endswith("_daily"), snapshots.split('\n'))
|
|
last_daily_snapshot = list(daily_snapshots)[-1]
|
|
assert '@' in last_daily_snapshot
|
|
assert last_daily_snapshot.split('@')[0] == dataset
|
|
return last_daily_snapshot
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Backup service data using restic")
|
|
parser.add_argument("--root-dataset", type=str, required=True,
|
|
help="The root data root whose children are to be backed up")
|
|
parser.add_argument("--bucket-endpoint", type=str, required=True,
|
|
help="S3 bucket endpoint for the backups")
|
|
args = parser.parse_args()
|
|
|
|
volume_datasets = get_volume_datasets(args.root_dataset)
|
|
for volume, properties in volume_datasets.items():
|
|
properties["snapshot"] = get_last_daily_snapshot(properties["dataset"])
|
|
|
|
for volume, properties in volume_datasets.items():
|
|
mountpoint = properties["mountpoint"]
|
|
snapshot = properties["snapshot"]
|
|
backup_path = os.path.normpath(os.path.join("/", "mnt", os.path.relpath(mountpoint, "/")))
|
|
bucket_name = f"the-nine-worlds---{volume}"
|
|
restic_cmd_base = "restic " \
|
|
f"--repo s3:https://{args.bucket_endpoint}/{bucket_name} " \
|
|
"--option s3.storage-class=ONEZONE_IA"
|
|
|
|
print(f"Backing up {bucket_name} : {snapshot}", flush=True)
|
|
|
|
subprocess.run(f"zfs clone -o mountpoint={backup_path} {snapshot} rpool/restic",
|
|
shell=True, check=True)
|
|
try:
|
|
subprocess.run(f"{restic_cmd_base} snapshots || {restic_cmd_base} init",
|
|
shell=True, check=True)
|
|
subprocess.run(f"{restic_cmd_base} backup .", shell=True, cwd=backup_path, check=True)
|
|
subprocess.run(f"{restic_cmd_base} forget --prune --keep-daily 30 --keep-monthly 3",
|
|
shell=True, check=True)
|
|
subprocess.run(f"{restic_cmd_base} check", shell=True, check=True)
|
|
|
|
finally:
|
|
subprocess.run("zfs destroy rpool/restic", shell=True, check=True)
|