Files
felhom.eu/documentation/audits/more-night-apps-2026-09-30/tools/floatpush.py
T
admin 42bf40bd2c
gates / gates (push) Successful in 29s
More apps update themselves at night (30+2 -> 35+3); the same-name security-fix gap measured (R-740, decision for the operator)
- Twelve steps published on both venues (catalog): first ladders for calibre-web, gitea, wger,
  crafty-controller, uptime-kuma, zipline (two steps); within-major emby, ghost, home-assistant,
  outline, rallly. immich's step v3.0.3 -> v3.2.2 re-proven at 768M (Part D).
- Part C: the night leg skips a digest-only change AND the catalog never records a same-tag re-test,
  so a same-name upstream fix reaches no box. Row R-740; the decision in STATUS; `09` decision 30
  carries a dated note (the decision itself unchanged).
- Rows: 369 -> 377. Opened R-735..R-742; closed R-735, R-738, R-742; narrowed R-462, R-624, R-446,
  R-440, R-734, R-732. The currency audit gains §1b (and corrects its 31+1 to 30+2).

Evidence: documentation/audits/more-night-apps-2026-09-30/. Report: REPORT-more-night-apps-2026-09-30.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 20:27:28 +02:00

51 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""floatpush.py — Part C (2026-09-30): for every FLOATING line in the live catalog (the currency audit's definition:
fewer than three numbers in the tag's first version run), (1) does the registry serve today the digest the newest
ladder entry TESTED for it, and (2) when was the tag last pushed (Docker Hub `tag_last_pushed`; ghcr/lscr have no
anonymous timestamp — said, not guessed). Read-only; the audit's helpers are imported."""
import importlib.util, json, os, re, sys, datetime, requests
CAT = "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu"
A = "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/catalog-currency-2026-09-30/00-currency.py"
sys.argv = ["x", CAT, "/dev/null"]
s = importlib.util.spec_from_file_location("c", A); c = importlib.util.module_from_spec(s); s.loader.exec_module(c)
sys.path.insert(0, CAT + "/scripts"); import ladder
now = datetime.datetime.now(datetime.timezone.utc)
print(f"# floating lines in the live catalog, read {now.isoformat(timespec='seconds')}")
lines = {}
for app in sorted(os.listdir(CAT + "/templates")):
d = f"{CAT}/templates/{app}"
imgs = ladder.images_in(open(d + "/docker-compose.yml").read())
entries, _, _ = ladder.parse(open(d + "/.felhom.yml").read())
for svc, ref in imgs.items():
repo, tag = c.split_ref(ref)
if not c.is_floating(tag):
continue
tested = None
for e in reversed(entries or []):
if e.get("to", {}).get(svc) == ref:
tested = (e.get("digest") or {}).get(svc); break
lines.setdefault(ref, []).append((app, svc, tested))
recent = 0
for ref, uses in sorted(lines.items()):
repo, tag = c.split_ref(ref); host, r = c.host_repo(repo)
try:
now_d = c.digest(host, r, tag)
now_d = now_d[0] if isinstance(now_d, tuple) else now_d
except Exception as e:
now_d = f"ERR {e!r}"[:60]
pushed = "no anonymous timestamp on " + host
if host == "registry-1.docker.io":
try:
j = requests.get(f"https://hub.docker.com/v2/repositories/{r}/tags/{tag}", timeout=30).json()
pushed = j.get("tag_last_pushed") or j.get("last_updated") or "?"
age = (now - datetime.datetime.fromisoformat(pushed.replace("Z", "+00:00"))).days
pushed = f"{pushed} ({age} days ago)"
if age <= 30: recent += 1
except Exception as e:
pushed = f"ERR {e!r}"[:80]
print(f"\n{ref} last pushed: {pushed}\n registry digest today: {str(now_d)[:19]}")
for app, svc, tested in uses:
st = "no ladder digest" if not tested else ("= tested" if tested == now_d else f"MOVED since the test (tested {tested[:19]})")
print(f" {app}/{svc}: {st}")
print(f"\n# {len(lines)} distinct floating lines; Docker Hub lines pushed within 30 days: {recent}")