left4me: pull node-agnostic metadata into the bundle

Nodes should only carry node-specific metadata. Previously each node
running left4me had to declare git_url, git_branch, secret_key, plus
nginx vhost / letsencrypt / monitoring / nftables-input blocks for
every game port. All of those are derivable from one truly node-
specific value: the domain.

Move into the bundle:
  - git_url + git_branch as defaults (override per-node only if needed).
  - secret_key as a per-node vault-derived value
    (random_bytes_as_base64_for f'{node.name} left4me secret_key',
    same convention as postgresql/mosquitto/etc.).
  - backup/paths defaults (set-merged with backup group / node paths).

Add a `derived_from_domain` reactor that reads left4me/domain and
emits:
  - nginx/vhosts/<domain> proxying 127.0.0.1:8000
  - letsencrypt/domains/<domain>
  - monitoring/services/left4me-web (curl /health)
  - nftables/input rules for the configured port range
    (defaults 27015-27115, derived from left4me/port_range_*).

Net effect: a node opting into left4me declares only
  metadata.left4me.domain = 'whatever.tld'
plus the universal node-level stuff (id, vm/cores, network, …).
This commit is contained in:
CroneKorkN 2026-05-10 18:23:34 +02:00
parent 3bffd7b8f5
commit 90f14b69e4
Signed by: cronekorkn
SSH key fingerprint: SHA256:v0410ZKfuO1QHdgKBsdQNF64xmTxOF8osF1LIqwTcVw

View file

@ -6,8 +6,16 @@ assert node.has_bundle('systemd'), (
)
# Per-node random secret. Convention follows postgresql, mosquitto, etc.
_secret_key = repo.vault.random_bytes_as_base64_for(f'{node.name} left4me secret_key', length=32).value
defaults = {
'left4me': {
# Application-wide defaults; node only overrides if it really needs to.
'git_url': 'git@git.sublimity.de:cronekorkn/left4me',
'git_branch': 'master',
'secret_key': _secret_key,
'gunicorn_workers': 1,
'gunicorn_threads': 32,
'job_worker_threads': 4,
@ -55,6 +63,56 @@ defaults = {
# uses Slice=.
},
},
'backup': {
# Application-owned paths. Set-merged with backup group / node-level paths.
'paths': {
'/var/lib/left4me',
'/etc/left4me',
},
},
}
@metadata_reactor.provides(
'nginx/vhosts',
'letsencrypt/domains',
'monitoring/services',
'nftables/input',
)
def derived_from_domain(metadata):
domain = metadata.get('left4me/domain')
port_start = metadata.get('left4me/port_range_start')
port_end = metadata.get('left4me/port_range_end')
return {
'nginx': {
'vhosts': {
domain: {
'content': 'nginx/proxy_pass.conf',
'context': {
'target': 'http://127.0.0.1:8000',
},
},
},
},
'letsencrypt': {
'domains': {
domain: {},
},
},
'monitoring': {
'services': {
'left4me-web': {
'vars.command': f'/usr/bin/curl -X GET -L --fail --no-progress-meter -o /dev/null https://{domain}/health',
},
},
},
'nftables': {
'input': {
f'udp dport {port_start}-{port_end} accept',
f'tcp dport {port_start}-{port_end} accept',
},
},
}