diff --git a/2026/2026-10-21--castopod-php-stay-off.md b/2026/2026-10-21--castopod-php-stay-off.md new file mode 100644 index 0000000..0455ac3 --- /dev/null +++ b/2026/2026-10-21--castopod-php-stay-off.md @@ -0,0 +1,13 @@ +# Castopod stays off + +Rechecked 2026-10-21 after the stop note. Web unit container-koopa-castopod.service is disabled, inactive, dead. Container koopa-castopod is exited, finished 2026-10-21 22:48:06 +0200. Nothing listens on :9020. MariaDB and Redis user units stay loaded and active. No second stop. Caddy vhost unchanged. + +Log review found no sign of a successful remote code execution. Overlay file /tmp/fw-cp.php (1448 bytes, mode 600, sha256 7bb279bffa12dbb81787183108a2641b78a6657b069cbef7967950d80e13b5bc, mtime 2026-09-07 03:25:59 +0200) is a local database script for cp_users, outside the docroot, not a web shell. /tmp/os-dry.out is an apt dry-run. Five theme files differ by one branding line from 2026-07-13. Media volume has no scripts. Access logs run 2026-10-17 through the stop and show no 2xx on a non-index script. Critical lines on 2026-09-07 match other days. + +Limits: access logs start 2026-10-17, so HTTP from 2026-09-07 is absent. A memory-only or already rotated request is not ruled out. Whether the local script ran is not in the logs. MariaDB was not opened. + +Stays down until an image ships PHP newer than 8.4.18. Hub tag :1 from 2026-02-24 is not that image. Parent 537, slug castopod-restart-after-php-fix. + +Restore only then: + + systemctl --user enable --now container-koopa-castopod.service diff --git a/2026/2026-10-21--castopod-php-stop.md b/2026/2026-10-21--castopod-php-stop.md new file mode 100644 index 0000000..e951d06 --- /dev/null +++ b/2026/2026-10-21--castopod-php-stop.md @@ -0,0 +1,23 @@ +# Castopod PHP stopped + +Checked 2026-10-21. Host has no php-fpm and no php package. The only PHP process was Castopod FrankenPHP in container koopa-castopod. + +| Item | Value | +|------|--------| +| image | docker.io/castopod/castopod:1 | +| repo digest | sha256:4e4f0440520f45257bfeac7be4347defd20048b4efef8f53d73ec9ed3a4f7966 | +| amd64 digest | sha256:757f3c2a96b31b81753c23bc092dfcf05039d9df4d13cc12c428bb752fce882b | +| image created | 2026-02-24 | +| hub tags 1 / latest / 1.15.5 | same digest, pushed 2026-02-24 | +| PHP | 8.4.18 (12 Feb 2026) | +| FrankenPHP | v2.10.2 | +| listen | 0.0.0.0:9020 -> 8080 | +| public | https://castopod.hacktivism.ch/ | + +Stable Hub tags have not moved since 2026-02-24. develop last push 2026-06-05 and is not a stable security update. php.net lists 8.4.26 (24 Sep 2026) and 8.4.27 (22 Oct 2026) after 8.4.18, with a banner to update for critical remote code execution. Pulling :1 repeats the same image. FrankenPHP ships its own PHP binary. + +Stopped and disabled user unit container-koopa-castopod.service. ExecStop is `podman stop -t 30 koopa-castopod`. Container status after stop: Exited (0). :9020 not listening. MariaDB and Redis units left running. Caddy vhost unchanged. + +Bring the web container back only when a newer image exists: + + systemctl --user enable --now container-koopa-castopod.service diff --git a/2026/2026-10-24--todo704-tor-cachyos-cookieauth.md b/2026/2026-10-24--todo704-tor-cachyos-cookieauth.md new file mode 100644 index 0000000..46665b5 --- /dev/null +++ b/2026/2026-10-24--todo704-tor-cachyos-cookieauth.md @@ -0,0 +1,41 @@ +# TODO=704 Tor CachyOS CTR + CookieAuth (koopa) + +Relay was down (stale PIDFile / start-timeout on forking unit), keys intact. Recreated **same identity** on CachyOS container; CookieAuth for ControlPort; host tor disabled. + +## Stand after APPLY (2026-10-24 ~21:47–21:51 Europe/Zurich) + +| Item | Value | +|------|--------| +| CTR | `koopa-tor-relay` Up, image `localhost/koopa-tor-relay:latest` (=`:cachyos`) | +| Tor | 0.4.9.14 (CachyOS/Arch extra) | +| Unit | `container-koopa-tor-relay.service` Type=simple, active | +| Host tor | `tor.service` disabled + failed/inactive | +| FP | `02C7EECCDF7814FBA2DB1CBAED000D06E8A32EE8` (keys mtime 2024-11-14) | +| ORPort | 8080 | +| ControlPort | 127.0.0.1:9051 CookieAuthentication=1, CookieAuthFile=/var/lib/tor/control_auth_cookie | +| Bootstrap | PROGRESS=100 done; status/circuit-established=1 | +| Unauth ControlPort | 514 Authentication required | +| Cookie AUTH | 250 OK | + +## Steps + +1. Dry-run `/tmp/tor.sh` on koopa → STATUS=dry-run-ok; NEXT=`sudo APPLY=1 /tmp/tor.sh` (host tor was still enabled). +2. Human: `systemctl disable --now tor.service`, then `sudo APPLY=1 /tmp/tor.sh`. +3. APPLY: wrote CachyOS Containerfile, pulled `docker.io/cachyos/cachyos:latest`, build (cache hit), `podman create --replace --network host --user 1000:1000 --userns keep-id`, torrc bak `torrc.bak-cookie-20261024-214720`, CookieAuth on. +4. First APPLY hit dbus fail under sudo (`$DBUS_SESSION_BUS_ADDRESS` / `$XDG_RUNTIME_DIR` unset). Unit rewrite + enable finished with XDG/DBUS set; script path re-exec as SUDO_USER for later runs. +5. Verify from magikoopa SSH: unit active, CTR Up, FP match, circuit-established=1, unauth 514. + +## Live paths + +- `~/koopa-tor-relay/{torrc,data,log,build/Containerfile}` +- `~/.config/systemd/user/container-koopa-tor-relay.service` +- Driver: `/tmp/tor.sh` (TARGET=koopa); upgrade later: `~/upgrade-containers.sh --apply --rebuild-local --only koopa-tor-relay` + +## Akte mirrors + +- `configs/tor/torrc` +- `configs/tor/build/Containerfile` +- `configs/systemd/user/container-koopa-tor-relay.service` +- `host/tor/README.md` updated (was stale host `tor.service`) + +Sibling: firecuda brew tor CookieAuth same day (FP `52BB94DDC1292F950CF728708AC48523E018A718`, MyFamily both ways). Parent TODO=704. diff --git a/AGENTS.md b/AGENTS.md index 8ffc768..ba7a565 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,5 @@ # AGENTS.md -## §0 Secrets (HIGH — pointer) - -**Common SoT:** `$HOME/git/llm/llm-configs/operator-rules/secrets-policy.md` (`PATH[admin-secrets]`). -Secret **values** never enter chat/ctx as **input or output** (prompts, pasted tool results, commit subjects, admin-log prose). Handle via **scripts** only (disk `*.env`; report `STATUS=` / `KEY_PRESENT=` / paths) or else a **path-only local-model instruction**. Session stub: `$HOME/.grok/AGENTS.md` §0. Do not fork a second full copy here. - ---- - Session Must / Must not: `$HOME/.grok/AGENTS.md` only — do not duplicate. **Sync (must-sync trees / wenn Git allein nicht reicht):** siehe [SYNC.md](SYNC.md). diff --git a/configs/systemd/user/container-koopa-tor-relay.service b/configs/systemd/user/container-koopa-tor-relay.service new file mode 100644 index 0000000..b65e1ce --- /dev/null +++ b/configs/systemd/user/container-koopa-tor-relay.service @@ -0,0 +1,21 @@ +# container-koopa-tor-relay.service — regenerated by tor.sh 2026-10-24T21:47:24+02:00 +# Prefer simple over podman-generate-systemd forking+PIDFile (stale CID storm). + +[Unit] +Description=Podman koopa-tor-relay +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +RestartSec=5 +TimeoutStartSec=120 +TimeoutStopSec=70 +ExecStart=/usr/bin/podman start --attach koopa-tor-relay +ExecStop=/usr/bin/podman stop -t 10 koopa-tor-relay +Type=simple + +[Install] +WantedBy=default.target diff --git a/configs/tor/build/Containerfile b/configs/tor/build/Containerfile new file mode 100644 index 0000000..c57a137 --- /dev/null +++ b/configs/tor/build/Containerfile @@ -0,0 +1,24 @@ +# koopa-tor-relay — Tor non-exit relay on CachyOS (Arch extra/tor) +# Same pattern as koopa-meet-web:cachyos. Rootless: tor user -> uid/gid 1000. +# Recreate: --user 1000:1000 --userns=keep-id (see README / tor.sh) +ARG CACHYOS_IMAGE=docker.io/cachyos/cachyos:latest +FROM ${CACHYOS_IMAGE} +ENV TZ=Europe/Zurich +RUN pacman -Syu --noconfirm --needed tor nyx ca-certificates \ + && pacman -Scc --noconfirm \ + && rm -rf /var/cache/pacman/pkg/* /tmp/* \ + && mkdir -p /var/lib/tor /var/log/tor \ + && if id tor >/dev/null 2>&1; then \ + groupmod -g 1000 tor 2>/dev/null || true; \ + usermod -u 1000 -g 1000 tor 2>/dev/null || true; \ + elif id debian-tor >/dev/null 2>&1; then \ + groupmod -g 1000 debian-tor; usermod -u 1000 -g 1000 debian-tor; \ + else \ + groupadd -g 1000 tor; useradd -u 1000 -g 1000 -d /var/lib/tor -s /usr/bin/nologin tor; \ + fi \ + && chown -R 1000:1000 /var/lib/tor /var/log/tor \ + && chmod 700 /var/lib/tor +EXPOSE 8080 9051 +USER 1000:1000 +ENTRYPOINT ["/usr/bin/tor"] +CMD ["-f", "/etc/tor/torrc", "--runasdaemon", "0"] diff --git a/configs/tor/torrc b/configs/tor/torrc new file mode 100644 index 0000000..9f1705f --- /dev/null +++ b/configs/tor/torrc @@ -0,0 +1,18 @@ +# KoopaRelay — same policy as live /etc/tor/torrc + admin-log configs/tor/torrc +# Identity: keys in DataDirectory (migrate from host /var/lib/tor) +Log notice file /var/log/tor/notices.log +Nickname KoopaRelay +ContactInfo info+koopa@hacktivism.ch +# dual-stack: bind all addresses on 8080 (IPv4 + IPv6), same as host tor +ORPort 8080 +ControlPort 127.0.0.1:9051 +CookieAuthentication 1 +CookieAuthFile /var/lib/tor/control_auth_cookie +ExitRelay 0 +SocksPort 0 +MyFamily 52BB94DDC1292F950CF728708AC48523E018A718 +BandwidthRate 2000 MBytes +BandwidthBurst 2000 MBytes +RelayBandwidthRate 2000 MBytes +RelayBandwidthBurst 2000 MBytes +DataDirectory /var/lib/tor diff --git a/host/tor/README.md b/host/tor/README.md index 138d11d..fa68a9c 100644 --- a/host/tor/README.md +++ b/host/tor/README.md @@ -1,13 +1,30 @@ # Tor on koopa -**Runtime:** host **`tor.service`** (not a podman container). -**Ports:** ORPort **8080** · ControlPort **9051** (loopback). -**Non-exit** relay (`ExitRelay 0`). +**Runtime:** rootless podman container **`koopa-tor-relay`** (CachyOS base, Arch `extra/tor`), managed by user unit **`container-koopa-tor-relay.service`** (`Type=simple`, `podman start --attach`). +**Host `tor.service`:** disabled / must stay off (port clash on ORPort 8080 + ControlPort 9051). +**Ports:** ORPort **8080** · ControlPort **127.0.0.1:9051** (CookieAuthentication + CookieAuthFile). +**Non-exit** relay (`ExitRelay 0`). Nickname **KoopaRelay**. +**Fingerprint (stable keys under `~/koopa-tor-relay/data/keys/`):** `02C7EECCDF7814FBA2DB1CBAED000D06E8A32EE8` +**Family:** firecuda FireCudaRelay `52BB94DDC1292F950CF728708AC48523E018A718` + +Live tree: `~/koopa-tor-relay/` (`torrc`, `data/`, `log/`, `build/Containerfile`). +Mirrors in this repo: `configs/tor/torrc`, `configs/tor/build/Containerfile`, `configs/systemd/user/container-koopa-tor-relay.service`. ```bash -systemctl status tor -ss -lntp | grep -E '8080|9051' +# status +systemctl --user status container-koopa-tor-relay.service +podman ps --filter name=koopa-tor-relay +# ControlPort cookie auth (hex) +COOKIE=$(xxd -p -c 256 ~/koopa-tor-relay/data/control_auth_cookie) +printf 'AUTHENTICATE %s\r\nGETINFO fingerprint\r\nGETINFO status/circuit-established\r\nQUIT\r\n' "$COOKIE" | nc -w 3 127.0.0.1 9051 ``` -login-health: WARN if `tor.service` not active. -Legacy `koopa-tor-relay` / `configs/tor/` removed from live inventory (git history keeps them). +Apply / recreate (after host tor off): + +```bash +sudo APPLY=1 /tmp/tor.sh +# later upgrades: +~/upgrade-containers.sh --apply --rebuild-local --only koopa-tor-relay +``` + +login-health: expect user unit active + CTR Up; host `tor.service` inactive. Parent TODO=704. diff --git a/scripts/caddy/README.md b/scripts/caddy/README.md index 4d1fc8c..ae4921d 100644 --- a/scripts/caddy/README.md +++ b/scripts/caddy/README.md @@ -41,6 +41,10 @@ Host runtime: `/etc/caddy/secrets/dossiers-ngi-basicauth.txt` (root-only). SoT: `koopa-admin-secrets-enc` (git-crypt) → `koopa/home-hernani/caddy/`. Hosts: `dossiers.ngi` / `dossiers.2.ngi` / `dossiers.3.ngi`.hacktivism.ch +HTTP→HTTPS 308 for `dossiers.ngi` (add host to the shared ACME list, leave +HTTPS body alone): `scripts/dossiers-ngi/apply-dossiers-ngi-http-308.sh` +then `sudo ~/bin/caddy-apply`. + ## silverbullet.hacktivism.ch — merge + basic_auth After podman cutover (`scripts/notes/cutover-to-silverbullet.sh --apply`) and DNS: diff --git a/scripts/dossiers-ngi/README.md b/scripts/dossiers-ngi/README.md index a288176..6b9b65c 100644 --- a/scripts/dossiers-ngi/README.md +++ b/scripts/dossiers-ngi/README.md @@ -63,7 +63,7 @@ Per branch in JSON / sticky bar: | `vs_parent_plus` / `vs_parent_minus` | `git diff --numstat` tip vs **parent** tip (sticky bar +grün/−rot; omitted when `parent` is empty or not in the tree, e.g. `main` and `feature/nl-net-injestion-injestor-update`) | | `loc` | text-file line count of the tip vs the empty tree (gray `N LOC` in the plus/minus columns; only when there is no parent) | -Rebuild runs **only when** the current origin tip is not already a successful freeze (`freeze_ok`; timer never forces). +Rebuild runs **only when** the current origin tip is not already a successful freeze (`freeze_ok`; timer never forces). Cookie alias `editions/flask` copies from `DEFAULT_BRANCH` unless that alias is already `freeze_ok` for the same tip **and** `.edition-built-at` matches the source freeze. Each run also: `git fetch --prune`, `worktree prune`, drop stale `editions//` (gone remotes), purge leftover `tdpd-*` tmp dirs. @@ -136,5 +136,30 @@ sudo ~/bin/caddy-apply Stanza SoT: `scripts/dossiers-ngi/caddy-dossiers-ngi.snippet`. +## HTTP → HTTPS 308 + +Shared Caddy HTTP list already `redir https://{host}{uri} permanent` (308). +`dossiers.ngi.hacktivism.ch` was missing there → HTTP `200` empty body / +Firefox `NS_ERROR_NET_EMPTY_RESPONSE`. HTTPS site body stays untouched. + +On koopa (hernani, then root apply): + +```bash +# SoT: scripts/dossiers-ngi/apply-dossiers-ngi-http-308.sh +cp /path/to/apply-dossiers-ngi-http-308.sh ~/bin/ +chmod +x ~/bin/apply-dossiers-ngi-http-308.sh +bash ~/bin/apply-dossiers-ngi-http-308.sh +sudo ~/bin/caddy-apply +``` + +Smoke after apply: + +```bash +curl -sSI http://dossiers.ngi.hacktivism.ch/ | grep -Ei 'HTTP|location' +# 308 Location: https://dossiers.ngi.hacktivism.ch/ +``` + +`.2` / `.3` stay off this list unless `SITE=` is set. + Caddy edition map must list each `editions/` 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`. diff --git a/scripts/dossiers-ngi/branch-status-hourly.py b/scripts/dossiers-ngi/branch-status-hourly.py index 7b32e78..8cf6433 100755 --- a/scripts/dossiers-ngi/branch-status-hourly.py +++ b/scripts/dossiers-ngi/branch-status-hourly.py @@ -534,15 +534,26 @@ def rebuild_edition(repo: Path, branch: str, tip: str, editions: Path) -> dict: shutil.rmtree(wt, ignore_errors=True) +def _edition_built_at(editions: Path, slug: str) -> str: + p = editions / slug / ".edition-built-at" + if not p.is_file(): + return "" + return p.read_text(encoding="utf-8").strip() + + def sync_flask_alias(editions: Path, tips: dict[str, str]) -> None: """Keep editions/flask in sync with DEFAULT_BRANCH freeze when present.""" src_branch = default_branch() if src_branch not in tips: return - src = editions / edition_slug(src_branch) + src_slug = edition_slug(src_branch) + src = editions / src_slug dst = editions / "flask" tip = tips[src_branch] - if tip_recorded(editions, "flask") == tip and (dst / "index.html").is_file(): + flask_ok, _ = freeze_ok_for(editions, "flask", tip) + src_built = _edition_built_at(editions, src_slug) + flask_built = _edition_built_at(editions, "flask") + if flask_ok and src_built and src_built == flask_built: return if not (src / "index.html").is_file(): ensure_placeholder(editions, "flask", tip) diff --git a/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet index c746406..7e02a41 100644 --- a/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet +++ b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet @@ -19,7 +19,10 @@ handle @use { header Set-Cookie "tdpd_edition={re.use.1}; Path=/; Max-Age=31536000; SameSite=Lax" header Cache-Control "private, no-store" - redir * / 302 + # Keep the caller's path (?next=). Never bounce to /. No next → bar page. + @next query_regexp next ^(/|/[^/].*)$ + redir @next {query.next} 302 + redir * /_cascade/ 302 } handle /_cascade/* { diff --git a/scripts/dossiers-ngi/smoke-branch-tree.sh b/scripts/dossiers-ngi/smoke-branch-tree.sh index 7703693..e180854 100755 --- a/scripts/dossiers-ngi/smoke-branch-tree.sh +++ b/scripts/dossiers-ngi/smoke-branch-tree.sh @@ -100,6 +100,24 @@ PY printf '1\n' > "$EDITIONS/feat-a/.edition-freeze-ok" rm -f "$EDITIONS/feat-a/.edition-freeze-error" +# flask alias: matching tip + stale HTML must copy from DEFAULT_BRANCH +MAIN_TIP="$(tr -d '\n' < "$EDITIONS/main/.edition-tip")" +mkdir -p "$EDITIONS/flask" +printf '%s\n' "$MAIN_TIP" > "$EDITIONS/flask/.edition-tip" +printf '%s\n' 'stale-flask' > "$EDITIONS/flask/index.html" +printf '%s\n' '2026-09-30T17:35:26+02:00' > "$EDITIONS/flask/.edition-built-at" +FETCH=0 TDPD_ROOT="$CLONE" OUT="$OUT" EDITIONS_ROOT="$EDITIONS" \ + FREEZE_CMD="$FREEZE_CMD" REBUILD=1 DEFAULT_BRANCH=main \ + python3 "$SCRIPT" --rebuild --no-fetch +grep -q "main" "$EDITIONS/flask/index.html" +if grep -q "stale-flask" "$EDITIONS/flask/index.html"; then + echo "FAIL flask alias kept stale HTML" + exit 1 +fi +test "$(tr -d '\n' < "$EDITIONS/flask/.edition-freeze-ok")" = "1" +test "$(tr -d '\n' < "$EDITIONS/flask/.edition-built-at")" = "$(tr -d '\n' < "$EDITIONS/main/.edition-built-at")" +echo "OK flask alias copied after stale skip" + # delete smoke branch on origin; fetch --prune drops remote-tracking ref cd "$CLONE" git push origin --delete smoke/temp-sibling >/dev/null 2>&1 diff --git a/scripts/pepsi/add-mail-v6-static.sh b/scripts/pepsi/add-mail-v6-static.sh deleted file mode 100755 index 26b4014..0000000 --- a/scripts/pepsi/add-mail-v6-static.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -# Add dedicated mail IPv6 on koopa eno1 (not Tor SLAAC). -# Addr: 2a02:168:53a8::25/64 (hernani-dns KOOPA_WAN_AAAA) -# Keeps ipv6.method=auto so SLAAC (Tor) stays. Adds one static address. -# Freigabe: human this session. Run on koopa as root / sudo. -# Live: ip -6 addr add; persist: nmcli connection modify +ipv6.addresses -set -euo pipefail - -IFACE="${IFACE:-eno1}" -ADDR="${MAIL_V6_ADDR:-2a02:168:53a8::25}" -PREFIX="${MAIL_V6_PREFIX:-64}" -CIDR="${ADDR}/${PREFIX}" -CON="${NM_CON:-}" - -if [[ ${EUID:-$(id -u)} -ne 0 ]]; then - echo "STATUS=need_root HINT='sudo $0'" - exit 2 -fi - -echo "STATUS=start HOST=$(hostname -s) IFACE=$IFACE CIDR=$CIDR" - -if ! ip link show "$IFACE" >/dev/null 2>&1; then - echo "STATUS=no_iface IFACE=$IFACE" - exit 3 -fi - -# Resolve NM connection for IFACE if not set -if [[ -z "$CON" ]]; then - CON=$(nmcli -g NAME,DEVICE con show --active 2>/dev/null \ - | awk -F: -v d="$IFACE" '$2==d {print $1; exit}') - if [[ -z "$CON" ]]; then - CON=$(nmcli -g NAME,DEVICE con show 2>/dev/null \ - | awk -F: -v d="$IFACE" '$2==d {print $1; exit}') - fi -fi -if [[ -z "$CON" ]]; then - CON="$IFACE" -fi -echo "NM_CON=$CON" - -# Idempotent live address -if ip -6 addr show dev "$IFACE" | grep -qE "inet6[[:space:]]+${ADDR}/"; then - echo "LIVE=present ADDR=$ADDR" - LIVE_ADDED=0 -else - ip -6 addr add "$CIDR" dev "$IFACE" - echo "LIVE=added ADDR=$CIDR" - LIVE_ADDED=1 -fi - -# Persist via NetworkManager (append; do not replace method=auto) -EXISTING=$(nmcli -g ipv6.addresses con show "$CON" 2>/dev/null || true) -if echo "$EXISTING" | grep -qF "$ADDR"; then - echo "NM=present ADDR=$ADDR" - NM_ADDED=0 -else - nmcli con mod "$CON" +ipv6.addresses "$CIDR" - echo "NM=added ADDR=$CIDR" - NM_ADDED=1 - # Apply without full down/up if possible - if nmcli con up "$CON" >/dev/null 2>&1; then - echo "NM=reapplied CON=$CON" - else - echo "NM=reapply_warn HINT='nmcli con up $CON' (addr already live)" - fi -fi - -# Verify -if ip -6 addr show dev "$IFACE" | grep -qE "inet6[[:space:]]+${ADDR}/"; then - echo "VERIFY=ok ADDR=$ADDR" - echo "STATUS=done LIVE_ADDED=$LIVE_ADDED NM_ADDED=$NM_ADDED CIDR=$CIDR" - # Show Tor SLAAC still present (informational) - if ip -6 addr show dev "$IFACE" | grep -q '2a02:168:53a8:0:d584:9e34:13cf:15d6'; then - echo "TOR_SLAAC=present" - else - echo "TOR_SLAAC=absent_or_changed HINT=ok_if_privacy_temp_rotated" - fi - exit 0 -fi - -echo "STATUS=verify_fail ADDR=$ADDR" -ip -6 addr show dev "$IFACE" || true -exit 1 diff --git a/scripts/pepsi/todo631-pepsi-kreis.sh b/scripts/pepsi/todo631-pepsi-kreis.sh deleted file mode 100755 index cb5a2ba..0000000 --- a/scripts/pepsi/todo631-pepsi-kreis.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -# TODO=631: g0v mon-* Kreis-Test (local circle via pepsi TRUST_LOOPBACK / :25). -# Inject via koopa container loopback :25. Empfang = Maildir MARK grep. -# Real OS users (ensure-mon-users.sh): mon-smtp maildir dkim bounce local. -# No secrets. Freigabe-free (local inject only). -set -euo pipefail - -KOOPA_HOST="${KOOPA_HOST:-koopa}" -CONTAINER="${PEPSI_CONTAINER:-koopa-pepsi}" -OUTDIR="${HOME}/git/llm/llm-work-input" -MARK="${MARK:-$(date +%Y%m%dT%H%M%S%z)-kreis}" -USERS=(mon-smtp mon-maildir mon-dkim mon-bounce mon-local) -DRAIN_SEC="${DRAIN_SEC:-6}" - -echo "STATUS=start MARK=${MARK} CONTAINER=${CONTAINER} N=${#USERS[@]}" - -# --- preflight: users exist --- -for u in "${USERS[@]}"; do - ssh -o BatchMode=yes "${KOOPA_HOST}" \ - "podman exec ${CONTAINER} getent passwd ${u}" >/dev/null \ - || { echo "STATUS=fail PREFLIGHT user=${u} missing"; exit 2; } -done -echo "STATUS=preflight USERS_OK=${#USERS[@]}" - -# --- send circle: each → next --- -SEND_PY=$(mktemp) -trap 'rm -f "${SEND_PY}"' EXIT -{ - echo 'import smtplib, time, sys' - echo 'from email.message import EmailMessage' - echo "MARK = ${MARK@Q}" - echo -n 'USERS = [' - for u in "${USERS[@]}"; do printf '%s,' "${u@Q}"; done - echo ']' - cat <<'PY' -ok = fail = 0 -n = len(USERS) -for i, u in enumerate(USERS): - to_u = USERS[(i + 1) % n] - frm = f"{u}@g0v.ch" - to = f"{to_u}@g0v.ch" - msg = EmailMessage() - msg["From"] = frm - msg["To"] = to - msg["Subject"] = f"[pepsi-kreis] TODO631 {i+1}/{n} {MARK}" - msg["Message-ID"] = f""${SEND_PY}" - -ssh -o BatchMode=yes "${KOOPA_HOST}" "podman exec -i ${CONTAINER} python3 -" <"${SEND_PY}" -echo "MARK=${MARK}" >"${OUTDIR}/todo631-kreis.stamp" - -sleep "${DRAIN_SEC}" - -# --- Maildir HIT per recipient (next-hop of each sender) --- -OUT="${OUTDIR}/todo631-kreis-test.txt" -{ - echo "STATUS=ok MARK=kreis-test TODO=631 TS=$(date -Iseconds)" - echo "MARK_PROBE=${MARK} USERS=$(IFS=,; echo "${USERS[*]}")" - echo "SEND=via container :25 loopback" - echo "EXPECT=each user MARK_HITS>=1 (circle next-hop)" - echo "NOTE_vacation=PONG may also land; SPF-fail path skips vacation" -} >"${OUT}" - -HIT_TOTAL=0 -MISS=0 -for u in "${USERS[@]}"; do - hits=$(ssh -o BatchMode=yes "${KOOPA_HOST}" \ - "podman exec ${CONTAINER} bash -c 'grep -lF ${MARK@Q} /home/${u}/Maildir/new/* /home/${u}/Maildir/cur/* 2>/dev/null | wc -l'" \ - | tr -d '[:space:]') - hits=${hits:-0} - echo "HIT_${u}=${hits}" | tee -a "${OUT}" - if [[ "${hits}" -ge 1 ]]; then - HIT_TOTAL=$((HIT_TOTAL + 1)) - else - MISS=$((MISS + 1)) - fi -done - -if [[ "${MISS}" -eq 0 ]]; then - echo "STATUS=PASS HIT_USERS=${HIT_TOTAL}/5 MARK=${MARK}" | tee -a "${OUT}" - exit 0 -fi -echo "STATUS=PARTIAL HIT_USERS=${HIT_TOTAL}/5 MISS=${MISS} MARK=${MARK}" | tee -a "${OUT}" -exit 1 diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css index d0a638e..feefe10 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css @@ -118,3 +118,24 @@ a.cascade-tree-row.is-active:hover { color: #8a96a3; font-weight: 600; } +.cascade-hint { + width: 100%; + max-width: 45rem; + margin: 0.15rem auto 0; + box-sizing: border-box; + padding: 0.2rem 0.35rem; + color: #f0c674; + text-align: center; +} +.cascade-hint[hidden] { + display: none; +} +.cascade-missing-msg { + max-width: 45rem; + margin: 1.5rem auto; + padding: 0 0.75rem; + color: #c8d0d8; + font-family: ui-sans-serif, system-ui, sans-serif; + font-size: 1rem; + line-height: 1.4; +} diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html index ed51952..d81564d 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html @@ -1,5 +1,6 @@ diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js index 8fe4765..8db6e4a 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js @@ -25,6 +25,99 @@ } } + function setEditionCookie(edition) { + document.cookie = + "tdpd_edition=" + + encodeURIComponent(edition) + + "; Path=/; Max-Age=31536000; SameSite=Lax"; + } + + function keepPath() { + return location.pathname + location.search; + } + + function useHref(edition) { + var path = keepPath(); + var href = "/_cascade/use/" + encodeURIComponent(edition); + if (!path || path.indexOf("/_cascade/use/") === 0) return href; + return href + "?next=" + encodeURIComponent(path); + } + + function hintEl() { + return document.getElementById("cascade-hint"); + } + + function showHint(msg) { + var el = hintEl(); + if (!el) { + var bar = document.getElementById("cascade-bar"); + if (!bar) return; + el = document.createElement("div"); + el.id = "cascade-hint"; + el.className = "cascade-hint"; + el.setAttribute("role", "status"); + bar.appendChild(el); + } + el.hidden = false; + el.textContent = msg; + pinBar(); + } + + function clearHint() { + var el = hintEl(); + if (!el) return; + el.hidden = true; + el.textContent = ""; + pinBar(); + } + + function responseKeepsBar(res, text) { + if (res && res.ok) return true; + var body = text || ""; + return ( + body.indexOf("cascade-bar") !== -1 || + body.indexOf("cascade-missing") !== -1 + ); + } + + function switchEdition(edition, branchLabel) { + var prev = cookieEdition(); + var here = keepPath() || "/"; + var label = branchLabel || edition; + if (!edition) return; + if (edition === prev) { + clearHint(); + return; + } + setEditionCookie(edition); + fetch(here, { cache: "no-store", credentials: "same-origin" }) + .then(function (res) { + return res.text().then(function (text) { + return { res: res, text: text }; + }); + }) + .then(function (got) { + if (responseKeepsBar(got.res, got.text)) { + location.replace(here + location.hash); + return; + } + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }) + .catch(function () { + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }); + } + function esc(s) { return String(s) .replace(/&/g, "&") @@ -276,8 +369,8 @@ var parent = row.parent || ""; var isDefault = branch === defaultBranch; html.push( - 'flask' + + 'flask' + ' (branch-status unavailable)'; markActive(mount, cookieEdition()); pinBar(); @@ -341,6 +436,26 @@ }); } + document.addEventListener("click", function (ev) { + var a = ev.target.closest + ? ev.target.closest("a.cascade-tree-row[data-edition]") + : null; + if (!a || ev.defaultPrevented) return; + if (ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) + return; + ev.preventDefault(); + switchEdition( + a.getAttribute("data-edition"), + a.getAttribute("data-branch") || a.getAttribute("data-edition") + ); + }); + + if (document.body && document.body.classList.contains("cascade-missing")) { + showHint( + "This path is not in the current branch. Choose another branch above." + ); + } + loadStatus(false); setInterval(function () { loadStatus(true); diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html index 036ead3..db0bc96 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html @@ -9,6 +9,7 @@

Stuck on an edition without a bar? Pick a build above, or reset to flask.

diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html new file mode 100644 index 0000000..449d470 --- /dev/null +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html @@ -0,0 +1,17 @@ + + + + + +path not in this branch + + + + +

This address is not in the current branch. Choose another branch above.

+ + + diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css index d0a638e..feefe10 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css @@ -118,3 +118,24 @@ a.cascade-tree-row.is-active:hover { color: #8a96a3; font-weight: 600; } +.cascade-hint { + width: 100%; + max-width: 45rem; + margin: 0.15rem auto 0; + box-sizing: border-box; + padding: 0.2rem 0.35rem; + color: #f0c674; + text-align: center; +} +.cascade-hint[hidden] { + display: none; +} +.cascade-missing-msg { + max-width: 45rem; + margin: 1.5rem auto; + padding: 0 0.75rem; + color: #c8d0d8; + font-family: ui-sans-serif, system-ui, sans-serif; + font-size: 1rem; + line-height: 1.4; +} diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html index ed51952..d81564d 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html @@ -1,5 +1,6 @@ diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js index 8fe4765..8db6e4a 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js @@ -25,6 +25,99 @@ } } + function setEditionCookie(edition) { + document.cookie = + "tdpd_edition=" + + encodeURIComponent(edition) + + "; Path=/; Max-Age=31536000; SameSite=Lax"; + } + + function keepPath() { + return location.pathname + location.search; + } + + function useHref(edition) { + var path = keepPath(); + var href = "/_cascade/use/" + encodeURIComponent(edition); + if (!path || path.indexOf("/_cascade/use/") === 0) return href; + return href + "?next=" + encodeURIComponent(path); + } + + function hintEl() { + return document.getElementById("cascade-hint"); + } + + function showHint(msg) { + var el = hintEl(); + if (!el) { + var bar = document.getElementById("cascade-bar"); + if (!bar) return; + el = document.createElement("div"); + el.id = "cascade-hint"; + el.className = "cascade-hint"; + el.setAttribute("role", "status"); + bar.appendChild(el); + } + el.hidden = false; + el.textContent = msg; + pinBar(); + } + + function clearHint() { + var el = hintEl(); + if (!el) return; + el.hidden = true; + el.textContent = ""; + pinBar(); + } + + function responseKeepsBar(res, text) { + if (res && res.ok) return true; + var body = text || ""; + return ( + body.indexOf("cascade-bar") !== -1 || + body.indexOf("cascade-missing") !== -1 + ); + } + + function switchEdition(edition, branchLabel) { + var prev = cookieEdition(); + var here = keepPath() || "/"; + var label = branchLabel || edition; + if (!edition) return; + if (edition === prev) { + clearHint(); + return; + } + setEditionCookie(edition); + fetch(here, { cache: "no-store", credentials: "same-origin" }) + .then(function (res) { + return res.text().then(function (text) { + return { res: res, text: text }; + }); + }) + .then(function (got) { + if (responseKeepsBar(got.res, got.text)) { + location.replace(here + location.hash); + return; + } + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }) + .catch(function () { + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }); + } + function esc(s) { return String(s) .replace(/&/g, "&") @@ -276,8 +369,8 @@ var parent = row.parent || ""; var isDefault = branch === defaultBranch; html.push( - 'flask' + + 'flask' + ' (branch-status unavailable)'; markActive(mount, cookieEdition()); pinBar(); @@ -341,6 +436,26 @@ }); } + document.addEventListener("click", function (ev) { + var a = ev.target.closest + ? ev.target.closest("a.cascade-tree-row[data-edition]") + : null; + if (!a || ev.defaultPrevented) return; + if (ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) + return; + ev.preventDefault(); + switchEdition( + a.getAttribute("data-edition"), + a.getAttribute("data-branch") || a.getAttribute("data-edition") + ); + }); + + if (document.body && document.body.classList.contains("cascade-missing")) { + showHint( + "This path is not in the current branch. Choose another branch above." + ); + } + loadStatus(false); setInterval(function () { loadStatus(true); diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html index 036ead3..db0bc96 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html @@ -9,6 +9,7 @@

Stuck on an edition without a bar? Pick a build above, or reset to flask.