defaults = { 'backup-server': { 'my_ssh_port': 22, }, 'openssh': { 'allowed_users': { # Usernames for backup clients always start with 'c-' 'c-*', }, }, } @metadata_reactor.provides( 'backup-server/clients', 'backup-server/my_hostname', ) def get_my_clients(metadata): my_clients = {} retain_defaults = { 'daily': 14, 'weekly': 4, 'monthly': 6, } for rnode in repo.nodes: if not rnode.has_bundle('backup-client') or rnode.metadata.get('backups/exclude_from_backups', False): continue if node.name != rnode.metadata.get('backup-client/target'): continue my_clients[rnode.name] = { 'user': rnode.metadata.get('backup-client/user-name'), 'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24), 'retain': { 'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']), 'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']), 'monthly': rnode.metadata.get('backups/retain/monthly', retain_defaults['monthly']), }, } return { 'backup-server': { 'clients': my_clients, 'my_hostname': metadata.get('hostname'), }, } @metadata_reactor.provides( 'backup-server/zfs-base', 'dm-crypt/encrypted-devices', 'zfs/pools', ) def zfs_pool(metadata): if not metadata.get('backup-server/encrypted-devices', {}): return {} crypt_devices = {} pool_devices = set() unlock_actions = set() for number, (device, passphrase) in enumerate(sorted(metadata.get('backup-server/encrypted-devices', {}).items())): crypt_devices[device] = { 'dm-name': f'backup{number}', 'passphrase': passphrase, } pool_devices.add(f'/dev/mapper/backup{number}') unlock_actions.add(f'action:dm-crypt_open_backup{number}') pool_opts = { 'devices': pool_devices, } if len(pool_devices) > 2: pool_opts['type'] = 'raidz' elif len(pool_devices) > 1: pool_opts['type'] = 'mirror' return { 'backup-server': { 'zfs-base': 'backups', }, 'dm-crypt': { 'encrypted-devices': crypt_devices, }, 'zfs': { 'pools': { 'backups': { 'when_creating': { 'config': [ pool_opts, ], }, 'needs': unlock_actions, # That's a bit hacky. We do it this way to auto-import # the pool after decrypting the devices. Otherwise # the pool wouldn't exist, which leads to bundlewrap # trying to re-create the pool. # Also, -N to not auto-mount anything. 'unless': 'zpool import -N backups', }, }, } } @metadata_reactor.provides( 'zfs/datasets', 'zfs/snapshots/snapshot_never', ) def zfs_datasets_and_snapshots(metadata): zfs_datasets = {} for client in metadata.get('backup-server/clients', {}).keys(): dataset = '{}/{}'.format(metadata.get('backup-server/zfs-base'), client) zfs_datasets[dataset] = { 'mountpoint': '/srv/backups/{}'.format(client), 'compression': 'on', } return { 'zfs': { 'datasets': zfs_datasets, 'snapshots': { 'snapshot_never': { metadata.get('backup-server/zfs-base'), }, }, }, } @metadata_reactor.provides( 'icinga2_api/backup-server/services', ) def monitoring(metadata): services = {} for client, config in metadata.get('backup-server/clients', {}).items(): if config.get('exclude_from_monitoring', False): continue services[f'BACKUPS FOR NODE {client}'] = { 'command_on_monitored_host': 'sudo /usr/local/share/icinga/plugins/check_backup_for_node {} {}'.format( client, config['one_backup_every_hours'], ), 'vars.sshmon_timeout': 20, } return { 'icinga2_api': { 'backup-server': { 'services': services, }, }, }