Files
bf-infra/tools/build-cfd-image.py
Christoph Emsenhuber 60360fc3cc TP16.3 §2/§3 durch: cloudflared auf ap1/ap2/ap3, bf-public healthy mit 12 Connectoren
Merge von claude/nervous-williams-acf000 (d5b3ce3) plus Ergebnis der
Vor-Ort-Session vom 2026-08-31.

Ergebnis
- je ein cloudflared 2026.6.1 auf ap1/ap2/ap3, bf-public healthy/12
  (vorher 0 Connectoren), Flash je AP 54-55 MiB frei, WLAN zurueck.

Drei Runbook-Fehler aufgedeckt und korrigiert (§2.2, §3.3, §3.5, §8)
- Flash-Budget war Faktor 2,5 zu optimistisch: cloudflared entpackt
  69,5 MiB (gemessen mit docker export), nicht 28 MB -- das offizielle
  Image passt auf keinen der drei APs. Ersatz: eigenes FROM-scratch-Image
  mit 35,3 MiB aus dem statisch gelinkten Binary, Binary-SHA e93824f6
  gepinnt, Neubau byte-identisch. tools/build-cfd-image.py.
- docker save schreibt seit Docker 25 OCI statt docker-v1 -> RouterOS
  meldet "no config found in manifest". tools/oci2docker.py als Konverter.
- /container/envs nimmt list=, nicht name= (auch MikroMCPs
  manage_container_env ist dadurch fuer dieses Feld unbrauchbar).

Weitere Funde
- Der device-mode-Bestaetigungs-Boot installiert keine Pakete; erst
  Paket-Reboot, dann device-mode.
- "A - AVAILABLE" in /system package print ist ein Download-Katalog,
  kein Paket auf dem Geraet (Gegenprobe am Core: 2,9 MB frei, listet
  10,1 MB wifi-qcom als verfuegbar).
- MikroMCPs export_config exportiert unvollstaendig (301 B statt 6,2 KiB).

Offen und entscheidungsbeduerftig
- Compliance 128/135: §3.4 verlangt Direkt-Writes fuer ip address/nat,
  die unter GC-Kontrolle stehen -> sechs neue extra-Drifts, zwei je AP.
  Inhaltlich richtig, gehoeren ins Soll -- kein Re-Baseline, kein Rueckbau.
- Zwei Nautobot-Tokens sind in dieser Session ins Transkript geraten
  (unzureichende sed-Maskierung) -> Rotation noetig, TP30. Merkregel in
  CLAUDE.md ergaenzt.

Merge-Konflikte aufgeloest
- Nummernkonflikt: beide Branches hatten ein TP37. main behaelt TP37
  (Nautobot als Netzwerkdoku, User-Okay 08-30), OOB-Mgmt wird TP38.
  Braucht noch Christophs Okay.
- Drei CF-Rollback-Snapshots hatten gleichen Namen bei verschiedenem
  Inhalt -> aelterer Stand als *-vor-hostname-schnitt.json erhalten,
  keiner verworfen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 16:15:13 +02:00

60 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Baut ein minimales docker-v1-Image-Archiv (nur cloudflared + CA-Bundle),
wie RouterOS /container es einliest. Kein Docker noetig, damit das Format
nicht vom containerd-Store der Build-Maschine abhaengt."""
import hashlib, io, json, os, sys, tarfile, time
binp, cap, out = sys.argv[1], sys.argv[2], sys.argv[3]
MTIME = 0 # feste mtime -> reproduzierbares Archiv
def add(tf, name, data, mode, typ=tarfile.REGTYPE):
ti = tarfile.TarInfo(name)
ti.mode, ti.mtime, ti.uid, ti.gid, ti.type = mode, MTIME, 0, 0, typ
ti.size = len(data) if typ == tarfile.REGTYPE else 0
tf.addfile(ti, io.BytesIO(data) if typ == tarfile.REGTYPE else None)
# --- Layer bauen ---
layer = io.BytesIO()
with tarfile.open(fileobj=layer, mode="w", format=tarfile.GNU_FORMAT) as tf:
for d in ("usr/", "usr/local/", "usr/local/bin/", "etc/", "etc/ssl/",
"etc/ssl/certs/", "tmp/"):
add(tf, d, b"", 0o755 if d != "tmp/" else 0o1777, tarfile.DIRTYPE)
add(tf, "usr/local/bin/cloudflared", open(binp, "rb").read(), 0o755)
add(tf, "etc/ssl/certs/ca-certificates.crt", open(cap, "rb").read(), 0o644)
layer_bytes = layer.getvalue()
diff_id = "sha256:" + hashlib.sha256(layer_bytes).hexdigest()
# --- Image-Config ---
cfg = {
"architecture": "arm64",
"os": "linux",
"config": {
"Env": ["PATH=/usr/local/bin:/usr/bin:/bin",
"SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"],
"Entrypoint": ["/usr/local/bin/cloudflared"],
"Cmd": ["version"],
"WorkingDir": "/",
},
"rootfs": {"type": "layers", "diff_ids": [diff_id]},
"history": [{"created": "1970-01-01T00:00:00Z",
"created_by": "bf-infra TP16.3: cloudflared + CA, FROM scratch"}],
}
cfg_raw = json.dumps(cfg, sort_keys=True, separators=(",", ":")).encode()
cfg_name = hashlib.sha256(cfg_raw).hexdigest() + ".json"
manifest = json.dumps([{"Config": cfg_name,
"RepoTags": ["cfd-min:2026.6.1"],
"Layers": ["layer/layer.tar"]}]).encode()
with tarfile.open(out, "w", format=tarfile.GNU_FORMAT) as tf:
add(tf, cfg_name, cfg_raw, 0o644)
add(tf, "layer/", b"", 0o755, tarfile.DIRTYPE)
add(tf, "layer/VERSION", b"1.0", 0o644)
add(tf, "layer/json", json.dumps({"id": "layer"}).encode(), 0o644)
add(tf, "layer/layer.tar", layer_bytes, 0o644)
add(tf, "manifest.json", manifest, 0o644)
print(f"Layer roh : {len(layer_bytes)} B ({len(layer_bytes)/1048576:.1f} MiB)")
print(f"Archiv : {os.path.getsize(out)} B ({os.path.getsize(out)/1048576:.1f} MiB)")
print(f"diff_id : {diff_id}")