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>
This commit is contained in:
69
tools/oci2docker.py
Executable file
69
tools/oci2docker.py
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/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)")
|
||||
Reference in New Issue
Block a user