#!/usr/bin/env bash # TOML syntax + structural sanity for every manifest in this repo. # Used by `make validate` and by .gitea/workflows/ci.yaml. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" python3 - "$ROOT" <<'PY' import sys, tomllib, pathlib root = pathlib.Path(sys.argv[1]) errs = [] count = 0 for p in sorted(root.glob('manifests/**/*.toml')): count += 1 try: data = tomllib.load(open(p, 'rb')) except Exception as e: errs.append(f'{p}: TOML parse: {e}') continue svcs = data.get('service') if not svcs: errs.append(f'{p}: no [[service]] block') continue for svc in svcs: for required in ('name', 'image'): if required not in svc: errs.append(f'{p}: service missing required field "{required}"') # forbidden nesting bugs for sub in ('placement', 'resources', 'env', 'volume'): if isinstance(svc.get(sub), dict): for fb in ('depends_on', 'extra_ports', 'cmd', 'mounts'): if fb in svc[sub]: errs.append(f'{p}: "{fb}" nested under [service.{sub}] — must be at [[service]] level') # placement.node must match parent vm directory node = (svc.get('placement') or {}).get('node') vm_dir = p.parent.name if node and node != vm_dir: errs.append(f'{p}: placement.node "{node}" mismatches dir "{vm_dir}"') if not node: errs.append(f'{p}: missing placement.node') mem = (svc.get('resources') or {}).get('memory') if not mem: errs.append(f'{p}: missing resources.memory (mandatory per §8 rule 5)') # Validate overlays parse too for p in sorted(root.glob('overlays/*/overlay.toml')): count += 1 try: tomllib.load(open(p, 'rb')) except Exception as e: errs.append(f'{p}: TOML parse: {e}') print(f'checked {count} files') for e in errs: print(' ', e) sys.exit(1 if errs else 0) PY