| .. | ||
| apply-dossiers-ngi-edition-switch.sh | ||
| branch-status-hourly.py | ||
| caddy-dossiers-ngi.snippet | ||
| inject-cascade-bar.py | ||
| inject-editions-bar.sh | ||
| README.md | ||
| smoke-branch-tree.sh | ||
dossiers-ngi — dynamic edition bar + 10‑min rebuild
SoT for the sticky edition bar on dossiers.ngi.hacktivism.ch. Live freezes
sit under editions/<slug>/ on koopa; cookie tdpd_edition selects the build.
The bar renders a semi-graphic parent→children tree from
/branch-status.json (all origin/* tips, not a hard-coded ladder).
| Path | Role |
|---|---|
sites/…/_cascade/ |
Sticky bar shell + JS tree renderer |
editions/ on koopa www |
Full freezes switched by the bar |
sites/…/cascade/ |
Legacy tip stubs (optional) |
scripts/dossiers-ngi/branch-status-hourly.py |
Fetch remotes → tree JSON → on-demand rebuild |
scripts/dossiers-ngi/smoke-branch-tree.sh |
Temp-branch smoke (creates/deletes local origin only) |
configs/systemd/user/dossiers-ngi-branch-status.{timer,service} |
Every 10 min oneshot on koopa (flock against overlap) |
Env
| Var | Default | Meaning |
|---|---|---|
TDPD_ROOT |
$HOME/git/src/ngi/tech-dossier-project-data |
tdpd git checkout (may be git-dir only) |
TDPD_VENV |
(TDPD_ROOT)/.venv |
Frozen-Flask venv; symlinked into worktrees |
OUT |
kal sites/…/branch-status.json |
write path (live www on koopa) |
EDITIONS_ROOT |
(none) | …/editions — required for --rebuild |
REBUILD / --rebuild |
off | rebuild when .edition-tip ≠ tip |
FORCE_REBUILD / --force |
off | rebuild every branch (timing) |
FETCH / --no-fetch |
fetch on | git fetch --prune origin |
FREEZE_CMD |
(make/venv freeze) | stub for smoke tests; {root} {branch} {slug} |
DEFAULT_BRANCH |
feat/website-views |
behind cookie flask |
HOST |
https://dossiers.ngi.hacktivism.ch |
public host in JSON |
On koopa: %h/src/koopa/koopa-admin-log +
~/.config/dossiers-ngi/branch-status.env for path overrides.
NGI clone token lives in koopa-admin-secrets-enc (not this tree).
Manual run
# JSON only
python3 scripts/dossiers-ngi/branch-status-hourly.py
# rebuild stale freezes + timing block in JSON
EDITIONS_ROOT=/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch/editions \
python3 scripts/dossiers-ngi/branch-status-hourly.py --rebuild
# force all branches (wall clock → rebuild_timing)
… --rebuild --force
rebuild_timing fields: wall_seconds, built, skipped, errors,
built_seconds_sum, built_seconds_max. Per-branch rebuild[name].seconds.
Per branch in JSON / sticky bar:
| Field | Meaning |
|---|---|
tip |
short SHA |
tip_time |
committer time of tip (%cI) |
built_at |
when this edition freeze was written |
build_seconds |
wall time of that freeze |
vs_parent_plus / vs_parent_minus |
git diff --numstat tip vs parent tip (sticky bar +grün/−rot; omitted when no parent) |
Rebuild runs only when .edition-tip ≠ current tip (timer never forces).
Each run also: git fetch --prune, worktree prune, drop stale
editions/<slug>/ (gone remotes), purge leftover tdpd-* tmp dirs.
Smoke
bash scripts/dossiers-ngi/smoke-branch-tree.sh
# → SMOKE_OK (temp origin + smoke/temp-sibling deleted)
Edition freeze missing the bar (cookie trap)
feat/website-views freeze can omit {% include "_cascade/bar.html" %}.
Cookie tdpd_edition then has no switcher. No zypper / Caddy plugin.
Way back: https://dossiers.ngi.hacktivism.ch/_cascade/use/flask
or /_cascade/ (index.html switcher).
On koopa (hernani, no root):
bash scripts/dossiers-ngi/inject-editions-bar.sh
Hourly rebuild injects on skip and freeze-error (keep existing HTML + include).
The bar is position: fixed (not sticky); 100vw overflow is avoided.
Deploy landing (Freigabe)
Do not --delete the whole www root. Sync:
SITE="$HOME/git/admin-logs/local/koopa-admin-log/sites/dossiers.ngi.hacktivism.ch"
WWW=hernani@koopa:/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch
rsync -avz --exclude .DS_Store "$SITE/_cascade/" "$WWW/_cascade/"
rsync -avz --exclude .DS_Store "$SITE/branch-status.json" "$WWW/branch-status.json"
# editions/ from a --rebuild run
Sticky cascade bar
bar.js fetches /branch-status.json and draws:
main @ d2fbd79 · tip 2026-09-01 16:52 · build 8.4s · at 2026-09-30 15:23
├── feat/sm-content @ … · tip YYYY-MM-DD HH:MM · …
│ └── …
└── project_template @ …
tip / at always include date + HH:MM (ISO already in JSON; clock-only was misleading across days). The bar is viewport-centered (max-width 45em, mobile-friendly). Poll every 30s updates tip/build/at text in place when the branch structure is unchanged (no full tree remount).
Each name links to /_cascade/use/<edition> (cookie + 302 /). Caddy map:
~^[A-Za-z0-9_-]+$ {http.request.cookie.tdpd_edition}
Also serve /cascade/* from the site www root (path stubs). Live Caddy must not use a trailing ~.* flask catch-all.
Edition purity: responses send Vary: Cookie and Cache-Control: private, no-cache so /static/css/style.css (same path, different bodies per edition) cannot stick the green design CSS onto main after a views visit.
so new branches need no Caddy edit — only an editions/<slug>/ tree.
bash ~/bin/apply-dossiers-ngi-edition-switch.sh
sudo ~/bin/caddy-apply
Stanza SoT: scripts/dossiers-ngi/caddy-dossiers-ngi.snippet.
Caddy edition map must list each editions/<slug> explicitly. A map output of {http.request.cookie.tdpd_edition} did not expand on Caddy v2.11.4 (only flask worked → 404 for main/views). After new tips appear, refresh the snippet map (or regenerate from ls editions) before apply-dossiers-ngi-edition-switch + caddy-apply.