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>
70 lines
2.5 KiB
Python
Executable File
70 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""OCI-Archiv (docker save ab Docker 25 mit containerd-Store) -> klassisches
|
|
docker-archive v1, wie RouterOS /container es erwartet."""
|
|
import gzip, hashlib, json, os, shutil, sys, tarfile, tempfile
|
|
|
|
src, dst = sys.argv[1], sys.argv[2]
|
|
work = tempfile.mkdtemp(prefix="oci2docker.")
|
|
ext = os.path.join(work, "in")
|
|
out = os.path.join(work, "out")
|
|
os.makedirs(ext); os.makedirs(out)
|
|
|
|
with tarfile.open(src) as t:
|
|
t.extractall(ext)
|
|
|
|
man = json.load(open(os.path.join(ext, "manifest.json")))
|
|
if len(man) != 1:
|
|
sys.exit(f"erwarte genau 1 Image im Archiv, gefunden: {len(man)}")
|
|
entry = man[0]
|
|
|
|
# --- Config ---
|
|
cfg_src = os.path.join(ext, entry["Config"])
|
|
cfg_raw = open(cfg_src, "rb").read()
|
|
cfg_sha = hashlib.sha256(cfg_raw).hexdigest()
|
|
cfg_name = f"{cfg_sha}.json"
|
|
open(os.path.join(out, cfg_name), "wb").write(cfg_raw)
|
|
cfg = json.loads(cfg_raw)
|
|
|
|
# --- Layer: entpacken, falls gzip ---
|
|
layers_out = []
|
|
for i, lp in enumerate(entry["Layers"]):
|
|
blob = os.path.join(ext, lp)
|
|
with open(blob, "rb") as f:
|
|
magic = f.read(2)
|
|
d = os.path.join(out, f"layer{i:02d}")
|
|
os.makedirs(d)
|
|
target = os.path.join(d, "layer.tar")
|
|
if magic == b"\x1f\x8b":
|
|
with gzip.open(blob, "rb") as fi, open(target, "wb") as fo:
|
|
shutil.copyfileobj(fi, fo, 1024 * 1024)
|
|
else:
|
|
shutil.copyfile(blob, target)
|
|
open(os.path.join(d, "VERSION"), "w").write("1.0")
|
|
open(os.path.join(d, "json"), "w").write(json.dumps({"id": f"layer{i:02d}"}))
|
|
layers_out.append(f"layer{i:02d}/layer.tar")
|
|
|
|
# --- diff_ids gegenpruefen: sie muessen die UNKOMPRIMIERTEN Digests sein ---
|
|
diff_ids = cfg.get("rootfs", {}).get("diff_ids", [])
|
|
if len(diff_ids) != len(layers_out):
|
|
sys.exit(f"diff_ids ({len(diff_ids)}) != Layer ({len(layers_out)})")
|
|
for want, rel in zip(diff_ids, layers_out):
|
|
h = hashlib.sha256()
|
|
with open(os.path.join(out, rel), "rb") as f:
|
|
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
|
h.update(chunk)
|
|
got = "sha256:" + h.hexdigest()
|
|
if got != want:
|
|
sys.exit(f"Layer-Digest weicht ab:\n erwartet {want}\n erhalten {got}")
|
|
|
|
json.dump([{"Config": cfg_name,
|
|
"RepoTags": entry.get("RepoTags") or [],
|
|
"Layers": layers_out}],
|
|
open(os.path.join(out, "manifest.json"), "w"))
|
|
|
|
with tarfile.open(dst, "w") as t:
|
|
for n in sorted(os.listdir(out)):
|
|
t.add(os.path.join(out, n), arcname=n)
|
|
|
|
shutil.rmtree(work)
|
|
print(f"ok: {dst} ({os.path.getsize(dst)} Bytes, {len(layers_out)} Layer, diff_ids gepruegt)")
|