bundlewrap/bundles/backup-server/metadata.py
CroneKorkN 9d9ac7ac5e
feat(backup-server): keep received snapshots by its own retention
Clients deleted every auto-backup_ snapshot on the server except the
newest, so the backup held one state only. prune-snapshots now keeps
the newest per day, week and month (14/8/24 by default) independent of
the source, and the clients call it after each receive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RywMtsL8qXG7GDY3MHq9Uh
2026-10-23 14:30:25 +02:00

131 lines
3.6 KiB
Python

from ipaddress import ip_interface
defaults = {
'apt': {
'packages': {
'rsync': {},
},
},
'users': {
'backup-receiver': {
'authorized_keys': set(),
},
},
'backup-server': {
# auto-backup_ snapshots kept per received dataset (prune-snapshots)
'retention': {
'daily': 14,
'weekly': 8,
'monthly': 24,
},
},
'sudoers': {
'backup-receiver': {
'/usr/bin/rsync',
'/sbin/zfs',
'/opt/backup-server/prune-snapshots',
},
},
'zfs': {
'datasets': {
'tank': {
'recordsize': "1048576",
},
},
},
}
@metadata_reactor.provides(
'zfs/datasets'
)
def zfs(metadata):
datasets = {}
for other_node in repo.nodes:
if (
not other_node.dummy and
other_node.has_bundle('backup') and
other_node.metadata.get('backup/server') == node.name
):
id = other_node.metadata.get('id')
base_dataset = f'tank/{id}'
# container
datasets[base_dataset] = {
'mountpoint': None,
'readonly': 'on',
'compression': 'lz4',
'com.sun:auto-snapshot': 'false',
'backup': False,
}
# for rsync backups
datasets[f'{base_dataset}/fs'] = {
'mountpoint': f"/mnt/backups/{id}",
'readonly': 'off',
'compression': 'lz4',
'com.sun:auto-snapshot': 'true',
'backup': False,
}
# for zfs send/recv
if other_node.has_bundle('zfs'):
# base datasets for each tank
for pool in other_node.metadata.get('zfs/pools'):
datasets[f'{base_dataset}/{pool}'] = {
'mountpoint': None,
'readonly': 'on',
'compression': 'lz4',
'com.sun:auto-snapshot': 'false',
'backup': False,
}
# actual datasets
for path in other_node.metadata.get('backup/paths'):
for dataset, config in other_node.metadata.get('zfs/datasets').items():
if path == config.get('mountpoint'):
datasets[f'{base_dataset}/{dataset}'] = {
'mountpoint': None,
'readonly': 'on',
'compression': 'lz4',
'com.sun:auto-snapshot': 'false',
'backup': False,
}
continue
return {
'zfs': {
'datasets': datasets,
},
}
@metadata_reactor.provides(
'dns',
)
def dns(metadata):
return {
'dns': {
metadata.get('backup-server/hostname'): repo.libs.ip.get_a_records(metadata),
}
}
@metadata_reactor.provides(
'users/backup-receiver/authorized_keys'
)
def backup_authorized_keys(metadata):
return {
'users': {
'backup-receiver': {
'authorized_keys': {
other_node.metadata.get('users/root/pubkey')
for other_node in repo.nodes
if other_node.has_bundle('backup')
and other_node.metadata.get('backup/server') == node.name
},
},
},
}