migrate to bundlewrap 5

- pin bundlewrap ~=5.0
- rewrite non-reading and KeyError-driven metadata reactors per
  https://docs.bundlewrap.org/guide/migrate_45/ (defaults / metadata.get
  paths / MetadataUnavailable)
- rename custom Download item methods (cdict/sdict/get_auto_deps ->
  expected_state/actual_state/get_auto_attrs)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CroneKorkN 2026-05-10 11:56:49 +02:00
parent e99fd4b1a4
commit 186d5039af
Signed by: cronekorkn
SSH key fingerprint: SHA256:v0410ZKfuO1QHdgKBsdQNF64xmTxOF8osF1LIqwTcVw
13 changed files with 93 additions and 171 deletions

View file

@ -5,6 +5,7 @@ defaults = {
'installed': False, 'installed': False,
}, },
'ca-certificates': {}, 'ca-certificates': {},
'unattended-upgrades': {},
}, },
'config': { 'config': {
'DPkg': { 'DPkg': {
@ -22,6 +23,10 @@ defaults = {
}, },
}, },
'APT': { 'APT': {
'Periodic': {
'Update-Package-Lists': '1',
'Unattended-Upgrade': '1',
},
'NeverAutoRemove': { 'NeverAutoRemove': {
'^firmware-linux.*', '^firmware-linux.*',
'^linux-firmware$', '^linux-firmware$',
@ -49,6 +54,11 @@ defaults = {
'Error-Mode': 'any', 'Error-Mode': 'any',
}, },
}, },
'Unattended-Upgrade': {
'Origins-Pattern': {
"origin=*",
},
},
}, },
'sources': {}, 'sources': {},
}, },
@ -107,33 +117,6 @@ def signed_by(metadata):
} }
@metadata_reactor.provides(
'apt/config',
'apt/packages',
)
def unattended_upgrades(metadata):
return {
'apt': {
'config': {
'APT': {
'Periodic': {
'Update-Package-Lists': '1',
'Unattended-Upgrade': '1',
},
},
'Unattended-Upgrade': {
'Origins-Pattern': {
"origin=*",
},
},
},
'packages': {
'unattended-upgrades': {},
},
},
}
# @metadata_reactor.provides( # @metadata_reactor.provides(
# 'apt/config', # 'apt/config',
# 'apt/list_changes', # 'apt/list_changes',

View file

@ -5,6 +5,11 @@ defaults = {
'needs': { 'needs': {
'zfs_dataset:tank/downloads' 'zfs_dataset:tank/downloads'
}, },
'authorized_users': {
f'build-server@{other_node.name}': {}
for other_node in repo.nodes
if other_node.has_bundle('build-server')
},
}, },
}, },
'zfs': { 'zfs': {
@ -14,14 +19,6 @@ defaults = {
}, },
}, },
}, },
}
@metadata_reactor.provides(
'systemd-mount'
)
def mount_certs(metadata):
return {
'systemd-mount': { 'systemd-mount': {
'/var/lib/downloads_nginx': { '/var/lib/downloads_nginx': {
'source': '/var/lib/downloads', 'source': '/var/lib/downloads',
@ -47,20 +44,3 @@ def nginx(metadata):
}, },
}, },
} }
@metadata_reactor.provides(
'users/downloads/authorized_users',
)
def ssh_keys(metadata):
return {
'users': {
'downloads': {
'authorized_users': {
f'build-server@{other_node.name}': {}
for other_node in repo.nodes
if other_node.has_bundle('build-server')
},
},
},
}

View file

@ -43,11 +43,11 @@ def units(metadata):
'Service': { 'Service': {
'Environment': { 'Environment': {
f'{k}={v}' f'{k}={v}'
for k, v in conf.get('env', {}).items() for k, v in metadata.get(f'flask/{name}/env', {}).items()
}, },
'User': conf['user'], 'User': metadata.get(f'flask/{name}/user'),
'Group': conf['group'], 'Group': metadata.get(f'flask/{name}/group'),
'ExecStart': f"/opt/{name}/venv/bin/gunicorn -w {conf['workers']} -b 127.0.0.1:{conf['port']} --timeout {conf['timeout']} {conf['app_module']}:app" 'ExecStart': f"/opt/{name}/venv/bin/gunicorn -w {metadata.get(f'flask/{name}/workers')} -b 127.0.0.1:{metadata.get(f'flask/{name}/port')} --timeout {metadata.get(f'flask/{name}/timeout')} {metadata.get(f'flask/{name}/app_module')}:app"
}, },
'Install': { 'Install': {
'WantedBy': { 'WantedBy': {
@ -55,7 +55,7 @@ def units(metadata):
} }
}, },
} }
for name, conf in metadata.get('flask').items() for name in metadata.get('flask')
} }
} }
} }

View file

@ -39,6 +39,17 @@ defaults = {
}, },
} }
if node.has_bundle('zfs'):
defaults['zfs'] = {
'datasets': {
'tank/influxdb': {
'mountpoint': '/var/lib/influxdb',
'recordsize': '8192',
'atime': 'off',
},
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'influxdb/password', 'influxdb/password',
'influxdb/admin_token', 'influxdb/admin_token',
@ -52,26 +63,6 @@ def admin_password(metadata):
} }
@metadata_reactor.provides(
'zfs/datasets',
)
def zfs(metadata):
if not node.has_bundle('zfs'):
return {}
return {
'zfs': {
'datasets': {
'tank/influxdb': {
'mountpoint': '/var/lib/influxdb',
'recordsize': '8192',
'atime': 'off',
},
},
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'dns', 'dns',
) )

View file

@ -50,7 +50,7 @@ def user(metadata):
'sshmon': { 'sshmon': {
conf['vars.command'] conf['vars.command']
for conf in metadata.get('monitoring/services').values() for conf in metadata.get('monitoring/services').values()
if conf['check_command'] == 'sshmon' if conf.get('check_command') == 'sshmon'
and conf.get('vars.sudo', None) and conf.get('vars.sudo', None)
}, },
}, },

View file

@ -37,11 +37,12 @@ def dhcp(metadata):
'modules-load', 'modules-load',
) )
def units(metadata): def units(metadata):
networks = metadata.get('network', {})
if node.has_bundle('systemd-networkd'): if node.has_bundle('systemd-networkd'):
units = {} units = {}
modules_load = set() modules_load = set()
for network_name, network_conf in metadata.get('network').items(): for network_name, network_conf in networks.items():
interface_type = network_conf.get('type', None) interface_type = network_conf.get('type', None)
# network # network
@ -116,6 +117,7 @@ def units(metadata):
'systemd/units', 'systemd/units',
) )
def queuing_disciplines(metadata): def queuing_disciplines(metadata):
networks = metadata.get('network', {})
if node.has_bundle('systemd-networkd'): if node.has_bundle('systemd-networkd'):
return { return {
'systemd': { 'systemd': {
@ -136,7 +138,7 @@ def queuing_disciplines(metadata):
'WantedBy': 'network-online.target', 'WantedBy': 'network-online.target',
}, },
} }
for network_name, network_conf in metadata.get('network').items() for network_name, network_conf in networks.items()
if 'qdisc' in network_conf if 'qdisc' in network_conf
}, },
}, },

View file

@ -31,6 +31,17 @@ defaults = {
'grafana_rows': set(), 'grafana_rows': set(),
} }
if node.has_bundle('zfs'):
defaults['zfs'] = {
'datasets': {
'tank/postgresql': {
'mountpoint': '/var/lib/postgresql',
'recordsize': '8192',
'atime': 'off',
},
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'postgresql/conf', 'postgresql/conf',
@ -77,26 +88,6 @@ def apt(metadata):
@metadata_reactor.provides(
'zfs/datasets',
)
def zfs(metadata):
if not node.has_bundle('zfs'):
return {}
return {
'zfs': {
'datasets': {
'tank/postgresql': {
'mountpoint': '/var/lib/postgresql',
'recordsize': '8192',
'atime': 'off',
},
},
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'telegraf/inputs/postgresql/default', 'telegraf/inputs/postgresql/default',
) )

View file

@ -6,11 +6,19 @@ defaults = {
}, },
} }
if node.has_bundle('zfs'):
defaults['zfs'] = {
'kernel_params': {
'zfs_txg_timeout': 300,
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'telegraf/agent', 'telegraf/agent',
) )
def telegraf(metadata): def telegraf(metadata):
metadata.get('telegraf/agent') # only override if telegraf bundle is present
return { return {
'telegraf': { 'telegraf': {
'agent': { 'agent': {
@ -19,20 +27,3 @@ def telegraf(metadata):
}, },
}, },
} }
@metadata_reactor.provides(
'zfs/kernel_params',
'zfs/datasets',
)
def zfs(metadata):
if not node.has_bundle('zfs'):
return {}
return {
'zfs': {
'kernel_params': {
'zfs_txg_timeout': 300,
},
},
}

View file

@ -1,3 +1,13 @@
defaults = {
'systemd-timers': {
'raspberrymatic-cert': {
'command': '/opt/raspberrymatic-cert',
'when': 'daily',
},
},
}
@metadata_reactor.provides( @metadata_reactor.provides(
'letsencrypt/domains', 'letsencrypt/domains',
) )
@ -11,17 +21,3 @@ def letsencrypt(metadata):
}, },
}, },
} }
@metadata_reactor.provides(
'systemd-timers/raspberrymatic-cert',
)
def systemd_timers(metadata):
return {
'systemd-timers': {
'raspberrymatic-cert': {
'command': '/opt/raspberrymatic-cert',
'when': 'daily',
}
},
}

View file

@ -7,6 +7,11 @@ defaults = {
# needed by crystal plugins: # needed by crystal plugins:
'libgc-dev': {}, 'libgc-dev': {},
'libevent-dev': {}, 'libevent-dev': {},
(
'libpcre2-8-0'
if node.os == 'debian' and node.os_version >= (13,)
else 'libpcre3'
): {},
}, },
'sources': { 'sources': {
'influxdata': { 'influxdata': {
@ -148,20 +153,3 @@ def influxdb(metadata):
# crystal based (procio, pressure_stall): # crystal based (procio, pressure_stall):
@metadata_reactor.provides(
'apt/packages/libpcre2-8-0',
'apt/packages/libpcre3',
)
def libpcre(metadata):
if node.os == 'debian' and node.os_version >= (13,):
libpcre_package = 'libpcre2-8-0'
else:
libpcre_package = 'libpcre3'
return {
'apt': {
'packages': {
libpcre_package: {},
},
},
}

View file

@ -112,20 +112,18 @@ def systemd_networkd_netdevs(metadata):
}, },
} }
for peer, config in { for kind in ('s2s', 'clients'):
**metadata.get('wireguard/s2s'), for peer in metadata.get(f'wireguard/{kind}'):
**metadata.get('wireguard/clients'), peer_id = metadata.get(f'wireguard/{kind}/{peer}/peer_id')
}.items(): netdev[f'WireGuardPeer#{peer}'] = {
netdev.update({ 'PublicKey': repo.libs.wireguard.pubkey(peer_id),
f'WireGuardPeer#{peer}': { 'PresharedKey': repo.libs.wireguard.psk(peer_id, metadata.get('id')),
'PublicKey': repo.libs.wireguard.pubkey(config['peer_id']), 'AllowedIPs': ', '.join(metadata.get(f'wireguard/{kind}/{peer}/allowed_ips', [])),
'PresharedKey': repo.libs.wireguard.psk(config['peer_id'], metadata.get('id')),
'AllowedIPs': ', '.join(config.get('allowed_ips', [])),
'PersistentKeepalive': 30, 'PersistentKeepalive': 30,
} }
}) endpoint = metadata.get(f'wireguard/{kind}/{peer}/endpoint', None)
if config.get('endpoint'): if endpoint:
netdev[f'WireGuardPeer#{peer}']['Endpoint'] = config['endpoint'] netdev[f'WireGuardPeer#{peer}']['Endpoint'] = endpoint
return { return {
'systemd': { 'systemd': {

View file

@ -65,7 +65,8 @@ class Download(Item):
url=quote(self.attributes['url']) url=quote(self.attributes['url'])
)) ))
def cdict(self): @property
def expected_state(self):
"""This is how the world should be""" """This is how the world should be"""
cdict = { cdict = {
'type': 'download', 'type': 'download',
@ -85,7 +86,8 @@ class Download(Item):
return cdict return cdict
def sdict(self): @property
def actual_state(self):
"""This is how the world is right now""" """This is how the world is right now"""
path_info = PathInfo(self.node, self.name) path_info = PathInfo(self.node, self.name)
if not path_info.exists: if not path_info.exists:
@ -135,10 +137,10 @@ class Download(Item):
item=item_id, item=item_id,
)) ))
def get_auto_deps(self, items): def get_auto_attrs(self, items):
deps = [] deps = []
for item in items: for item in items:
# debian TODO: add other package manager # debian TODO: add other package manager
if item.ITEM_TYPE_NAME == 'pkg_apt' and item.name == 'curl': if item.ITEM_TYPE_NAME == 'pkg_apt' and item.name == 'curl':
deps.append(item.id) deps.append(item.id)
return deps return {'needs': deps}

View file

@ -1,4 +1,4 @@
bundlewrap ~=4.0, >=4.24 bundlewrap ~=5.0, >=5.0.3
pycryptodome pycryptodome
PyNaCl PyNaCl
PyYAML PyYAML