Compare commits
39 Commits
efb0f44fa7
...
claude/pri
| Author | SHA1 | Date | |
|---|---|---|---|
| 909e604ea6 | |||
| 614025eafb | |||
| 677dade184 | |||
| b20edd6b62 | |||
| a38ed0fe1d | |||
| 5a29c482bd | |||
| 4eba3673b7 | |||
| 2b856d3d42 | |||
| f1d565bc88 | |||
| 43c592e5d4 | |||
| 33b18a08db | |||
| 799b693ffa | |||
| 4aeced30b5 | |||
| 89d94dbd45 | |||
| 6f60e03679 | |||
| 2f4866ca9a | |||
| 8e41268b4b | |||
| b915d02a82 | |||
| efb03979a0 | |||
| 268fd859ab | |||
| 7a20a1db4f | |||
| bca8f6e27e | |||
| d63dc7bb78 | |||
| b200401c5a | |||
| 384f356290 | |||
| e96fca5a52 | |||
| 1625c30fd7 | |||
| 83fd69ef8d | |||
| 56746cb24d | |||
| a0ba87d555 | |||
| 674e5fff0d | |||
| c024f85579 | |||
| 9caf145341 | |||
| 7edf01b021 | |||
| f507c50623 | |||
| f8982df200 | |||
| c23628f62f | |||
| b75336c9db | |||
| 70afb165b7 |
45
CLAUDE.md
45
CLAUDE.md
@@ -6,7 +6,7 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der
|
||||
|
||||
## Pflichtlektüre pro Sitzung (Reihenfolge)
|
||||
|
||||
1. **`docs/teilprojekte.md`** (Quick-Index-Block) — kanonischer Stand, Scope, Abhängigkeiten, **offene** TPs. **1 Read reicht.**
|
||||
1. **Status/Index = Gitea-Board** (seit TP36-Cutover 2026-09-07): [`admin/bf-infra` · Issues](https://gitea.emsenhuber.net/admin/bf-infra/issues) — TP = Issue, Status = Label `status:*`, Gruppierung = `thema:*`. Für den **Detail/Kontext** eines TP dann den `### TPnn`-Prosa-Abschnitt in **`docs/teilprojekte.md`** (aktiv) bzw. **`docs/teilprojekte-archiv.md`** lesen. *(Die frühere „Quick-Index-Block, 1 Read reicht"-Zeile ist überholt — Index/Status stehen nicht mehr in der Datei.)*
|
||||
2. **`docs/handoff-*.md`** (neueste) — wo die letzte Session aufgehört hat.
|
||||
3. Bei Live-Writes oder Inventur: **`docs/cf-tunnel-mapping.md`**, **`docs/teilprojekte-archiv.md`**.
|
||||
4. ✅ **TP16.3 §5 durch (2026-09-01):** [`docs/handoff-20260901-tp16.3-paragraph5-flip-durch.md`](docs/handoff-20260901-tp16.3-paragraph5-flip-durch.md)
|
||||
@@ -17,14 +17,38 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der
|
||||
— die Abschlussmessung ergab am 09-02 nur **4 `reauthenticating` in 8,6 h** (alle iPhone `F2:62`,
|
||||
isolierte Einzel, 0 Ausfaelle), Verlauf **218 → 40 → 4**; Vorbedingung geprueft (Core-Uptime 1d18h,
|
||||
`caps joined=0`). Hebel 1 (`ft-over-ds=no`) war widerlegt (6,4 h allein → 124× + 2 Ausfaelle) und ist
|
||||
am 09-01 zurueckgebaut; Hebel 2–4 entfallen, **kein Geraete-Write** (Soll steht: `ft=yes ft-over-ds=yes`,
|
||||
**Compliance 140/140**). Offen in TP25 bleibt nur **ap2 unterperformt** (2 m/−80 dBm). Vorstufen:
|
||||
am 09-01 zurueckgebaut; Hebel 2–4 entfallen, **kein Geraete-Write** (Soll stand: `ft=yes ft-over-ds=yes`,
|
||||
**Compliance 140/140**). **Am 2026-09-04 hat Christoph `ft=no` auf `sec-main` als Dauerzustand
|
||||
entschieden** (Hebel 2 doch gezogen: Template + bf-golden-config + Nautobot-Sync, `wifi-security` gruen).
|
||||
🔴 **Nachtrag 09-04: `ft=no` war NICHT uniform.** ap3 trug einen Werks-Inline `security.ft=yes` auf
|
||||
`wifi1/wifi2` (Bring-up unset `!ft-over-ds`, aber nicht `!ft`; Abnahme zählt nur benannte
|
||||
security-Objekte → Inline-Wert unsichtbar). Gemischtes FT-ESS → Strong-Signal-Abbrüche (−43/−45 dBm)
|
||||
**nur auf ap3** bei 3/3 Strichen. Fix: `!security.ft` auf ap3 (uniform `ft=no`), Quelle
|
||||
`tp28-ap3-phase2-cap.rsc` korrigiert. **Merkregel: nach jeder `sec-main`-FT-Änderung jeden CAP einzeln
|
||||
`get wifiN security.ft` prüfen — CAP-lokale Inline-Overrides sind compliance-unsichtbar.**
|
||||
Der TP25-Kanalplan ist am 09-04 committed (DFS-Frequenzlisten + ap1-Provisioning, **140/140, 0 Drifts**).
|
||||
Offen in TP25 bleibt nur **ap2 unterperformt** (2 m/−80 dBm). Vorstufen:
|
||||
[Befund](docs/handoff-20260831-tp27-upgrade-und-hebel1-befund.md) ·
|
||||
[Eingriff](docs/handoff-20260831-fund-e-hebel1-gezogen.md) ·
|
||||
[Rueckbau](docs/handoff-20260901-fund-e-hebel1-zurueckgebaut.md).
|
||||
6. ap3-Bring-up 2026-08-30: [`docs/handoff-20260830-ap3.md`](docs/handoff-20260830-ap3.md) — sechs Funde zu Werks-MikroTiks, CAPsMAN und Golden-Config-Lücken.
|
||||
7. Inventur-Befund 2026-08-27: [`docs/handoff-20260827-inventur-und-architektur.md`](docs/handoff-20260827-inventur-und-architektur.md) — Master-Struktur, Asymmetrie, Drift.
|
||||
|
||||
## SSoT & Branch-Konvention (TP36-Cutover 2026-09-07)
|
||||
|
||||
- **SSoT-Dreiteilung:** **Index/Status = Gitea-Issues** (`admin/bf-infra`, Labels `status:*`/`thema:*`) ·
|
||||
**Prosa/Detail = Repo** (`docs/teilprojekte.md` aktiv + `teilprojekte-archiv.md`, `### TPnn`-Abschnitte) ·
|
||||
**Live-Gerätedaten = Nautobot** (Compliance-Zahl, RouterOS-Version …). Ein Status ändert sich **in Gitea**,
|
||||
nicht mehr in einer Repo-Datei. Sync-/Abgleich-Werkzeug fürs Archiv: `bin/gitea-tp-sync.py` (Dry-Run default,
|
||||
Token aus `~/.hermes/.env`, **nie ausgeben**).
|
||||
- **Branch-Konvention:** Branch `tpNN-kurz` **jederzeit** mitten in der Session möglich (`git switch -c`,
|
||||
uncommittete Arbeit wandert mit); für Multi-Session-TPs der Normalfall. **Merge nach `main`, sobald der
|
||||
Code/Config des TP steht.** Weil Steuerungs-Updates nach **Gitea** gehen (nicht in `teilprojekte.md`),
|
||||
kollidieren TP-Branches nicht mehr auf der Steuerungs-Doku — nur noch der eigene `### TPnn`-Prosa-Abschnitt
|
||||
wird angefasst.
|
||||
- **TP anlegen/schließen:** neues aktives TP = **direkt als Gitea-Issue** (Labels setzen), Prosa-Abschnitt
|
||||
im Repo dazu; abschließen = Issue `closed`, Prosa wandert bei Bedarf ins Archiv. **Keine Handtabelle mehr.**
|
||||
|
||||
## Was du sofort wissen musst (Inventur 2026-08-27)
|
||||
|
||||
- **`bf-infra`** ist aktiver Master **mit Gitea-Remote**: `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git` (live verifiziert 2026-08-30). ⚠️ **Die frühere Zeile „lokal, kein Gitea-Remote → verloren bei Festplattencrash" ist überholt** — sie stammt aus der Inventur vom 2026-08-27 und war damals richtig; das Remote existiert seit spätestens 2026-08-28 (`origin/main` = `b917691`). **Die Backup-Lücke ist damit keine Struktur-, sondern eine Disziplinfrage: `git push`.** Am 2026-08-30 lagen vier Commits nur lokal. 🔴 **Das Remote ist nur so viel wert wie der letzte Push** — der Backup-Guard (`~/dev/.bin/backup-guard.sh`, TP35 Schicht 5) bricht Commits ab, solange kein Backup markiert ist; **das ist die Erinnerung ans Pushen, kein Hindernis.** Nicht mit `--no-verify` umgehen. 🔴 **Merkregel:** diese Zeile hat eine Session dazu gebracht, „kein Remote" zu behaupten, statt `git remote -v` zu tippen — vor jeder Aussage über Remotes/Backups **live prüfen**.
|
||||
@@ -33,7 +57,7 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der
|
||||
- **`admin/blaueblueten-n8n`** (Gitea) ist Master für terpene-Repo.
|
||||
- **`inventory/` ist tot** (Pipeline seit 2025-06 stale). Live-Daten aus MikroMCP/CF/Portainer.
|
||||
- **`mcp/INVENTORY.md`** ist BF-Netz-SSoT; `~/.claude.json`/`opencode.jsonc` sind globalere Werkzeugkästen.
|
||||
- **terpene.teilzeitladen.xyz**: Worker läuft auf Mac mini (`10.40.20.167:8890`), **kein Caddy-Block, kein CF-Tunnel** — Backfill via DB-Migration blockiert (Handoff 2026-08-27).
|
||||
- **Terpenfinder = `420.teilzeitladen.xyz`** (CF-Worker, Repo `admin/blaueblueten-n8n`, hinter CF Access), **kein Caddy, kein Mac mini im Pfad**. `terpene.*` **existiert nicht und soll es nicht** (live geprüft 2026-09-10). Einziger terpene-benannter CF-Gegenstand: der **interne** Tunnel `terpene-db` (`f2d2fc8a`, ohne öffentlichen Hostname, Hyperdrive→VPC→Postgres, s. `docs/cf-tunnel-mapping.md` §17). ⚠️ **Grenze:** blaueblüten ist ein eigenes Projekt — nicht anfassen. *(Die frühere „terpene.teilzeitladen.xyz:8890 via Tunnel"-Zeile war veraltet — bf-mm ist gelöscht.)*
|
||||
- **shelly-mcp / portainer-mcp bewusst NICHT als MCP-Client** (User-Direktive 2026-08-28): shelly-mcp-Container auf DS923 wird via **Portainer-API** (Skript-Aufrufe, `mcp/portainer/.env` als Token-Quelle) deployt, nicht via MCP-Protokoll. Inventar-Eintrag spiegelt das. `mcp-host-policy.md` ist SSoT für die Host-/IP-Konvention.
|
||||
- **Mac mini `10.40.20.167`** ist der **n8n-Host** (n8n `:5678`, n8n-redis `:6379`, n8n-mcp `:3001`) — **NICHT** der zentrale MCP-Host. Performance-Überlegung: MCP-Clients würden auf dem macmini (User-Workstation) laufen und die Box zusätzlich belasten; deshalb liegt der MCP-Host auf der **DS923** (`nas-bf`, Portainer Endpoint 14). gitea-mcp läuft aktuell noch auf dem macmini (historisch, eigene Container-Definition dort) — gehört **bei Gelegenheit** auf die DS923 umgezogen (separater Punkt, **nicht** Teil dieser Session). Container-Definitionen für n8n leben auf dem macmini (Docker Desktop); FQDN→Backend-Mapping im `caddy/Caddyfile`.
|
||||
|
||||
@@ -41,12 +65,15 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der
|
||||
|
||||
Wenn während einer Session ein neues Teilprojekt, eine Abhängigkeit oder ein Querschnittsthema sichtbar wird, das bisher in keinem TP abgebildet ist:
|
||||
- Vorschlagen und mit dir abstimmen (nicht eigenmächtig anlegen).
|
||||
- Auf dein Okay hin anlegen: Quick-Index-Zeile + `### TP<n>`-Abschnitt mit Status `🔜`, Scope,
|
||||
Abhängigkeiten, Abgrenzung. Nächste freie Nummer vergeben (höchste vergebene + 1; Lücken aus
|
||||
verworfenen TPs bleiben frei).
|
||||
- Ist-Stand-Block mitziehen (Datum, betroffene Zeilen).
|
||||
- Auf dein Okay hin anlegen: **Gitea-Issue** `TPnn — <Kurztitel>` mit Labels `status:🔜`(todo) +
|
||||
passendem `thema:*` + Body (Anriss + Verweis auf den Prosa-Abschnitt) **und** einen
|
||||
`### TP<n>`-Prosa-Abschnitt in `docs/teilprojekte.md` (Scope, Abhängigkeiten, Abgrenzung).
|
||||
Nächste freie Nummer vergeben (höchste vergebene + 1; Lücken aus verworfenen TPs bleiben frei).
|
||||
- **Status/Reihenfolge/„was lief zuletzt" gehen ins Gitea-Issue** (Label ändern, Kommentar), **nicht**
|
||||
in eine Quick-Index-Tabelle oder einen Ist-Stand-Block — die gibt es seit dem TP36-Cutover (2026-09-07)
|
||||
nicht mehr in der Datei.
|
||||
|
||||
**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt.** Ein archiviertes TP ist ein *geschlossenes Ticket, kein versiegeltes*: bei neuer Arbeit an derselben Baustelle wandert der **Volltext zurück** nach `teilprojekte.md` (Zeiger bleibt im Archiv), **kein** Zwillings-TP mit neuer Nummer (Christoph-Direktive 2026-08-30, erster Fall TP28/`ap3`) — und der **Ist-Stand-Block wird bei jeder Änderung mitgezogen**, nicht nur der betroffene TP-Abschnitt.
|
||||
**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis) und ihr Gitea-Issue ist `closed` (Label `status:✅`/`❌`). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt** (Issue dann `reopen`). Ein archiviertes TP ist ein *geschlossenes Ticket, kein versiegeltes*: bei neuer Arbeit an derselben Baustelle wandert der **Volltext zurück** nach `teilprojekte.md` (Zeiger bleibt im Archiv), **kein** Zwillings-TP mit neuer Nummer (Christoph-Direktive 2026-08-30, erster Fall TP28/`ap3`) — nur noch der betroffene `### TPnn`-Prosa-Abschnitt wird angefasst.
|
||||
|
||||
---
|
||||
|
||||
|
||||
18
__init__.py
Normal file
18
__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Macht die bf-infra-Repo-Wurzel zu einem regulären Python-Paket.
|
||||
|
||||
🔴 LOAD-BEARING — NICHT löschen (sonst laden die Nautobot-Jobs nicht mehr).
|
||||
|
||||
Nautobot klont dieses Repo nach GIT_ROOT/bf_infra/ und lädt Jobs über
|
||||
`import_modules_privately()` → `pkgutil.walk_packages([GIT_ROOT])`. walk_packages
|
||||
entdeckt ausschließlich *reguläre* Pakete (mit __init__.py), niemals
|
||||
Namespace-Packages (Verzeichnisse ohne __init__.py). Ohne diese Datei ist
|
||||
`bf_infra` für den Loader unsichtbar und `jobs/` wird nie importiert — Symptom:
|
||||
Git-Sync SUCCESS, aber Log "No jobs were registered on loading the
|
||||
`bf_infra.jobs` submodule" und kein Job-DB-Eintrag.
|
||||
|
||||
Der module_path-Filter des Loaders (["bf_infra","jobs"]) importiert nur
|
||||
`bf_infra` + `bf_infra.jobs.*`; die übrigen Verzeichnisse (nautobot/, routeros/,
|
||||
bin/, …) bleiben unangetastet und laufen weiter als Standalone-Skripte.
|
||||
|
||||
Verifiziert 2026-09-10 (Nautobot 3.1.7, Python 3.14) — TP44 Teil B.
|
||||
"""
|
||||
@@ -13,7 +13,9 @@ http:
|
||||
session_ttl: 30d
|
||||
users:
|
||||
- name: admin
|
||||
password: $2a$10$ue8PEedG4t2JRyT83XiGHeqIU4PFRzhn8rjDOZGYGt0sFC2qju2fi
|
||||
password: '!on-device (siehe ~/.secrets + Gerät)'
|
||||
- name: automation
|
||||
password: '!on-device (siehe ~/.secrets + Gerät)'
|
||||
auth_attempts: 5
|
||||
block_auth_min: 15
|
||||
http_proxy: ""
|
||||
@@ -202,69 +204,9 @@ filtering:
|
||||
- domain: gitea.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: nautobot.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ds-bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ccu.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: iobroker.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: z2m25.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: pve-mcp.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ha-mcp.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: n8n-mcp.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: gitea-mcp.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: pve.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: portainer.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: pbs.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: macmini.emsenhuber.net
|
||||
answer: 10.40.20.167
|
||||
enabled: true
|
||||
- domain: mfc.emsenhuber.net
|
||||
answer: 10.40.10.6
|
||||
enabled: true
|
||||
- domain: tv.emsenhuber.net
|
||||
answer: 10.40.40.11
|
||||
enabled: true
|
||||
- domain: docker.emsenhuber.net
|
||||
answer: 10.40.20.126
|
||||
enabled: true
|
||||
- domain: platform-pg.emsenhuber.net
|
||||
answer: 10.40.20.20
|
||||
enabled: true
|
||||
- domain: core.emsenhuber.net
|
||||
answer: 10.40.99.1
|
||||
enabled: true
|
||||
- domain: ap1.emsenhuber.net
|
||||
answer: 10.40.99.11
|
||||
enabled: true
|
||||
- domain: ap2.emsenhuber.net
|
||||
answer: 10.40.99.12
|
||||
enabled: true
|
||||
- domain: branch.emsenhuber.net
|
||||
answer: 10.40.99.2
|
||||
enabled: true
|
||||
- domain: nas.bf.emsenhuber.net
|
||||
answer: 10.40.20.10
|
||||
enabled: true
|
||||
@@ -316,7 +258,232 @@ clients:
|
||||
rdns: true
|
||||
dhcp: true
|
||||
hosts: true
|
||||
persistent: []
|
||||
persistent:
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: dns-bypass-exempt
|
||||
ids:
|
||||
- 10.40.10.82
|
||||
- 10.40.10.183
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 381d431a-1a43-41ea-adaa-2d67ada9aab0
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: false
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: true
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-guest
|
||||
ids:
|
||||
- 10.40.90.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: af70dd86-43a9-4165-8228-26c7b90db5e6
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: false
|
||||
filtering_enabled: true
|
||||
parental_enabled: true
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-infra
|
||||
ids:
|
||||
- 10.40.20.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 78b2da82-1946-41ee-b386-b04eaee49bc3
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: false
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-iot
|
||||
ids:
|
||||
- 10.40.30.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 7a8ac674-4365-4b06-ad51-2516400903a1
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: true
|
||||
filtering_enabled: true
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-main
|
||||
ids:
|
||||
- 10.40.10.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 39955cc1-6ec2-4cf6-a757-233672b9dc3d
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: true
|
||||
filtering_enabled: true
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-media
|
||||
ids:
|
||||
- 10.40.40.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 44e4a1d7-d1b6-446c-a753-65e714ec93aa
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: false
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-mgmt
|
||||
ids:
|
||||
- 10.40.99.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 4c5eeafb-f7f2-4fb1-ab87-fd2a2366d57a
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: false
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
- safe_search:
|
||||
enabled: false
|
||||
bing: false
|
||||
duckduckgo: false
|
||||
ecosia: false
|
||||
google: false
|
||||
pixabay: false
|
||||
yandex: false
|
||||
youtube: false
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids: []
|
||||
name: grp-untrusted
|
||||
ids:
|
||||
- 10.40.50.0/24
|
||||
tags: []
|
||||
upstreams: []
|
||||
uid: 7f182498-7b82-4580-b1b0-0a2d46c205ef
|
||||
upstreams_cache_size: 0
|
||||
upstreams_cache_enabled: false
|
||||
use_global_settings: true
|
||||
filtering_enabled: true
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
use_global_blocked_services: true
|
||||
ignore_querylog: false
|
||||
ignore_statistics: false
|
||||
log:
|
||||
enabled: true
|
||||
file: ""
|
||||
|
||||
57
adguard/README.md
Normal file
57
adguard/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# AdGuard Home — `agh-bf1` (.36) / `agh-bf2` (.37) · TP23 Stufe 2/4/5
|
||||
|
||||
Das AGH-**Paar** (pve LXC 110/111, `10.40.20.36` / `.37`) ist der Client-Resolver des BF-Netzes
|
||||
(DHCP liefert beide in allen 7 VLANs). Upstream: DoH zum CF-Gateway; conditional
|
||||
`[/bf.emsenhuber.net/] → 10.40.20.1`. **Kein natives HA** → Sync ist Betriebsaufwand.
|
||||
|
||||
## Dateien
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `AdGuardHome.yaml` | **Repo-SSoT** der Config. **Passwort-Hashes REDIGIERT** (`'<redacted>'`) — keine Secrets im Git. Echte Hashes leben auf den Geräten; Klartext des API-Users in `~/.secrets`. |
|
||||
| `agh-sync.py` | `check` (Drift `.36`↔`.37`) · `push` (SSoT auf beide, gestaffelter Restart, reinjiziert Hashes live). |
|
||||
| `agh-exception.py` | Exception-Primitive (Stufe 5) gegen **beide** Instanzen. |
|
||||
|
||||
## Client-Policy (Stufe 4, `clients.persistent`)
|
||||
Ein VLAN = eine CIDR-Gruppe. Alle nutzen denselben CF-Upstream-Filter; Divergenz = per-Client-Toggle.
|
||||
|
||||
| Gruppe | CIDR | filtering | safe_search | parental |
|
||||
|---|---|---|---|---|
|
||||
| grp-main | 10.40.10.0/24 | an (global) | – | – |
|
||||
| grp-infra | 10.40.20.0/24 | **aus** | – | – |
|
||||
| grp-iot | 10.40.30.0/24 | an | – | – |
|
||||
| grp-media | 10.40.40.0/24 | **aus** | – | – |
|
||||
| grp-untrusted | 10.40.50.0/24 | an | – | – |
|
||||
| grp-guest | 10.40.90.0/24 | an | **on** | **on** |
|
||||
| grp-mgmt | 10.40.99.0/24 | **aus** | – | – |
|
||||
| dns-bypass-exempt | 10.40.10.183 + .82 | **aus** | – | – |
|
||||
|
||||
`filtering aus` bei INFRA/MEDIA/MGMT = bewusste Reduktion (Streaming/Server/Mgmt nicht brechen), per Gruppe rückholbar.
|
||||
|
||||
## Sync-Invariante
|
||||
AGH **schreibt die YAML beim Restart um** (normalisiert) → **nicht** gegen die Repo-Datei diffen,
|
||||
sondern **`.36` == `.37`**. Genau das prüft `agh-sync.py check`.
|
||||
|
||||
## Exception-Contract (Stufe 5 — was die HA-Schicht auslöst)
|
||||
Auth: Basic, User `automation` (`~/.secrets` → `AGH_AUTOMATION_*`). **Immer auf `.36` UND `.37`.**
|
||||
|
||||
🔴 **Wichtig für D:** die beiden AGH-Endpunkte sind **read-modify-write**, kein simples Append —
|
||||
`set_rules` **ersetzt die ganze Liste**, `clients/update` braucht das **volle Client-Objekt**.
|
||||
Ein zustandsloses HA-`rest_command` reicht deshalb **nicht**; `agh-exception.py` ist die
|
||||
Referenz-RMW-Implementierung, die D aufruft (Deploy-Weg auf HAOS in Etappe D zu entscheiden:
|
||||
`shell_command`/`command_line` gegen die deployte CLI **oder** ein dünner Wrapper-Endpunkt).
|
||||
|
||||
| Aktion | CLI | AGH-API (RMW) |
|
||||
|---|---|---|
|
||||
| Domain global entsperren | `allow-domain <d>` | `GET /control/filtering/status` → +`@@\|\|<d>^` → `POST /control/filtering/set_rules` |
|
||||
| Entsperrung zurück | `unallow-domain <d>` | dito, Regel entfernen |
|
||||
| Client entfiltern | `unfilter <ip>` | `GET /control/clients` → Objekt, `filtering_enabled=false` → `POST /control/clients/update` |
|
||||
| Client wieder filtern | `refilter <ip>` | dito, zurück auf global |
|
||||
|
||||
**Kein Timing in der CLI** — Ablauf/Revert macht der Aufrufer (HA-Timer) und, ab Stufe 6, der
|
||||
RouterOS-Backstop.
|
||||
|
||||
## RouterOS-Backstop (Stufe 6, noch NICHT gebaut)
|
||||
Analog `jit-admin` (`routeros/tp5-etappe4-input-mgmt.rsc`): Address-List `jit-dns-bypass` mit
|
||||
`timeout` + Forward-Regel, die dieser Liste 53/853 nach außen erlaubt — der Fail-safe, falls HA
|
||||
den Revert verpasst. Gebaut via **Nautobot GC** (kein Direkt-Write), erst wenn die Bypass-Sperre
|
||||
scharf geschaltet wird.
|
||||
129
adguard/agh-exception.py
Executable file
129
adguard/agh-exception.py
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agh-exception.py — TP23 Stufe 5 Exception-Primitive gegen das AGH-Paar (.36 + .37).
|
||||
|
||||
Von der HA-Self-Service-Schicht (rest_command) ODER von Hand aufrufbar. Wirkt IMMER auf
|
||||
BEIDE Instanzen (kein Sync im Datenpfad). KEIN eigenes Timing — Ablauf/Revert macht der
|
||||
Aufrufer (HA-Timer) bzw. der RouterOS-Backstop (Stufe 6). Diese CLI ist reines add/remove.
|
||||
|
||||
allow-domain <domain> → user_rules += @@||domain^ (Domain für ALLE entsperren)
|
||||
unallow-domain <domain> → user_rules -= @@||domain^
|
||||
unfilter <ip> → Client (dessen ids <ip> enthält) filtering_enabled=false
|
||||
refilter <ip> → denselben Client wieder filtern (use_global_settings=true)
|
||||
status → user_rules + Client-Policy beider Instanzen
|
||||
|
||||
Auth: AGH_AUTOMATION_USER/_PASS aus ~/.secrets/credentials.env (nie in argv).
|
||||
"""
|
||||
import sys, os, json, base64, urllib.request, urllib.error, pathlib, re
|
||||
|
||||
DOMAIN_RE = re.compile(r"^(?=.{1,253}$)([a-z0-9](-?[a-z0-9]+)*\.)+[a-z]{2,}$", re.I)
|
||||
IP_RE = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
|
||||
|
||||
INST = ["10.40.20.36", "10.40.20.37"]
|
||||
# Creds-Quellen in Reihenfolge: erste, die User+Pass liefert, gewinnt.
|
||||
# So läuft derselbe Code auf dem Mac (~/.secrets) und auf HAOS (/config).
|
||||
CRED_FILES = [
|
||||
os.environ.get("AGH_CREDS_FILE"),
|
||||
str(pathlib.Path.home() / ".secrets/credentials.env"),
|
||||
"/config/agh-exception.secrets",
|
||||
]
|
||||
|
||||
|
||||
def _from_file(path):
|
||||
u = p = None
|
||||
try:
|
||||
for line in open(path).read().splitlines():
|
||||
if line.startswith("AGH_AUTOMATION_USER="): u = line.split("=", 1)[1].strip()
|
||||
if line.startswith("AGH_AUTOMATION_PASS="): p = line.split("=", 1)[1].strip()
|
||||
except OSError:
|
||||
return None
|
||||
return (u, p) if (u and p) else None
|
||||
|
||||
|
||||
def creds():
|
||||
eu, ep = os.environ.get("AGH_AUTOMATION_USER"), os.environ.get("AGH_AUTOMATION_PASS")
|
||||
if eu and ep:
|
||||
return eu, ep
|
||||
for f in CRED_FILES:
|
||||
if f and (r := _from_file(f)):
|
||||
return r
|
||||
sys.exit("AGH_AUTOMATION_USER/_PASS: weder Env noch eine Creds-Datei gefunden")
|
||||
|
||||
|
||||
AUTH = "Basic " + base64.b64encode(":".join(creds()).encode()).decode()
|
||||
|
||||
|
||||
def api(ip, path, method="GET", body=None):
|
||||
url = f"http://{ip}{path}"
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, method=method,
|
||||
headers={"Authorization": AUTH,
|
||||
"Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=8) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit(f"{ip} {path} → HTTP {e.code}: {e.read().decode()[:200]}")
|
||||
except Exception as e:
|
||||
sys.exit(f"{ip} {path} → {e}")
|
||||
|
||||
|
||||
def rule(domain):
|
||||
return f"@@||{domain}^"
|
||||
|
||||
|
||||
def allow(domain, remove=False):
|
||||
domain = domain.strip().lower()
|
||||
if not DOMAIN_RE.match(domain):
|
||||
sys.exit(f"ungültige Domain: {domain!r}")
|
||||
r = rule(domain)
|
||||
for ip in INST:
|
||||
st = api(ip, "/control/filtering/status")
|
||||
rules = st.get("user_rules", [])
|
||||
if remove:
|
||||
rules = [x for x in rules if x != r]
|
||||
elif r not in rules:
|
||||
rules = rules + [r]
|
||||
api(ip, "/control/filtering/set_rules", "POST", {"rules": rules})
|
||||
print(f"{ip}: {'entfernt' if remove else 'gesetzt'} {r} (user_rules={len(rules)})")
|
||||
|
||||
|
||||
def set_filter(ip_addr, enabled):
|
||||
if not IP_RE.match(ip_addr):
|
||||
sys.exit(f"ungültige IP: {ip_addr!r}")
|
||||
for ip in INST:
|
||||
data = api(ip, "/control/clients")
|
||||
c = None
|
||||
for cand in data.get("clients", []):
|
||||
if ip_addr in cand.get("ids", []):
|
||||
c = cand; break
|
||||
if not c:
|
||||
sys.exit(f"{ip}: kein Client mit id exakt {ip_addr} — für Gruppen die CIDR angeben")
|
||||
c = dict(c)
|
||||
c["use_global_settings"] = enabled
|
||||
c["filtering_enabled"] = enabled
|
||||
api(ip, "/control/clients/update", "POST", {"name": c["name"], "data": c})
|
||||
print(f"{ip}: {c['name']} filtering={'an' if enabled else 'AUS'}")
|
||||
|
||||
|
||||
def status():
|
||||
for ip in INST:
|
||||
st = api(ip, "/control/filtering/status")
|
||||
allows = [r for r in st.get("user_rules", []) if r.startswith("@@")]
|
||||
cl = api(ip, "/control/clients").get("clients", [])
|
||||
print(f"== {ip} == allow-rules={allows or '—'}")
|
||||
for c in cl:
|
||||
print(f" {c['name']:16} ids={c['ids']} global={c['use_global_settings']} filt={c['filtering_enabled']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:]
|
||||
if not a: sys.exit(__doc__)
|
||||
cmd = a[0]
|
||||
if cmd == "allow-domain": allow(a[1])
|
||||
elif cmd == "unallow-domain": allow(a[1], remove=True)
|
||||
elif cmd == "unfilter": set_filter(a[1], False)
|
||||
elif cmd == "refilter": set_filter(a[1], True)
|
||||
elif cmd == "status": status()
|
||||
else: sys.exit(f"unbekannt: {cmd}\n{__doc__}")
|
||||
113
adguard/agh-sync.py
Executable file
113
adguard/agh-sync.py
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agh-sync.py — AdGuard-Home-Paar agh-bf1 (.36) / agh-bf2 (.37) synchron halten.
|
||||
|
||||
TP23 Stufe 2/4: die beiden AGH-Instanzen (pve LXC 110/111) haben KEIN natives HA →
|
||||
Config-Sync ist Betriebsaufwand. Dieses Werkzeug ist der Sync.
|
||||
|
||||
SSoT = adguard/AdGuardHome.yaml (Passwort-Hashes REDIGIERT — keine Secrets im Git).
|
||||
Wahrheit im Datenpfad = die beiden Live-Instanzen; die Invariante ist .36 == .37
|
||||
(AGH normalisiert die YAML beim Restart, daher NICHT gegen die Repo-Datei vergleichen,
|
||||
sondern die beiden Live-Configs gegeneinander).
|
||||
|
||||
check pull .36 + .37, Passwörter redigiert diffen → meldet Drift (exit 1 bei Drift)
|
||||
push Repo-SSoT nehmen, echte users-Hashes aus .36 (live) reinjizieren, validieren,
|
||||
auf beide pushen, GESTAFFELT neu starten (110 → prüfen → 111), .36==.37 + API prüfen
|
||||
|
||||
Kein Direkt-`sshpass -p` (Leak). SSH-Key zu root@pve vorausgesetzt.
|
||||
"""
|
||||
import subprocess, sys, tempfile, base64, os, re, pathlib, urllib.request
|
||||
|
||||
PVE = "root@10.40.20.100"
|
||||
INST = {"110": "10.40.20.36", "111": "10.40.20.37"} # ct-id -> ip
|
||||
PRIMARY = "110" # Reihenfolge: primary zuerst
|
||||
SVC = "AdGuardHome.service"
|
||||
CFG = "/opt/AdGuardHome/AdGuardHome.yaml"
|
||||
REPO = pathlib.Path(__file__).resolve().parent
|
||||
SSOT = REPO / "AdGuardHome.yaml"
|
||||
PW_RE = re.compile(r"^( password: ).*$")
|
||||
|
||||
|
||||
def sh(*args, inp=None):
|
||||
return subprocess.run(args, input=inp, capture_output=True, text=True)
|
||||
|
||||
|
||||
def pull(ct):
|
||||
r = sh("ssh", "-o", "ConnectTimeout=8", PVE, f"pct exec {ct} -- cat {CFG}")
|
||||
if r.returncode:
|
||||
sys.exit(f"pull {ct} fehlgeschlagen: {r.stderr.strip()}")
|
||||
return r.stdout
|
||||
|
||||
|
||||
def redact(text):
|
||||
return "\n".join(PW_RE.sub(r"\1'<redacted>'", l) for l in text.splitlines())
|
||||
|
||||
|
||||
def live_users_hashes(ct):
|
||||
"""echte password-Zeilen (in Reihenfolge) aus der Live-Config von ct."""
|
||||
out, inusers = [], False
|
||||
for l in pull(ct).splitlines():
|
||||
if l.startswith("users:"): inusers = True; continue
|
||||
if inusers and l and not l.startswith((" ", "\t")): break
|
||||
if inusers:
|
||||
m = PW_RE.match(l)
|
||||
if m: out.append(l)
|
||||
return out
|
||||
|
||||
|
||||
def cmd_check():
|
||||
a, b = redact(pull("110")), redact(pull("111"))
|
||||
if a == b:
|
||||
print("✅ .36 == .37 (synchron)")
|
||||
return 0
|
||||
print("🔴 DRIFT zwischen .36 und .37:")
|
||||
import difflib
|
||||
for line in difflib.unified_diff(a.splitlines(), b.splitlines(),
|
||||
".36", ".37", lineterm=""):
|
||||
print(line)
|
||||
return 1
|
||||
|
||||
|
||||
def cmd_push():
|
||||
if not SSOT.exists():
|
||||
sys.exit(f"SSoT fehlt: {SSOT}")
|
||||
ssot = SSOT.read_text()
|
||||
# echte Hashes aus dem primary reinjizieren (SSoT trägt nur Redaktions-Platzhalter)
|
||||
hashes = live_users_hashes(PRIMARY)
|
||||
red = [i for i, l in enumerate(ssot.splitlines()) if PW_RE.match(l)]
|
||||
if len(red) != len(hashes):
|
||||
sys.exit(f"users-Anzahl SSoT({len(red)}) != live({len(hashes)}) — manuell prüfen")
|
||||
lines = ssot.splitlines()
|
||||
for idx, real in zip(red, hashes):
|
||||
lines[idx] = real
|
||||
merged = "\n".join(lines) + "\n"
|
||||
# validieren
|
||||
try:
|
||||
import yaml
|
||||
doc = yaml.safe_load(merged)
|
||||
assert doc["users"] and doc["clients"]["persistent"]
|
||||
except Exception as e:
|
||||
sys.exit(f"merge invalide: {e}")
|
||||
b64 = base64.b64encode(merged.encode()).decode()
|
||||
order = [PRIMARY] + [c for c in INST if c != PRIMARY]
|
||||
for ct in order:
|
||||
print(f"→ push + restart {ct} ({INST[ct]}) …")
|
||||
r = sh("ssh", "-o", "ConnectTimeout=10", PVE,
|
||||
f"pct exec {ct} -- sh -c 'base64 -d > {CFG}.new && "
|
||||
f"cp -a {CFG} {CFG}.presync && mv {CFG}.new {CFG} && "
|
||||
f"systemctl restart {SVC} && sleep 2 && systemctl is-active {SVC}'",
|
||||
inp=b64)
|
||||
if r.returncode or "active" not in r.stdout:
|
||||
sys.exit(f"push {ct} fehlgeschlagen: {r.stdout} {r.stderr}")
|
||||
print(f" {ct} active")
|
||||
print(cmd_check_msg())
|
||||
return cmd_check()
|
||||
|
||||
|
||||
def cmd_check_msg():
|
||||
return "— Verifikation:"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "check"
|
||||
sys.exit({"check": cmd_check, "push": cmd_push}.get(cmd, cmd_check)())
|
||||
63
adguard/ha-self-service.md
Normal file
63
adguard/ha-self-service.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# TP23 Stufe 5 — HA-Self-Service für DNS-Ausnahmen
|
||||
|
||||
Home Assistant (HAOS-VM 100, `10.40.20.105`) gibt eine Ein-Klick-Oberfläche, um eine Domain
|
||||
temporär (JIT) über das AGH-Paar `.36`/`.37` zu entsperren. Ablauf/Revert nativ per `timer`.
|
||||
|
||||
**Warum kein `rest_command`:** die AGH-Endpunkte sind read-modify-write (`set_rules` ersetzt die
|
||||
ganze Liste). HA ruft deshalb die deployte RMW-CLI `agh-exception.py` per `shell_command`.
|
||||
|
||||
## Bausteine (alle in HA .storage, via ha-mcp angelegt — keine YAML außer shell_command)
|
||||
| Typ | entity_id | Zweck |
|
||||
|---|---|---|
|
||||
| input_text | `input_text.dns_ausnahme_domain` | einzugebende Domain |
|
||||
| input_text | `input_text.dns_ausnahme_aktiv` | aktuell aktive Ausnahme (für leak-freien Revert; init `""`) |
|
||||
| input_select | `input_select.dns_ausnahme_dauer` | 15 Minuten / 1 Stunde / 4 Stunden |
|
||||
| timer | `timer.dns_ausnahme` | JIT-Countdown (restore=false) |
|
||||
| input_button | `input_button.dns_ausnahme_starten` | Auslöser |
|
||||
| automation | `automation.dns_ausnahme_starten` | Button → validieren, alte Ausnahme aufräumen, `agh_allow_domain`, Timer starten |
|
||||
| automation | `automation.dns_ausnahme_ablauf` | `timer.finished`/`timer.cancelled` → `agh_unallow_domain`, `_aktiv` leeren |
|
||||
|
||||
## `configuration.yaml` (der einzige YAML-Teil, Backup `.bak-tp23-*`)
|
||||
```yaml
|
||||
shell_command:
|
||||
agh_allow_domain: "python3 /config/agh-exception.py allow-domain '{{ domain }}'"
|
||||
agh_unallow_domain: "python3 /config/agh-exception.py unallow-domain '{{ domain }}'"
|
||||
agh_unfilter_client: "python3 /config/agh-exception.py unfilter '{{ ip }}'"
|
||||
agh_refilter_client: "python3 /config/agh-exception.py refilter '{{ ip }}'"
|
||||
```
|
||||
Neue Domain → **HA-Neustart** nötig (reload_all lädt eine erstmals hinzugefügte Domain nicht).
|
||||
|
||||
## Deploy auf HAOS (Zugang `ssh admin@10.40.20.105`, sudo passwortlos)
|
||||
- `/config/agh-exception.py` — Kopie von `adguard/agh-exception.py` (liest Creds via `/config/agh-exception.secrets`).
|
||||
- `/config/agh-exception.secrets` (chmod 600, root) — `AGH_AUTOMATION_USER`/`_PASS`.
|
||||
- Redeploy nach Script-Änderung: `base64 < adguard/agh-exception.py | ssh admin@10.40.20.105 "base64 -d | sudo tee /config/agh-exception.py >/dev/null"`.
|
||||
|
||||
## Bedienung
|
||||
Domain eintragen → Dauer wählen → **DNS-Ausnahme starten**. Bei Ablauf/Abbruch wird die Regel auf
|
||||
beiden AGH automatisch zurückgenommen. Ungültige Domains werden HA- **und** CLI-seitig abgelehnt.
|
||||
|
||||
## Dashboard (1. Draft, 2026-09-06)
|
||||
Eigenes storage-mode Dashboard `url_path: dns-ausnahmen` (Sidebar, `require_admin: false`),
|
||||
angelegt via `ha_config_set_dashboard`. Da es in HA-`.storage` lebt, hier die Repro-Quelle:
|
||||
```json
|
||||
{"views":[{"title":"DNS-Ausnahmen","path":"dns","type":"sections","icon":"mdi:dns","max_columns":2,"sections":[
|
||||
{"type":"grid","cards":[
|
||||
{"type":"heading","heading":"Domain temporaer freigeben","icon":"mdi:lock-open-variant"},
|
||||
{"type":"entities","entities":[{"entity":"input_text.dns_ausnahme_domain","name":"Domain"},{"entity":"input_select.dns_ausnahme_dauer","name":"Dauer"}]},
|
||||
{"type":"button","name":"DNS-Ausnahme starten","icon":"mdi:lock-open-check","tap_action":{"action":"perform-action","perform_action":"input_button.press","target":{"entity_id":"input_button.dns_ausnahme_starten"}},"grid_options":{"columns":"full"}}]},
|
||||
{"type":"grid","cards":[
|
||||
{"type":"heading","heading":"Status","icon":"mdi:information-outline"},
|
||||
{"type":"tile","entity":"timer.dns_ausnahme","name":"Restlaufzeit"},
|
||||
{"type":"tile","entity":"input_text.dns_ausnahme_aktiv","name":"Aktive Ausnahme"},
|
||||
{"type":"markdown","content":"**AGH-Resolver-Paar** — .36: {{ states('binary_sensor.adguard_bf1_status') }} / .37: {{ states('binary_sensor.adguard_bf2_status') }}"}]},
|
||||
{"type":"grid","cards":[
|
||||
{"type":"heading","heading":"VLAN-Filter-Policy (AGH-Gruppen)","heading_style":"subtitle"},
|
||||
{"type":"markdown","content":"MAIN/IOT/UNTRUSTED=an · INFRA/MEDIA/MGMT=aus · GUEST=an+SafeSearch/Parental · dns-bypass-exempt=aus"}]}
|
||||
]}]}
|
||||
```
|
||||
⚠️ Draft-Notiz: `binary_sensor.adguard_bf1_status` ist stale (hing am alten `.16`-Container, TP42) —
|
||||
zeigt evtl. `unavailable`, obwohl `.36` läuft. Sensor-Quelle gehört bei Gelegenheit auf `.36` gefixt.
|
||||
|
||||
## Verifiziert (2026-09-04)
|
||||
Button → `@@||doubleclick.net^` auf `.36`+`.37`, Timer 15:00, `_aktiv` gesetzt; Timer-Abbruch →
|
||||
Regel auf beiden entfernt, `_aktiv` geleert, `.36==.37`.
|
||||
218
bin/gitea-tp-sync.py
Normal file
218
bin/gitea-tp-sync.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP36 (reframed) — teilprojekte.md Index/Status → Gitea Issues (admin/bf-infra).
|
||||
|
||||
Idempotenter Sync (get-or-create, Match ueber "TPnn" im Titel):
|
||||
- Archiv-Header in docs/teilprojekte-archiv.md -> closed Issues (nur TPs, die in Gitea
|
||||
NICHT offen sind; ein reopentes TP mit Archiv-Zeiger bleibt offen).
|
||||
- Labels status:* (Marker) + thema:* (Gruppierung) werden vorab get-or-create.
|
||||
|
||||
SSoT-Modell (seit TP36-Cutover 2026-09-07): Gitea fuehrt Index+Status — aktive TPs werden
|
||||
DIREKT als Gitea-Issue angelegt/gepflegt, NICHT mehr aus einer Datei-Tabelle abgeleitet
|
||||
(die Quick-Index-Tabelle wurde aus teilprojekte.md entfernt). Dieses Werkzeug haelt nur noch
|
||||
die ARCHIVIERTEN (closed) TPs mit teilprojekte-archiv.md idempotent abgeglichen und fasst
|
||||
offene Issues nie an. Die per-TP-Prosa bleibt in docs/teilprojekte.md / teilprojekte-archiv.md.
|
||||
Token: ~/.hermes/.env (GITEA_ACCESS_TOKEN, GITEA_HOST) — wird NIE ausgegeben.
|
||||
|
||||
Aufruf:
|
||||
python3 bin/gitea-tp-sync.py # Dry-Run (default) — zeigt, was passieren wuerde
|
||||
python3 bin/gitea-tp-sync.py --commit # legt Labels + Issues an / gleicht sie ab
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
ACTIVE = REPO / "docs" / "teilprojekte.md"
|
||||
ARCHIV = REPO / "docs" / "teilprojekte-archiv.md"
|
||||
HERMES = Path.home() / ".hermes" / ".env"
|
||||
OWNER_REPO = "admin/bf-infra"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
# Marker -> (status-label, gitea-state)
|
||||
MARKER = {
|
||||
"🔜": ("status:todo", "open"),
|
||||
"⏳": ("status:wip", "open"),
|
||||
"⏸️": ("status:paused", "open"),
|
||||
"💡": ("status:idea", "open"),
|
||||
"🟢": ("status:concept", "open"),
|
||||
"✅": ("status:done", "closed"),
|
||||
"❌": ("status:dropped", "closed"),
|
||||
}
|
||||
LABEL_COLORS = {
|
||||
"status:todo": "#ededed", "status:wip": "#fbca04", "status:paused": "#c5def5",
|
||||
"status:idea": "#d4c5f9", "status:concept": "#bfdadc", "status:done": "#0e8a16",
|
||||
"status:dropped": "#e11d21",
|
||||
"thema:firewall-dns": "#b60205", "thema:wlan": "#1d76db", "thema:cloudflare-proxy": "#f9a825",
|
||||
"thema:nautobot-doku": "#5319e7", "thema:infra": "#0e8a16", "thema:ipv6-matter": "#006b75",
|
||||
"thema:identity": "#e99695", "thema:backlog": "#cccccc",
|
||||
}
|
||||
# TP-Nummer -> Themen-Label (aus dem Statusbaum-Gruppierung; nur aktive TPs brauchen ein Board-Thema)
|
||||
THEME = {
|
||||
"5.1": "firewall-dns", "23": "firewall-dns",
|
||||
"25": "wlan", "28": "wlan", "13": "wlan", "17.1": "wlan", "17.3": "wlan",
|
||||
"16": "cloudflare-proxy", "16.2": "cloudflare-proxy", "16.3": "cloudflare-proxy",
|
||||
"31": "cloudflare-proxy", "41": "cloudflare-proxy", "14.4": "cloudflare-proxy",
|
||||
"42": "cloudflare-proxy", "43": "cloudflare-proxy", "44": "cloudflare-proxy",
|
||||
"32": "nautobot-doku", "34": "nautobot-doku", "35": "nautobot-doku",
|
||||
"36": "nautobot-doku", "37": "nautobot-doku",
|
||||
"30": "infra", "40": "infra", "20.1": "infra", "18": "infra",
|
||||
"24": "infra", "38": "infra", "11": "infra",
|
||||
"12": "ipv6-matter", "12.1": "ipv6-matter",
|
||||
"8": "identity",
|
||||
"29": "backlog", "33": "backlog",
|
||||
}
|
||||
|
||||
|
||||
def hermes_env():
|
||||
tok = host = None
|
||||
for line in HERMES.read_text().splitlines():
|
||||
m = re.match(r"\s*(GITEA_ACCESS_TOKEN|GITEA_HOST)\s*=\s*(.+?)\s*$", line)
|
||||
if not m:
|
||||
continue
|
||||
if m.group(1) == "GITEA_ACCESS_TOKEN":
|
||||
tok = m.group(2).strip().strip('"').strip("'")
|
||||
else:
|
||||
host = m.group(2).strip().strip('"').strip("'")
|
||||
if not tok:
|
||||
sys.exit("GITEA_ACCESS_TOKEN nicht in %s gefunden." % HERMES)
|
||||
host = (host or "https://gitea.emsenhuber.net").rstrip("/")
|
||||
if not host.startswith("http"):
|
||||
host = "https://" + host
|
||||
return tok, host
|
||||
|
||||
|
||||
TOKEN, HOST = hermes_env()
|
||||
API = HOST + "/api/v1"
|
||||
|
||||
|
||||
def api(method, path, body=None):
|
||||
url = API + path
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, method=method, headers={
|
||||
"Authorization": "token " + TOKEN,
|
||||
"Accept": "application/json", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, (json.load(e) if e.headers.get("content-type", "").startswith("application/json") else {})
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- parse
|
||||
def parse_archiv(exclude_nums):
|
||||
"""Archiv-Header -> archivierte TPs. exclude_nums = TP-Nummern, die in Gitea OFFEN sind
|
||||
(reopente TPs mit Archiv-Zeiger) -> die bleiben offen, nicht als closed abgleichen."""
|
||||
out = []
|
||||
hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\s*—\s*(.+?)\s*▸", )
|
||||
seen = set()
|
||||
for line in ARCHIV.read_text().splitlines():
|
||||
m = hdr.match(line)
|
||||
if not m:
|
||||
continue
|
||||
tp, title = m.groups()
|
||||
num = tp[2:]
|
||||
if num in exclude_nums or num in seen:
|
||||
continue
|
||||
seen.add(num)
|
||||
short = re.sub(r"\*\*|`", "", title).strip()[:80]
|
||||
out.append({"tp": tp, "num": num, "marker": "✅",
|
||||
"title": short, "date": "", "src": "archiv"})
|
||||
return out
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- gitea sync
|
||||
def ensure_labels(existing):
|
||||
by_name = {l["name"]: l for l in existing}
|
||||
for name, color in LABEL_COLORS.items():
|
||||
if name in by_name:
|
||||
continue
|
||||
if COMMIT:
|
||||
api("POST", f"/repos/{OWNER_REPO}/labels",
|
||||
{"name": name, "color": color.lstrip("#")})
|
||||
print(f" [{'+' if COMMIT else 'DRY'}] label {name}")
|
||||
st, cur = api("GET", f"/repos/{OWNER_REPO}/labels?limit=100")
|
||||
return {l["name"]: l["id"] for l in (cur if isinstance(cur, list) else [])}
|
||||
|
||||
|
||||
def all_issues():
|
||||
out, page = [], 1
|
||||
while True:
|
||||
st, d = api("GET", f"/repos/{OWNER_REPO}/issues?state=all&type=issues&limit=50&page={page}")
|
||||
if not isinstance(d, list) or not d:
|
||||
break
|
||||
out += d
|
||||
page += 1
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
print(f"Gitea {HOST} repo {OWNER_REPO} mode={'COMMIT' if COMMIT else 'DRY-RUN'}")
|
||||
|
||||
st, existing_labels = api("GET", f"/repos/{OWNER_REPO}/labels?limit=100")
|
||||
label_id = ensure_labels(existing_labels if isinstance(existing_labels, list) else [])
|
||||
|
||||
issues = all_issues()
|
||||
by_tp = {}
|
||||
open_nums = set()
|
||||
for iss in issues:
|
||||
# Titel ist "TPxx — …"; volle ID inkl. Buchstaben/Bindestrich (TP14.2a, TP-R) bis zum " — "
|
||||
m = re.match(r"^(TP[\w.\-]*?)\s+—", iss["title"])
|
||||
if m:
|
||||
by_tp[m.group(1)] = iss
|
||||
if iss.get("state") == "open":
|
||||
open_nums.add(m.group(1)[2:])
|
||||
|
||||
# Aktive TPs sind seit dem TP36-Cutover SSoT direkt in Gitea -> nur das Archiv abgleichen,
|
||||
# und offene (reopente) TPs davon ausschliessen, damit sie nicht faelschlich geschlossen werden.
|
||||
items = parse_archiv(open_nums)
|
||||
print(f"Geparst: {len(items)} archivierte TP (aktive TPs = SSoT in Gitea: "
|
||||
f"{len(open_nums)} offen, nicht angetastet)\n")
|
||||
|
||||
created = updated = 0
|
||||
for it in items:
|
||||
status_label, state = MARKER.get(it["marker"], ("status:todo", "open"))
|
||||
labels = [status_label]
|
||||
th = THEME.get(it["num"])
|
||||
if th:
|
||||
labels.append(f"thema:{th}")
|
||||
title = f"{it['tp']} — {it['title']}"
|
||||
body = (f"Steuerungs-SSoT: Index/Status hier in Gitea; **Prosa/Detail** im Repo unter "
|
||||
f"`docs/{'teilprojekte-archiv.md' if it['src']=='archiv' else 'teilprojekte.md'}` "
|
||||
f"(Abschnitt `{it['tp']}`).\n\n"
|
||||
f"- Status: {it['marker']} ({status_label})\n"
|
||||
+ (f"- Abschluss: {it['date']}\n" if it["date"] else ""))
|
||||
want_ids = [label_id[l] for l in labels if l in label_id]
|
||||
|
||||
cur = by_tp.get(it["tp"])
|
||||
if cur is None:
|
||||
print(f" [{'+' if COMMIT else 'DRY'}] NEW {title} {labels} state={state}")
|
||||
if COMMIT:
|
||||
sc, res = api("POST", f"/repos/{OWNER_REPO}/issues",
|
||||
{"title": title, "body": body, "labels": want_ids})
|
||||
if state == "closed" and isinstance(res, dict) and res.get("number"):
|
||||
api("PATCH", f"/repos/{OWNER_REPO}/issues/{res['number']}", {"state": "closed"})
|
||||
created += 1
|
||||
else:
|
||||
have = {l["name"] for l in cur.get("labels", [])}
|
||||
drift = (have != set(labels)) or (cur.get("state") != state)
|
||||
if drift:
|
||||
print(f" [{'~' if COMMIT else 'DRY'}] UPD #{cur['number']} {title} "
|
||||
f"{sorted(have)}->{labels} state {cur.get('state')}->{state}")
|
||||
if COMMIT:
|
||||
api("PATCH", f"/repos/{OWNER_REPO}/issues/{cur['number']}",
|
||||
{"labels": want_ids, "state": state})
|
||||
updated += 1
|
||||
|
||||
print(f"\n{'Angelegt' if COMMIT else 'Wuerde anlegen'}: {created} · "
|
||||
f"{'Abgeglichen' if COMMIT else 'Wuerde abgleichen'}: {updated}")
|
||||
if not COMMIT:
|
||||
print("Dry-Run — nichts geschrieben. Mit --commit ausfuehren.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -3,14 +3,38 @@
|
||||
> 🆕 **SSoT-Wechsel im Aufbau (TP44, seit 2026-09-02):** Die CF-Estate (Tunnel, WARP-Connector,
|
||||
> VPC-Services, Hyperdrive, Workers, Access-Apps) wird als **native `nautobot.cloud`-Objekte** in
|
||||
> Nautobot (`10.40.20.30`) geführt — Etappe 1 (manuelle Erstbefüllung) ✅. Seeder:
|
||||
> `nautobot/tp36-cf-estate-seed.py`, Detail: `docs/handoff-20260902-cf-estate-nautobot-ssot.md`.
|
||||
> `nautobot/tp44-cf-estate-seed.py`, Detail: `docs/handoff-20260902-cf-estate-nautobot-ssot.md`.
|
||||
> **CF bleibt operativer Master, Nautobot = Dokumentations-SSoT.** Dieses File bleibt gültige Historie
|
||||
> und As-Built-Log; die *strukturierte* Estate liest man ab jetzt in Nautobot. Etappe 2 (CF-API→Nautobot-Sync)
|
||||
> ist offen.
|
||||
|
||||
> **Aktueller Stand: → §10 (2026-07-10, TP16 UMGESETZT ✅).** §9 = Analyse/Zielbild, §10 = As-Built.
|
||||
> Abschnitte 1–8 sind Historie (Track A, 2026-06-30); die „3 Connectors gut/Zielbild"-Aussagen dort sind
|
||||
> durch §9/§10 **überholt** (Round-Robin = macvlan-Host-Isolation → 1/N-Ausfälle). Track A aus `docs/handoff-cf-tunnel-1033.md`.
|
||||
> **Aktueller Stand: → §0 (unten).** §1–16 sind Historie/As-Built-Log früherer Sessions und beschreiben
|
||||
> Topologien, die **nicht mehr live** sind (v.a. das 3-Tunnel-HA `bf-mm`/`bf-nas`/`bf-pve`). §9 = Analyse,
|
||||
> §10–15 = As-Built des inzwischen abgelösten 3-Tunnel-Designs. Track A aus `docs/handoff-cf-tunnel-1033.md`.
|
||||
|
||||
## §0 — Live-Ist-Stand (2026-09-08, maßgeblich)
|
||||
|
||||
> Der einzige aktuelle Topologie-Stand. **§1–16 nicht als aktuelle Konfiguration lesen** — insbesondere das
|
||||
> 3-Tunnel-HA `bf-mm`/`bf-nas`/`bf-pve` (§10–15) existiert seit dem TP16.3-Cutover (bf-public/Caddy,
|
||||
> 2026-08-27) **nicht mehr**. Strukturierte Estate = Nautobot (TP44, deckt sich mit diesem Stand);
|
||||
> operativer Master = Cloudflare. Read-only gemessen gegen die CF-API (`cfd_tunnel`, `warp_connector`,
|
||||
> `teamnet/routes`) am 2026-09-08.
|
||||
|
||||
**cloudflared-Tunnel (live) — es gibt genau drei:**
|
||||
|
||||
| Tunnel | ID | Conns | Zweck |
|
||||
|---|---|---|---|
|
||||
| **bf-public** | `48d3b0b6-7233-4781-a76a-1de4a5e957ce` | 2, healthy | **einziger öffentlicher BF-Tunnel** (TP31/TP16.3), erstellt 2026-08-27 |
|
||||
| **fm.emsenhuber.net** | `b697d124-53bf-4a33-8fcf-384cced4db1a` | 1, healthy | Standort FM (`ds-fm`, `gateway-fm`), separat |
|
||||
| **terpene-db** | `f2d2fc8a-54ae-41df-b09a-97312cdb9fe2` | 1, healthy | DB-Pfad (Hyperdrive→VPC-Service), Rebuild 2026-09-01 — s. §17 |
|
||||
|
||||
**bf-public-Ingress** → alle vier auf **Caddy `10.40.20.40:443`** (Caddy macht das Fan-out), catch-all 404:
|
||||
`vw` · `hass` · `n8n` · `gitea` (`.emsenhuber.net`).
|
||||
|
||||
**WARP / Private Access — nativer Mesh, KEIN cloudflared:**
|
||||
- warp_connector **bf-core-mesh** `d1da7c8a-2736-4cbb-8af1-32db5393fb9c`, healthy — nativer
|
||||
WireGuard-Mesh-Node auf dem Core (TP16.1). **Live und produktiv** (nicht mehr „gated/Konzept" wie in §14).
|
||||
- Routen: `10.40.0.0/16` → **bf-core-mesh** · `10.101.0.0/24` → **fm**. Ein vnet `vnetemsenhuber` (default).
|
||||
|
||||
## 1. Alle Tunnel im Account (`393c…66c36`)
|
||||
| Tunnel | ID | Status | Conns | Verwendung |
|
||||
@@ -234,6 +258,9 @@ Der macmini ist vollwertiger dritter Knoten (nicht „vergessen").
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>🗄️ <b>ARCHIV (§10–16)</b> — überholtes 3-Tunnel-HA <code>bf-mm</code>/<code>bf-nas</code>/<code>bf-pve</code>. Seit dem TP16.3-Cutover (2026-08-27) nicht mehr live — siehe §0. Aufklappen nur für die Historie.</summary>
|
||||
|
||||
## 10. AS-BUILT 2026-07-10 (TP16 umgesetzt) — reziprokes 3-Tunnel-HA ✅
|
||||
> Gebaut & verifiziert in eigener Session. Round-Robin-#1033 **strukturell weg** (Loop-Gegentest 0×530 über alle 19 Live-FQDNs, HA per Host-Ausfall-Sim bewiesen). Schritte 1–4+HA durch; **Schritt 5 (Access/WARP + `vw`) offen** (user-gated).
|
||||
|
||||
@@ -337,6 +364,10 @@ Letzte in §9.2/§8 als „bleibt `.41`" geführte Origin-Klasse (`pve .41.100`,
|
||||
|
||||
## 14. Richtungsentscheid 2026-07-16 — Private Access via native Cloudflare Mesh (WARP Connector) auf dem CRS326 Core-Switch (TP16.1)
|
||||
|
||||
> ✅ **Nachtrag 2026-09-08 (live):** umgesetzt. warp_connector **bf-core-mesh** (`d1da7c8a-…`) ist healthy
|
||||
> und trägt die Route `10.40.0.0/16` — der „Scharfschalten = eigene Session, gated"-Status unten ist
|
||||
> überholt. Siehe §0.
|
||||
|
||||
> ✅ **Update 2026-07-22 (User-Entscheid):** Ziel-Device retargeted von **ap1** auf den **CRS326 Core-Switch** — konsistent mit TP15-Endstand (kein RB5009, Router bleibt ap1/ap2) und TP21s Kabel-Fabric-Modell. Reine Ziel-Doku-Änderung, Scharfschalten bleibt gated bis TP21 den CRS326 abdeckt. Der Rest dieser Sektion ist entsprechend auf den neuen Zielhost umgeschrieben; CRS326 ist aktuell noch Werkszustand, Management-IP/Interface TBD.
|
||||
|
||||
**Trennung der Concerns (wichtig):**
|
||||
@@ -452,6 +483,8 @@ Netzwerk-Namespace, sieht ohnehin alles), und die cloudflared-Connectoren laufen
|
||||
eigene Maschinen, auf denen die Blindheit prinzipiell nicht auftreten kann. Der Shim bleibt als Option
|
||||
für den Fall, dass später doch ein Dienst neben macvlan-Nachbarn einziehen soll.
|
||||
|
||||
</details>
|
||||
|
||||
## 17. Terpenfinder-DB-Pfad neu aufgebaut 2026-09-02 (Fremdprojekt-Handout blaueblüten)
|
||||
|
||||
**Auslöser:** Handout `blaueblueten-n8n/docs/handout-bf-infra-db-pfad-20260902.md` — der Cloudflare-Worker
|
||||
|
||||
@@ -41,10 +41,10 @@ Manufacturer "Cloudflare"
|
||||
|
||||
## Werkzeug
|
||||
|
||||
`nautobot/tp36-cf-estate-seed.py` — **idempotenter** Seeder (get-or-create), Datenstand als Inline-Struktur.
|
||||
`nautobot/tp44-cf-estate-seed.py` — **idempotenter** Seeder (get-or-create), Datenstand als Inline-Struktur.
|
||||
Token aus `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored). Aufruf: Dry-Run default,
|
||||
`--commit` schreibt. Re-Run = 26/26 „existiert".
|
||||
*(Dateiname trägt noch `tp36`-Präfix aus dem ersten Arbeitsstand; inhaltlich TP44. Umbenennen bei Gelegenheit.)*
|
||||
*(Datei am 2026-09-06 von `tp36-…` auf `tp44-cf-estate-seed.py` umbenannt — Präfix jetzt korrekt.)*
|
||||
|
||||
## Gotchas (für Etappe 2 / Folgesessions)
|
||||
|
||||
@@ -54,15 +54,17 @@ Token aus `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored). Aufruf: Dry-Ru
|
||||
- VPC-Service-API liegt unter `connectivity/directory/services` (Update = **PUT**, voller Body), nicht `/vpc/`.
|
||||
- Der Repo-`CF_API_TOKEN` darf `cfd_tunnel`, aber **nicht** Workers-VPC (`connectivity/directory`) — dort
|
||||
über den `cloudflare`-MCP-Credential gehen.
|
||||
> ⚠️ **ÜBERHOLT (2026-09-11):** falsch. Live getestet liest der `CF_API_TOKEN` aus
|
||||
> `~/.secrets/credentials.env` Hyperdrive/VPC/Workers (alle 200) — der schmale ist `CF_DNS_API_TOKEN`.
|
||||
> Der Teil-B-Sync deckt damit 14/14 ab. Details: `handoff-20260910-tp44-teilb-sync-live.md`.
|
||||
|
||||
## Offen (Etappe 2, eigene Session)
|
||||
|
||||
1. **CF-API→Nautobot-Sync** (Python/n8n): Inline-Daten im Seeder durch Live-Pull ersetzen, periodisch
|
||||
(analog golden-config-bot). Dann ist Nautobot driftfrei.
|
||||
2. **TP44 in `teilprojekte.md`** eintragen (Quick-Index + Abschnitt + Ist-Stand), sobald die dortigen
|
||||
fremden Änderungen committet/geklärt sind.
|
||||
2. ✅ **TP44 in `teilprojekte.md`** eingetragen (Quick-Index + Abschnitt + Ist-Stand) — 2026-09-06.
|
||||
3. IPAM-Prefixe `10.40.0.0/16` + `10.101.0.0/24` anlegen → dann greifen die Prefix-Assignments automatisch.
|
||||
4. Seeder-Datei nach `tp44-…` umbenennen.
|
||||
4. ✅ Seeder-Datei nach `tp44-cf-estate-seed.py` umbenannt — 2026-09-06.
|
||||
|
||||
## Verifikation (2026-09-02)
|
||||
|
||||
|
||||
70
docs/handoff-20260904-tp25-konsole-n-clients.md
Normal file
70
docs/handoff-20260904-tp25-konsole-n-clients.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Handoff 2026-09-04 — TP25: Klick-Konsole auf N Clients + Pin-UI
|
||||
|
||||
**Kurz:** Die Klick-Konsole (`docs/tp6-marker/server.py`, Port 3031) war beim Multi-Pin-Umbau
|
||||
(2026-09-04, `tp25-pin-multi.sh` + `TP25_PHONES`) **nicht** mitgezogen worden — sie zeigte weiter
|
||||
hartkodiert zwei Clients (iPhone/S22) und pinnte ueber die alte Gegen-AP-Automatik. Jetzt ist sie
|
||||
auf **N Clients** generalisiert und hat eine **Pin-UI**: Signal/AP/Ping-Verlust je Client live,
|
||||
Pinnen band-genau direkt in der Konsole. Die alte 2-Client-Automatik ist entfernt.
|
||||
|
||||
## Was umgebaut wurde
|
||||
|
||||
`docs/tp6-marker/server.py` (Hauptumbau):
|
||||
- **Client-Liste aus Datei statt hartkodiert:** `geraete()` liest `geraete.txt` aus dem
|
||||
Laufverzeichnis (schreibt `tp25-walk-messung.sh`, eine Zeile `name|ip|mac` je Client). Fallback
|
||||
auf die zwei alten MACs fuer Alt-Laeufe.
|
||||
- **Kein eigenes SSH mehr in der Konsole:** der `beobachter()`-Thread (2 MACs per SSH,
|
||||
`beide-telefone.csv`) ist raus. Stattdessen liest die Konsole je Client die **letzte Zeile** von
|
||||
`sampler-<name>.csv` (`sta_iface`, `sta_signal`) und den **juengsten Ping-Verlust** aus
|
||||
`walk-ping-<name>.csv` (leeres `ms` = Verlust). Eine SSH-Session weniger gegen den Core-SPOF.
|
||||
- **N Client-Karten** statt der festen iPhone/S22-Zeile: Name, AP (via `ap_map()`), Signal dBm,
|
||||
Ping-Verlust der letzten ~30 s. **Alarm**, wenn ein **gepinnter** Client `sta_iface == "absent"`
|
||||
ist → „<name> HAENGT NIRGENDS — nicht weitergehen" (Nachfolger des alten S22-Alarms).
|
||||
- **Pin-UI** (Karte „Pinnen (Multi-Pin)"): je Client ein `<select>` mit `frei` / `apX/5GHz` /
|
||||
`apX/2,4GHz` (Ziele aus `ap_map()` abgeleitet) plus drei Aktionen:
|
||||
- **„Pin setzen"** → baut `TP25_PINSPEC="name|mac|ap|band;…"` aus den Nicht-`frei`-Auswahlen und
|
||||
ruft `bash docs/tp25-pin-multi.sh set` im Thread.
|
||||
- **„Pin loesen"** → `bash docs/tp25-pin-multi.sh off`.
|
||||
- **„verify"** → `bash docs/tp25-pin-multi.sh verify`, Ausgabe als Statuszeile.
|
||||
Neuer Endpunkt `POST /pinmulti` (analog zum alten `/pin`).
|
||||
- **Entfernt:** `IPHONE_MAC`/`S22_MAC`, `_live`/`_auto`, `entscheide()`, `pin_lesen()`/
|
||||
`pin_setzen()` (via `tp25-pin-s22.sh`), die ap1/ap2/auto-Buttons und der `PIN_SH`-Aufruf.
|
||||
`tp25-pin-s22.sh` bleibt als Skript erhalten (liefert `map`), wird aber von der Konsole nicht
|
||||
mehr zum Pinnen aufgerufen.
|
||||
|
||||
`docs/tp25-runde.sh`:
|
||||
- `aufraeumen()` loest jetzt `tp25-pin-multi.sh off` statt `tp25-pin-s22.sh off` (Kommentar
|
||||
angepasst: die Konsole pinnt nicht mehr automatisch).
|
||||
|
||||
## Bedienung (Messablauf)
|
||||
|
||||
```
|
||||
bash docs/tp25-runde.sh [name] # Konsole (3031) + Messung, Adresse fuer das Telefon
|
||||
```
|
||||
|
||||
1. Am Telefon die Konsole oeffnen, einmal irgendwo tippen (schaltet den Signalton frei).
|
||||
2. In der Konsole **pinnen**: je Client AP/Band waehlen, „Pin setzen". Clients ohne Ziel bleiben
|
||||
frei (das Skript raeumt vorher alle `TP25-PINM-*`-Regeln ab).
|
||||
3. Losgehen. 🚶 beim Losgehen, 📍 beim Stehenbleiben, Raum antippen. Alarm = ein gepinnter Client
|
||||
haengt nirgends.
|
||||
4. „⏹ Aufzeichnung stoppen" (oder Ctrl-C im Terminal). Der Wrapper loest den Multi-Pin beim Beenden.
|
||||
|
||||
Auswertung: `python3 docs/tp25-gehweg-auswertung.py --dir docs/messdaten/tp25-runde-<name>`
|
||||
|
||||
## Was noch offen ist (braucht die Geraete vor Ort)
|
||||
|
||||
- **Live-Test mit N Geraeten im Netz** (Plan-Verifikation Schritt 3): `TP25_PHONES="…" bash
|
||||
docs/tp25-runde.sh test` → N Karten sichtbar, pinnen → `bash docs/tp25-pin-multi.sh verify` zeigt
|
||||
die Regeln, gehen/Raeume/stoppen → Auswertung liefert die Raumtabelle mit N Geraeten.
|
||||
- **Aufraeumen** (Schritt 4): nach dem Lauf `bash docs/tp25-pin-multi.sh off` — keine
|
||||
`TP25-PINM-*`-Regeln am Manager zurueck.
|
||||
|
||||
Statisch verifiziert (ohne Geraete): AST-Parse ok, keine stale Referenzen auf die alte Automatik,
|
||||
Smoke-Render mit 2 Test-Clients ok, `bash -n` auf allen drei Shell-Skripten ok.
|
||||
|
||||
## Gotchas (unveraendert, aus CLAUDE.md)
|
||||
|
||||
- **`cap-wifiN`-Karte wandert bei jedem Rejoin** — `tp25-walk-messung.sh` friert sie als
|
||||
`ap-map.txt` im Messlauf ein; die Konsole beschriftet daraus.
|
||||
- **Registration-Table nur am Manager** — die Sampler (`tp6-monitor-core.sh`) fragen den Core ab,
|
||||
nicht die CAPs.
|
||||
- **`sshpass -e`, nie `-p`** — gilt weiter fuer alle Skripte, die den Core ansprechen.
|
||||
127
docs/handoff-20260906-tp37-34-36-plan.md
Normal file
127
docs/handoff-20260906-tp37-34-36-plan.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Handoff 2026-09-06 — Plan TP37 (P1+2) → TP34, TP36 parallel
|
||||
|
||||
> **Sessiontyp:** „erst planen, nichts bauen" (User-Entscheid 2026-09-06). **Kein Nautobot-Write.**
|
||||
> Dieses Dokument ist der verzahnte Runbook für die drei nachfolgenden Build-Sessions.
|
||||
> Datenmodell-Entscheid: **native Nautobot-Modelle, keine eigene App** (konsistent zu TP44).
|
||||
|
||||
## 1. Ausgangslage — live gegen die API gelesen (2026-09-06)
|
||||
|
||||
| | Wert | Bedeutung |
|
||||
|---|---|---|
|
||||
| Nautobot | `http://10.40.20.30:8080`, v3.1.7 | Apps: **Golden Config 3.0.7 + Nornir** (cloud-Modelle sind Core) |
|
||||
| Devices | 9 | 4 aktive MikroTik + decommissioned/Rest — in Build-Schritt 0 verifizieren |
|
||||
| **VirtualMachines** | **0** | TP37 P1 offen |
|
||||
| **Clusters** | **0** | TP37 P1 offen |
|
||||
| **ipam.Service** | **0** | TP37 P2 offen |
|
||||
| **CustomFields** | **0** | TP36 offen |
|
||||
| Relationships / Tags | 0 / 0 | — |
|
||||
| `cloud.*` (Account/ResType/Network/Service) | 1 / 6 / 4 / 14 | ✅ **TP44 Etappe 1** — CF-Estate steht |
|
||||
|
||||
**Kernbefund:** TP37 **Punkt 3 (CF-Connectoren) ist durch TP44 erledigt** (native cloud-Modelle). Echt
|
||||
offen bleibt das **Substrat** (P1) und die **Dienste** (P2) — und genau darauf sitzt TP34.
|
||||
|
||||
## 2. Entscheidungen (User, 2026-09-06)
|
||||
|
||||
1. **Erst planen, nichts bauen** — diese Session schreibt nichts nach Nautobot.
|
||||
2. **Native Modelle** — `virtualization.Cluster/VirtualMachine` + `ipam.Service`, **keine eigene App**.
|
||||
3. **TP44-Doku aufgeräumt** — Quick-Index-Zeile + Abschnitt in `teilprojekte.md`, Seeder
|
||||
`nautobot/tp36-…` → `tp44-cf-estate-seed.py` umbenannt.
|
||||
|
||||
## 3. Reihenfolge / Abhängigkeit (korrigiert)
|
||||
|
||||
```
|
||||
TP37 P3 (CF-Connectoren) ─── ✅ ERLEDIGT via TP44 ───┐
|
||||
│ gleiche native-Modell-Linie
|
||||
TP37 P1 (Hosts/Container → VirtualMachine) │
|
||||
└─► TP37 P2 (Dienste → ipam.Service) ─► TP34 (Caddyfile via GC)
|
||||
|
||||
TP36 (CustomFields auf den 4 Devices + Datei-Split) ── ∥ unabhängig, jederzeit parallel
|
||||
```
|
||||
|
||||
**Grund für die Unabhängigkeit von TP36:** seine Live-Daten-CustomFields hängen an den **bestehenden
|
||||
`dcim.Device`**, nicht an neuen VM/Service-Objekten. TP34 dagegen kann ohne die Backend-`ipam.Service`
|
||||
aus P2 nichts rendern → **TP37 vor TP34** bleibt zwingend.
|
||||
|
||||
---
|
||||
|
||||
## 4. TP37 Punkt 1 — Substrat: Hosts + Container als VMs
|
||||
|
||||
**Modell-Mapping (nativ):**
|
||||
- `virtualization.ClusterType`: `Proxmox VE`, `Synology Docker`, `Docker Desktop`.
|
||||
- `virtualization.Cluster`: `pve`, `nas-bf`, `macmini` (je einer, passender ClusterType).
|
||||
*(Die physischen Hosts als eigene `dcim.Device` sind **optional** und kommen frühestens in einer
|
||||
Etappe 2 — für den Doku-SSoT reicht der Cluster als Gruppierung. Empfehlung: Etappe 1 ohne Host-Devices.)*
|
||||
- `virtualization.VirtualMachine` je Workload, mit `cluster` + `primary_ip4`:
|
||||
- **pve:** alle LXCs (u.a. `platform-pg` 105/`.20.20`, Docker-LXC 400/`.20.126`, `mcast-relay` 108,
|
||||
influxdb, MariaDB 302/`.20.23`, …).
|
||||
- **nas-bf (DS923, Portainer ep14):** Nautobot-Stack-Container, ggf. Caddy/weitere.
|
||||
- **macmini (Docker Desktop):** `n8n`/`n8n-redis`/`n8n-mcp`/`gitea-mcp` + terpene-Worker `:8890`.
|
||||
|
||||
**Build-Schritt 0 (read-only Inventur, BEVOR geseedet wird):** je Host die Workloads mit Name, Rolle,
|
||||
primärer IP enumerieren — `pct list` + `pct config <id>` über **pve-mcp**, Portainer-API (ep14) für die
|
||||
NAS, `docker ps` auf dem macmini. Ergebnis in eine **Inline-Datenstruktur** (Muster: `tp44-cf-estate-seed.py`)
|
||||
→ idempotenter Seeder `nautobot/tp37-substrat-seed.py` (get-or-create, Dry-Run default, `--commit`).
|
||||
|
||||
## 5. TP37 Punkt 2 — Dienste als `ipam.Service`
|
||||
|
||||
- Je exponiertem Port ein `ipam.Service`, an die VM (bzw. später das Device) gebunden, das ihn betreibt:
|
||||
Caddy `:80/:443`, AdGuard `:53` (bf1/bf2), Nautobot `:8080`, n8n `:5678`, HA `:8123`, Portainer `:9443`,
|
||||
`pve-mcp :3000`, gitea, Vaultwarden, …
|
||||
- **Quelle ohne Raten:** die Ports stehen in `CLAUDE.md` (Hardware/Tooling) **und** in den
|
||||
`extra_config.origin/port`-Feldern der TP44-`CloudService`-Objekte — gegeneinander abgleichen.
|
||||
- Idempotenter Seeder `nautobot/tp37-dienste-seed.py`.
|
||||
|
||||
## 6. 🔴 Auflage: Compliance-Gegenprobe (aus TP37 selbst)
|
||||
|
||||
Nach P1+2 **muss** `python3 nautobot/gc-transaktion.py status` bzw. `gc-configplan.py --refresh-compliance`
|
||||
zeigen, dass die Geräte-Compliance **unverändert 140/140** ist. VMs/Services hinzuzufügen darf die
|
||||
Golden-Config-Pipeline der 4 MikroTik **nicht** berühren. P1 und P2 sind je ein **abnehmbarer** Schritt
|
||||
mit dieser Gegenprobe danach — nicht als Nebeneffekt von etwas anderem.
|
||||
|
||||
---
|
||||
|
||||
## 7. TP34 — Caddyfile via Golden Config (nach P2)
|
||||
|
||||
- **Vorbedingung:** Backend-Dienste existieren als `ipam.Service` (P2).
|
||||
- **Modell (nativ):** Proxy-Route = **Relationship** (oder CustomField) Frontend `caddy`-Service ↔ Backend-
|
||||
Service, plus den öffentlichen Hostname am Backend. GC-Job + Jinja rendert den Caddyfile, Diff gegen
|
||||
live `caddy-bf1`, neue ComplianceRule `caddyfile`.
|
||||
- **Schritt 0 / Quick-Win (sobald gebaut wird):** `terpene.teilzeitladen.xyz → http://10.40.20.167:8890`
|
||||
als **manueller** Caddy-Block, damit terpene läuft, bevor die GC-Pipeline steht.
|
||||
- ⚠️ **Offener Design-Punkt:** Caddy ist **kein RouterOS-Gerät** — GC an ein Nicht-Netzgerät zu deployen
|
||||
(Caddy-Reload) ist neu (die „nicht in TP37"-Grenze). Deploy-Pfad in der TP34-Build-Session entscheiden.
|
||||
|
||||
## 8. TP36 — Live-Daten-CustomFields + Datei-Split (parallel)
|
||||
|
||||
- **CustomFields auf `dcim.Device`:** `ros_version`, `compliance_pct`, `dhcp_server_count`, `mgmt_ip`,
|
||||
`dns_upstream_mode`. Compliance-Zahl kommt aus **GC selbst**, der Rest aus MikroMCP/REST.
|
||||
- **Skript `nautobot/gc-snapshot.py`** (neu — existiert noch nicht): liest live, setzt die CFs, rendert
|
||||
`docs/teilprojekte-aktuell.md` als **read-only Snapshot**.
|
||||
- **Datei-Split:** `teilprojekte-index.md` (nur aktive TPs, eine Zeile) · `teilprojekte-aktuell.md`
|
||||
(Live-Snapshot) · `teilprojekte-archiv.md` (unverändert). `/projektstatus` liest **Index + Archiv**.
|
||||
- **Unabhängig von TP37** → kann sofort/parallel starten.
|
||||
|
||||
---
|
||||
|
||||
## 9. Empfohlene Reihenfolge der Build-Sessions
|
||||
|
||||
1. **TP37 P1** (Substrat) — Inventur → Seeder → Compliance-Gegenprobe.
|
||||
2. **TP37 P2** (Dienste) — schließt an P1 an, gleiche Gegenprobe.
|
||||
3. **TP34** — nach P2 (Quick-Win terpene als Schritt 0).
|
||||
4. **TP36** — jederzeit parallel zu 1–3.
|
||||
|
||||
## 10. Offene Entscheidungen je Build-Session (nicht heute)
|
||||
|
||||
- **TP34:** Proxy-Route-Modell (Relationship vs. CustomField) + Caddy-Deploy-Pfad (GC an Nicht-Netzgerät).
|
||||
- **TP37:** Hosts als eigene `dcim.Device` anlegen oder nur Cluster? (Empfehlung: Etappe 1 nur Cluster.)
|
||||
- **TP36:** genaues Feld-Set + wie oft `gc-snapshot.py` läuft (Bot analog golden-config vs. on-demand).
|
||||
|
||||
## 11. Was diese Session geändert hat (nur Doku)
|
||||
|
||||
- `nautobot/tp36-cf-estate-seed.py` → **`nautobot/tp44-cf-estate-seed.py`** umbenannt (+ 4 Referenzen,
|
||||
+ Docstring-Titel TP36→TP44).
|
||||
- `teilprojekte.md`: Quick-Index-Zeile TP44; Ist-Stand-Bullet 2026-09-06; TP34/TP36/TP37-Abschnitte auf
|
||||
den korrigierten Stand gezogen; **neuer TP44-Abschnitt** mit Anker.
|
||||
- `handoff-20260902-cf-estate-nautobot-ssot.md` + `cf-tunnel-mapping.md`: Dateiname-Referenzen + erledigte
|
||||
TODOs nachgezogen.
|
||||
- **TP23-Quick-Index-Zeile** (fremde, korrekte Fremd-Änderung ✅→⏳) auf User-Okay mit übernommen.
|
||||
57
docs/handoff-20260910-tp44-teilb-sync-live.md
Normal file
57
docs/handoff-20260910-tp44-teilb-sync-live.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Handoff 2026-09-10 — TP44 Teil B: CF-Estate-Sync als Nautobot-Job LIVE
|
||||
|
||||
> **Ergebnis:** Der Cloudflare-Estate→Nautobot-Sync läuft als **täglicher Nautobot-Job**
|
||||
> (`CFEstateSync`, grouping `TP44 / Cloudflare`). Dry-Run + Commit-Run sauber: **4 CloudNetworks /
|
||||
> 14 CloudServices, alle CF-Endpoints lesbar, 0 Drift.** CF bleibt operativer Master, Nautobot ist
|
||||
> read-only Dokumentations-SSoT. **TP44 ist damit vollständig durch.**
|
||||
|
||||
## Governance (User, 2026-09-09)
|
||||
Periodische externe→Nautobot-Syncs leben als **Nautobot-Jobs** in `bf-infra/jobs/` und werden **in
|
||||
Nautobot geplant** — kein per-Task-Container, kein Cron. Künftige Syncs = weitere Jobs in `jobs/`.
|
||||
|
||||
## Was live ist
|
||||
- **Runtime:** `jobs/cf_estate_sync.py` (`CFEstateSync`, ORM). Pull der CF-API (urllib+Bearer),
|
||||
Upsert per `extra_config`-Match (`tunnel_id`/`script`/`id`/`service_id`), Verwaisung = `stale`-Flag
|
||||
statt Löschen. 403-tolerant (fehlender Scope kippt den Sync nicht; Verwaisung wird dann übersprungen).
|
||||
- **Registrierung:** `jobs/__init__.py` ruft `register_jobs(CFEstateSync)`; **Repo-Wurzel-`__init__.py`**
|
||||
macht `bf_infra` als reguläres Paket sichtbar.
|
||||
- **Token:** Nautobot-Secret `cf-api-token` (Provider `environment-variable`) → `CF_API_TOKEN` am
|
||||
`nautobot-worker`, verdrahtet im Anchor-`environment` von `docs/nas-vlan20/stacks/210-nautobot.yml`
|
||||
(gitignored). Wert = der breite `CF_API_TOKEN` aus `~/.secrets/credentials.env` (liest 14/14).
|
||||
- **Schedule:** `ScheduledJob "CF Estate Sync – taeglich"`, `interval=daily`, `start_time=2026-09-11T03:00:00Z`
|
||||
(= 05:00 Europe/Vienna, vor den Golden-Config-Läufen), `enabled=True`, `dryrun=False`.
|
||||
- **GitRepository:** `bf-infra` (`provided_contents=['extras.job']`) — war schon verdrahtet.
|
||||
|
||||
## Drei load-bearing Funde (sonst lädt/plant der Job nicht)
|
||||
1. **Repo-Wurzel `__init__.py` ist Pflicht.** Nautobot 3.1.7 lädt Git-Repo-Jobs über
|
||||
`import_modules_privately()` → `pkgutil.walk_packages([GIT_ROOT])`. walk_packages entdeckt **nur**
|
||||
reguläre Pakete (mit `__init__.py`), **keine** Namespace-Packages. Ohne die Datei ist der geklonte
|
||||
`GIT_ROOT/bf_infra/` unsichtbar → `jobs/` wird nie importiert. Symptom: Git-Sync **SUCCESS**, aber
|
||||
Log „No jobs were registered on loading the `bf_infra.jobs` submodule" und **kein** Job-DB-Eintrag.
|
||||
bf-infra ist das **erste** Git-Repo mit `extras.job` — daher nie zuvor aufgetreten. Der `module_path`-
|
||||
Filter des Loaders importiert nur `bf_infra` + `bf_infra.jobs.*`; `nautobot/`, `routeros/` etc. bleiben
|
||||
unberührt.
|
||||
2. **`register_jobs()` gehört ins Paket-`__init__`**, nicht nur ins Submodul (Doku bestätigt).
|
||||
3. **`Meta.has_sensitive_variables = False`** nötig — Default `True` ⇒ Nautobot verweigert das Scheduling
|
||||
(„Job may have sensitive input variables"). Der Job nimmt nur `dryrun` (Boolean); das Token kommt aus
|
||||
dem Secret, nicht aus einer Job-Variable.
|
||||
|
||||
## Zwei Daten-/Code-Funde beim Dry-Run
|
||||
- **VPC-Bug:** VPC-Directory-Services tragen ihren Identifier in **`service_id`**, nicht `id`. Der Job las
|
||||
`id` (=None) → per `.get(mk)==None` matchte er ein Fremdobjekt (App Launcher) und ließ den echten
|
||||
`terpene-db` auf stale laufen. Fix: VPC nutzt `service_id`; `_upsert` überspringt `mv=None` generell.
|
||||
- **Seed-Artefakt:** der geseedete CloudService `terpene-db` hatte `extra_config.service_id: null`
|
||||
(der echte `019fa3d5-…` stand beim Seed nur in den Hyperdrive-Einträgen als `vpc_service_id`).
|
||||
**Einmalig reconciled** (PATCH `service_id=019fa3d5-…`) → seither matcht der Sync sauber, 0 Drift.
|
||||
|
||||
## Drift-Korrektur (nicht stillschweigend)
|
||||
Die frühere Notiz „Repo-`CF_API_TOKEN` darf kein Workers-VPC → über den `cloudflare`-MCP-Credential"
|
||||
(TP44-Prosa + `handoff-20260902-…`) war **falsch**. Live getestet liest der `CF_API_TOKEN` aus
|
||||
`~/.secrets/credentials.env` Hyperdrive/VPC/Workers (alle 200). Der schmale Token ist `CF_DNS_API_TOKEN`
|
||||
(403). Der `cloudflare`-MCP ist der **remote OAuth-MCP** (kein API-Token) und für einen serverseitigen
|
||||
Job ohnehin nicht nutzbar.
|
||||
|
||||
## Nächster CF-Sync = neuer Job im selben Muster
|
||||
Modul in `jobs/` anlegen, Klasse importieren + `register_jobs(...)` im `jobs/__init__.py` ergänzen,
|
||||
`Meta.has_sensitive_variables=False` setzen, Git-Sync, enablen, Dry-Run, dann Schedule. Die Wurzel-
|
||||
`__init__.py` gilt schon.
|
||||
6
docs/messdaten/tp25-runde-1er/ap-map.txt
Normal file
6
docs/messdaten/tp25-runde-1er/ap-map.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
cap-wifi1=ap2/5GHz
|
||||
cap-wifi2=ap3/5GHz
|
||||
cap-wifi3=ap1/5GHz
|
||||
cap-wifi4=ap3/2,4GHz
|
||||
cap-wifi5=ap1/2,4GHz
|
||||
cap-wifi6=ap2/2,4GHz
|
||||
2
docs/messdaten/tp25-runde-1er/geraete.txt
Normal file
2
docs/messdaten/tp25-runde-1er/geraete.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
iphone|10.40.10.196|F2:62:4C:E1:AF:09
|
||||
s22|10.40.10.178|2A:0D:B0:5D:AD:A7
|
||||
6
docs/messdaten/tp25-runde-1er/markers.csv
Normal file
6
docs/messdaten/tp25-runde-1er/markers.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
ts_epoch,ts_local,marker
|
||||
1788526676,14:57:56,PINM=off
|
||||
1788526735,14:58:55,PINM=off
|
||||
1788526763,14:59:23,PINM=off
|
||||
1788526765,14:59:25,PINM=off
|
||||
1788526770,14:59:30,PINM=s22|2A:0D:B0:5D:AD:A7|ap1|24
|
||||
|
1
docs/messdaten/tp25-runde-1er/phase.json
Normal file
1
docs/messdaten/tp25-runde-1er/phase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"mode": "langzeit", "phase": "GEHWEG", "title": "Roaming-Messung l\u00e4uft", "instruction": "Aufzeichnung gestoppt.", "step": 0, "steps": 0, "ends": 0}
|
||||
26
docs/messdaten/tp25-runde-1er/sampler-iphone.csv
Normal file
26
docs/messdaten/tp25-runde-1er/sampler-iphone.csv
Normal file
@@ -0,0 +1,26 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526644,14:57:24,14:57:24,41,472232,195392,cap-wifi3,-57,309700000,325000000,2800,2960,00:04:37,00:00:00
|
||||
1788526646,14:57:26,14:57:26,11,229864,36304,cap-wifi3,-57,309700000,325000000,2800,2960,00:04:39,00:00:00
|
||||
1788526648,14:57:28,14:57:28,8,132384,26968,cap-wifi3,-59,309700000,309700000,6160,6512,00:04:41,00:00:00
|
||||
1788526650,14:57:30,14:57:30,30,196672,48240,cap-wifi3,-59,309700000,344100000,6160,6512,00:04:43,00:00:00
|
||||
1788526652,14:57:32,14:57:32,12,131304,37448,cap-wifi3,-59,309700000,344100000,6160,6512,00:04:45,00:00:00
|
||||
1788526654,14:57:34,14:57:34,11,92000,18872,cap-wifi3,-59,309700000,412900000,5600,5920,00:04:47,00:00:00
|
||||
1788526656,14:57:36,14:57:36,12,130144,24840,cap-wifi3,-60,309700000,390000000,6048,6512,00:04:49,00:00:00
|
||||
1788526658,14:57:38,14:57:38,9,105160,20800,cap-wifi3,-60,309700000,390000000,6048,6512,00:04:51,00:00:00
|
||||
1788526660,14:57:40,14:57:40,14,177144,59112,cap-wifi3,-57,309700000,344100000,6048,6288,00:04:53,00:00:00
|
||||
1788526662,14:57:42,14:57:42,11,136152,39360,cap-wifi3,-59,309700000,325000000,5600,5920,00:04:55,00:00:00
|
||||
1788526664,14:57:44,14:57:44,7,132416,46304,cap-wifi3,-59,309700000,325000000,5600,5920,00:04:57,00:00:00
|
||||
1788526666,14:57:46,14:57:46,8,462016,41704,cap-wifi3,-60,309700000,390000000,5600,6296,00:04:59,00:00:00
|
||||
1788526668,14:57:48,14:57:48,11,129736,41416,cap-wifi3,-59,309700000,390000000,5600,5920,00:05:01,00:00:00
|
||||
1788526670,14:57:50,14:57:51,14,401784,54464,cap-wifi3,-59,309700000,390000000,5600,5920,00:05:03,00:00:00
|
||||
1788526672,14:57:52,14:57:53,8,146064,92728,cap-wifi3,-56,309700000,433300000,6640,7008,00:05:05,00:00:00
|
||||
1788526674,14:57:54,14:57:55,14,111952,31488,cap-wifi3,-59,309700000,433300000,6160,6512,00:05:07,00:00:00
|
||||
1788526677,14:57:57,14:57:57,9,123096,20552,cap-wifi3,-57,309700000,433300000,6160,6512,00:05:09,00:00:00
|
||||
1788526679,14:57:59,14:57:59,15,113008,17656,cap-wifi3,-57,309700000,433300000,6160,6512,00:05:11,00:00:00
|
||||
1788526681,14:58:01,14:58:01,16,172400,59040,cap-wifi3,-58,309700000,433300000,6160,6512,00:05:14,00:00:00
|
||||
1788526683,14:58:03,14:58:03,10,190968,57448,cap-wifi3,-59,309700000,433300000,5600,5920,00:05:16,00:00:00
|
||||
1788526685,14:58:05,14:58:05,8,183848,56872,cap-wifi3,-59,309700000,433300000,5600,5920,00:05:18,00:00:00
|
||||
1788526687,14:58:07,14:58:07,9,305320,47576,cap-wifi3,-59,309700000,487500000,5600,5920,00:05:20,00:00:00
|
||||
1788526689,14:58:09,14:58:09,20,331408,59856,cap-wifi3,-58,309700000,455800000,5600,5920,00:05:22,00:00:00
|
||||
1788526691,14:58:11,14:58:11,10,322448,65576,cap-wifi3,-58,309700000,455800000,5600,5920,00:05:24,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-1er/sampler-iphone.out
Normal file
1
docs/messdaten/tp25-runde-1er/sampler-iphone.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-1er/sampler-iphone.csv (Takt 2s, Client F2:62:4C:E1:AF:09)
|
||||
1
docs/messdaten/tp25-runde-1er/sampler-iphone.stderr.log
Normal file
1
docs/messdaten/tp25-runde-1er/sampler-iphone.stderr.log
Normal file
@@ -0,0 +1 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
26
docs/messdaten/tp25-runde-1er/sampler-s22.csv
Normal file
26
docs/messdaten/tp25-runde-1er/sampler-s22.csv
Normal file
@@ -0,0 +1,26 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526644,14:57:24,14:57:24,41,472232,195392,cap-wifi5,-61,137600000,229400000,2800,2960,01:04:55,00:00:00
|
||||
1788526646,14:57:26,14:57:26,11,229864,36304,cap-wifi5,-59,172100000,229400000,2800,2960,01:04:57,00:00:00
|
||||
1788526648,14:57:28,14:57:28,8,143088,22488,cap-wifi5,-60,172100000,229400000,2800,2368,01:04:59,00:00:00
|
||||
1788526650,14:57:30,14:57:30,30,192288,49440,cap-wifi5,-60,172100000,229400000,2800,2368,01:05:01,00:00:00
|
||||
1788526652,14:57:32,14:57:32,12,131304,37448,cap-wifi5,-60,172100000,229400000,3360,3552,01:05:03,00:00:00
|
||||
1788526654,14:57:34,14:57:34,11,92000,18872,cap-wifi5,-60,172100000,229400000,2800,2960,01:05:05,00:00:00
|
||||
1788526656,14:57:36,14:57:36,12,130144,24840,cap-wifi5,-60,172100000,229400000,2800,2960,01:05:07,00:00:00
|
||||
1788526658,14:57:38,14:57:38,9,105160,20800,cap-wifi5,-59,129000000,258100000,3856,2960,01:05:09,00:00:00
|
||||
1788526660,14:57:40,14:57:40,14,177144,59112,cap-wifi5,-58,114700000,229400000,7888,15664,01:05:11,00:00:00
|
||||
1788526662,14:57:42,14:57:42,11,136152,39360,cap-wifi5,-58,114700000,229400000,7888,15664,01:05:13,00:00:00
|
||||
1788526664,14:57:44,14:57:45,7,132416,46304,cap-wifi5,-57,114700000,206500000,3360,2960,01:05:15,00:00:00
|
||||
1788526666,14:57:46,14:57:47,8,462016,41704,cap-wifi5,-57,114700000,206500000,2800,2960,01:05:17,00:00:00
|
||||
1788526669,14:57:49,14:57:49,11,129736,41416,cap-wifi5,-57,154900000,172100000,3360,3552,01:05:19,00:00:00
|
||||
1788526671,14:57:51,14:57:51,14,401784,54464,cap-wifi5,-57,154900000,172100000,3360,3552,01:05:21,00:00:00
|
||||
1788526673,14:57:53,14:57:53,8,146064,92728,cap-wifi5,-58,154900000,206500000,2800,2960,01:05:23,00:00:00
|
||||
1788526675,14:57:55,14:57:55,14,111952,31488,cap-wifi5,-58,154900000,229400000,2800,2960,01:05:26,00:00:00
|
||||
1788526677,14:57:57,14:57:57,9,123096,20552,cap-wifi5,-58,154900000,229400000,2800,2960,01:05:28,00:00:00
|
||||
1788526679,14:57:59,14:57:59,17,113008,17656,cap-wifi5,-58,103200000,229400000,2800,2960,01:05:30,00:00:00
|
||||
1788526681,14:58:01,14:58:01,9,172400,59040,cap-wifi5,-58,103200000,206500000,3248,3328,01:05:32,00:00:00
|
||||
1788526683,14:58:03,14:58:03,10,190968,57448,cap-wifi5,-58,103200000,206500000,3248,3328,01:05:34,00:00:00
|
||||
1788526685,14:58:05,14:58:05,10,183848,56872,cap-wifi5,-58,103200000,206500000,2800,2960,01:05:36,00:00:00
|
||||
1788526687,14:58:07,14:58:07,9,305320,47576,cap-wifi5,-59,103200000,229400000,2800,2960,01:05:38,00:00:00
|
||||
1788526689,14:58:09,14:58:09,20,331408,59856,cap-wifi5,-59,103200000,229400000,2800,2960,01:05:40,00:00:00
|
||||
1788526691,14:58:11,14:58:11,10,322448,65576,cap-wifi5,-58,103200000,206500000,2800,2960,01:05:42,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-1er/sampler-s22.out
Normal file
1
docs/messdaten/tp25-runde-1er/sampler-s22.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-1er/sampler-s22.csv (Takt 2s, Client 2A:0D:B0:5D:AD:A7)
|
||||
1
docs/messdaten/tp25-runde-1er/sampler-s22.stderr.log
Normal file
1
docs/messdaten/tp25-runde-1er/sampler-s22.stderr.log
Normal file
@@ -0,0 +1 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
242
docs/messdaten/tp25-runde-1er/walk-ping-iphone.csv
Normal file
242
docs/messdaten/tp25-runde-1er/walk-ping-iphone.csv
Normal file
@@ -0,0 +1,242 @@
|
||||
ts_local,seq,ms
|
||||
14:57:24,0,3ms102us
|
||||
14:57:24,1,2ms682us
|
||||
14:57:25,2,2ms739us
|
||||
14:57:25,3,2ms889us
|
||||
14:57:25,4,2ms955us
|
||||
14:57:25,5,4ms434us
|
||||
14:57:25,6,4ms59us
|
||||
14:57:26,7,3ms540us
|
||||
14:57:26,8,98ms220us
|
||||
14:57:26,9,2ms841us
|
||||
14:57:26,10,95ms173us
|
||||
14:57:26,11,2ms688us
|
||||
14:57:27,12,113ms888us
|
||||
14:57:27,13,4ms866us
|
||||
14:57:27,14,2ms909us
|
||||
14:57:27,15,2ms776us
|
||||
14:57:27,16,2ms572us
|
||||
14:57:28,17,2ms931us
|
||||
14:57:28,18,2ms663us
|
||||
14:57:28,19,2ms796us
|
||||
14:57:28,20,2ms736us
|
||||
14:57:28,21,2ms826us
|
||||
14:57:29,22,2ms778us
|
||||
14:57:29,23,2ms741us
|
||||
14:57:29,24,2ms904us
|
||||
14:57:29,25,2ms744us
|
||||
14:57:29,26,2ms845us
|
||||
14:57:30,27,2ms850us
|
||||
14:57:30,28,2ms746us
|
||||
14:57:30,29,2ms973us
|
||||
14:57:30,30,2ms907us
|
||||
14:57:30,31,2ms771us
|
||||
14:57:31,32,2ms778us
|
||||
14:57:31,33,2ms793us
|
||||
14:57:31,34,2ms823us
|
||||
14:57:31,35,2ms717us
|
||||
14:57:31,36,5ms311us
|
||||
14:57:32,37,2ms752us
|
||||
14:57:32,38,2ms799us
|
||||
14:57:32,39,2ms964us
|
||||
14:57:32,40,2ms760us
|
||||
14:57:32,41,3ms53us
|
||||
14:57:33,42,2ms775us
|
||||
14:57:33,43,2ms793us
|
||||
14:57:33,44,4ms125us
|
||||
14:57:33,45,2ms696us
|
||||
14:57:33,46,2ms686us
|
||||
14:57:34,47,2ms738us
|
||||
14:57:34,48,2ms824us
|
||||
14:57:34,49,2ms607us
|
||||
14:57:34,50,2ms944us
|
||||
14:57:34,51,2ms854us
|
||||
14:57:35,52,2ms724us
|
||||
14:57:35,53,3ms453us
|
||||
14:57:35,54,3ms417us
|
||||
14:57:35,55,3ms535us
|
||||
14:57:35,56,2ms719us
|
||||
14:57:36,57,4ms498us
|
||||
14:57:36,58,2ms706us
|
||||
14:57:36,59,4ms279us
|
||||
14:57:36,60,2ms836us
|
||||
14:57:36,61,2ms646us
|
||||
14:57:37,62,2ms873us
|
||||
14:57:37,63,2ms782us
|
||||
14:57:37,64,2ms624us
|
||||
14:57:37,65,2ms881us
|
||||
14:57:37,66,2ms716us
|
||||
14:57:38,67,2ms763us
|
||||
14:57:38,68,2ms833us
|
||||
14:57:38,69,2ms879us
|
||||
14:57:38,70,2ms851us
|
||||
14:57:38,71,2ms777us
|
||||
14:57:39,72,2ms937us
|
||||
14:57:39,73,2ms821us
|
||||
14:57:39,74,2ms729us
|
||||
14:57:39,75,2ms892us
|
||||
14:57:39,76,2ms729us
|
||||
14:57:40,77,2ms755us
|
||||
14:57:40,78,2ms932us
|
||||
14:57:40,79,2ms701us
|
||||
14:57:40,80,2ms662us
|
||||
14:57:40,81,2ms651us
|
||||
14:57:41,82,5ms991us
|
||||
14:57:41,83,2ms728us
|
||||
14:57:41,84,2ms756us
|
||||
14:57:41,85,2ms716us
|
||||
14:57:41,86,2ms707us
|
||||
14:57:42,87,2ms785us
|
||||
14:57:42,88,2ms594us
|
||||
14:57:42,89,2ms747us
|
||||
14:57:42,90,2ms712us
|
||||
14:57:42,91,3ms258us
|
||||
14:57:43,92,2ms727us
|
||||
14:57:43,93,2ms623us
|
||||
14:57:43,94,2ms723us
|
||||
14:57:43,95,2ms749us
|
||||
14:57:43,96,2ms700us
|
||||
14:57:44,97,2ms723us
|
||||
14:57:44,98,2ms735us
|
||||
14:57:44,99,3ms646us
|
||||
14:57:44,100,2ms718us
|
||||
14:57:44,101,2ms740us
|
||||
14:57:45,102,2ms768us
|
||||
14:57:45,103,2ms663us
|
||||
14:57:45,104,2ms639us
|
||||
14:57:45,105,2ms731us
|
||||
14:57:45,106,3ms290us
|
||||
14:57:46,107,2ms822us
|
||||
14:57:46,108,2ms844us
|
||||
14:57:46,109,2ms617us
|
||||
14:57:46,110,2ms734us
|
||||
14:57:46,111,3ms447us
|
||||
14:57:47,112,3ms169us
|
||||
14:57:47,113,3ms247us
|
||||
14:57:47,114,2ms564us
|
||||
14:57:47,115,4ms516us
|
||||
14:57:47,116,2ms623us
|
||||
14:57:48,117,2ms675us
|
||||
14:57:48,118,2ms877us
|
||||
14:57:48,119,3ms310us
|
||||
14:57:48,120,2ms851us
|
||||
14:57:48,121,2ms803us
|
||||
14:57:49,122,7ms998us
|
||||
14:57:49,123,2ms887us
|
||||
14:57:49,124,2ms852us
|
||||
14:57:49,125,2ms846us
|
||||
14:57:49,126,2ms899us
|
||||
14:57:50,127,2ms913us
|
||||
14:57:50,128,2ms680us
|
||||
14:57:50,129,2ms715us
|
||||
14:57:50,130,3ms374us
|
||||
14:57:50,131,2ms670us
|
||||
14:57:51,132,3ms148us
|
||||
14:57:51,133,2ms932us
|
||||
14:57:51,134,2ms831us
|
||||
14:57:51,135,4ms780us
|
||||
14:57:51,136,2ms911us
|
||||
14:57:52,137,3ms85us
|
||||
14:57:52,138,3ms589us
|
||||
14:57:52,139,2ms697us
|
||||
14:57:52,140,4ms876us
|
||||
14:57:52,141,2ms867us
|
||||
14:57:53,142,3ms283us
|
||||
14:57:53,143,2ms710us
|
||||
14:57:53,144,2ms801us
|
||||
14:57:53,145,2ms928us
|
||||
14:57:53,146,2ms640us
|
||||
14:57:54,147,2ms678us
|
||||
14:57:54,148,4ms976us
|
||||
14:57:54,149,3ms408us
|
||||
14:57:54,150,2ms706us
|
||||
14:57:54,151,2ms827us
|
||||
14:57:55,152,2ms884us
|
||||
14:57:55,153,2ms855us
|
||||
14:57:55,154,2ms787us
|
||||
14:57:55,155,2ms695us
|
||||
14:57:55,156,2ms902us
|
||||
14:57:56,157,2ms749us
|
||||
14:57:56,158,2ms838us
|
||||
14:57:56,159,3ms937us
|
||||
14:57:57,160,3ms43us
|
||||
14:57:57,161,2ms870us
|
||||
14:57:57,162,3ms357us
|
||||
14:57:57,163,2ms756us
|
||||
14:57:57,164,2ms679us
|
||||
14:57:57,165,2ms863us
|
||||
14:57:57,166,2ms768us
|
||||
14:57:58,167,2ms858us
|
||||
14:57:58,168,3ms30us
|
||||
14:57:58,169,2ms677us
|
||||
14:57:58,170,2ms725us
|
||||
14:57:58,171,2ms919us
|
||||
14:57:59,172,2ms688us
|
||||
14:57:59,173,2ms821us
|
||||
14:57:59,174,2ms805us
|
||||
14:57:59,175,2ms709us
|
||||
14:57:59,176,2ms989us
|
||||
14:58:00,177,2ms589us
|
||||
14:58:00,178,3ms835us
|
||||
14:58:00,179,3ms194us
|
||||
14:58:00,180,2ms662us
|
||||
14:58:00,181,2ms686us
|
||||
14:58:01,182,2ms704us
|
||||
14:58:01,183,2ms752us
|
||||
14:58:01,184,2ms656us
|
||||
14:58:01,185,2ms904us
|
||||
14:58:01,186,2ms823us
|
||||
14:58:02,187,2ms656us
|
||||
14:58:02,188,2ms813us
|
||||
14:58:02,189,2ms644us
|
||||
14:58:02,190,2ms782us
|
||||
14:58:02,191,2ms722us
|
||||
14:58:03,192,2ms674us
|
||||
14:58:03,193,5ms657us
|
||||
14:58:03,194,4ms701us
|
||||
14:58:03,195,2ms772us
|
||||
14:58:03,196,2ms661us
|
||||
14:58:04,197,2ms660us
|
||||
14:58:04,198,2ms859us
|
||||
14:58:04,199,2ms800us
|
||||
14:58:04,200,2ms706us
|
||||
14:58:04,201,2ms847us
|
||||
14:58:05,202,2ms625us
|
||||
14:58:05,203,2ms816us
|
||||
14:58:05,204,2ms703us
|
||||
14:58:05,205,2ms818us
|
||||
14:58:05,206,3ms162us
|
||||
14:58:06,207,2ms673us
|
||||
14:58:06,208,2ms781us
|
||||
14:58:06,209,2ms607us
|
||||
14:58:06,210,2ms990us
|
||||
14:58:06,211,2ms602us
|
||||
14:58:07,212,2ms711us
|
||||
14:58:07,213,2ms819us
|
||||
14:58:07,214,2ms757us
|
||||
14:58:07,215,2ms605us
|
||||
14:58:07,216,2ms770us
|
||||
14:58:08,217,2ms682us
|
||||
14:58:08,218,4ms824us
|
||||
14:58:08,219,2ms623us
|
||||
14:58:08,220,2ms566us
|
||||
14:58:08,221,2ms724us
|
||||
14:58:09,222,3ms737us
|
||||
14:58:09,223,3ms273us
|
||||
14:58:09,224,2ms809us
|
||||
14:58:09,225,2ms619us
|
||||
14:58:09,226,2ms709us
|
||||
14:58:10,227,2ms909us
|
||||
14:58:10,228,2ms707us
|
||||
14:58:10,229,2ms909us
|
||||
14:58:10,230,2ms610us
|
||||
14:58:10,231,3ms251us
|
||||
14:58:11,232,2ms689us
|
||||
14:58:11,233,2ms714us
|
||||
14:58:11,234,2ms680us
|
||||
14:58:11,235,2ms644us
|
||||
14:58:11,236,2ms665us
|
||||
14:58:12,237,3ms326us
|
||||
14:58:12,238,2ms749us
|
||||
14:58:12,239,2ms713us
|
||||
14:58:12,,RECONNECT
|
||||
|
238
docs/messdaten/tp25-runde-1er/walk-ping-s22.csv
Normal file
238
docs/messdaten/tp25-runde-1er/walk-ping-s22.csv
Normal file
@@ -0,0 +1,238 @@
|
||||
ts_local,seq,ms
|
||||
14:57:24,0,
|
||||
14:57:25,1,
|
||||
14:57:25,2,
|
||||
14:57:25,3,
|
||||
14:57:25,4,
|
||||
14:57:25,5,
|
||||
14:57:26,6,
|
||||
14:57:26,7,
|
||||
14:57:26,8,
|
||||
14:57:26,9,
|
||||
14:57:26,10,
|
||||
14:57:27,11,
|
||||
14:57:27,12,
|
||||
14:57:27,13,
|
||||
14:57:27,14,
|
||||
14:57:27,15,unre...
|
||||
14:57:28,16,
|
||||
14:57:28,17,
|
||||
14:57:28,18,
|
||||
14:57:28,19,
|
||||
14:57:28,20,
|
||||
14:57:29,21,
|
||||
14:57:29,22,
|
||||
14:57:29,23,
|
||||
14:57:29,24,
|
||||
14:57:29,25,
|
||||
14:57:30,26,
|
||||
14:57:30,27,
|
||||
14:57:30,28,
|
||||
14:57:30,29,
|
||||
14:57:30,30,
|
||||
14:57:31,31,unre...
|
||||
14:57:31,32,
|
||||
14:57:31,33,
|
||||
14:57:31,34,
|
||||
14:57:31,35,
|
||||
14:57:32,36,
|
||||
14:57:32,37,
|
||||
14:57:32,38,
|
||||
14:57:32,39,
|
||||
14:57:32,40,
|
||||
14:57:33,41,
|
||||
14:57:33,42,
|
||||
14:57:33,43,
|
||||
14:57:33,44,
|
||||
14:57:33,45,
|
||||
14:57:34,46,
|
||||
14:57:34,47,unre...
|
||||
14:57:34,48,
|
||||
14:57:34,49,
|
||||
14:57:34,50,
|
||||
14:57:35,51,
|
||||
14:57:35,52,
|
||||
14:57:35,53,
|
||||
14:57:35,54,
|
||||
14:57:35,55,
|
||||
14:57:36,56,
|
||||
14:57:36,57,
|
||||
14:57:36,58,
|
||||
14:57:36,59,
|
||||
14:57:37,60,
|
||||
14:57:37,61,
|
||||
14:57:37,62,
|
||||
14:57:37,63,unre...
|
||||
14:57:38,64,
|
||||
14:57:38,65,
|
||||
14:57:38,66,
|
||||
14:57:38,67,
|
||||
14:57:38,68,
|
||||
14:57:38,69,
|
||||
14:57:39,70,
|
||||
14:57:39,71,
|
||||
14:57:39,72,
|
||||
14:57:39,73,
|
||||
14:57:39,74,
|
||||
14:57:40,75,
|
||||
14:57:40,76,
|
||||
14:57:40,77,
|
||||
14:57:40,78,
|
||||
14:57:40,79,unre...
|
||||
14:57:41,80,
|
||||
14:57:41,81,
|
||||
14:57:41,82,
|
||||
14:57:41,83,
|
||||
14:57:41,84,
|
||||
14:57:42,85,
|
||||
14:57:42,86,
|
||||
14:57:42,87,
|
||||
14:57:42,88,
|
||||
14:57:42,89,
|
||||
14:57:43,90,
|
||||
14:57:43,91,
|
||||
14:57:43,92,
|
||||
14:57:43,93,
|
||||
14:57:43,94,
|
||||
14:57:43,95,unre...
|
||||
14:57:44,96,
|
||||
14:57:44,97,
|
||||
14:57:44,98,
|
||||
14:57:44,99,
|
||||
14:57:45,100,
|
||||
14:57:45,101,
|
||||
14:57:45,102,
|
||||
14:57:45,103,
|
||||
14:57:45,104,
|
||||
14:57:46,105,
|
||||
14:57:46,106,
|
||||
14:57:46,107,
|
||||
14:57:46,108,
|
||||
14:57:46,109,
|
||||
14:57:47,110,
|
||||
14:57:47,111,unre...
|
||||
14:57:47,112,
|
||||
14:57:47,113,
|
||||
14:57:47,114,
|
||||
14:57:48,115,
|
||||
14:57:48,116,
|
||||
14:57:48,117,
|
||||
14:57:48,118,
|
||||
14:57:49,119,
|
||||
14:57:49,120,
|
||||
14:57:49,121,
|
||||
14:57:49,122,
|
||||
14:57:49,123,
|
||||
14:57:50,124,
|
||||
14:57:50,125,
|
||||
14:57:50,126,
|
||||
14:57:50,127,unre...
|
||||
14:57:50,128,
|
||||
14:57:51,129,
|
||||
14:57:51,130,
|
||||
14:57:51,131,
|
||||
14:57:51,132,
|
||||
14:57:51,133,
|
||||
14:57:52,134,
|
||||
14:57:52,135,
|
||||
14:57:52,136,
|
||||
14:57:52,137,
|
||||
14:57:52,138,
|
||||
14:57:53,139,
|
||||
14:57:53,140,
|
||||
14:57:53,141,
|
||||
14:57:53,142,
|
||||
14:57:53,143,unre...
|
||||
14:57:54,144,
|
||||
14:57:54,145,
|
||||
14:57:54,146,
|
||||
14:57:54,147,
|
||||
14:57:54,148,
|
||||
14:57:55,149,
|
||||
14:57:55,150,
|
||||
14:57:55,151,
|
||||
14:57:55,152,
|
||||
14:57:55,153,
|
||||
14:57:56,154,
|
||||
14:57:56,155,
|
||||
14:57:56,156,
|
||||
14:57:57,157,
|
||||
14:57:57,158,
|
||||
14:57:57,159,unre...
|
||||
14:57:57,160,
|
||||
14:57:57,161,
|
||||
14:57:57,162,
|
||||
14:57:57,163,
|
||||
14:57:58,164,
|
||||
14:57:58,165,
|
||||
14:57:58,166,
|
||||
14:57:58,167,
|
||||
14:57:58,168,
|
||||
14:57:59,169,
|
||||
14:57:59,170,
|
||||
14:57:59,171,
|
||||
14:57:59,172,
|
||||
14:57:59,173,
|
||||
14:58:00,174,
|
||||
14:58:00,175,unre...
|
||||
14:58:00,176,
|
||||
14:58:00,177,
|
||||
14:58:00,178,
|
||||
14:58:01,179,
|
||||
14:58:01,180,
|
||||
14:58:01,181,
|
||||
14:58:01,182,
|
||||
14:58:01,183,
|
||||
14:58:02,184,
|
||||
14:58:02,185,
|
||||
14:58:02,186,
|
||||
14:58:02,187,
|
||||
14:58:02,188,
|
||||
14:58:03,189,
|
||||
14:58:03,190,
|
||||
14:58:03,191,unre...
|
||||
14:58:03,192,
|
||||
14:58:03,193,
|
||||
14:58:04,194,
|
||||
14:58:04,195,
|
||||
14:58:04,196,
|
||||
14:58:04,197,
|
||||
14:58:04,198,
|
||||
14:58:05,199,
|
||||
14:58:05,200,
|
||||
14:58:05,201,
|
||||
14:58:05,202,
|
||||
14:58:05,203,
|
||||
14:58:06,204,
|
||||
14:58:06,205,
|
||||
14:58:06,206,
|
||||
14:58:06,207,unre...
|
||||
14:58:07,208,
|
||||
14:58:07,209,
|
||||
14:58:07,210,
|
||||
14:58:07,211,
|
||||
14:58:07,212,
|
||||
14:58:08,213,
|
||||
14:58:08,214,
|
||||
14:58:08,215,
|
||||
14:58:08,216,
|
||||
14:58:08,217,
|
||||
14:58:09,218,
|
||||
14:58:09,219,
|
||||
14:58:09,220,
|
||||
14:58:09,221,
|
||||
14:58:09,222,
|
||||
14:58:09,223,unre...
|
||||
14:58:10,224,
|
||||
14:58:10,225,
|
||||
14:58:10,226,
|
||||
14:58:10,227,
|
||||
14:58:11,228,
|
||||
14:58:11,229,
|
||||
14:58:11,230,
|
||||
14:58:11,231,
|
||||
14:58:11,232,
|
||||
14:58:12,233,
|
||||
14:58:12,234,
|
||||
14:58:12,235,
|
||||
14:58:12,,RECONNECT
|
||||
|
6
docs/messdaten/tp25-runde-dauerping/ap-map.txt
Normal file
6
docs/messdaten/tp25-runde-dauerping/ap-map.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
cap-wifi1=ap2/5GHz
|
||||
cap-wifi2=ap3/5GHz
|
||||
cap-wifi3=ap1/5GHz
|
||||
cap-wifi4=ap3/2,4GHz
|
||||
cap-wifi5=ap1/2,4GHz
|
||||
cap-wifi6=ap2/2,4GHz
|
||||
@@ -0,0 +1 @@
|
||||
ts_epoch,ts_local,marker
|
||||
|
@@ -0,0 +1,43 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526610,14:56:50,14:56:50,50,160216,1109704,cap-wifi2,-69,137600000,275300000,45848,2240,01:04:23,00:00:00
|
||||
1788526612,14:56:52,14:56:52,6,151312,62480,cap-wifi2,-69,137600000,275300000,45848,2240,01:04:25,00:00:00
|
||||
1788526614,14:56:54,14:56:54,15,127416,44832,cap-wifi2,-69,137600000,309700000,36152,6320,01:04:27,00:00:00
|
||||
1788526616,14:56:56,14:56:56,4,127712,21960,cap-wifi2,-69,103200000,325000000,42008,9736,01:04:29,00:00:00
|
||||
1788526618,14:56:58,14:56:58,9,157888,29816,cap-wifi2,-69,103200000,325000000,42008,9736,01:04:31,00:00:00
|
||||
1788526620,14:57:00,14:57:00,16,322208,124400,cap-wifi2,-69,103200000,344100000,123600,58608,01:04:33,00:00:00
|
||||
1788526622,14:57:02,14:57:03,10,451416,4332552,cap-wifi2,-69,137600000,275300000,193568,29704,01:04:35,00:00:00
|
||||
1788526624,14:57:04,14:57:05,9,398720,136640,cap-wifi2,-69,137600000,292500000,40792,7440,01:04:37,00:00:00
|
||||
1788526627,14:57:07,14:57:07,15,947800,446984,cap-wifi2,-69,137600000,292500000,40792,7440,01:04:40,00:00:00
|
||||
1788526629,14:57:09,14:57:09,12,7610296,3321592,cap-wifi2,-69,154900000,292500000,89400,14304,01:04:42,00:00:00
|
||||
1788526631,14:57:11,14:57:11,18,2720136,2091232,cap-wifi2,-69,103200000,275300000,69264,10760,01:04:44,00:00:00
|
||||
1788526633,14:57:13,14:57:13,9,2261568,2680088,cap-wifi2,-69,103200000,275300000,69264,10760,01:04:46,00:00:00
|
||||
1788526635,14:57:15,14:57:15,7,108584,42016,cap-wifi2,-69,103200000,309700000,78112,11920,01:04:48,00:00:00
|
||||
1788526637,14:57:17,14:57:17,4,309312,51096,cap-wifi2,-68,103200000,309700000,117392,10152,01:04:50,00:00:00
|
||||
1788526639,14:57:19,14:57:19,14,309864,66576,cap-wifi2,-68,103200000,309700000,117392,10152,01:04:52,00:00:00
|
||||
1788526641,14:57:21,14:57:21,6,150056,22800,cap-wifi2,-68,103200000,309700000,53888,10272,01:04:54,00:00:00
|
||||
1788526643,14:57:23,14:57:23,14,510016,213944,cap-wifi2,-68,103200000,309700000,61328,9088,01:04:56,00:00:00
|
||||
1788526645,14:57:25,14:57:25,19,507656,169768,cap-wifi2,-68,103200000,309700000,61328,9088,01:04:58,00:00:00
|
||||
1788526647,14:57:27,14:57:27,9,136752,33856,cap-wifi2,-69,103200000,309700000,41640,8560,01:05:00,00:00:00
|
||||
1788526649,14:57:29,14:57:29,18,143088,22488,cap-wifi2,-69,103200000,292500000,61120,8192,01:05:02,00:00:00
|
||||
1788526651,14:57:31,14:57:31,15,192288,49440,cap-wifi2,-69,103200000,292500000,61120,8192,01:05:04,00:00:00
|
||||
1788526653,14:57:33,14:57:33,9,131304,37448,cap-wifi2,-69,137600000,309700000,57760,8136,01:05:06,00:00:00
|
||||
1788526655,14:57:35,14:57:35,11,92000,18872,cap-wifi2,-69,137600000,344100000,56408,8592,01:05:08,00:00:00
|
||||
1788526657,14:57:37,14:57:37,9,130144,24840,cap-wifi2,-69,137600000,344100000,56408,8592,01:05:10,00:00:00
|
||||
1788526659,14:57:39,14:57:39,20,105160,20800,cap-wifi2,-69,137600000,325000000,19344,5200,01:05:12,00:00:00
|
||||
1788526661,14:57:41,14:57:41,11,177144,59112,cap-wifi2,-71,154900000,275300000,111184,14496,01:05:14,00:00:00
|
||||
1788526663,14:57:43,14:57:43,16,136152,39360,cap-wifi2,-71,154900000,275300000,111184,14496,01:05:16,00:00:00
|
||||
1788526665,14:57:45,14:57:45,16,132416,46304,cap-wifi2,-70,154900000,309700000,44928,8560,01:05:18,00:00:00
|
||||
1788526667,14:57:47,14:57:47,11,462016,41704,cap-wifi2,-70,154900000,309700000,397536,18704,01:05:20,00:00:00
|
||||
1788526669,14:57:49,14:57:49,24,129736,41416,cap-wifi2,-70,154900000,309700000,397536,18704,01:05:22,00:00:00
|
||||
1788526671,14:57:51,14:57:51,7,401784,54464,cap-wifi2,-76,103200000,206500000,44784,8560,01:05:24,00:00:00
|
||||
1788526673,14:57:53,14:57:53,14,101992,21720,cap-wifi2,-69,103200000,292500000,55400,9120,01:05:26,00:00:00
|
||||
1788526675,14:57:55,14:57:55,14,152504,61272,cap-wifi2,-69,103200000,292500000,55400,9120,01:05:28,00:00:00
|
||||
1788526677,14:57:57,14:57:57,22,130520,26552,cap-wifi2,-70,103200000,260000000,55304,10816,01:05:30,00:00:00
|
||||
1788526679,14:57:59,14:57:59,17,155408,24688,cap-wifi2,-69,103200000,275300000,58480,8720,01:05:32,00:00:00
|
||||
1788526681,14:58:01,14:58:01,9,244456,41424,cap-wifi2,-69,103200000,275300000,58480,8720,01:05:34,00:00:00
|
||||
1788526683,14:58:03,14:58:03,10,115640,26032,cap-wifi2,-69,103200000,260000000,38208,22096,01:05:36,00:00:00
|
||||
1788526685,14:58:05,14:58:05,10,154824,24456,cap-wifi2,-70,137600000,260000000,65000,8000,01:05:38,00:00:00
|
||||
1788526687,14:58:07,14:58:07,9,135144,25000,cap-wifi2,-70,137600000,260000000,65000,8000,01:05:40,00:00:00
|
||||
1788526689,14:58:09,14:58:10,20,122800,29936,cap-wifi2,-68,137600000,275300000,18856,4048,01:05:42,00:00:00
|
||||
1788526691,14:58:11,14:58:12,10,152160,65768,cap-wifi2,-68,137600000,206500000,40064,12024,01:05:44,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-ipad.csv (Takt 2s, Client CA:B8:D4:3F:8A:81)
|
||||
@@ -0,0 +1,43 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526610,14:56:50,14:56:50,50,160216,1109704,cap-wifi3,-58,309700000,412900000,0,0,00:04:03,00:00:03.010
|
||||
1788526612,14:56:52,14:56:52,6,151312,62480,cap-wifi3,-57,309700000,412900000,0,0,00:04:05,00:00:00
|
||||
1788526614,14:56:54,14:56:54,15,127416,44832,cap-wifi3,-57,309700000,412900000,2800,2960,00:04:07,00:00:00
|
||||
1788526616,14:56:56,14:56:56,4,127712,21960,cap-wifi3,-57,309700000,412900000,2800,2960,00:04:09,00:00:00
|
||||
1788526618,14:56:58,14:56:58,9,157888,29816,cap-wifi3,-57,309700000,412900000,3248,3552,00:04:11,00:00:00
|
||||
1788526620,14:57:00,14:57:00,16,322208,124400,cap-wifi3,-57,344100000,325000000,3360,3552,00:04:13,00:00:00
|
||||
1788526622,14:57:02,14:57:03,10,451416,4332552,cap-wifi3,-58,344100000,344100000,2800,2960,00:04:15,00:00:00
|
||||
1788526624,14:57:04,14:57:05,9,398720,136640,cap-wifi3,-58,344100000,344100000,2800,2960,00:04:17,00:00:00
|
||||
1788526627,14:57:07,14:57:07,15,947800,446984,cap-wifi3,-59,344100000,325000000,3360,3552,00:04:20,00:00:00
|
||||
1788526629,14:57:09,14:57:09,12,7610296,3321592,cap-wifi3,-58,344100000,344100000,2800,2960,00:04:22,00:00:00
|
||||
1788526631,14:57:11,14:57:11,18,2720136,2091232,cap-wifi3,-58,344100000,344100000,2800,2960,00:04:24,00:00:00
|
||||
1788526633,14:57:13,14:57:13,9,2261568,2680088,cap-wifi3,-57,344100000,325000000,3360,3552,00:04:26,00:00:00
|
||||
1788526635,14:57:15,14:57:15,7,108584,42016,cap-wifi3,-57,344100000,344100000,2800,2960,00:04:28,00:00:00
|
||||
1788526637,14:57:17,14:57:17,4,309312,51096,cap-wifi3,-57,344100000,344100000,2800,2960,00:04:30,00:00:00
|
||||
1788526639,14:57:19,14:57:19,14,309864,66576,cap-wifi3,-58,344100000,325000000,2800,2960,00:04:32,00:00:00
|
||||
1788526641,14:57:21,14:57:21,6,150056,22800,cap-wifi3,-57,309700000,344100000,3808,4000,00:04:34,00:00:00
|
||||
1788526643,14:57:23,14:57:23,14,510016,213944,cap-wifi3,-57,309700000,344100000,3808,4000,00:04:36,00:00:00
|
||||
1788526645,14:57:25,14:57:25,19,507656,169768,cap-wifi3,-57,309700000,325000000,2800,2960,00:04:38,00:00:00
|
||||
1788526647,14:57:27,14:57:27,9,136752,33856,cap-wifi3,-59,309700000,309700000,6160,6512,00:04:40,00:00:00
|
||||
1788526649,14:57:29,14:57:29,18,143088,22488,cap-wifi3,-59,309700000,309700000,6160,6512,00:04:42,00:00:00
|
||||
1788526651,14:57:31,14:57:31,15,192288,49440,cap-wifi3,-59,309700000,344100000,6160,6512,00:04:44,00:00:00
|
||||
1788526653,14:57:33,14:57:33,9,131304,37448,cap-wifi3,-59,309700000,412900000,5600,5920,00:04:46,00:00:00
|
||||
1788526655,14:57:35,14:57:35,11,92000,18872,cap-wifi3,-59,309700000,412900000,5600,5920,00:04:48,00:00:00
|
||||
1788526657,14:57:37,14:57:37,9,130144,24840,cap-wifi3,-60,309700000,390000000,6048,6512,00:04:50,00:00:00
|
||||
1788526659,14:57:39,14:57:39,20,105160,20800,cap-wifi3,-57,309700000,344100000,6048,6288,00:04:52,00:00:00
|
||||
1788526661,14:57:41,14:57:41,11,177144,59112,cap-wifi3,-57,309700000,344100000,6048,6288,00:04:54,00:00:00
|
||||
1788526663,14:57:43,14:57:43,16,136152,39360,cap-wifi3,-59,309700000,325000000,5600,5920,00:04:56,00:00:00
|
||||
1788526665,14:57:45,14:57:45,16,132416,46304,cap-wifi3,-60,309700000,390000000,5600,6296,00:04:58,00:00:00
|
||||
1788526667,14:57:47,14:57:47,11,462016,41704,cap-wifi3,-60,309700000,390000000,5600,6296,00:05:00,00:00:00
|
||||
1788526669,14:57:49,14:57:49,24,129736,41416,cap-wifi3,-59,309700000,390000000,5600,5920,00:05:02,00:00:00
|
||||
1788526671,14:57:51,14:57:51,7,234376,122480,cap-wifi3,-56,309700000,433300000,6640,7008,00:05:04,00:00:00
|
||||
1788526673,14:57:53,14:57:53,14,101992,21720,cap-wifi3,-56,309700000,433300000,6640,7008,00:05:06,00:00:00
|
||||
1788526675,14:57:55,14:57:55,14,152504,61272,cap-wifi3,-59,309700000,433300000,6160,6512,00:05:08,00:00:00
|
||||
1788526677,14:57:57,14:57:57,22,130520,26552,cap-wifi3,-57,309700000,433300000,6160,6512,00:05:10,00:00:00
|
||||
1788526679,14:57:59,14:57:59,17,155408,24688,cap-wifi3,-57,309700000,433300000,6160,6512,00:05:12,00:00:00
|
||||
1788526681,14:58:01,14:58:01,9,244456,41424,cap-wifi3,-58,309700000,433300000,6160,6512,00:05:14,00:00:00
|
||||
1788526683,14:58:03,14:58:03,10,115640,26032,cap-wifi3,-59,309700000,433300000,5600,5920,00:05:16,00:00:00
|
||||
1788526685,14:58:05,14:58:05,10,154824,24456,cap-wifi3,-59,309700000,433300000,5600,5920,00:05:18,00:00:00
|
||||
1788526687,14:58:07,14:58:08,9,135144,25000,cap-wifi3,-59,309700000,487500000,5600,5920,00:05:20,00:00:00
|
||||
1788526689,14:58:09,14:58:10,20,122800,29936,cap-wifi3,-58,309700000,455800000,5600,5920,00:05:22,00:00:00
|
||||
1788526691,14:58:11,14:58:12,10,152160,65768,cap-wifi3,-58,309700000,455800000,6160,6512,00:05:24,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-iphone.csv (Takt 2s, Client F2:62:4C:E1:AF:09)
|
||||
@@ -0,0 +1,42 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526611,14:56:51,14:56:51,38,120880,24032,cap-wifi6,-85,68800000,17200000,166280,4310536,01:04:25,00:00:00
|
||||
1788526613,14:56:53,14:56:53,9,151312,62480,cap-wifi6,-85,103200000,17200000,550344,592824,01:04:27,00:00:00
|
||||
1788526615,14:56:55,14:56:55,10,127416,44832,cap-wifi6,-86,68800000,25800000,413184,340240,01:04:29,00:00:00
|
||||
1788526617,14:56:57,14:56:57,6,127712,21960,cap-wifi6,-86,68800000,25800000,413184,340240,01:04:31,00:00:00
|
||||
1788526619,14:56:59,14:56:59,13,157888,29816,cap-wifi6,-85,103200000,34400000,1524128,1126592,01:04:33,00:00:00
|
||||
1788526621,14:57:01,14:57:01,7,322208,124400,cap-wifi6,-86,68800000,34400000,1886112,547568,01:04:35,00:00:00
|
||||
1788526623,14:57:03,14:57:03,13,451416,4332552,cap-wifi6,-86,68800000,34400000,1886112,547568,01:04:37,00:00:00
|
||||
1788526625,14:57:05,14:57:05,10,398720,136640,cap-wifi6,-85,68800000,17200000,1079440,2625392,01:04:39,00:00:00
|
||||
1788526627,14:57:07,14:57:07,12,947800,446984,cap-wifi6,-86,68800000,17200000,1108088,760040,01:04:42,00:00:00
|
||||
1788526629,14:57:09,14:57:09,20,7610296,3321592,cap-wifi6,-86,68800000,17200000,1108088,760040,01:04:44,00:00:00
|
||||
1788526631,14:57:11,14:57:11,8,2720136,2091232,cap-wifi6,-82,68800000,51600000,1684720,1013032,01:04:46,00:00:00
|
||||
1788526633,14:57:13,14:57:13,9,2261568,2680088,cap-wifi6,-87,103200000,25800000,324776,3447064,01:04:48,00:00:00
|
||||
1788526636,14:57:16,14:57:15,7,108584,42016,cap-wifi6,-87,103200000,25800000,324776,3447064,01:04:50,00:00:00
|
||||
1788526637,14:57:17,14:57:17,4,1478952,581848,cap-wifi6,-85,103200000,17200000,598680,602896,01:04:52,00:00:00
|
||||
1788526639,14:57:19,14:57:19,14,229272,54576,cap-wifi6,-84,103200000,34400000,1017664,775360,01:04:54,00:00:00
|
||||
1788526641,14:57:21,14:57:21,6,100688,29040,cap-wifi6,-84,103200000,34400000,1017664,775360,01:04:56,00:00:00
|
||||
1788526644,14:57:24,14:57:23,14,472232,195392,cap-wifi6,-86,68800000,17200000,14752,6688,01:04:58,00:00:00
|
||||
1788526645,14:57:25,14:57:25,19,229864,36304,cap-wifi6,-86,68800000,17200000,1094984,789880,01:05:00,00:00:00
|
||||
1788526647,14:57:27,14:57:28,9,132384,26968,cap-wifi6,-86,68800000,17200000,1094984,789880,01:05:02,00:00:00
|
||||
1788526649,14:57:29,14:57:30,18,196672,48240,cap-wifi6,-84,68800000,51600000,912296,563704,01:05:04,00:00:00
|
||||
1788526652,14:57:32,14:57:32,15,237176,31328,cap-wifi6,-87,68800000,11000000,2448600,1495240,01:05:06,00:00:00
|
||||
1788526654,14:57:34,14:57:34,9,147328,27456,cap-wifi6,-87,68800000,17200000,1326824,729536,01:05:08,00:00:00
|
||||
1788526656,14:57:36,14:57:36,11,122784,22816,cap-wifi6,-87,68800000,17200000,1326824,729536,01:05:10,00:00:00
|
||||
1788526658,14:57:38,14:57:38,9,122248,26192,cap-wifi6,-83,68800000,51600000,1292552,729408,01:05:12,00:00:00
|
||||
1788526660,14:57:40,14:57:40,14,167632,49032,cap-wifi6,-87,103200000,17200000,2131488,1138888,01:05:14,00:00:00
|
||||
1788526662,14:57:42,14:57:42,11,89248,17856,cap-wifi6,-87,103200000,17200000,2131488,1138888,01:05:16,00:00:00
|
||||
1788526664,14:57:44,14:57:44,7,125232,33536,cap-wifi6,-86,103200000,17200000,1495112,774112,01:05:18,00:00:00
|
||||
1788526666,14:57:46,14:57:46,8,1056872,260048,cap-wifi6,-86,103200000,17200000,773656,584032,01:05:20,00:00:00
|
||||
1788526668,14:57:48,14:57:48,11,172776,252208,cap-wifi6,-86,103200000,17200000,773656,584032,01:05:22,00:00:00
|
||||
1788526670,14:57:50,14:57:50,14,116656,22744,cap-wifi6,-87,51600000,17200000,256424,250360,01:05:24,00:00:00
|
||||
1788526672,14:57:52,14:57:52,8,234376,122480,cap-wifi6,-83,51600000,51600000,771864,575120,01:05:26,00:00:00
|
||||
1788526674,14:57:54,14:57:54,14,101992,21720,cap-wifi6,-83,51600000,51600000,771864,575120,01:05:28,00:00:00
|
||||
1788526676,14:57:56,14:57:56,9,152504,61272,cap-wifi6,-84,68800000,17200000,944720,547864,01:05:31,00:00:00
|
||||
1788526678,14:57:58,14:57:58,15,130520,26552,cap-wifi6,-88,51600000,17200000,1086104,614336,01:05:33,00:00:00
|
||||
1788526680,14:58:00,14:58:00,16,155408,24688,cap-wifi6,-88,51600000,17200000,1086104,614336,01:05:35,00:00:00
|
||||
1788526682,14:58:02,14:58:02,10,244456,41424,cap-wifi6,-85,68800000,17200000,1515776,563200,01:05:37,00:00:00
|
||||
1788526684,14:58:04,14:58:04,8,183848,56872,cap-wifi6,-84,103200000,25800000,888888,696272,01:05:39,00:00:00
|
||||
1788526686,14:58:06,14:58:06,9,305320,47576,cap-wifi6,-84,103200000,25800000,888888,696272,01:05:41,00:00:00
|
||||
1788526688,14:58:08,14:58:08,14,331408,59856,cap-wifi6,-84,68800000,34400000,860480,583048,01:05:43,00:00:00
|
||||
1788526690,14:58:10,14:58:10,17,322448,65576,cap-wifi6,-87,51600000,25800000,1068352,553088,01:05:45,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-macbook.csv (Takt 2s, Client 56:71:6C:22:E6:A9)
|
||||
@@ -0,0 +1,42 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788526611,14:56:51,14:56:51,38,120880,24032,cap-wifi5,-51,137600000,229400000,0,0,01:04:22,00:00:09
|
||||
1788526613,14:56:53,14:56:53,9,151312,62480,cap-wifi5,-51,137600000,258100000,1680,1776,01:04:24,00:00:00
|
||||
1788526615,14:56:55,14:56:55,10,127416,44832,cap-wifi5,-53,137600000,229400000,2800,2960,01:04:26,00:00:00
|
||||
1788526617,14:56:57,14:56:57,6,127712,21960,cap-wifi5,-53,137600000,229400000,2800,2960,01:04:28,00:00:00
|
||||
1788526619,14:56:59,14:56:59,13,157888,29816,cap-wifi5,-50,137600000,229400000,2800,2960,01:04:30,00:00:00
|
||||
1788526621,14:57:01,14:57:01,7,322208,124400,cap-wifi5,-51,137600000,229400000,3248,3328,01:04:32,00:00:00
|
||||
1788526623,14:57:03,14:57:03,13,451416,4332552,cap-wifi5,-51,137600000,229400000,3248,3328,01:04:34,00:00:00
|
||||
1788526625,14:57:05,14:57:05,10,398720,136640,cap-wifi5,-51,137600000,229400000,2800,2960,01:04:36,00:00:00
|
||||
1788526627,14:57:07,14:57:07,12,947800,446984,cap-wifi5,-51,137600000,229400000,2800,2960,01:04:38,00:00:00
|
||||
1788526629,14:57:09,14:57:09,20,7610296,3321592,cap-wifi5,-51,137600000,229400000,2800,2960,01:04:40,00:00:00
|
||||
1788526631,14:57:11,14:57:11,8,2720136,2091232,cap-wifi5,-52,137600000,229400000,3360,3552,01:04:42,00:00:00
|
||||
1788526633,14:57:13,14:57:13,9,2261568,2680088,cap-wifi5,-52,137600000,229400000,3360,3552,01:04:44,00:00:00
|
||||
1788526636,14:57:16,14:57:15,7,108584,42016,cap-wifi5,-52,137600000,229400000,3360,3552,01:04:46,00:00:00
|
||||
1788526637,14:57:17,14:57:17,4,1478952,581848,cap-wifi5,-53,137600000,258100000,2800,2960,01:04:48,00:00:00
|
||||
1788526639,14:57:19,14:57:19,14,229272,54576,cap-wifi5,-53,137600000,229400000,4184,4968,01:04:50,00:00:00
|
||||
1788526641,14:57:21,14:57:21,6,100688,29040,cap-wifi5,-53,137600000,229400000,4184,4968,01:04:52,00:00:00
|
||||
1788526644,14:57:24,14:57:23,14,472232,195392,cap-wifi5,-61,137600000,229400000,2800,2960,01:04:54,00:00:00
|
||||
1788526645,14:57:25,14:57:25,19,229864,36304,cap-wifi5,-59,172100000,229400000,2800,2960,01:04:56,00:00:00
|
||||
1788526647,14:57:27,14:57:28,9,132384,26968,cap-wifi5,-59,172100000,229400000,2800,2960,01:04:58,00:00:00
|
||||
1788526649,14:57:29,14:57:30,18,196672,48240,cap-wifi5,-60,172100000,229400000,2800,2368,01:05:00,00:00:00
|
||||
1788526652,14:57:32,14:57:32,15,237176,31328,cap-wifi5,-60,172100000,229400000,3360,3552,01:05:02,00:00:00
|
||||
1788526654,14:57:34,14:57:34,9,147328,27456,cap-wifi5,-60,172100000,229400000,2800,2960,01:05:04,00:00:00
|
||||
1788526656,14:57:36,14:57:36,11,122784,22816,cap-wifi5,-60,172100000,229400000,2800,2960,01:05:07,00:00:00
|
||||
1788526658,14:57:38,14:57:38,9,122248,26192,cap-wifi5,-59,129000000,258100000,3856,2960,01:05:09,00:00:00
|
||||
1788526660,14:57:40,14:57:40,14,167632,49032,cap-wifi5,-58,114700000,229400000,7888,15664,01:05:11,00:00:00
|
||||
1788526662,14:57:42,14:57:42,11,89248,17856,cap-wifi5,-58,114700000,229400000,7888,15664,01:05:13,00:00:00
|
||||
1788526664,14:57:44,14:57:44,7,125232,33536,cap-wifi5,-57,114700000,206500000,3360,2960,01:05:15,00:00:00
|
||||
1788526666,14:57:46,14:57:46,8,1056872,260048,cap-wifi5,-57,114700000,206500000,2800,2960,01:05:17,00:00:00
|
||||
1788526668,14:57:48,14:57:48,11,172776,252208,cap-wifi5,-57,114700000,206500000,2800,2960,01:05:19,00:00:00
|
||||
1788526670,14:57:50,14:57:50,14,116656,22744,cap-wifi5,-57,154900000,172100000,3360,3552,01:05:21,00:00:00
|
||||
1788526672,14:57:52,14:57:52,8,234376,122480,cap-wifi5,-58,154900000,206500000,2800,2960,01:05:23,00:00:00
|
||||
1788526674,14:57:54,14:57:54,14,101992,21720,cap-wifi5,-58,154900000,206500000,2800,2960,01:05:25,00:00:00
|
||||
1788526676,14:57:56,14:57:56,9,152504,61272,cap-wifi5,-58,154900000,229400000,2800,2960,01:05:27,00:00:00
|
||||
1788526678,14:57:58,14:57:58,15,130520,26552,cap-wifi5,-58,103200000,229400000,2800,2960,01:05:29,00:00:00
|
||||
1788526680,14:58:00,14:58:00,16,155408,24688,cap-wifi5,-58,103200000,229400000,2800,2960,01:05:31,00:00:00
|
||||
1788526682,14:58:02,14:58:02,10,244456,41424,cap-wifi5,-58,103200000,206500000,3248,3328,01:05:33,00:00:00
|
||||
1788526684,14:58:04,14:58:04,8,183848,56872,cap-wifi5,-58,103200000,206500000,2800,2960,01:05:35,00:00:00
|
||||
1788526686,14:58:06,14:58:06,9,305320,47576,cap-wifi5,-58,103200000,206500000,2800,2960,01:05:37,00:00:00
|
||||
1788526688,14:58:08,14:58:08,14,331408,59856,cap-wifi5,-59,103200000,229400000,2800,2960,01:05:39,00:00:00
|
||||
1788526690,14:58:10,14:58:10,17,322448,65576,cap-wifi5,-58,103200000,206500000,2800,2960,01:05:41,00:00:00
|
||||
1788526692,14:58:12,RECONNECT
|
||||
|
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-s22.csv (Takt 2s, Client 2A:0D:B0:5D:AD:A7)
|
||||
2397
docs/messdaten/tp25-runde-dauerping/archiv-150553/walk-ping-ipad.csv
Normal file
2397
docs/messdaten/tp25-runde-dauerping/archiv-150553/walk-ping-ipad.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2398
docs/messdaten/tp25-runde-dauerping/archiv-150553/walk-ping-s22.csv
Normal file
2398
docs/messdaten/tp25-runde-dauerping/archiv-150553/walk-ping-s22.csv
Normal file
File diff suppressed because it is too large
Load Diff
4
docs/messdaten/tp25-runde-dauerping/geraete.txt
Normal file
4
docs/messdaten/tp25-runde-dauerping/geraete.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
iphone|10.40.10.196|F2:62:4C:E1:AF:09
|
||||
s22|10.40.10.179|2A:0D:B0:5D:AD:A7
|
||||
ipad|10.40.10.180|CA:B8:D4:3F:8A:81
|
||||
macbook|10.40.10.82|56:71:6C:22:E6:A9
|
||||
0
docs/messdaten/tp25-runde-dauerping/konsole.log
Normal file
0
docs/messdaten/tp25-runde-dauerping/konsole.log
Normal file
6
docs/messdaten/tp25-runde-dauerping/markers.csv
Normal file
6
docs/messdaten/tp25-runde-dauerping/markers.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
ts_epoch,ts_local,marker
|
||||
1788527256,15:07:36,PINM=off
|
||||
1788527283,15:08:03,PINM=off
|
||||
1788527311,15:08:31,PINM=off
|
||||
1788527320,15:08:40,PINM=off
|
||||
1788527414,15:10:14,STOP
|
||||
|
1
docs/messdaten/tp25-runde-dauerping/phase.json
Normal file
1
docs/messdaten/tp25-runde-dauerping/phase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"mode": "langzeit", "phase": "GEHWEG", "title": "Roaming-Messung l\u00e4uft", "instruction": "Aufzeichnung gestoppt.", "step": 0, "steps": 0, "ends": 0}
|
||||
131
docs/messdaten/tp25-runde-dauerping/sampler-ipad.csv
Normal file
131
docs/messdaten/tp25-runde-dauerping/sampler-ipad.csv
Normal file
@@ -0,0 +1,131 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788527155,15:05:55,15:05:55,26,110192,66624,cap-wifi3,-58,6000000,216700000,0,0,00:07:03,00:00:00
|
||||
1788527157,15:05:57,15:05:57,10,248200,181784,cap-wifi3,-58,6000000,216700000,0,0,00:07:05,00:00:00
|
||||
1788527159,15:05:59,15:05:59,27,895392,176224,cap-wifi3,-54,309700000,455800000,100936,106200,00:07:07,00:00:00
|
||||
1788527161,15:06:01,15:06:01,5,271768,3546664,cap-wifi3,-55,309700000,309700000,4800,18208,00:07:09,00:00:00
|
||||
1788527163,15:06:03,15:06:03,7,212384,57592,cap-wifi3,-55,309700000,309700000,4800,18208,00:07:11,00:00:00
|
||||
1788527165,15:06:05,15:06:05,6,119880,34152,cap-wifi3,-52,309700000,455800000,62952,10824,00:07:13,00:00:00
|
||||
1788527167,15:06:07,15:06:07,6,285512,62760,cap-wifi3,-48,309700000,412900000,2800,2960,00:07:15,00:00:00
|
||||
1788527169,15:06:09,15:06:09,18,135200,33432,cap-wifi3,-48,309700000,412900000,2800,2960,00:07:17,00:00:00
|
||||
1788527171,15:06:11,15:06:11,2,80168,35552,cap-wifi3,-50,309700000,433300000,2800,3360,00:07:19,00:00:00
|
||||
1788527173,15:06:13,15:06:13,6,263824,45616,cap-wifi3,-49,309700000,455800000,2800,2960,00:07:21,00:00:00
|
||||
1788527175,15:06:15,15:06:15,4,139448,79056,cap-wifi3,-49,309700000,455800000,2800,2960,00:07:23,00:00:00
|
||||
1788527177,15:06:17,15:06:17,7,125944,34792,cap-wifi3,-51,309700000,487500000,2800,2960,00:07:25,00:00:00
|
||||
1788527179,15:06:19,15:06:19,21,228896,34848,cap-wifi3,-51,309700000,516200000,3360,3552,00:07:27,00:00:00
|
||||
1788527181,15:06:21,15:06:21,6,91344,100616,cap-wifi3,-51,309700000,516200000,3360,3552,00:07:29,00:00:00
|
||||
1788527183,15:06:23,15:06:23,8,1161368,460544,cap-wifi3,-52,309700000,541700000,2800,3520,00:07:31,00:00:00
|
||||
1788527185,15:06:25,15:06:25,9,669424,189984,cap-wifi3,-51,309700000,433300000,2800,2960,00:07:33,00:00:00
|
||||
1788527187,15:06:27,15:06:27,8,220472,72928,cap-wifi3,-51,309700000,433300000,2800,2960,00:07:35,00:00:00
|
||||
1788527189,15:06:29,15:06:29,19,85696,19672,cap-wifi3,-50,309700000,455800000,11872,3328,00:07:37,00:00:00
|
||||
1788527191,15:06:31,15:06:31,7,88056,17368,cap-wifi3,-52,309700000,516200000,3360,3552,00:07:39,00:00:00
|
||||
1788527193,15:06:33,15:06:33,7,78232,15688,cap-wifi3,-52,309700000,516200000,3360,3552,00:07:41,00:00:00
|
||||
1788527195,15:06:35,15:06:35,7,77824,19488,cap-wifi3,-50,309700000,433300000,2800,2960,00:07:43,00:00:00
|
||||
1788527197,15:06:37,15:06:37,11,91648,29960,cap-wifi3,-56,309700000,455800000,2800,2960,00:07:45,00:00:00
|
||||
1788527199,15:06:39,15:06:39,19,479584,403096,cap-wifi3,-54,309700000,325000000,2800,2960,00:07:47,00:00:00
|
||||
1788527201,15:06:41,15:06:41,6,83504,18032,cap-wifi3,-54,309700000,325000000,2800,2960,00:07:49,00:00:00
|
||||
1788527203,15:06:43,15:06:43,8,94312,27480,cap-wifi3,-53,309700000,344100000,3248,2960,00:07:51,00:00:00
|
||||
1788527205,15:06:45,15:06:45,7,139816,113728,cap-wifi3,-52,309700000,390000000,2800,2960,00:07:53,00:00:00
|
||||
1788527207,15:06:47,15:06:47,8,77512,23304,cap-wifi3,-52,309700000,390000000,2800,2960,00:07:55,00:00:00
|
||||
1788527209,15:06:49,15:06:49,22,135424,86176,cap-wifi3,-53,309700000,412900000,2800,3360,00:07:57,00:00:00
|
||||
1788527211,15:06:51,15:06:52,6,80072,20128,cap-wifi3,-52,309700000,433300000,2800,2960,00:07:59,00:00:00
|
||||
1788527213,15:06:53,15:06:54,7,81544,23592,cap-wifi3,-52,309700000,433300000,2800,2960,00:08:01,00:00:00
|
||||
1788527216,15:06:56,15:06:56,8,118248,77704,cap-wifi3,-52,309700000,455800000,3808,3552,00:08:03,00:00:00
|
||||
1788527218,15:06:58,15:06:58,6,115296,40584,cap-wifi3,-54,309700000,487500000,2800,2960,00:08:05,00:00:00
|
||||
1788527220,15:07:00,15:07:00,36,72144,17920,cap-wifi3,-54,309700000,487500000,2800,2960,00:08:07,00:00:00
|
||||
1788527222,15:07:02,15:07:02,6,87272,19464,cap-wifi3,-53,309700000,516200000,3808,3920,00:08:09,00:00:00
|
||||
1788527224,15:07:04,15:07:04,7,341584,177744,cap-wifi3,-54,309700000,487500000,3488,2960,00:08:11,00:00:00
|
||||
1788527226,15:07:06,15:07:06,5,96008,29072,cap-wifi3,-54,309700000,487500000,3488,2960,00:08:14,00:00:00
|
||||
1788527228,15:07:08,15:07:08,9,88184,20760,cap-wifi3,-54,309700000,516200000,2800,2960,00:08:16,00:00:00
|
||||
1788527230,15:07:10,15:07:10,19,245616,37632,cap-wifi3,-54,309700000,541700000,3424,2960,00:08:18,00:00:00
|
||||
1788527232,15:07:12,15:07:12,7,136904,41680,cap-wifi3,-54,309700000,541700000,3424,2960,00:08:20,00:00:00
|
||||
1788527234,15:07:14,15:07:14,5,176432,193240,cap-wifi3,-54,309700000,573500000,3360,2960,00:08:22,00:00:00
|
||||
1788527236,15:07:16,15:07:16,5,668616,412672,cap-wifi3,-52,309700000,573500000,2800,2960,00:08:24,00:00:00
|
||||
1788527238,15:07:18,15:07:18,12,10040200,401976,cap-wifi3,-52,309700000,573500000,2800,2960,00:08:26,00:00:00
|
||||
1788527240,15:07:20,15:07:20,21,6694840,181608,cap-wifi3,-52,309700000,573500000,2800,2960,00:08:28,00:00:00
|
||||
1788527242,15:07:22,15:07:22,8,269904,95904,cap-wifi3,-52,309700000,573500000,2800,2960,00:08:30,00:00:00
|
||||
1788527244,15:07:24,15:07:24,10,97040,38768,cap-wifi3,-52,309700000,573500000,2800,2960,00:08:32,00:00:00
|
||||
1788527246,15:07:26,15:07:26,8,265536,57040,cap-wifi3,-51,309700000,573500000,2800,2960,00:08:34,00:00:00
|
||||
1788527248,15:07:28,15:07:28,8,79496,17712,cap-wifi3,-52,309700000,573500000,3360,8208,00:08:36,00:00:00
|
||||
1788527250,15:07:30,15:07:30,5,82824,26656,cap-wifi3,-52,309700000,573500000,3360,8208,00:08:38,00:00:00
|
||||
1788527252,15:07:32,15:07:32,8,79440,24984,cap-wifi3,-52,309700000,573500000,3360,7808,00:08:40,00:00:00
|
||||
1788527254,15:07:34,15:07:34,7,84240,17432,cap-wifi3,-52,309700000,573500000,5248,2960,00:08:42,00:00:00
|
||||
1788527256,15:07:36,15:07:36,15,74928,18888,cap-wifi3,-52,309700000,573500000,5248,2960,00:08:44,00:00:00
|
||||
1788527258,15:07:38,15:07:38,6,79856,17376,cap-wifi3,-51,309700000,573500000,3328,3520,00:08:46,00:00:00
|
||||
1788527260,15:07:40,15:07:40,5,91680,31720,cap-wifi3,-52,275300000,573500000,2800,2960,00:08:48,00:00:00
|
||||
1788527262,15:07:42,15:07:42,6,82760,21192,cap-wifi3,-52,275300000,573500000,2800,2960,00:08:50,00:00:00
|
||||
1788527264,15:07:44,15:07:44,7,91960,31680,cap-wifi3,-51,275300000,573500000,2800,2960,00:08:52,00:00:00
|
||||
1788527266,15:07:46,15:07:46,6,986832,256184,cap-wifi3,-51,275300000,573500000,3920,4144,00:08:54,00:00:00
|
||||
1788527268,15:07:48,15:07:48,7,204344,305984,cap-wifi3,-51,275300000,573500000,3920,4144,00:08:56,00:00:00
|
||||
1788527270,15:07:50,15:07:50,9,90376,25984,cap-wifi3,-52,258100000,573500000,2240,2368,00:08:58,00:00:00
|
||||
1788527272,15:07:52,15:07:52,5,106024,28632,cap-wifi3,-52,258100000,286800000,2800,2960,00:09:00,00:00:00
|
||||
1788527274,15:07:54,15:07:54,8,90816,27368,cap-wifi3,-52,258100000,286800000,2800,2960,00:09:02,00:00:00
|
||||
1788527276,15:07:56,15:07:56,9,80656,22520,cap-wifi3,-52,258100000,573500000,2800,2960,00:09:04,00:00:00
|
||||
1788527278,15:07:58,15:07:58,8,74792,15952,cap-wifi3,-54,258100000,412900000,2800,2960,00:09:06,00:00:00
|
||||
1788527280,15:08:00,15:08:00,9,97280,32008,cap-wifi3,-54,258100000,412900000,2800,2960,00:09:08,00:00:00
|
||||
1788527282,15:08:02,15:08:02,22,405584,131400,cap-wifi3,-55,258100000,412900000,2800,2960,00:09:10,00:00:00
|
||||
1788527284,15:08:04,15:08:04,15,99616,29344,cap-wifi3,-53,258100000,433300000,2800,2960,00:09:12,00:00:00
|
||||
1788527286,15:08:06,15:08:06,3,116040,34784,cap-wifi3,-53,258100000,433300000,2800,2960,00:09:14,00:00:00
|
||||
1788527288,15:08:08,15:08:08,6,263752,46256,cap-wifi3,-54,258100000,455800000,2800,3360,00:09:16,00:00:00
|
||||
1788527290,15:08:10,15:08:10,5,154688,74104,cap-wifi3,-54,258100000,487500000,2800,2960,00:09:18,00:00:00
|
||||
1788527292,15:08:12,15:08:13,4,437800,144600,cap-wifi3,-54,258100000,487500000,2800,2960,00:09:20,00:00:00
|
||||
1788527294,15:08:14,15:08:15,5,273784,69248,cap-wifi3,-56,258100000,390000000,3808,3552,00:09:22,00:00:00
|
||||
1788527297,15:08:17,15:08:17,4,110840,33232,cap-wifi3,-56,258100000,433300000,2800,2960,00:09:24,00:00:00
|
||||
1788527299,15:08:19,15:08:19,5,80584,34112,cap-wifi3,-55,258100000,455800000,3360,3552,00:09:26,00:00:00
|
||||
1788527301,15:08:21,15:08:21,5,89992,25208,cap-wifi3,-55,258100000,455800000,3360,3552,00:09:28,00:00:00
|
||||
1788527303,15:08:23,15:08:23,5,258520,122376,cap-wifi3,-56,258100000,433300000,2800,2960,00:09:30,00:00:00
|
||||
1788527305,15:08:25,15:08:25,8,484544,159104,cap-wifi3,-55,258100000,455800000,2800,2960,00:09:33,00:00:00
|
||||
1788527307,15:08:27,15:08:27,5,85832,21400,cap-wifi3,-55,258100000,455800000,2800,2960,00:09:35,00:00:00
|
||||
1788527309,15:08:29,15:08:29,7,86968,23984,cap-wifi3,-56,258100000,433300000,2800,2960,00:09:37,00:00:00
|
||||
1788527311,15:08:31,15:08:31,4,92848,27216,cap-wifi3,-57,258100000,455800000,2800,2960,00:09:39,00:00:00
|
||||
1788527313,15:08:33,15:08:33,13,88112,30272,cap-wifi3,-57,258100000,455800000,2800,2960,00:09:41,00:00:00
|
||||
1788527315,15:08:35,15:08:35,6,97568,38688,cap-wifi3,-57,258100000,433300000,2800,2960,00:09:43,00:00:00
|
||||
1788527317,15:08:37,15:08:37,7,69376,15424,cap-wifi3,-58,258100000,455800000,3360,3552,00:09:45,00:00:00
|
||||
1788527319,15:08:39,15:08:39,22,95672,27872,cap-wifi3,-58,258100000,455800000,3360,3552,00:09:47,00:00:00
|
||||
1788527321,15:08:41,15:08:41,20,81008,14064,cap-wifi3,-58,258100000,433300000,2800,2960,00:09:49,00:00:00
|
||||
1788527323,15:08:43,15:08:43,9,79480,19824,cap-wifi3,-59,258100000,325000000,3328,2960,00:09:51,00:00:00
|
||||
1788527325,15:08:45,15:08:45,11,83080,28888,cap-wifi3,-59,258100000,325000000,3328,2960,00:09:53,00:00:00
|
||||
1788527327,15:08:47,15:08:47,7,77224,19568,cap-wifi3,-59,258100000,390000000,3360,3952,00:09:55,00:00:00
|
||||
1788527329,15:08:49,15:08:49,20,87976,23544,cap-wifi3,-58,258100000,412900000,2800,2960,00:09:57,00:00:00
|
||||
1788527331,15:08:51,15:08:51,6,99288,43960,cap-wifi3,-58,258100000,412900000,2800,2960,00:09:59,00:00:00
|
||||
1788527333,15:08:53,15:08:53,4,78784,16504,cap-wifi3,-58,258100000,390000000,3808,3552,00:10:01,00:00:00
|
||||
1788527335,15:08:55,15:08:55,8,100664,43024,cap-wifi3,-52,6000000,412900000,3360,3552,00:10:03,00:00:00
|
||||
1788527337,15:08:57,15:08:57,6,75392,68792,cap-wifi3,-52,6000000,412900000,3360,3552,00:10:05,00:00:00
|
||||
1788527339,15:08:59,15:08:59,18,84648,23296,cap-wifi3,-52,258100000,433300000,2800,3360,00:10:07,00:00:00
|
||||
1788527341,15:09:01,15:09:01,5,146608,49608,cap-wifi3,-53,258100000,455800000,2800,2960,00:10:09,00:00:00
|
||||
1788527343,15:09:03,15:09:03,6,271512,52760,cap-wifi3,-53,258100000,455800000,2800,2960,00:10:11,00:00:00
|
||||
1788527345,15:09:05,15:09:05,6,400696,3660640,cap-wifi3,-56,258100000,433300000,2800,2960,00:10:13,00:00:00
|
||||
1788527347,15:09:07,15:09:07,6,131696,25096,cap-wifi3,-55,258100000,390000000,2800,2960,00:10:15,00:00:00
|
||||
1788527349,15:09:09,15:09:09,19,213416,92736,cap-wifi3,-55,258100000,390000000,2800,2960,00:10:17,00:00:00
|
||||
1788527351,15:09:11,15:09:11,6,319104,63216,cap-wifi3,-52,6000000,309700000,2800,2960,00:10:19,00:00:00
|
||||
1788527353,15:09:13,15:09:13,8,153896,48144,cap-wifi3,-54,258100000,344100000,3360,3552,00:10:21,00:00:00
|
||||
1788527355,15:09:15,15:09:15,5,190952,58360,cap-wifi3,-54,258100000,344100000,3360,3552,00:10:23,00:00:00
|
||||
1788527357,15:09:17,15:09:17,7,232928,149512,cap-wifi3,-55,258100000,390000000,4080,6912,00:10:25,00:00:00
|
||||
1788527359,15:09:19,15:09:20,19,108000,26560,cap-wifi3,-56,258100000,412900000,3360,3552,00:10:27,00:00:00
|
||||
1788527361,15:09:21,15:09:22,10,2754208,213840,cap-wifi3,-56,258100000,412900000,3360,3552,00:10:29,00:00:00
|
||||
1788527364,15:09:24,15:09:24,7,314040,73608,cap-wifi3,-55,258100000,216700000,15040,7600,00:10:31,00:00:00
|
||||
1788527366,15:09:26,15:09:26,5,485488,100744,cap-wifi3,-54,258100000,229400000,3304,6992,00:10:33,00:00:00
|
||||
1788527368,15:09:28,15:09:28,6,123816,28072,cap-wifi3,-54,258100000,229400000,3304,6992,00:10:35,00:00:00
|
||||
1788527370,15:09:30,15:09:30,19,124944,35616,cap-wifi3,-56,258100000,433300000,2800,2960,00:10:37,00:00:00
|
||||
1788527372,15:09:32,15:09:32,5,171352,44496,cap-wifi3,-54,258100000,433300000,4928,0,00:10:39,00:00:00
|
||||
1788527374,15:09:34,15:09:34,6,166240,43784,cap-wifi3,-54,6000000,433300000,2800,0,00:10:42,00:00:00
|
||||
1788527376,15:09:36,15:09:36,6,150760,49464,cap-wifi3,-54,6000000,433300000,2800,0,00:10:44,00:00:00
|
||||
1788527378,15:09:38,15:09:38,10,185768,47120,cap-wifi3,-55,258100000,433300000,2800,6496,00:10:46,00:00:00
|
||||
1788527380,15:09:40,15:09:40,18,190032,49512,cap-wifi3,-54,258100000,433300000,2240,0,00:10:48,00:00:00
|
||||
1788527382,15:09:42,15:09:42,8,97808,22304,cap-wifi3,-54,258100000,433300000,2240,0,00:10:50,00:00:00
|
||||
1788527384,15:09:44,15:09:44,8,148400,53672,cap-wifi3,-54,6000000,433300000,13464,368,00:10:52,00:00:00
|
||||
1788527386,15:09:46,15:09:46,2,97568,37160,cap-wifi3,-54,258100000,455800000,2800,2960,00:10:54,00:00:00
|
||||
1788527388,15:09:48,15:09:48,4,76456,19280,cap-wifi3,-54,258100000,455800000,2800,2960,00:10:56,00:00:00
|
||||
1788527390,15:09:50,15:09:50,9,74488,14496,cap-wifi3,-54,258100000,390000000,2928,0,00:10:58,00:00:00
|
||||
1788527392,15:09:52,15:09:52,9,81688,114120,cap-wifi3,-54,258100000,390000000,2240,0,00:11:00,00:00:00
|
||||
1788527394,15:09:54,15:09:54,11,139472,94312,cap-wifi3,-54,258100000,390000000,2240,0,00:11:02,00:00:00
|
||||
1788527396,15:09:56,15:09:56,8,99376,34800,cap-wifi3,-54,258100000,390000000,2240,0,00:11:04,00:00:00
|
||||
1788527398,15:09:58,15:09:58,7,95624,20936,cap-wifi3,-54,258100000,390000000,2800,0,00:11:06,00:00:00
|
||||
1788527400,15:10:00,15:10:00,11,75688,17080,cap-wifi3,-54,258100000,390000000,2800,0,00:11:08,00:00:00
|
||||
1788527402,15:10:02,15:10:02,8,194448,59984,cap-wifi3,-54,258100000,390000000,2240,0,00:11:10,00:00:00
|
||||
1788527404,15:10:04,15:10:04,5,87848,23392,cap-wifi3,-54,258100000,390000000,5040,0,00:11:12,00:00:00
|
||||
1788527406,15:10:06,15:10:06,4,102440,31824,cap-wifi3,-54,258100000,390000000,5040,0,00:11:14,00:00:00
|
||||
1788527408,15:10:08,15:10:08,7,140352,49312,cap-wifi3,-54,258100000,390000000,2800,0,00:11:16,00:00:00
|
||||
1788527410,15:10:10,15:10:10,20,81216,18128,cap-wifi3,-53,258100000,390000000,2688,0,00:11:18,00:00:00
|
||||
1788527412,15:10:12,15:10:12,8,223088,23248,cap-wifi3,-53,258100000,390000000,2688,0,00:11:20,00:00:00
|
||||
1788527414,15:10:14,15:10:14,7,139784,51840,cap-wifi3,-54,258100000,390000000,2240,0,00:11:22,00:00:00
|
||||
1788527415,15:10:15,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-dauerping/sampler-ipad.out
Normal file
1
docs/messdaten/tp25-runde-dauerping/sampler-ipad.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-ipad.csv (Takt 2s, Client CA:B8:D4:3F:8A:81)
|
||||
@@ -0,0 +1,2 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
15:10:15 Verbindung weg -- neuer Versuch in 2 s
|
||||
130
docs/messdaten/tp25-runde-dauerping/sampler-iphone.csv
Normal file
130
docs/messdaten/tp25-runde-dauerping/sampler-iphone.csv
Normal file
@@ -0,0 +1,130 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788527155,15:05:55,RECONNECT
|
||||
1788527159,15:05:59,15:05:59,26,895392,176224,cap-wifi3,-63,154900000,309700000,15624,7104,00:13:12,00:00:00
|
||||
1788527161,15:06:01,15:06:01,12,271768,3546664,cap-wifi3,-61,154900000,325000000,4576,3552,00:13:14,00:00:00
|
||||
1788527163,15:06:03,15:06:03,11,212384,57592,cap-wifi3,-61,154900000,325000000,4576,3552,00:13:16,00:00:00
|
||||
1788527165,15:06:05,15:06:05,6,119880,34152,cap-wifi3,-59,154900000,325000000,2240,0,00:13:18,00:00:00
|
||||
1788527167,15:06:07,15:06:07,6,285512,62760,cap-wifi3,-58,154900000,325000000,3360,0,00:13:20,00:00:00
|
||||
1788527169,15:06:09,15:06:09,18,135200,33432,cap-wifi3,-58,154900000,325000000,3360,0,00:13:22,00:00:00
|
||||
1788527171,15:06:11,15:06:11,2,80168,35552,cap-wifi3,-58,154900000,325000000,2240,0,00:13:24,00:00:00
|
||||
1788527173,15:06:13,15:06:13,6,263824,45616,cap-wifi3,-57,154900000,325000000,3360,0,00:13:26,00:00:00
|
||||
1788527175,15:06:15,15:06:15,4,139448,79056,cap-wifi3,-57,154900000,325000000,3360,0,00:13:28,00:00:00
|
||||
1788527177,15:06:17,15:06:17,7,125944,34792,cap-wifi3,-58,154900000,325000000,1680,0,00:13:30,00:00:00
|
||||
1788527179,15:06:19,15:06:19,21,228896,34848,cap-wifi3,-60,154900000,325000000,0,0,00:13:32,00:00:00
|
||||
1788527181,15:06:21,15:06:21,6,91344,100616,cap-wifi3,-60,154900000,325000000,0,0,00:13:34,00:00:00
|
||||
1788527183,15:06:23,15:06:23,8,1161368,460544,cap-wifi3,-62,154900000,325000000,4368,4592,00:13:36,00:00:00
|
||||
1788527185,15:06:25,15:06:25,9,669424,189984,cap-wifi3,-56,154900000,344100000,2800,3712,00:13:38,00:00:00
|
||||
1788527187,15:06:27,15:06:27,8,220472,72928,cap-wifi3,-56,154900000,344100000,2800,3712,00:13:40,00:00:00
|
||||
1788527189,15:06:29,15:06:29,19,85696,19672,cap-wifi3,-60,154900000,325000000,4880,10808,00:13:42,00:00:00
|
||||
1788527191,15:06:31,15:06:31,7,80008,30360,cap-wifi3,-48,154900000,309700000,4080,4992,00:13:44,00:00:00
|
||||
1788527193,15:06:33,15:06:33,7,85392,35808,cap-wifi3,-48,154900000,309700000,4080,4992,00:13:46,00:00:00
|
||||
1788527195,15:06:35,15:06:35,7,81000,33304,cap-wifi3,-52,154900000,325000000,2800,2960,00:13:48,00:00:00
|
||||
1788527197,15:06:37,15:06:37,11,80840,16392,cap-wifi3,-60,154900000,344100000,2800,2960,00:13:50,00:00:00
|
||||
1788527199,15:06:39,15:06:39,19,479584,403096,cap-wifi3,-60,154900000,344100000,2800,2960,00:13:52,00:00:00
|
||||
1788527201,15:06:41,15:06:41,6,83504,18032,cap-wifi3,-54,154900000,390000000,2800,2960,00:13:54,00:00:00
|
||||
1788527203,15:06:43,15:06:43,8,94312,27480,cap-wifi3,-52,154900000,412900000,3776,3552,00:13:56,00:00:00
|
||||
1788527205,15:06:45,15:06:45,7,139816,113728,cap-wifi3,-54,154900000,455800000,3360,3552,00:13:58,00:00:00
|
||||
1788527207,15:06:47,15:06:47,8,77512,23304,cap-wifi3,-54,154900000,455800000,3360,3552,00:14:00,00:00:00
|
||||
1788527209,15:06:49,15:06:49,22,135424,86176,cap-wifi3,-53,206500000,455800000,2800,2960,00:14:02,00:00:00
|
||||
1788527211,15:06:51,15:06:51,6,80072,20128,cap-wifi3,-53,12000000,516200000,2800,2960,00:14:04,00:00:00
|
||||
1788527213,15:06:53,15:06:53,7,81544,23592,cap-wifi3,-53,12000000,516200000,2800,2960,00:14:06,00:00:00
|
||||
1788527215,15:06:55,15:06:55,8,118248,77704,cap-wifi3,-54,206500000,573500000,3248,3552,00:14:08,00:00:00
|
||||
1788527217,15:06:57,15:06:58,6,115296,40584,cap-wifi3,-53,206500000,573500000,3360,3552,00:14:10,00:00:00
|
||||
1788527219,15:06:59,15:07:00,36,72144,17920,cap-wifi3,-53,206500000,573500000,3360,3552,00:14:12,00:00:00
|
||||
1788527221,15:07:01,15:07:02,6,87272,19464,cap-wifi3,-55,206500000,412900000,2800,2960,00:14:14,00:00:00
|
||||
1788527224,15:07:04,15:07:04,7,341584,177744,cap-wifi3,-55,206500000,455800000,2800,2960,00:14:16,00:00:00
|
||||
1788527226,15:07:06,15:07:06,5,96008,29072,cap-wifi3,-55,206500000,455800000,2800,2960,00:14:18,00:00:00
|
||||
1788527228,15:07:08,15:07:08,9,88184,20760,cap-wifi3,-54,206500000,455800000,2800,2960,00:14:21,00:00:00
|
||||
1788527230,15:07:10,15:07:10,19,245616,37632,cap-wifi3,-55,206500000,433300000,2800,2960,00:14:23,00:00:00
|
||||
1788527232,15:07:12,15:07:12,7,136904,41680,cap-wifi3,-55,206500000,433300000,2800,2960,00:14:25,00:00:00
|
||||
1788527234,15:07:14,15:07:14,5,176432,193240,cap-wifi3,-54,206500000,455800000,2800,2960,00:14:27,00:00:00
|
||||
1788527236,15:07:16,15:07:16,5,668616,412672,cap-wifi3,-53,206500000,487500000,3360,3552,00:14:29,00:00:00
|
||||
1788527238,15:07:18,15:07:18,12,10040200,401976,cap-wifi3,-53,206500000,487500000,3360,3552,00:14:31,00:00:00
|
||||
1788527240,15:07:20,15:07:20,21,6694840,181608,cap-wifi3,-53,6000000,516200000,2800,3336,00:14:33,00:00:00
|
||||
1788527242,15:07:22,15:07:22,7,269904,95904,cap-wifi3,-53,206500000,487500000,2800,2960,00:14:35,00:00:00
|
||||
1788527244,15:07:24,15:07:24,6,97040,38768,cap-wifi3,-53,206500000,487500000,2800,2960,00:14:37,00:00:00
|
||||
1788527246,15:07:26,15:07:26,6,265536,57040,cap-wifi3,-53,206500000,516200000,3360,3552,00:14:39,00:00:00
|
||||
1788527248,15:07:28,15:07:28,8,79496,17712,cap-wifi3,-52,206500000,487500000,2800,2960,00:14:41,00:00:00
|
||||
1788527250,15:07:30,15:07:30,20,82824,26656,cap-wifi3,-52,206500000,487500000,2800,2960,00:14:43,00:00:00
|
||||
1788527252,15:07:32,15:07:32,8,79440,24984,cap-wifi3,-53,206500000,516200000,2800,2960,00:14:45,00:00:00
|
||||
1788527254,15:07:34,15:07:34,7,84240,17432,cap-wifi3,-51,206500000,541700000,4560,3552,00:14:47,00:00:00
|
||||
1788527256,15:07:36,15:07:36,15,74928,18888,cap-wifi3,-51,206500000,541700000,4560,3552,00:14:49,00:00:00
|
||||
1788527258,15:07:38,15:07:38,6,79856,17376,cap-wifi3,-51,206500000,573500000,2800,2960,00:14:51,00:00:00
|
||||
1788527260,15:07:40,15:07:40,5,91680,31720,cap-wifi3,-51,206500000,573500000,2800,2960,00:14:53,00:00:00
|
||||
1788527262,15:07:42,15:07:42,6,82760,21192,cap-wifi3,-51,206500000,573500000,2800,2960,00:14:55,00:00:00
|
||||
1788527264,15:07:44,15:07:44,7,91960,31680,cap-wifi3,-51,206500000,573500000,2800,2960,00:14:57,00:00:00
|
||||
1788527266,15:07:46,15:07:46,6,986832,256184,cap-wifi3,-51,206500000,573500000,2800,2960,00:14:59,00:00:00
|
||||
1788527268,15:07:48,15:07:48,7,204344,305984,cap-wifi3,-51,206500000,573500000,2800,2960,00:15:01,00:00:00
|
||||
1788527270,15:07:50,15:07:50,9,90376,25984,cap-wifi3,-51,206500000,516200000,3248,3328,00:15:03,00:00:00
|
||||
1788527272,15:07:52,15:07:52,5,106024,28632,cap-wifi3,-50,12000000,516200000,3808,3920,00:15:05,00:00:00
|
||||
1788527274,15:07:54,15:07:54,8,86752,24568,cap-wifi3,-50,12000000,516200000,3808,3920,00:15:07,00:00:00
|
||||
1788527276,15:07:56,15:07:56,9,68568,17040,cap-wifi3,-51,206500000,541700000,2800,2960,00:15:09,00:00:00
|
||||
1788527278,15:07:58,15:07:58,8,104792,53200,cap-wifi3,-51,206500000,390000000,3360,3552,00:15:11,00:00:00
|
||||
1788527280,15:08:00,15:08:00,9,304368,102592,cap-wifi3,-51,206500000,390000000,3360,3552,00:15:13,00:00:00
|
||||
1788527282,15:08:02,15:08:02,22,74432,14824,cap-wifi3,-50,24000000,412900000,2800,2960,00:15:15,00:00:00
|
||||
1788527284,15:08:04,15:08:04,15,99616,29344,cap-wifi3,-51,154900000,433300000,3360,3552,00:15:17,00:00:00
|
||||
1788527286,15:08:06,15:08:06,3,116040,34784,cap-wifi3,-51,154900000,433300000,3360,3552,00:15:19,00:00:00
|
||||
1788527288,15:08:08,15:08:08,6,263752,46256,cap-wifi3,-50,24000000,455800000,2800,2960,00:15:21,00:00:00
|
||||
1788527290,15:08:10,15:08:10,5,154688,74104,cap-wifi3,-51,154900000,487500000,2800,2960,00:15:23,00:00:00
|
||||
1788527292,15:08:12,15:08:12,4,437800,144600,cap-wifi3,-51,154900000,487500000,2800,2960,00:15:25,00:00:00
|
||||
1788527294,15:08:14,15:08:14,5,273784,69248,cap-wifi3,-50,154900000,516200000,3248,3552,00:15:27,00:00:00
|
||||
1788527296,15:08:16,15:08:16,4,110840,33232,cap-wifi3,-50,154900000,541700000,2800,2960,00:15:29,00:00:00
|
||||
1788527298,15:08:18,15:08:18,5,80584,34112,cap-wifi3,-50,154900000,541700000,2800,2960,00:15:31,00:00:00
|
||||
1788527300,15:08:20,15:08:20,5,89992,25208,cap-wifi3,-51,206500000,573500000,3360,3928,00:15:33,00:00:00
|
||||
1788527302,15:08:22,15:08:23,5,258520,122376,cap-wifi3,-50,206500000,573500000,2800,2960,00:15:35,00:00:00
|
||||
1788527304,15:08:24,15:08:25,8,484544,159104,cap-wifi3,-50,206500000,573500000,2800,2960,00:15:37,00:00:00
|
||||
1788527306,15:08:26,15:08:27,5,85832,21400,cap-wifi3,-52,206500000,573500000,6336,6536,00:15:39,00:00:00
|
||||
1788527309,15:08:29,15:08:29,7,86968,23984,cap-wifi3,-51,206500000,573500000,4384,5704,00:15:41,00:00:00
|
||||
1788527311,15:08:31,15:08:31,4,92848,27216,cap-wifi3,-50,24000000,573500000,3360,2960,00:15:44,00:00:00
|
||||
1788527313,15:08:33,15:08:33,13,88112,30272,cap-wifi3,-50,24000000,573500000,3360,2960,00:15:46,00:00:00
|
||||
1788527315,15:08:35,15:08:35,6,97568,38688,cap-wifi3,-51,172100000,573500000,2800,2960,00:15:48,00:00:00
|
||||
1788527317,15:08:37,15:08:37,7,69376,15424,cap-wifi3,-51,172100000,573500000,2800,2960,00:15:50,00:00:00
|
||||
1788527319,15:08:39,15:08:39,7,95672,27872,cap-wifi3,-51,172100000,573500000,2800,2960,00:15:52,00:00:00
|
||||
1788527321,15:08:41,15:08:41,8,81008,14064,cap-wifi3,-52,172100000,573500000,5648,5744,00:15:54,00:00:00
|
||||
1788527323,15:08:43,15:08:43,8,79480,19824,cap-wifi3,-51,172100000,573500000,3360,3552,00:15:56,00:00:00
|
||||
1788527325,15:08:45,15:08:45,6,83080,28888,cap-wifi3,-51,172100000,573500000,3360,3552,00:15:58,00:00:00
|
||||
1788527327,15:08:47,15:08:47,9,77224,19568,cap-wifi3,-52,172100000,573500000,2800,2960,00:16:00,00:00:00
|
||||
1788527329,15:08:49,15:08:49,7,87976,23544,cap-wifi3,-53,172100000,573500000,2800,2960,00:16:02,00:00:00
|
||||
1788527331,15:08:51,15:08:51,6,99288,43960,cap-wifi3,-53,172100000,573500000,2800,2960,00:16:04,00:00:00
|
||||
1788527333,15:08:53,15:08:53,4,78784,16504,cap-wifi3,-56,172100000,455800000,4416,4840,00:16:06,00:00:00
|
||||
1788527335,15:08:55,15:08:55,8,100664,43024,cap-wifi3,-56,154900000,344100000,3408,3656,00:16:08,00:00:00
|
||||
1788527337,15:08:57,15:08:57,6,75392,68792,cap-wifi3,-56,154900000,344100000,3408,3656,00:16:10,00:00:00
|
||||
1788527339,15:08:59,15:08:59,18,84648,23296,cap-wifi3,-53,154900000,344100000,3936,4496,00:16:12,00:00:00
|
||||
1788527341,15:09:01,15:09:01,5,146608,49608,cap-wifi3,-53,154900000,325000000,3408,3656,00:16:14,00:00:00
|
||||
1788527343,15:09:03,15:09:03,6,271512,52760,cap-wifi3,-53,154900000,325000000,3408,3656,00:16:16,00:00:00
|
||||
1788527345,15:09:05,15:09:05,6,400696,3660640,cap-wifi3,-51,154900000,344100000,3408,2960,00:16:18,00:00:00
|
||||
1788527347,15:09:07,15:09:07,6,131696,25096,cap-wifi3,-58,154900000,325000000,4896,5048,00:16:20,00:00:00
|
||||
1788527349,15:09:09,15:09:09,19,213416,92736,cap-wifi3,-58,154900000,325000000,4896,5048,00:16:22,00:00:00
|
||||
1788527351,15:09:11,15:09:11,6,319104,63216,cap-wifi3,-50,206500000,344100000,3408,3656,00:16:24,00:00:00
|
||||
1788527353,15:09:13,15:09:13,8,153896,48144,cap-wifi3,-46,206500000,412900000,3968,4248,00:16:26,00:00:00
|
||||
1788527355,15:09:15,15:09:15,5,190952,58360,cap-wifi3,-46,206500000,412900000,3968,4248,00:16:28,00:00:00
|
||||
1788527357,15:09:17,15:09:17,7,232928,149512,cap-wifi3,-59,206500000,433300000,3408,3656,00:16:30,00:00:00
|
||||
1788527359,15:09:19,15:09:19,19,108000,26560,cap-wifi3,-60,206500000,344100000,3856,4032,00:16:32,00:00:00
|
||||
1788527361,15:09:21,15:09:21,10,2754208,213840,cap-wifi3,-60,206500000,344100000,3856,4032,00:16:34,00:00:00
|
||||
1788527363,15:09:23,15:09:24,7,314040,73608,cap-wifi3,-60,206500000,325000000,5488,11504,00:16:36,00:00:00
|
||||
1788527365,15:09:25,15:09:26,5,485488,100744,cap-wifi3,-60,206500000,344100000,4736,5096,00:16:38,00:00:00
|
||||
1788527367,15:09:28,15:09:28,6,123816,28072,cap-wifi3,-60,206500000,344100000,4736,5096,00:16:40,00:00:00
|
||||
1788527370,15:09:30,15:09:30,19,124944,35616,cap-wifi3,-61,154900000,292500000,2800,0,00:16:42,00:00:00
|
||||
1788527372,15:09:32,15:09:32,5,171352,44496,cap-wifi3,-60,137600000,325000000,3136,368,00:16:44,00:00:00
|
||||
1788527374,15:09:34,15:09:34,6,166240,43784,cap-wifi3,-60,137600000,325000000,3136,368,00:16:47,00:00:00
|
||||
1788527376,15:09:36,15:09:36,6,150760,49464,cap-wifi3,-55,172100000,309700000,15056,42256,00:16:49,00:00:00
|
||||
1788527378,15:09:38,15:09:38,10,185768,47120,cap-wifi3,-62,172100000,325000000,3408,3656,00:16:51,00:00:00
|
||||
1788527380,15:09:40,15:09:40,18,190032,49512,cap-wifi3,-62,172100000,344100000,3408,3656,00:16:53,00:00:00
|
||||
1788527382,15:09:42,15:09:42,8,97808,22304,cap-wifi3,-62,172100000,344100000,3408,3656,00:16:55,00:00:00
|
||||
1788527384,15:09:44,15:09:44,8,148400,53672,cap-wifi3,-63,12000000,412900000,3856,10808,00:16:57,00:00:00
|
||||
1788527386,15:09:46,15:09:46,2,97568,37160,cap-wifi3,-62,172100000,412900000,2800,2960,00:16:59,00:00:00
|
||||
1788527388,15:09:48,15:09:48,4,76456,19280,cap-wifi3,-62,172100000,412900000,2800,2960,00:17:01,00:00:00
|
||||
1788527390,15:09:50,15:09:50,17,74488,14496,cap-wifi3,-64,172100000,325000000,7688,7440,00:17:03,00:00:00
|
||||
1788527392,15:09:52,15:09:52,4,81688,114120,cap-wifi3,-62,172100000,344100000,3888,4768,00:17:05,00:00:00
|
||||
1788527394,15:09:54,15:09:54,8,139472,94312,cap-wifi3,-62,172100000,344100000,3888,4768,00:17:07,00:00:00
|
||||
1788527396,15:09:56,15:09:56,4,99376,34800,cap-wifi3,-63,172100000,412900000,3328,3424,00:17:09,00:00:00
|
||||
1788527398,15:09:58,15:09:58,5,95624,20936,cap-wifi3,-62,172100000,412900000,3360,3552,00:17:11,00:00:00
|
||||
1788527400,15:10:00,15:10:00,11,75688,17080,cap-wifi3,-62,172100000,412900000,3360,3552,00:17:13,00:00:00
|
||||
1788527402,15:10:02,15:10:02,8,194448,59984,cap-wifi3,-61,172100000,390000000,2800,0,00:17:15,00:00:00
|
||||
1788527404,15:10:04,15:10:04,5,87848,23392,cap-wifi3,-60,172100000,390000000,2128,368,00:17:17,00:00:00
|
||||
1788527406,15:10:06,15:10:06,4,102440,31824,cap-wifi3,-60,172100000,390000000,2128,368,00:17:19,00:00:00
|
||||
1788527408,15:10:08,15:10:08,7,140352,49312,cap-wifi3,-61,172100000,390000000,2240,0,00:17:21,00:00:00
|
||||
1788527410,15:10:10,15:10:10,20,88352,30376,cap-wifi3,-61,137600000,390000000,3248,0,00:17:23,00:00:00
|
||||
1788527412,15:10:12,15:10:12,8,124328,46808,cap-wifi3,-61,137600000,390000000,3248,0,00:17:25,00:00:00
|
||||
1788527414,15:10:14,15:10:14,7,84784,23040,cap-wifi3,-61,137600000,390000000,2800,0,00:17:27,00:00:00
|
||||
1788527415,15:10:15,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-dauerping/sampler-iphone.out
Normal file
1
docs/messdaten/tp25-runde-dauerping/sampler-iphone.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-iphone.csv (Takt 2s, Client F2:62:4C:E1:AF:09)
|
||||
@@ -0,0 +1,9 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory
|
||||
Permission denied, please try again.
|
||||
ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory
|
||||
Permission denied, please try again.
|
||||
ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory
|
||||
admin@10.40.99.1: Permission denied (publickey,password).
|
||||
15:05:55 Verbindung weg -- neuer Versuch in 2 s
|
||||
15:10:15 Verbindung weg -- neuer Versuch in 2 s
|
||||
130
docs/messdaten/tp25-runde-dauerping/sampler-macbook.csv
Normal file
130
docs/messdaten/tp25-runde-dauerping/sampler-macbook.csv
Normal file
@@ -0,0 +1,130 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788527155,15:05:55,15:05:55,26,215104,164208,cap-wifi3,-59,344100000,455800000,1108432,1179376,00:06:21,00:00:00
|
||||
1788527157,15:05:57,15:05:57,10,230144,103104,cap-wifi3,-58,412900000,455800000,376304,298808,00:06:23,00:00:00
|
||||
1788527159,15:05:59,15:05:59,27,997352,175928,cap-wifi3,-58,412900000,455800000,376304,298808,00:06:25,00:00:00
|
||||
1788527161,15:06:01,15:06:01,5,496624,179032,cap-wifi3,-60,455800000,455800000,2283840,1594024,00:06:27,00:00:00
|
||||
1788527163,15:06:03,15:06:03,7,112272,25664,cap-wifi3,-61,455800000,344100000,71224,20160,00:06:30,00:00:00
|
||||
1788527165,15:06:05,15:06:06,6,353280,101384,cap-wifi3,-61,455800000,344100000,71224,20160,00:06:32,00:00:00
|
||||
1788527167,15:06:08,15:06:08,6,137256,37360,cap-wifi3,-63,344100000,344100000,1205496,674488,00:06:34,00:00:00
|
||||
1788527170,15:06:10,15:06:10,18,373144,1851168,cap-wifi3,-62,344100000,455800000,625632,2151904,00:06:36,00:00:00
|
||||
1788527172,15:06:12,15:06:12,2,79504,15688,cap-wifi3,-62,344100000,455800000,625632,2151904,00:06:38,00:00:00
|
||||
1788527174,15:06:14,15:06:14,6,215576,3512448,cap-wifi3,-61,412900000,412900000,720568,420128,00:06:40,00:00:00
|
||||
1788527176,15:06:16,15:06:16,4,170440,37144,cap-wifi3,-63,344100000,309700000,1040816,801272,00:06:42,00:00:00
|
||||
1788527178,15:06:18,15:06:18,7,187488,1860720,cap-wifi3,-63,344100000,309700000,1040816,801272,00:06:44,00:00:00
|
||||
1788527180,15:06:20,15:06:20,21,418584,3563696,cap-wifi3,-63,412900000,275300000,496424,2170904,00:06:46,00:00:00
|
||||
1788527182,15:06:22,15:06:22,6,91920,32648,cap-wifi3,-64,412900000,275300000,539264,491728,00:06:48,00:00:00
|
||||
1788527184,15:06:24,15:06:24,8,229720,340576,cap-wifi3,-64,412900000,275300000,539264,491728,00:06:50,00:00:00
|
||||
1788527186,15:06:26,15:06:26,11,324680,63616,cap-wifi3,-63,412900000,344100000,768576,455936,00:06:52,00:00:00
|
||||
1788527188,15:06:28,15:06:28,7,323944,57120,cap-wifi3,-67,344100000,309700000,515712,335688,00:06:54,00:00:00
|
||||
1788527190,15:06:30,15:06:30,7,89536,26840,cap-wifi3,-67,344100000,309700000,515712,335688,00:06:56,00:00:00
|
||||
1788527192,15:06:32,15:06:32,6,88056,17368,cap-wifi3,-62,344100000,344100000,546008,422232,00:06:58,00:00:00
|
||||
1788527194,15:06:34,15:06:34,6,78232,15688,cap-wifi3,-62,412900000,344100000,403184,296104,00:07:00,00:00:00
|
||||
1788527196,15:06:36,15:06:36,6,77824,19488,cap-wifi3,-62,412900000,344100000,403184,296104,00:07:02,00:00:00
|
||||
1788527198,15:06:38,15:06:38,8,91648,29960,cap-wifi3,-62,412900000,344100000,665616,416832,00:07:04,00:00:00
|
||||
1788527200,15:06:40,15:06:40,6,479584,403096,cap-wifi3,-63,344100000,275300000,1141120,678936,00:07:06,00:00:00
|
||||
1788527202,15:06:42,15:06:42,5,83504,18032,cap-wifi3,-63,344100000,275300000,1141120,678936,00:07:08,00:00:00
|
||||
1788527204,15:06:44,15:06:44,12,94312,27480,cap-wifi3,-64,344100000,344100000,511368,235632,00:07:10,00:00:00
|
||||
1788527206,15:06:46,15:06:46,11,104592,25288,cap-wifi3,-65,412900000,309700000,882160,555064,00:07:12,00:00:00
|
||||
1788527208,15:06:48,15:06:48,9,77672,15824,cap-wifi3,-65,344100000,275300000,71864,300800,00:07:14,00:00:00
|
||||
1788527210,15:06:50,15:06:50,8,130536,65520,cap-wifi3,-65,344100000,275300000,71864,300800,00:07:16,00:00:00
|
||||
1788527212,15:06:52,15:06:52,6,179704,71320,cap-wifi3,-65,309700000,344100000,612752,430056,00:07:19,00:00:00
|
||||
1788527214,15:06:54,15:06:54,7,150376,135320,cap-wifi3,-64,309700000,309700000,556768,426072,00:07:21,00:00:00
|
||||
1788527216,15:06:56,15:06:57,7,180576,117008,cap-wifi3,-64,309700000,309700000,556768,426072,00:07:23,00:00:00
|
||||
1788527219,15:06:59,15:06:59,9,81080,17128,cap-wifi3,-65,344100000,412900000,687448,312096,00:07:25,00:00:00
|
||||
1788527221,15:07:01,15:07:01,6,267072,71760,cap-wifi3,-64,412900000,309700000,1456168,436368,00:07:27,00:00:00
|
||||
1788527223,15:07:03,15:07:03,7,229720,79648,cap-wifi3,-64,412900000,309700000,1456168,436368,00:07:29,00:00:00
|
||||
1788527225,15:07:05,15:07:05,12,613656,242376,cap-wifi3,-65,412900000,344100000,643504,429016,00:07:31,00:00:00
|
||||
1788527227,15:07:07,15:07:07,9,136256,40080,cap-wifi3,-64,412900000,344100000,619904,419664,00:07:33,00:00:00
|
||||
1788527229,15:07:09,15:07:09,11,113040,49536,cap-wifi3,-64,412900000,344100000,619904,419664,00:07:35,00:00:00
|
||||
1788527231,15:07:11,15:07:11,7,85432,17976,cap-wifi3,-64,412900000,309700000,1090376,738848,00:07:37,00:00:00
|
||||
1788527233,15:07:13,15:07:13,5,81944,26128,cap-wifi3,-65,412900000,344100000,947112,611480,00:07:39,00:00:00
|
||||
1788527235,15:07:15,15:07:15,5,954264,397144,cap-wifi3,-65,412900000,344100000,947112,611480,00:07:41,00:00:00
|
||||
1788527237,15:07:17,15:07:17,12,3430312,274976,cap-wifi3,-64,412900000,344100000,1132848,732040,00:07:43,00:00:00
|
||||
1788527239,15:07:19,15:07:19,21,5774264,240088,cap-wifi3,-63,412900000,344100000,886256,615784,00:07:45,00:00:00
|
||||
1788527241,15:07:21,15:07:21,7,419688,124632,cap-wifi3,-63,412900000,344100000,886256,615784,00:07:47,00:00:00
|
||||
1788527243,15:07:23,15:07:23,6,146872,47152,cap-wifi3,-64,412900000,344100000,588616,494568,00:07:49,00:00:00
|
||||
1788527245,15:07:25,15:07:25,6,469520,144920,cap-wifi3,-62,412900000,344100000,576512,462856,00:07:51,00:00:00
|
||||
1788527247,15:07:27,15:07:27,8,87480,21512,cap-wifi3,-62,412900000,344100000,576512,462856,00:07:53,00:00:00
|
||||
1788527249,15:07:29,15:07:29,20,89592,20496,cap-wifi3,-63,455800000,344100000,256840,24640,00:07:55,00:00:00
|
||||
1788527251,15:07:31,15:07:31,9,77304,18144,cap-wifi3,-63,455800000,309700000,883728,710608,00:07:57,00:00:00
|
||||
1788527253,15:07:33,15:07:33,7,84240,17432,cap-wifi3,-63,455800000,309700000,883728,710608,00:07:59,00:00:00
|
||||
1788527255,15:07:35,15:07:35,9,74928,18888,cap-wifi3,-62,455800000,412900000,33632,15216,00:08:01,00:00:00
|
||||
1788527257,15:07:37,15:07:37,12,79856,17376,cap-wifi3,-63,455800000,412900000,859640,23024,00:08:03,00:00:00
|
||||
1788527259,15:07:39,15:07:39,19,91680,31720,cap-wifi3,-62,455800000,412900000,1005448,689888,00:08:06,00:00:00
|
||||
1788527261,15:07:41,15:07:41,7,82760,21192,cap-wifi3,-62,455800000,412900000,1005448,689888,00:08:08,00:00:00
|
||||
1788527263,15:07:43,15:07:44,6,91960,31680,cap-wifi3,-63,516200000,412900000,393768,296624,00:08:10,00:00:00
|
||||
1788527266,15:07:46,15:07:46,12,986832,256184,cap-wifi3,-63,516200000,412900000,558880,414936,00:08:12,00:00:00
|
||||
1788527268,15:07:48,15:07:48,8,204344,305984,cap-wifi3,-63,516200000,412900000,558880,414936,00:08:14,00:00:00
|
||||
1788527270,15:07:50,15:07:50,21,90376,25984,cap-wifi3,-63,455800000,344100000,423096,346496,00:08:16,00:00:00
|
||||
1788527272,15:07:52,15:07:52,9,106024,28632,cap-wifi3,-63,455800000,412900000,549712,433200,00:08:18,00:00:00
|
||||
1788527274,15:07:54,15:07:54,8,86752,24568,cap-wifi3,-63,455800000,412900000,549712,433200,00:08:20,00:00:00
|
||||
1788527276,15:07:56,15:07:56,8,68568,17040,cap-wifi3,-63,455800000,344100000,391568,303576,00:08:22,00:00:00
|
||||
1788527278,15:07:58,15:07:58,7,104792,53200,cap-wifi3,-62,455800000,344100000,715856,336320,00:08:24,00:00:00
|
||||
1788527280,15:08:00,15:08:00,17,304368,102592,cap-wifi3,-62,455800000,344100000,715856,336320,00:08:26,00:00:00
|
||||
1788527282,15:08:02,15:08:02,10,74432,14824,cap-wifi3,-62,455800000,344100000,1540504,579064,00:08:28,00:00:00
|
||||
1788527284,15:08:04,15:08:04,15,84656,54904,cap-wifi3,-62,455800000,412900000,241664,55784,00:08:30,00:00:00
|
||||
1788527286,15:08:06,15:08:06,3,157752,48776,cap-wifi3,-62,455800000,412900000,241664,55784,00:08:32,00:00:00
|
||||
1788527288,15:08:08,15:08:08,6,93880,28544,cap-wifi3,-61,455800000,344100000,662200,323008,00:08:34,00:00:00
|
||||
1788527290,15:08:10,15:08:10,5,92704,21680,cap-wifi3,-62,455800000,412900000,1547016,993208,00:08:36,00:00:00
|
||||
1788527292,15:08:12,15:08:12,4,1096952,456376,cap-wifi3,-62,455800000,412900000,1547016,993208,00:08:38,00:00:00
|
||||
1788527294,15:08:14,15:08:14,5,96296,34168,cap-wifi3,-62,455800000,412900000,658464,380200,00:08:40,00:00:00
|
||||
1788527296,15:08:16,15:08:16,4,323536,108008,cap-wifi3,-62,516200000,412900000,1653400,1083248,00:08:42,00:00:00
|
||||
1788527298,15:08:18,15:08:18,5,106776,31872,cap-wifi3,-62,516200000,412900000,1653400,1083248,00:08:44,00:00:00
|
||||
1788527300,15:08:20,15:08:20,5,180440,70648,cap-wifi3,-62,516200000,412900000,601784,323352,00:08:46,00:00:00
|
||||
1788527302,15:08:22,15:08:22,5,258520,122376,cap-wifi3,-63,516200000,344100000,1414784,982048,00:08:48,00:00:00
|
||||
1788527304,15:08:24,15:08:24,8,484544,159104,cap-wifi3,-63,516200000,344100000,1414784,982048,00:08:50,00:00:00
|
||||
1788527306,15:08:26,15:08:26,5,85832,21400,cap-wifi3,-62,516200000,412900000,594728,332184,00:08:52,00:00:00
|
||||
1788527308,15:08:28,15:08:28,7,86968,23984,cap-wifi3,-62,455800000,412900000,1676496,1014016,00:08:54,00:00:00
|
||||
1788527310,15:08:30,15:08:30,4,92848,27216,cap-wifi3,-62,455800000,412900000,1676496,1014016,00:08:57,00:00:00
|
||||
1788527312,15:08:32,15:08:32,13,88112,30272,cap-wifi3,-61,455800000,412900000,862336,707464,00:08:59,00:00:00
|
||||
1788527314,15:08:34,15:08:35,6,97568,38688,cap-wifi3,-62,455800000,412900000,802192,493640,00:09:01,00:00:00
|
||||
1788527317,15:08:37,15:08:37,7,69376,15424,cap-wifi3,-63,455800000,412900000,410264,299608,00:09:03,00:00:00
|
||||
1788527319,15:08:39,15:08:39,7,95672,27872,cap-wifi3,-63,455800000,412900000,410264,299608,00:09:05,00:00:00
|
||||
1788527321,15:08:41,15:08:41,8,81008,14064,cap-wifi3,-62,516200000,412900000,1058008,922664,00:09:07,00:00:00
|
||||
1788527323,15:08:43,15:08:43,8,79480,19824,cap-wifi3,-62,516200000,412900000,396520,170800,00:09:09,00:00:00
|
||||
1788527325,15:08:45,15:08:45,6,83080,28888,cap-wifi3,-62,516200000,412900000,396520,170800,00:09:11,00:00:00
|
||||
1788527327,15:08:47,15:08:47,9,77224,19568,cap-wifi3,-62,516200000,455800000,899408,689584,00:09:13,00:00:00
|
||||
1788527329,15:08:49,15:08:49,7,87976,23544,cap-wifi3,-62,516200000,412900000,44600,15184,00:09:15,00:00:00
|
||||
1788527331,15:08:51,15:08:51,6,99288,43960,cap-wifi3,-62,516200000,412900000,44600,15184,00:09:17,00:00:00
|
||||
1788527333,15:08:53,15:08:53,4,78784,16504,cap-wifi3,-62,516200000,344100000,903552,691192,00:09:19,00:00:00
|
||||
1788527335,15:08:55,15:08:55,8,100664,43024,cap-wifi3,-64,412900000,309700000,69328,39008,00:09:21,00:00:00
|
||||
1788527337,15:08:57,15:08:57,6,75392,68792,cap-wifi3,-64,412900000,309700000,69328,39008,00:09:23,00:00:00
|
||||
1788527339,15:08:59,15:08:59,18,84648,23296,cap-wifi3,-63,344100000,309700000,1149696,710336,00:09:25,00:00:00
|
||||
1788527341,15:09:01,15:09:01,5,146608,49608,cap-wifi3,-62,412900000,344100000,2232624,903848,00:09:27,00:00:00
|
||||
1788527343,15:09:03,15:09:03,6,271512,52760,cap-wifi3,-62,412900000,344100000,2232624,903848,00:09:29,00:00:00
|
||||
1788527345,15:09:05,15:09:05,6,400696,3660640,cap-wifi3,-61,344100000,412900000,756464,448360,00:09:31,00:00:00
|
||||
1788527347,15:09:07,15:09:07,6,131696,25096,cap-wifi3,-63,344100000,344100000,1392608,910264,00:09:33,00:00:00
|
||||
1788527349,15:09:09,15:09:09,19,213416,92736,cap-wifi3,-63,344100000,344100000,1392608,910264,00:09:35,00:00:00
|
||||
1788527351,15:09:11,15:09:11,6,319104,63216,cap-wifi3,-62,412900000,309700000,1209392,477456,00:09:37,00:00:00
|
||||
1788527353,15:09:13,15:09:13,8,153896,48144,cap-wifi3,-62,412900000,344100000,1506680,898696,00:09:39,00:00:00
|
||||
1788527355,15:09:15,15:09:15,5,190952,58360,cap-wifi3,-62,412900000,344100000,1506680,898696,00:09:41,00:00:00
|
||||
1788527357,15:09:17,15:09:17,7,232928,149512,cap-wifi3,-64,412900000,344100000,1033704,613072,00:09:44,00:00:00
|
||||
1788527359,15:09:19,15:09:19,19,108000,26560,cap-wifi3,-64,309700000,344100000,1108648,735944,00:09:46,00:00:00
|
||||
1788527361,15:09:21,15:09:22,10,2754208,213840,cap-wifi3,-64,309700000,344100000,1108648,735944,00:09:48,00:00:00
|
||||
1788527363,15:09:23,15:09:24,7,314040,73608,cap-wifi3,-63,344100000,275300000,981328,613912,00:09:50,00:00:00
|
||||
1788527366,15:09:26,15:09:26,5,485488,100744,cap-wifi3,-58,309700000,309700000,1262008,690008,00:09:52,00:00:00
|
||||
1788527368,15:09:28,15:09:28,6,123816,28072,cap-wifi3,-58,309700000,309700000,1262008,690008,00:09:54,00:00:00
|
||||
1788527370,15:09:30,15:09:30,19,124944,35616,cap-wifi3,-63,309700000,206500000,260104,31680,00:09:56,00:00:00
|
||||
1788527372,15:09:32,15:09:32,5,171352,44496,cap-wifi3,-61,344100000,309700000,1238528,574776,00:09:58,00:00:00
|
||||
1788527374,15:09:34,15:09:34,6,166240,43784,cap-wifi3,-64,344100000,344100000,531000,227904,00:10:00,00:00:00
|
||||
1788527376,15:09:36,15:09:36,6,150760,49464,cap-wifi3,-64,344100000,344100000,531000,227904,00:10:02,00:00:00
|
||||
1788527378,15:09:38,15:09:38,10,185768,47120,cap-wifi3,-64,344100000,309700000,929200,634112,00:10:04,00:00:00
|
||||
1788527380,15:09:40,15:09:40,18,190032,49512,cap-wifi3,-64,344100000,309700000,666528,183736,00:10:06,00:00:00
|
||||
1788527382,15:09:42,15:09:42,8,97808,22304,cap-wifi3,-64,344100000,309700000,666528,183736,00:10:08,00:00:00
|
||||
1788527384,15:09:44,15:09:44,8,148400,53672,cap-wifi3,-63,412900000,344100000,981232,603784,00:10:10,00:00:00
|
||||
1788527386,15:09:46,15:09:46,2,97568,37160,cap-wifi3,-62,412900000,309700000,225464,141192,00:10:12,00:00:00
|
||||
1788527388,15:09:48,15:09:48,4,76456,19280,cap-wifi3,-62,412900000,309700000,225464,141192,00:10:14,00:00:00
|
||||
1788527390,15:09:50,15:09:50,17,74488,14496,cap-wifi3,-63,344100000,275300000,731840,568104,00:10:16,00:00:00
|
||||
1788527392,15:09:52,15:09:52,9,81688,114120,cap-wifi3,-64,412900000,309700000,217144,287216,00:10:18,00:00:00
|
||||
1788527394,15:09:54,15:09:54,11,139472,94312,cap-wifi3,-64,412900000,309700000,217144,287216,00:10:20,00:00:00
|
||||
1788527396,15:09:56,15:09:56,8,99376,34800,cap-wifi3,-63,412900000,344100000,522856,417128,00:10:22,00:00:00
|
||||
1788527398,15:09:58,15:09:58,7,95624,20936,cap-wifi3,-64,412900000,344100000,1177520,673088,00:10:24,00:00:00
|
||||
1788527400,15:10:00,15:10:00,11,75688,17080,cap-wifi3,-64,412900000,344100000,1177520,673088,00:10:26,00:00:00
|
||||
1788527402,15:10:02,15:10:02,8,194448,59984,cap-wifi3,-62,412900000,412900000,1678424,596296,00:10:28,00:00:00
|
||||
1788527404,15:10:04,15:10:04,5,87848,23392,cap-wifi3,-62,412900000,412900000,1099136,718696,00:10:30,00:00:00
|
||||
1788527406,15:10:06,15:10:06,4,102440,31824,cap-wifi3,-62,412900000,412900000,1099136,718696,00:10:32,00:00:00
|
||||
1788527408,15:10:08,15:10:08,7,140352,49312,cap-wifi3,-65,412900000,309700000,944912,595928,00:10:34,00:00:00
|
||||
1788527410,15:10:10,15:10:10,20,81216,18128,cap-wifi3,-64,455800000,344100000,1466688,782256,00:10:36,00:00:00
|
||||
1788527412,15:10:12,15:10:12,8,223088,23248,cap-wifi3,-64,455800000,344100000,1466688,782256,00:10:38,00:00:00
|
||||
1788527414,15:10:14,15:10:14,7,139784,51840,cap-wifi3,-63,412900000,344100000,1572056,878688,00:10:40,00:00:00
|
||||
1788527415,15:10:15,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-dauerping/sampler-macbook.out
Normal file
1
docs/messdaten/tp25-runde-dauerping/sampler-macbook.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-macbook.csv (Takt 2s, Client 56:71:6C:22:E6:A9)
|
||||
@@ -0,0 +1,2 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
15:10:15 Verbindung weg -- neuer Versuch in 2 s
|
||||
130
docs/messdaten/tp25-runde-dauerping/sampler-s22.csv
Normal file
130
docs/messdaten/tp25-runde-dauerping/sampler-s22.csv
Normal file
@@ -0,0 +1,130 @@
|
||||
ts_epoch,ts_local,ros_time,cpu,wan_rx_bps,wan_tx_bps,sta_iface,sta_signal,sta_tx_rate,sta_rx_rate,sta_tx_bps,sta_rx_bps,sta_uptime,sta_last_activity
|
||||
1788527155,15:05:55,15:05:55,26,215104,164208,cap-wifi3,-50,6000000,6000000,0,0,00:00:50,00:00:18.020
|
||||
1788527157,15:05:57,15:05:57,10,230144,103104,cap-wifi3,-49,309700000,344100000,63424,67648,00:00:52,00:00:00
|
||||
1788527159,15:05:59,15:05:59,27,997352,175928,cap-wifi3,-49,309700000,344100000,63424,67648,00:00:55,00:00:00
|
||||
1788527161,15:06:01,15:06:01,5,496624,179032,cap-wifi3,-49,309700000,344100000,2800,2960,00:00:57,00:00:00
|
||||
1788527163,15:06:03,15:06:03,7,112272,25664,cap-wifi3,-49,309700000,344100000,2800,2960,00:00:59,00:00:00
|
||||
1788527165,15:06:05,15:06:06,6,353280,101384,cap-wifi3,-49,309700000,344100000,2800,2960,00:01:01,00:00:00
|
||||
1788527168,15:06:08,15:06:08,6,137256,37360,cap-wifi3,-53,309700000,344100000,5472,4672,00:01:03,00:00:00
|
||||
1788527170,15:06:10,15:06:10,18,373144,1851168,cap-wifi3,-61,275300000,206500000,2800,2960,00:01:05,00:00:00
|
||||
1788527172,15:06:12,15:06:12,2,79504,15688,cap-wifi3,-61,275300000,206500000,2800,2960,00:01:07,00:00:00
|
||||
1788527174,15:06:14,15:06:14,6,215576,3512448,cap-wifi3,-56,154900000,258100000,3592,4872,00:01:09,00:00:00
|
||||
1788527176,15:06:16,15:06:16,4,170440,37144,cap-wifi3,-51,24000000,229400000,2800,10960,00:01:11,00:00:00
|
||||
1788527178,15:06:18,15:06:18,7,187488,1860720,cap-wifi3,-51,24000000,229400000,2800,10960,00:01:13,00:00:00
|
||||
1788527180,15:06:20,15:06:20,21,418584,3563696,cap-wifi3,-52,275300000,258100000,2800,2960,00:01:15,00:00:00
|
||||
1788527182,15:06:22,15:06:22,6,91920,32648,cap-wifi3,-51,275300000,286800000,2800,10960,00:01:17,00:00:00
|
||||
1788527184,15:06:24,15:06:24,8,229720,340576,cap-wifi3,-51,275300000,286800000,2800,10960,00:01:19,00:00:00
|
||||
1788527186,15:06:26,15:06:26,11,324680,63616,cap-wifi3,-52,275300000,286800000,4184,4376,00:01:21,00:00:00
|
||||
1788527188,15:06:28,15:06:28,7,323944,57120,cap-wifi3,-50,275300000,309700000,3360,3552,00:01:23,00:00:00
|
||||
1788527190,15:06:30,15:06:30,7,89536,26840,cap-wifi3,-50,275300000,309700000,3360,3552,00:01:25,00:00:00
|
||||
1788527192,15:06:32,15:06:32,6,88056,17368,cap-wifi3,-50,275300000,344100000,5344,8864,00:01:27,00:00:00
|
||||
1788527194,15:06:34,15:06:34,6,78232,15688,cap-wifi3,-52,275300000,309700000,3248,3328,00:01:29,00:00:00
|
||||
1788527196,15:06:36,15:06:36,6,77824,19488,cap-wifi3,-52,275300000,309700000,3248,3328,00:01:31,00:00:00
|
||||
1788527198,15:06:38,15:06:38,8,91648,29960,cap-wifi3,-58,275300000,344100000,2800,2960,00:01:33,00:00:00
|
||||
1788527200,15:06:40,15:06:40,6,479584,403096,cap-wifi3,-51,275300000,229400000,2800,2960,00:01:35,00:00:00
|
||||
1788527202,15:06:42,15:06:42,5,83504,18032,cap-wifi3,-51,275300000,229400000,2800,2960,00:01:37,00:00:00
|
||||
1788527204,15:06:44,15:06:44,12,94312,27480,cap-wifi3,-54,275300000,258100000,2800,2960,00:01:39,00:00:00
|
||||
1788527206,15:06:46,15:06:46,11,104592,25288,cap-wifi3,-53,275300000,286800000,2800,10960,00:01:41,00:00:00
|
||||
1788527208,15:06:48,15:06:48,9,77672,15824,cap-wifi3,-52,24000000,286800000,2800,2960,00:01:43,00:00:00
|
||||
1788527210,15:06:50,15:06:50,8,130536,65520,cap-wifi3,-52,24000000,286800000,2800,2960,00:01:46,00:00:00
|
||||
1788527212,15:06:52,15:06:52,6,179704,71320,cap-wifi3,-53,6000000,286800000,3360,3552,00:01:48,00:00:00
|
||||
1788527214,15:06:54,15:06:54,7,150376,135320,cap-wifi3,-51,275300000,309700000,2800,2960,00:01:50,00:00:00
|
||||
1788527216,15:06:56,15:06:57,7,180576,117008,cap-wifi3,-51,275300000,309700000,2800,2960,00:01:52,00:00:00
|
||||
1788527219,15:06:59,15:06:59,9,81080,17128,cap-wifi3,-52,275300000,309700000,2800,2960,00:01:54,00:00:00
|
||||
1788527221,15:07:01,15:07:01,6,267072,71760,cap-wifi3,-51,275300000,309700000,2800,2960,00:01:56,00:00:00
|
||||
1788527223,15:07:03,15:07:03,7,229720,79648,cap-wifi3,-51,275300000,309700000,2800,2960,00:01:58,00:00:00
|
||||
1788527225,15:07:05,15:07:05,12,613656,242376,cap-wifi3,-53,275300000,344100000,2800,2960,00:02:00,00:00:00
|
||||
1788527227,15:07:07,15:07:07,9,136256,40080,cap-wifi3,-52,275300000,344100000,3808,3920,00:02:02,00:00:00
|
||||
1788527229,15:07:09,15:07:09,11,113040,49536,cap-wifi3,-52,275300000,344100000,3808,3920,00:02:04,00:00:00
|
||||
1788527231,15:07:11,15:07:11,7,85432,17976,cap-wifi3,-53,275300000,309700000,2800,2960,00:02:06,00:00:00
|
||||
1788527233,15:07:13,15:07:13,5,81944,26128,cap-wifi3,-56,275300000,286800000,2800,3552,00:02:08,00:00:00
|
||||
1788527235,15:07:15,15:07:15,5,954264,397144,cap-wifi3,-56,275300000,286800000,2800,3552,00:02:10,00:00:00
|
||||
1788527237,15:07:17,15:07:17,12,3430312,274976,cap-wifi3,-50,275300000,344100000,11168,29128,00:02:12,00:00:00
|
||||
1788527239,15:07:19,15:07:19,21,5774264,240088,cap-wifi3,-50,275300000,344100000,3360,3552,00:02:14,00:00:00
|
||||
1788527241,15:07:21,15:07:21,7,419688,124632,cap-wifi3,-50,275300000,344100000,3360,3552,00:02:16,00:00:00
|
||||
1788527243,15:07:23,15:07:23,6,146872,47152,cap-wifi3,-50,275300000,344100000,2800,2960,00:02:18,00:00:00
|
||||
1788527245,15:07:25,15:07:25,6,469520,144920,cap-wifi3,-50,275300000,344100000,2800,2960,00:02:20,00:00:00
|
||||
1788527247,15:07:27,15:07:27,8,87480,21512,cap-wifi3,-50,275300000,344100000,2800,2960,00:02:22,00:00:00
|
||||
1788527249,15:07:29,15:07:29,20,89592,20496,cap-wifi3,-50,275300000,344100000,3360,3552,00:02:24,00:00:00
|
||||
1788527251,15:07:31,15:07:31,9,77304,18144,cap-wifi3,-50,275300000,344100000,2800,2960,00:02:26,00:00:00
|
||||
1788527253,15:07:33,15:07:33,7,84240,17432,cap-wifi3,-50,275300000,344100000,2800,2960,00:02:28,00:00:00
|
||||
1788527255,15:07:35,15:07:35,9,74928,18888,cap-wifi3,-51,275300000,344100000,2240,2368,00:02:30,00:00:00
|
||||
1788527257,15:07:37,15:07:37,12,79856,17376,cap-wifi3,-51,275300000,344100000,2800,2960,00:02:33,00:00:00
|
||||
1788527259,15:07:39,15:07:39,19,91680,31720,cap-wifi3,-52,275300000,344100000,3808,3920,00:02:35,00:00:00
|
||||
1788527261,15:07:41,15:07:41,7,82760,21192,cap-wifi3,-52,275300000,344100000,3808,3920,00:02:37,00:00:00
|
||||
1788527263,15:07:43,15:07:44,6,91960,31680,cap-wifi3,-51,309700000,344100000,4912,4080,00:02:39,00:00:00
|
||||
1788527266,15:07:46,15:07:46,12,986832,256184,cap-wifi3,-51,309700000,344100000,3360,3552,00:02:41,00:00:00
|
||||
1788527268,15:07:48,15:07:48,8,204344,305984,cap-wifi3,-51,309700000,344100000,3360,3552,00:02:43,00:00:00
|
||||
1788527270,15:07:50,15:07:50,21,90376,25984,cap-wifi3,-50,309700000,412900000,2800,2960,00:02:45,00:00:00
|
||||
1788527272,15:07:52,15:07:52,9,106024,28632,cap-wifi3,-51,309700000,344100000,3360,3552,00:02:47,00:00:00
|
||||
1788527274,15:07:54,15:07:54,8,86752,24568,cap-wifi3,-51,309700000,344100000,3360,3552,00:02:49,00:00:00
|
||||
1788527276,15:07:56,15:07:56,8,68568,17040,cap-wifi3,-51,309700000,412900000,2800,2960,00:02:51,00:00:00
|
||||
1788527278,15:07:58,15:07:58,7,104792,53200,cap-wifi3,-48,206500000,344100000,3360,3552,00:02:53,00:00:00
|
||||
1788527280,15:08:00,15:08:00,17,304368,102592,cap-wifi3,-48,206500000,344100000,3360,3552,00:02:55,00:00:00
|
||||
1788527282,15:08:02,15:08:02,10,74432,14824,cap-wifi3,-49,206500000,344100000,2800,2960,00:02:57,00:00:00
|
||||
1788527284,15:08:04,15:08:04,15,84656,54904,cap-wifi3,-48,206500000,344100000,2800,2960,00:02:59,00:00:00
|
||||
1788527286,15:08:06,15:08:06,3,157752,48776,cap-wifi3,-48,206500000,344100000,2800,2960,00:03:01,00:00:00
|
||||
1788527288,15:08:08,15:08:08,6,93880,28544,cap-wifi3,-48,206500000,344100000,3360,3552,00:03:03,00:00:00
|
||||
1788527290,15:08:10,15:08:10,5,92704,21680,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:05,00:00:00
|
||||
1788527292,15:08:12,15:08:12,4,1096952,456376,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:07,00:00:00
|
||||
1788527294,15:08:14,15:08:14,5,96296,34168,cap-wifi3,-48,206500000,344100000,2800,2960,00:03:09,00:00:00
|
||||
1788527296,15:08:16,15:08:16,4,323536,108008,cap-wifi3,-48,206500000,344100000,4184,4376,00:03:11,00:00:00
|
||||
1788527298,15:08:18,15:08:18,5,106776,31872,cap-wifi3,-48,206500000,344100000,4184,4376,00:03:13,00:00:00
|
||||
1788527300,15:08:20,15:08:20,5,180440,70648,cap-wifi3,-49,206500000,344100000,3360,3552,00:03:15,00:00:00
|
||||
1788527302,15:08:22,15:08:22,5,258520,122376,cap-wifi3,-49,206500000,344100000,2800,10960,00:03:17,00:00:00
|
||||
1788527304,15:08:24,15:08:24,8,484544,159104,cap-wifi3,-49,206500000,344100000,2800,10960,00:03:19,00:00:00
|
||||
1788527306,15:08:26,15:08:26,5,85832,21400,cap-wifi3,-48,206500000,344100000,3360,2960,00:03:21,00:00:00
|
||||
1788527308,15:08:28,15:08:28,7,86968,23984,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:24,00:00:00
|
||||
1788527310,15:08:30,15:08:30,4,92848,27216,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:26,00:00:00
|
||||
1788527312,15:08:32,15:08:32,13,88112,30272,cap-wifi3,-49,206500000,344100000,3360,3552,00:03:28,00:00:00
|
||||
1788527314,15:08:34,15:08:35,6,97568,38688,cap-wifi3,-50,206500000,344100000,2800,2960,00:03:30,00:00:00
|
||||
1788527317,15:08:37,15:08:37,7,69376,15424,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:32,00:00:00
|
||||
1788527319,15:08:39,15:08:39,7,95672,27872,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:34,00:00:00
|
||||
1788527321,15:08:41,15:08:41,8,81008,14064,cap-wifi3,-49,206500000,344100000,3360,3552,00:03:36,00:00:00
|
||||
1788527323,15:08:43,15:08:43,8,79480,19824,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:38,00:00:00
|
||||
1788527325,15:08:45,15:08:45,6,83080,28888,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:40,00:00:00
|
||||
1788527327,15:08:47,15:08:47,9,77224,19568,cap-wifi3,-49,206500000,309700000,3360,3552,00:03:42,00:00:00
|
||||
1788527329,15:08:49,15:08:49,7,87976,23544,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:44,00:00:00
|
||||
1788527331,15:08:51,15:08:51,6,99288,43960,cap-wifi3,-49,206500000,344100000,2800,2960,00:03:46,00:00:00
|
||||
1788527333,15:08:53,15:08:53,7,78784,16504,cap-wifi3,-45,206500000,309700000,2800,2960,00:03:48,00:00:00
|
||||
1788527335,15:08:55,15:08:55,8,100664,43024,cap-wifi3,-60,206500000,309700000,2800,2960,00:03:50,00:00:00
|
||||
1788527337,15:08:57,15:08:57,6,75392,68792,cap-wifi3,-60,206500000,309700000,2800,2960,00:03:52,00:00:00
|
||||
1788527339,15:08:59,15:08:59,18,84648,23296,cap-wifi3,-58,206500000,309700000,2800,2960,00:03:54,00:00:00
|
||||
1788527341,15:09:01,15:09:01,5,146608,49608,cap-wifi3,-54,6000000,6000000,3360,3552,00:03:56,00:00:00
|
||||
1788527343,15:09:03,15:09:03,6,271512,52760,cap-wifi3,-54,6000000,6000000,3360,3552,00:03:58,00:00:00
|
||||
1788527345,15:09:05,15:09:05,6,400696,3660640,cap-wifi3,-56,275300000,344100000,2800,2960,00:04:00,00:00:00
|
||||
1788527347,15:09:07,15:09:07,6,131696,25096,cap-wifi3,-54,275300000,344100000,3360,3552,00:04:02,00:00:00
|
||||
1788527349,15:09:09,15:09:09,19,174312,46976,cap-wifi3,-54,275300000,344100000,3360,3552,00:04:04,00:00:00
|
||||
1788527351,15:09:11,15:09:11,6,359504,63184,cap-wifi3,-54,275300000,309700000,2800,2960,00:04:06,00:00:00
|
||||
1788527353,15:09:13,15:09:13,8,181920,63704,cap-wifi3,-58,24000000,344100000,2800,4896,00:04:08,00:00:00
|
||||
1788527355,15:09:15,15:09:15,5,306368,67704,cap-wifi3,-58,24000000,344100000,2800,4896,00:04:10,00:00:00
|
||||
1788527357,15:09:17,15:09:17,7,306688,60896,cap-wifi3,-57,275300000,309700000,3656,3520,00:04:12,00:00:00
|
||||
1788527359,15:09:19,15:09:19,19,310240,63104,cap-wifi3,-57,154900000,309700000,4384,6048,00:04:14,00:00:00
|
||||
1788527361,15:09:21,15:09:21,10,228704,92160,cap-wifi3,-57,154900000,309700000,4384,6048,00:04:16,00:00:00
|
||||
1788527363,15:09:23,15:09:23,7,292296,160184,cap-wifi3,-61,154900000,309700000,10568,37976,00:04:18,00:00:00
|
||||
1788527365,15:09:25,15:09:25,5,695128,395648,cap-wifi3,-56,154900000,309700000,3360,3552,00:04:20,00:00:00
|
||||
1788527367,15:09:27,15:09:27,6,123816,28072,cap-wifi3,-56,154900000,309700000,3360,3552,00:04:22,00:00:00
|
||||
1788527369,15:09:29,15:09:29,19,124944,35616,cap-wifi3,-57,154900000,344100000,2800,2960,00:04:24,00:00:00
|
||||
1788527371,15:09:31,15:09:31,5,171352,44496,cap-wifi3,-57,154900000,275300000,4040,6584,00:04:26,00:00:00
|
||||
1788527373,15:09:33,15:09:33,6,166240,43784,cap-wifi3,-57,154900000,275300000,4040,6584,00:04:28,00:00:00
|
||||
1788527375,15:09:35,15:09:35,6,150760,49464,cap-wifi3,-56,154900000,309700000,2800,2960,00:04:31,00:00:00
|
||||
1788527377,15:09:37,15:09:37,10,185768,47120,cap-wifi3,-57,24000000,309700000,2800,2960,00:04:33,00:00:00
|
||||
1788527379,15:09:39,15:09:39,18,190032,49512,cap-wifi3,-57,24000000,309700000,2800,2960,00:04:35,00:00:00
|
||||
1788527381,15:09:41,15:09:41,8,97808,22304,cap-wifi3,-57,154900000,344100000,2800,2960,00:04:37,00:00:00
|
||||
1788527383,15:09:43,15:09:43,8,148400,53672,cap-wifi3,-57,154900000,344100000,2800,2960,00:04:39,00:00:00
|
||||
1788527386,15:09:46,15:09:46,2,97568,37160,cap-wifi3,-57,154900000,344100000,2800,2960,00:04:41,00:00:00
|
||||
1788527387,15:09:47,15:09:48,4,76456,19280,cap-wifi3,-57,154900000,309700000,2800,3552,00:04:43,00:00:00
|
||||
1788527389,15:09:49,15:09:50,17,74488,14496,cap-wifi3,-58,6000000,309700000,2800,2960,00:04:45,00:00:00
|
||||
1788527392,15:09:52,15:09:52,4,81688,114120,cap-wifi3,-58,6000000,309700000,2800,2960,00:04:47,00:00:00
|
||||
1788527394,15:09:54,15:09:54,8,139472,94312,cap-wifi3,-57,154900000,309700000,2800,2960,00:04:49,00:00:00
|
||||
1788527396,15:09:56,15:09:56,4,99376,34800,cap-wifi3,-57,154900000,309700000,2800,2960,00:04:51,00:00:00
|
||||
1788527398,15:09:58,15:09:58,5,95624,20936,cap-wifi3,-57,154900000,309700000,2800,2960,00:04:53,00:00:00
|
||||
1788527400,15:10:00,15:10:00,19,75688,17080,cap-wifi3,-57,154900000,344100000,2800,2960,00:04:55,00:00:00
|
||||
1788527402,15:10:02,15:10:02,12,194448,59984,cap-wifi3,-56,154900000,344100000,2800,2960,00:04:57,00:00:00
|
||||
1788527404,15:10:04,15:10:04,10,87848,23392,cap-wifi3,-58,154900000,344100000,2800,2960,00:04:59,00:00:00
|
||||
1788527406,15:10:06,15:10:06,9,102440,31824,cap-wifi3,-58,154900000,344100000,2800,2960,00:05:01,00:00:00
|
||||
1788527408,15:10:08,15:10:08,8,140352,49312,cap-wifi3,-56,154900000,309700000,2800,2960,00:05:03,00:00:00
|
||||
1788527410,15:10:10,15:10:10,25,88352,30376,cap-wifi3,-56,154900000,309700000,2800,2960,00:05:05,00:00:00
|
||||
1788527412,15:10:12,15:10:12,7,124328,46808,cap-wifi3,-56,154900000,309700000,2800,2960,00:05:07,00:00:00
|
||||
1788527414,15:10:14,15:10:14,8,84784,23040,cap-wifi3,-57,154900000,344100000,2800,2960,00:05:09,00:00:00
|
||||
1788527415,15:10:15,RECONNECT
|
||||
|
1
docs/messdaten/tp25-runde-dauerping/sampler-s22.out
Normal file
1
docs/messdaten/tp25-runde-dauerping/sampler-s22.out
Normal file
@@ -0,0 +1 @@
|
||||
Sampler laeuft -> /Users/christophemsenhuber/dev/bf-infra/docs/messdaten/tp25-runde-dauerping/sampler-s22.csv (Takt 2s, Client 2A:0D:B0:5D:AD:A7)
|
||||
@@ -0,0 +1,2 @@
|
||||
14:58:12 Verbindung weg -- neuer Versuch in 2 s
|
||||
15:10:15 Verbindung weg -- neuer Versuch in 2 s
|
||||
0
docs/messdaten/tp25-runde-dauerping/stop.flag
Normal file
0
docs/messdaten/tp25-runde-dauerping/stop.flag
Normal file
1280
docs/messdaten/tp25-runde-dauerping/walk-ping-ipad.csv
Normal file
1280
docs/messdaten/tp25-runde-dauerping/walk-ping-ipad.csv
Normal file
File diff suppressed because it is too large
Load Diff
1299
docs/messdaten/tp25-runde-dauerping/walk-ping-iphone.csv
Normal file
1299
docs/messdaten/tp25-runde-dauerping/walk-ping-iphone.csv
Normal file
File diff suppressed because it is too large
Load Diff
1304
docs/messdaten/tp25-runde-dauerping/walk-ping-macbook.csv
Normal file
1304
docs/messdaten/tp25-runde-dauerping/walk-ping-macbook.csv
Normal file
File diff suppressed because it is too large
Load Diff
1302
docs/messdaten/tp25-runde-dauerping/walk-ping-s22.csv
Normal file
1302
docs/messdaten/tp25-runde-dauerping/walk-ping-s22.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,11 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) |
|
||||
| [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) |
|
||||
| [TP39](#tp39) | LXC-Hygiene pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup | ✅ ABGESCHLOSSEN (2026-09-01) — pve+NAS-Sweep grün |
|
||||
| [TP34](#tp34) | Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) | ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell |
|
||||
| [TP44](#tp44) | Cloudflare-Estate als Dokumentations-SSoT in Nautobot | ✅ DURCH (2026-09-10) |
|
||||
| [TP41](#tp41) | CF-Access-Reparatur + Zero-Trust-Konsolidierung | ✅ ABGESCHLOSSEN (2026-09-10) — Gelis WARP via Android eingerichtet, letzter Blocker weg |
|
||||
| [TP43](#tp43) | dw-Standort-DNS-Uplink (site-spezifische Overrides) | ♻️ REOPENED (2026-09-11) — Volltext zurück in teilprojekte.md; interne BF-Namen über WG am dw offen |
|
||||
| [TP42](#tp42) | Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net | ✅ DURCH (2026-09-02) — `.bf.lan` raus, `.16` abgebaut, Compliance 140/140 |
|
||||
|
||||
Dazu am Ende: **[TP25 (Langfassung)](#tp25-lang)** — die Messchronik und die Funde A–E im Volltext.
|
||||
TP25 selbst ist **nicht** abgeschlossen und steht weiter in `teilprojekte.md`; nur seine 394 Zeilen
|
||||
@@ -1777,3 +1782,198 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein.
|
||||
- **Nicht** TP31/TP34 (Caddy) und nicht TP11 (Monitoring) — obwohl gilt: ein Monitoring, das
|
||||
DB-Verbindungszeiten kennt, hätte diesen Fall Monate früher gezeigt.
|
||||
- **Nicht** TP40: der HA-Recorder-Wildwuchs (Punkt 3) ist HA-seitig und ein eigenes TP.
|
||||
|
||||
---
|
||||
|
||||
### TP34 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ▸ ❌ **VERWORFEN (2026-09-08)**
|
||||
> 🔴 **Nachtrag 2026-09-08 (verworfen):** Der Ansatz „Caddyfile via Golden Config rendern" ist
|
||||
> zurückgezogen (Commits `d63dc7b`, `bca8f6e`; Gitea-Issue #24 → `status:dropped`, closed). Der
|
||||
> `420.teilzeitladen.xyz`-Block wurde wieder aus dem GC-gerenderten Caddyfile entfernt; **der
|
||||
> Caddyfile bleibt manuell** in `caddy/Caddyfile`. Gründe: kein zweites SSoT-Modell für Proxy-Routen
|
||||
> nötig, TP37 deckt die Dienst-Doku als `ipam.Service` bereits ab, und der einzige akute Anwendungsfall
|
||||
> ließ sich mit einem manuellen Block lösen. Text unten unverändert als Konzept-Historie.
|
||||
|
||||
> **Konzept + Architektur-Skizze:** [`docs/handoff-20260827-inventur-und-architektur.md`](handoff-20260827-inventur-und-architektur.md) — bitte dort weiterlesen (Architektur-Abschnitt).
|
||||
- **Scope:** Reverse-Proxy-Hosts werden im **Nautobot-Datenmodell** gepflegt (Site, Host, Backend-Spec). **Golden Config** rendert den Caddyfile aus den Daten, Diff-Check gegen die Live-Version auf `caddy-bf1`, Compliance-Checks + Transaktionen identisch zum bestehenden Workflow für Router-Configs.
|
||||
- **Auslöser (Inventur 2026-08-27):** der Caddyfile (`bf-infra/caddy/Caddyfile`) ist heute **manuell gepflegt** — kein Drift-Check, keine Transaktionen, andere Projekte können keine Sites anlegen ohne Christoph. Lösung: **Nautobot/GC**, kein Caddy-API-Sidecar (kein zweites SSoT).
|
||||
- **Erster Anwendungsfall:** `terpene.teilzeitladen.xyz → http://10.40.20.167:8890` (Mac-mini-Backend, dort PostgREST + sync + web via lokalem Caddy `:8890`). Vor TP34: manueller Quick-Win-Caddy-Block.
|
||||
- **Abh.:** TP22 (GC-Pipeline) ✅ · TP23 (DNS/Caddy-Voraussetzungen) ✅ Stufe 1 · 🔴 **NEU 2026-09-06: TP37 Punkt 1+2** — Caddy rendert *Config aus Daten*, die Backend-Dienste müssen als `ipam.Service` in Nautobot existieren, sonst gibt es nichts zu rendern. **TP37 vor TP34.**
|
||||
- **Datenmodell (entschieden 2026-09-06):** native Modelle — Backend = `ipam.Service`, Proxy-Route = Relationship/CustomField Frontend↔Backend, GC-Job rendert den Caddyfile. Keine eigene App (konsistent zu TP44).
|
||||
- **Status:** 🔜 Stub; wartet auf TP37 P1+2. Quick-Win `terpene.teilzeitladen.xyz → 10.40.20.167:8890` als manueller Caddy-Block ist **Schritt 0** des Runbooks. Plan: [`handoff-20260906-tp37-34-36-plan.md`](handoff-20260906-tp37-34-36-plan.md).
|
||||
|
||||
---
|
||||
|
||||
<a id="tp44"></a>
|
||||
### TP44 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot ▸ ✅ **DURCH (2026-09-10): Etappe 1 + Etappe 2 (Teil A IPAM-Prefixe, Teil B Live-Sync als täglicher Nautobot-Job)**
|
||||
> **Entscheid (User, 2026-09-02):** die volle CF-Estate (Tunnel + WARP-Connector + VPC-Service +
|
||||
> Hyperdrive + Workers + alle Access-Apps) als strukturierte, abfragbare SSoT in Nautobot abbilden —
|
||||
> **etappenweise: manuell jetzt, Sync später.** Nautobot kann CF nicht steuern → „SSoT" heißt
|
||||
> **Dokumentations-SSoT; CF bleibt operativer Master.** Detail-Handoff:
|
||||
> [`handoff-20260902-cf-estate-nautobot-ssot.md`](handoff-20260902-cf-estate-nautobot-ssot.md).
|
||||
|
||||
- **Warum eigenes TP, nicht TP36:** TP36 ist eng auf `teilprojekte.md`-Live-Daten geschnitten; gleiche
|
||||
Idee (Nautobot als Doku-SSoT), andere Dimension. TP44 = nächste freie Nummer nach TP43.
|
||||
- **Modell (native `nautobot.cloud`, Nautobot 3.1.7 — keine eigene App):** `Manufacturer "Cloudflare"` →
|
||||
`CloudAccount` (`393c051f…`) → `CloudResourceType ×6` → `CloudNetwork ×4` (`bf-public`/`fm`/`terpene-db`/
|
||||
`bf-core-mesh`) + `CloudService ×14`; Service↔Tunnel über das Through-Modell
|
||||
`cloud-service-network-assignments` (3 Rows). IDs/Ports/Hostnames/Origins je Objekt in `extra_config`.
|
||||
- **Werkzeug:** `nautobot/tp44-cf-estate-seed.py` — idempotenter Seeder (Re-Run = 26/26 „existiert"),
|
||||
Datenstand inline (Live-CF-Inventur 2026-09-02). Token aus `docs/nas-vlan20/stacks/210-nautobot.yml`.
|
||||
- ✅ **Verifikation 2026-09-06 (live gegen die API):** CloudAccount 1 · ResourceType 6 · CloudNetwork 4 ·
|
||||
CloudService 14 — unverändert seit 09-02.
|
||||
- 🔴 **Gotchas:** `CloudService.cloud_networks` ist `read_only` (nur Through-Rows schreiben/lesen) ·
|
||||
VPC-Service-API unter `connectivity/directory/services` (PUT, voller Body) · **VPC-Objekte tragen ihren
|
||||
Identifier in `service_id`, NICHT `id`** (kostete im Sync einen Fehl-Match) · ⚠️ **DRIFT-KORREKTUR
|
||||
2026-09-10:** die frühere Zeile „Repo-`CF_API_TOKEN` darf kein Workers-VPC → über den `cloudflare`-MCP"
|
||||
war **falsch**. Live getestet liest der `CF_API_TOKEN` aus `~/.secrets/credentials.env` Hyperdrive **und**
|
||||
VPC **und** Workers (alle 200). Der schmale Token ist `CF_DNS_API_TOKEN` (403). Der `cloudflare`-MCP ist
|
||||
der **remote OAuth-MCP** (kein API-Token) und für einen serverseitigen Job ohnehin nicht nutzbar.
|
||||
- ✅ **Etappe 2 Teil A (2026-09-08): IPAM-Prefixe live.** `10.40.0.0/16` (container, Route `bf-core-mesh`) +
|
||||
`10.101.0.0/24` (network, Route `fm`) angelegt via `nautobot/tp44-ipam-prefixes.py`; der Seeder-Re-Run
|
||||
hat die zwei `cloud-network-prefix-assignments` eingehängt (count=2, Idempotenz-Gegenprobe grün).
|
||||
- ✅ **Etappe 2 Teil B (2026-09-10): Live-Sync als Nautobot-Job — DURCH.** Governance (User 2026-09-09):
|
||||
periodische externe→Nautobot-Syncs leben als **Nautobot-Jobs** in `bf-infra/jobs/`, in Nautobot geplant —
|
||||
**kein per-Task-Container/-Cron.** Runtime `jobs/cf_estate_sync.py` (`CFEstateSync`, ORM, read-only Pull,
|
||||
403-tolerant, Verwaisung = `extra_config.stale` statt Löschen). Token = Nautobot-Secret `cf-api-token`
|
||||
(Provider env → `CF_API_TOKEN` am Worker, in `210-nautobot.yml` verdrahtet). **Schedule: täglich 03:00Z**
|
||||
(`ScheduledJob "CF Estate Sync – taeglich"`). Verifiziert: Dry-Run + Commit-Run sauber, **4 Networks /
|
||||
14 Services, alle Endpoints lesbar, 0 Drift**. Der REST-Einmalproof `nautobot/tp44-cf-sync.py` bleibt als
|
||||
workstation-seitiges Diagnose-Werkzeug liegen (nicht die kanonische Runtime).
|
||||
- 🔴 **Drei load-bearing Funde (Detail-Handoff `handoff-20260910-tp44-teilb-sync-live.md`):**
|
||||
(1) **Wurzel-`__init__.py` im Repo ist Pflicht** — Nautobot lädt Git-Repo-Jobs via
|
||||
`pkgutil.walk_packages([GIT_ROOT])`, das Namespace-Packages (ohne `__init__.py`) NICHT entdeckt; ohne die
|
||||
Datei bleibt `bf_infra.jobs` unsichtbar (Symptom: Sync SUCCESS + „No jobs were registered"). bf-infra ist
|
||||
das erste Git-Repo mit `extras.job`. (2) `register_jobs()` gehört ins **Paket-`__init__`**, nicht nur ins
|
||||
Submodul. (3) `Meta.has_sensitive_variables=False` nötig, sonst verweigert Nautobot das Scheduling.
|
||||
- **Beziehung zu TP37:** TP44 hat **TP37 Punkt 3** (CF-Connectoren) vorweggenommen — dieselbe native-Modell-
|
||||
Linie, die TP37 P1+2 (Substrat/Dienste) fortsetzt.
|
||||
|
||||
### TP41 — CF-Access-Reparatur + Zero-Trust-Konsolidierung ▸ ✅ **ABGESCHLOSSEN (2026-09-10)** — Auth-Kern seit 2026-09-01 komplett; **Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet** (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7.
|
||||
|
||||
**Auslöser:** CF-Access zwang Christoph **und Geli** täglich zum OTP-Login (soll: 30 Tage); GitHub/Google
|
||||
„funktionieren nicht". Root-Causes: Team-Domain-Rename `bf4`→`emsenhuber` (Provider-Callback veraltet),
|
||||
App-Session 24h, diverse Ordnungs-/Sicherheitslecks. Vollständiger Verlauf + offene Schritte:
|
||||
**[`docs/handoff-20260901-tp41.md`](handoff-20260901-tp41.md)**. Branch `tp41-cf-access-repair`.
|
||||
|
||||
**✅ Live erledigt:** Session 720h (Wildcard/vw/Warp-Login) · login_method-Loch zu (reusable Policy
|
||||
`bf-users` = nur `email:christoph`+`email:geli`) · `8e44f5d1` von 4 Apps auf **nur Terpenfinder**
|
||||
entkoppelt · Access-**Gruppen** `zt-humans`/`zt-home`/`zt-automation`, Policies zeigen darauf · **vw voll
|
||||
Bypass** (Lebensader offen) + **`vw/admin` per WAF-Regel öffentlich geblockt** (CF-Edge + Mobilfunk
|
||||
verifiziert) · API-Token auf das vollständige `…4148` konsolidiert (User hat Service-Tokens- + Zero-Trust-
|
||||
Write ergänzt), altes `…2174` revoken.
|
||||
|
||||
**✅ T4 DURCH (2026-09-01, Variante B):** ein Access-Service-Token **`zt-svc`** (`65f1821a`, rotiert) über
|
||||
Gruppe `zt-automation`; Terpene-Worker per `wrangler secret put TERPENE_SVC_CLIENT_ID` auf zt-svc
|
||||
umgestellt (Worker-Pin `index.js:44`); `terpene-verify`+`-v2` **gelöscht**; `test_whoami` grün.
|
||||
**Token-Altlast geklärt:** `b00722` = LIVE Caddy-DNS-Token (LXC130 caddy.env, SSoT), `d47435` = stale
|
||||
Kopie (kein Consumer) → **User revoket `d47435`**; `credentials.env`-Kopie auf b00722 realignt.
|
||||
|
||||
**✅ T3 APPLIED (2026-09-01):** Default-Profil (`0197d1b3`; das custom „Cloudflare Mesh" matcht nur den
|
||||
`warp_connector`) `fallback_domains += emsenhuber.net → [10.40.20.36, 10.40.20.37]`; Tunnel-Route
|
||||
`10.40.0.0/16` (`d1da7c8a`) da, kein `10/8`-Exclude → routbar. ⏳ Client-Verify: WARP neu verbinden.
|
||||
**✅ T2 DURCH (2026-09-01):** WAF-Regel `f26744eb` gelöscht; Caddy `vw`-Block `respond @cf_admin 404` bei
|
||||
Header `CF-Connecting-IP` (LXC130). Verify: Edge `/admin`=404, lokal/WARP `/admin`=200. ✅
|
||||
|
||||
**✅ T5 DURCH (2026-09-01, User-bestätigt):** GitHub-Login läuft; Google-Login läuft nach dem
|
||||
Consent-Screen-Fix (§A des Follow-up-Handoffs: User Type *Internal*→*External*/veröffentlicht).
|
||||
Callback für beide `https://emsenhuber.cloudflareaccess.com/cdn-cgi/access/callback`.
|
||||
**❌ Facebook/Meta für TP41 abgesagt (2026-09-01, User-Entscheid):** optional, kein Bedarf — Geli ist
|
||||
über OTP `angelikahy@outlook.de` + Google `pillepalle670@gmail.com` voll gedeckt. **Keine Facebook-App,
|
||||
kein CF-Facebook-IdP angelegt → nichts aufzuräumen.** Instagram hat ohnehin keinen CF-IdP.
|
||||
**✅ App-Launcher (D1):** je eine Bookmark-Kachel vw/hass/terpenfinder, Wildcard-Kachel raus, Launcher
|
||||
gestylt (Follow-up-Handoff §D, Policies unverändert).
|
||||
|
||||
**✅ Geli in WARP — CF-seitig nichts zu tun (2026-09-01 live geprüft):** Device-Enrollment läuft über
|
||||
die App **„Warp Login App"** (`6c32fe23…`, type `warp`) → Policy `bf-users` → Gruppe **`zt-humans`**, die
|
||||
Gelis beide Mails **schon enthält** → sie **darf bereits enrollen** (deshalb war „Device enrollment
|
||||
permissions" nicht als fehlendes Setting zu finden). Terpenfinder-`geli`-Policy lässt sie per E-Mail durch.
|
||||
✅ **Client am 2026-09-10 via Android erfolgreich eingerichtet** (WARP installiert, Team `emsenhuber`,
|
||||
Login OTP `angelikahy@outlook.de` — User-Bestätigung); damit ist der einzige verbliebene Handgriff erledigt.
|
||||
🔑 **Permission-Frage beantwortet:** CF-seitig **keine** nötig. Für generelle WARP/Enrollment/User-Verwaltung
|
||||
per Account-Token: *Access: Apps and Policies → Edit* + *Access: Organizations, IdPs, and Groups → Edit*
|
||||
(User erteilt 2026-09-01). **Seat-Verwaltung (`/access/seats`) bleibt per MCP gesperrt** — `10405 Method
|
||||
not allowed for this authentication scheme` (Endpoint will User-Token, nicht Account-Token; kein Scope-Fix,
|
||||
für Enroll irrelevant).
|
||||
|
||||
**🔻 Optionale Restpunkte (bewusst nicht gezogen, kein Blocker):** **T6** optional Rate-Limit ·
|
||||
🔴 `TERPENE_ANDROID_KEYSTORE_PW`-Rotation (→ **TP30 Posten #7**, Transkript-Leak) · **Security-Cleanup:**
|
||||
die `geli`-Terpenfinder-Policy hat **OR-Semantik** und enthält den breiten CGNAT-Bereich
|
||||
`94.154.138.0/24` als IP-Include → jeder aus dieser geteilten Range käme **ohne Login** rein. Über WARP ist
|
||||
der Egress ohnehin Cloudflare (nicht diese IP) → der IP-Include wäre dann überflüssig und entfernbar.
|
||||
**d47435-Revoke ✅ · T3-WARP-Client-Verify ✅** (User, 2026-09-01).
|
||||
|
||||
**Abgrenzung:** hass/gitea bleiben (diese Runde) öffentlich; Terpenfinder-Human-Policies nicht angefasst;
|
||||
**Gitea-Least-Privilege** = eigener künftiger TP (jedes Projekt repo-scoped Login, Admin nur Claude/Christoph).
|
||||
- Bezug **TP11** (Monitoring): eine Plattenfüllstand-Warnung hätte das Wochen früher gezeigt.
|
||||
|
||||
### TP43 — dw-Standort-DNS-Uplink (site-spezifische Overrides) ▸ ♻️ **REOPENED 2026-09-11 → Volltext zurück in [`teilprojekte.md`](teilprojekte.md) (Abschnitt TP43)**
|
||||
|
||||
Kurz zwischen 2026-09-11 verworfen und am selben Tag reopened: die 420-Prämisse war gegenstandslos
|
||||
(public CF-Worker, via WARP/TP41 gelöst), aber die **interne** BF-Namensauflösung über WG am dw blieb
|
||||
dabei offen (`portainer.bf.emsenhuber.net` am dw nicht erreichbar). Korrigierter Scope + verworfene
|
||||
420-Historie stehen jetzt aktiv in `teilprojekte.md`. Gitea-Issue #32 = `status:todo` · `thema:firewall-dns`.
|
||||
|
||||
### TP42 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net ▸ ✅ **DURCH (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`, `.bf.lan` raus, `.16` (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an **TP37** gekoppelte Nautobot-Doku-Teil.
|
||||
|
||||
**Auslöser (2026-09-01, TP41-Randbefund):** live gemessen liefern die realen Client-Resolver
|
||||
`10.40.20.36`/`.37` `*.emsenhuber.net → 10.40.20.40` (Caddy) — korrekt. Daneben existiert aber ein
|
||||
zweiter, gedrifteter interner Namensraum: der **Core** trägt in `/ip dns static` 11 Einträge auf
|
||||
`*.bf.lan`, der **Caddyfile** doppelt jeden internen Namen als `*.bf.emsenhuber.net`, und die alten
|
||||
AdGuard `.16`/`.17` antworten noch mit `nginx*.bf.emsenhuber.net` bzw. totem VLAN1 `10.40.41.123`.
|
||||
Christoph: „aus emsenhuber.net wurde schon wieder bf.lan — das müssen wir ein für alle mal aus allen
|
||||
Geräten rausprügeln und in Nautobot festhalten."
|
||||
|
||||
**Ziel:** genau **ein** interner Namensraum `emsenhuber.net` (Split-Horizon `.36/.37 → .40`). `.bf.lan`
|
||||
und `.bf.emsenhuber.net` werden entfernt; Sollzustand in Nautobot dokumentiert.
|
||||
|
||||
**Scope (Reihenfolge = eigene Session):**
|
||||
1. **Bestandsaufnahme** aller Fundstellen: Core `/ip dns static` (`*.bf.lan`), DHCP-Option
|
||||
`domain`/Search-Domain je Netz, Caddyfile-Aliase (`*.bf.emsenhuber.net`), AdGuard-Rewrites `.36/.37`,
|
||||
stale `.16/.17`, Geräte-`identity`.
|
||||
2. **Entscheidung Zielschema** (nur `emsenhuber.net`, ggf. `bf.emsenhuber.net` als *einzige* interne
|
||||
Zone — Christoph-Entscheid) und Migrationsreihenfolge ohne DNS-Loch.
|
||||
3. **Umsetzen:** Core-DNS + DHCP + Caddyfile angleichen; stale `.16/.17` klären/abbauen.
|
||||
4. **Nautobot:** interne Namen/Records als Sollzustand erfassen (Bezug TP37 Datenmodell).
|
||||
|
||||
**Abgrenzung:** kein CF-Public-DNS-Umbau (Zone `emsenhuber.net` öffentlich bleibt); kein Resolver-Swap
|
||||
(`.36/.37` bleiben); `.1`-als-DNS-Reste auf Clients sind separat/tolerierbar (User: „bis jetzt kein
|
||||
Problem"). **Abh.:** TP37 (Nautobot-Datenmodell) für Punkt 4; sonst nichts Offenes.
|
||||
|
||||
#### ✅ Bestandsaufnahme LIVE (2026-09-02) — Drift hat 80 % schon erledigt
|
||||
Gegen die Geräte gemessen (MikroMCP + dig), nicht aus dem Handoff:
|
||||
|
||||
| Fundstelle | Handoff 09-01 | Live 09-02 |
|
||||
|---|---|---|
|
||||
| Core `/ip dns static` | 11× `*.bf.lan` | ✅ **0× `.bf.lan`** — 10× `*.bf.emsenhuber.net` (deckt sich mit GC-Template Z.284–293) |
|
||||
| DHCP `domain` (7 Netze) | „prüfen" | `emsenhuber.net` (GC-Template Z.133 = gleich ⇒ **keine Drift**), DNS `.36/.37` |
|
||||
| `.36/.37` (Soll) | aktiv | `vw.emsenhuber.net→.40` ✅ · `macmini.bf.emsenhuber.net→.20.167` ✅ · **`macmini.emsenhuber.net→leer`** ❌ |
|
||||
| Caddyfile | `*.bf.emsenhuber.net`-Aliase | unverändert, **konsistent** mit Core-Static |
|
||||
| `.16`/`.17` | tot | laufen (:53), liefern totes VLAN1; **kein Core-Objekt** (Firewall/NAT/DNS-Upstream/Addr-List) hängt an `.16` |
|
||||
|
||||
**Kernkonflikt:** DHCP verteilt `emsenhuber.net` (bare), Records leben unter `bf.emsenhuber.net` ⇒ bare `macmini` löst nicht.
|
||||
|
||||
#### ✅ Entscheidungen (User, 2026-09-02)
|
||||
1. **Zielschema = `bf.emsenhuber.net`** (der wenig-Arbeit-Weg): DHCP-domain → `bf.emsenhuber.net`, dann
|
||||
löst `macmini` bare korrekt; Core-Static + Caddyfile bleiben (tragen schon `.bf.`). Öffentliche Zone
|
||||
`emsenhuber.net` bleibt getrennt.
|
||||
2. **`.16` (adguard_bf1) abbauen**, **`.17` (adguard_fm2 = Standort FM) NICHT anfassen.**
|
||||
|
||||
#### ✅ Umsetzung DURCH (2026-09-02)
|
||||
- **(A) DHCP-domain `emsenhuber.net → bf.emsenhuber.net` über GC ✅** — kein Direkt-Write:
|
||||
`mikrotik_routeros.j2` Z.133 gehoben → `push-gc-template.py` (Commit `7a4068c4`, **Nebenbefund:** das
|
||||
Remote-Template hing dem lokalen hinterher — es trug noch `*.bf.lan`+`router.lan`, obwohl Gerät+lokal
|
||||
längst `.bf.emsenhuber.net` fuhren; der Push hat das GC-Repo an den Live-Stand angeglichen) →
|
||||
GitRepo-Sync (SUCCESS) → manueller ConfigPlan **`tp42-dhcp-domain`** (`set [find] domain=…`, weil
|
||||
`add` das Netz doppelt anlegen würde) → Deploy an **crs326-core** → Live **7/7 = bf.emsenhuber.net**,
|
||||
Compliance **140/140, keine Drifts**. Der Job steht dauerhaft in `nautobot/gc-configplan.py`.
|
||||
- **(B) `.16`-Teardown ✅** — `adguard_bf1` lag im geteilten Portainer-Stack **195 `adguard`** (Docker-LXC
|
||||
`.126`, **ep2** — *nicht* ep14) **zusammen mit `adguard_fm2` (`.17`, Standort FM!)**. Deshalb **nicht**
|
||||
Stack löschen, sondern compose auf nur `.17` getrimmt + Stack redeployt (Prune): `.16`-Container weg,
|
||||
`.16:53` tot; **`.17` läuft** (⚠️ ~40 s mit-neugestartet durch den Redeploy — kurzer FM-DNS-Blip,
|
||||
danach sauber). Rollback-compose gesichert. 🧹 Daten-Dirs `/opt/docker/adguard_bf1/*` bleiben als
|
||||
harmlose Leichen auf dem LXC.
|
||||
- **(C) Nautobot-Soll** (Scope-Punkt 4) bleibt an **TP37** gekoppelt (Datenmodell), separat — kein Blocker.
|
||||
|
||||
**Status:** ✅ **Namensraum vereinheitlicht (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`,
|
||||
`.bf.lan` raus, `.16` abgebaut, Compliance 140/140. Offen nur der TP37-gekoppelte Nautobot-Doku-Teil.
|
||||
Handoff: [`docs/handoff-20260901-tp42-naming.md`](handoff-20260901-tp42-naming.md).
|
||||
|
||||
@@ -25,443 +25,24 @@ IP-Blöcke: `.1–10` Router/Gateways · `.11–20` Switches/APs. **GW je VLAN =
|
||||
Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`.
|
||||
*(Historisch, überholt: „ap1=.1, ap2=.2 · ALLNET=.11, Omada=.12" — aus der Zeit vor dem Core-Cutover.)*
|
||||
|
||||
## Ist-Stand (Kurz)
|
||||
## Status & Index → Gitea-Board (SSoT seit 2026-09-07, TP36-Cutover)
|
||||
|
||||
**Stand 2026-09-02 — live gegen die Geräte geprüft (MikroMCP/REST/SSH), nicht aus der Doku abgeschrieben.**
|
||||
**Compliance 140/140** (nach TP42, 2026-09-02).
|
||||
*(Die Fassung vom 2026-08-16 stand bis zum 18. hier und war an vier Stellen überholt. Wer diesen Block
|
||||
liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)*
|
||||
- ✅ **TP25 Fund E GESCHLOSSEN (2026-09-02):** die vorgemerkte Abschlussmessung ist gelaufen —
|
||||
`reauthenticating` am 09-02 nur noch **4 in 8,6 h** (alle iPhone `F2:62`, isolierte Einzel, 0 Ausfälle)
|
||||
gegen Baseline 93 bzw. Hebel-1 **19/h**. Verlauf **218 → 40 → 4**; Vorbedingung geprüft (Core-Uptime
|
||||
1d18h, `caps joined=0` → keine Reboot-Artefakte). ⇒ **7.24.1 (TP27) war die Ursache/der Fix, nicht
|
||||
`ft-over-ds`.** Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen, **kein Geräte-Write**. Offen in
|
||||
TP25 bleibt nur der separate Punkt **ap2 unterperformt** (2 m/−80 dBm). Handoff:
|
||||
[`docs/handoff-20260902-tp25-fund-e-messung.md`](handoff-20260902-tp25-fund-e-messung.md).
|
||||
- ⏸️ **TP41 CF-Access-Reparatur — ZURÜCKGESTELLT 2026-09-01 (User-Entscheid; Rest wartet auf Info):** Session 720h,
|
||||
login_method-Loch zu (`bf-users` email-only), `zt-`-Gruppen, vw voll Bypass + `vw/admin` per WAF
|
||||
geblockt (Mobilfunk-verifiziert), API-Token auf vollständiges `…4148` konsolidiert. **T4 ✅**
|
||||
(ein Access-Service-Token `zt-svc`, Terpene-Worker per wrangler auf zt-svc umgestellt, Alt-Tokens
|
||||
gelöscht). **T3 ✅** (WARP Default-Profil fallback_domains `emsenhuber.net → .36/.37`). **T2 ✅**
|
||||
(WAF-Regel gelöscht, Caddy-Origin-404 für `/admin` via CF-Header; Edge=404, lokal=200).
|
||||
**T5 ✅** (GitHub- **und** Google-Login laufen, User-bestätigt 2026-09-01; Google via Consent-Fix §A).
|
||||
**Facebook/Meta für TP41 abgesagt** (optional, kein CF-IdP angelegt) — Geli über OTP
|
||||
`angelikahy@outlook.de` + Google `pillepalle670@gmail.com` voll gedeckt.
|
||||
**Token-Altlast geklärt:** `b00722`=live Caddy-DNS-Token, `d47435`=stale Kopie → **✅ d47435 revoked**,
|
||||
**✅ T3-WARP-Client-Verify** (User). **Geli CF-seitig fertig** (Enrollment via `zt-humans` + `geli`-Policy,
|
||||
2026-09-01 live geprüft) → verbleibt nur ein **Device-Handgriff bei Geli** (WARP-Client, Team `emsenhuber`).
|
||||
**Claude-/CF-Seite damit vollständig.** Handoff: [`docs/handoff-20260901-tp41.md`](handoff-20260901-tp41.md) · [Follow-up](handoff-20260901-tp41-followup.md).
|
||||
- ✅ **TP14.4 als in TP41 T4 aufgegangen markiert (2026-09-02):** kein eigenständiger `claude-mcp`-Token
|
||||
mehr — er wurde in TP41 T4 zu `zt-svc` konsolidiert, `mcp/cloudflare/.env` trägt jetzt
|
||||
`CF_ACCESS_CLIENT_ID`/`_SECRET`. Die TP14.4-Ist-Stand-Notiz („.env leer") war seit 09-01 stale;
|
||||
Quick-Index + TP-Abschnitt nachgezogen.
|
||||
- ✅ **TP31 abgeschlossen markiert (2026-09-02):** Schritt 5+6 sind über den Piloten TP16.3 (09-01)
|
||||
miterledigt — `bf-public` 12 conns, vier öffentliche Namen über Caddy, Alttunnel weg. Abnahme-Tool
|
||||
`caddy/cf-sync.py` von einem Fehlalarm befreit (prüfte nur `http://`, gebaut ist `https://…:443`) und
|
||||
**grün** (nur `ds-bf` als bewusst intern). Schritt-6-Teil „MCP auf FQDN" durch TP35 Schicht 1
|
||||
umgekehrt (LAN-IP), damit gegenstandslos.
|
||||
- ✅ **TP42 durch (2026-09-02):** interner Namensraum auf **einen** Suffix `bf.emsenhuber.net` vereinheitlicht.
|
||||
(A) DHCP-domain über die GC-Pipeline geflippt (Template + `push-gc-template.py` `7a4068c4` + neuer Job
|
||||
`tp42-dhcp-domain` + Deploy an core) → **7/7 live, Compliance 140/140**. Nebenbefund: Remote-GC-Template
|
||||
hing hinterher (`.bf.lan`), mit angeglichen. (B) `.16` (`adguard_bf1`) aus dem **mit FM geteilten** Stack
|
||||
195 getrimmt-redeployt — `.16` weg, `.17` (FM) läuft (kurzer ~40 s Blip). Nautobot-Doku-Teil (Scope 4)
|
||||
an TP37 gekoppelt.
|
||||
- 🔜 **TP42 Namens-Vereinheitlichung (2026-09-01, angelegt):** internes `.bf.lan` (Core-DNS, DHCP-Search-
|
||||
Domain) und die `.bf.emsenhuber.net`-Aliase (Caddyfile) auf **einen** Namensraum `emsenhuber.net`
|
||||
ziehen; stale `.16/.17`-Rewrites abbauen; Sollzustand in Nautobot. Handoff:
|
||||
[`docs/handoff-20260901-tp42-naming.md`](handoff-20260901-tp42-naming.md).
|
||||
- 🔜 **TP43 dw-Standort-DNS-Uplink (2026-09-01, angelegt):** dem dw-Standort einen eigenen Resolver/
|
||||
DNS-Uplink mit **site-spezifischen Overrides** geben (erster Fall `420.teilzeitladen.xyz` → BF-Caddy
|
||||
über WG), **inkl. Verhalten bei WG-Disconnect** (Override auf BF-interne IP fällt sonst aus). FritzBox
|
||||
standard kann keinen Per-Host-Override. Geli-Sofortlösung = **WARP** (nicht Teil von TP43). Cross-Projekt.
|
||||
- ✅ **TP16.3 ABGESCHLOSSEN (2026-09-01):** Flip **und** Abbau durch. Öffentlich laufen nur noch
|
||||
`bf-public` (12 Connectoren) und `fm` — die drei Alttunnel `bf-mm`/`bf-nas`/`bf-pve` **und** der
|
||||
projektfremde `terpene-db` sind gelöscht, **7 cloudflared-Container** entfernt (inkl.
|
||||
`terpene-cloudflared`, weil `420.teilzeitladen.xyz` nun über Caddy/CF läuft — vor der Löschung mit
|
||||
echtem Origin-Inhalt belegt). §6 wurde auf User-Entscheid vorgezogen. **TP wandert ins Archiv.**
|
||||
- 🆕 **TP16.3 §5 DURCH (2026-09-01): `n8n`/`gitea`/`hass`/`vw` laufen über `bf-public`** — der
|
||||
öffentliche Verkehr geht damit erstmals über **Caddy** statt an ihm vorbei, und die
|
||||
Host→Origin-Zuordnung lebt nur noch im `Caddyfile`. Je Name einzeln geflippt, dazwischen gemessen;
|
||||
Antwortzeiten vorher/nachher **unverändert** (Caddy im Pfad kostet nichts Messbares).
|
||||
Nachweise: `gitea` echte Gitea-Antwort · `hass` **WebSocket `101 Switching Protocols`** (= der
|
||||
Companion-App-Pfad, den das Runbook als „nur vom User prüfbar" führte) · `vw` `/alive` + `/api/config`
|
||||
200, WS-Hub `401` von Vaultwarden selbst · `n8n` von Christoph im Browser bestätigt.
|
||||
🚫 **§6 (Abbau) bewusst NICHT** — die drei Alttunnel bleiben als Rückfallebene (healthy/8).
|
||||
🔴 **Christoph hat den Flip ausdrücklich vorgezogen freigegeben** („aus Gründen"); die 7-Tage-Regel
|
||||
betrifft ohnehin §6, nicht den Flip.
|
||||
- 🆕 **TP39 ABGESCHLOSSEN + archiviert (2026-09-01):** letzter offener Punkt — der Resolver-Sweep —
|
||||
ist durch: **pve alle 13 LXCs** auf `10.40.20.1`, und die **DS923-Container via Portainer** (ep14)
|
||||
gegengeprüft — kein `.41.254`-Override, alle laufenden lösen in **66–110 ms** auf (inkl. Grenzfall
|
||||
`gitea`, erstellt 2026-07-10), kein eingefrorener Dead-Resolver, kein Recreate nötig. Die
|
||||
Abnahme-Regel („vor jedem LXC-Reboot `systemctl --failed` + Port-Vergleich") steht als
|
||||
CLAUDE.md-Gotcha, der Sweep ist als wiederkehrende Kontrolle dort verankert. **Volltext → Archiv.**
|
||||
🔴 Rest offen nur in **anderen** TPs: root-DB-PW-Rotation (→ TP30), HA-Recorder-Retention (→ TP40).
|
||||
- 🆕 **TP39 fast durch (2026-09-01):** `skip-name-resolve` auf der MariaDB **aktiviert** (mysql.user
|
||||
vorher geprüft — kein Hostname-Grant, gefahrlos; Wirkung: gitea/vw/hass 0 von 36 frischen Anfragen
|
||||
langsam). LXC 302 war **100 % voll** → `+30G` online (95 GB/69 %). 🔴 **root-DB-Passwort dabei ins
|
||||
Transkript geraten** (`.my.cnf` ohne `=`), zu rotieren (→ TP30).
|
||||
- 🆕 **TP40 angelegt (2026-09-01, User-Okay):** HA-Recorder-Retention — die `homeassistant`-DB frisst
|
||||
59 GB (`states.ibd` **41 GB**), unbegrenzt gewachsen. Das `+30G` aus TP39 ist ein Puffer; die
|
||||
dauerhafte Lösung (`purge_keep_days`, `exclude`, `purge repack`) ist HA-seitig und **Christophs
|
||||
Policy-Entscheidung** — kein eigenmächtiges Löschen.
|
||||
- 🆕 **TP39: `networking.service` in vier Containern repariert (2026-09-01).** Ursache war dieselbe
|
||||
Altlast wie beim Resolver: tote `auto`-Stanzas in `/etc/network/interfaces` aus dem VLAN1-Schema
|
||||
(`eth0` → `10.40.41.x`) und alten Versuchen — `ifup -a` scheiterte an Interfaces, die es nicht gibt,
|
||||
während `veth20` lief. **Abnahme war ein echter Reboot:** influxdb kam danach **von selbst** hoch
|
||||
(vorher gar kein Startversuch). Alle 13 LXCs stehen jetzt auf `dns=10.40.20.1`, `networking` überall
|
||||
sauber. **Offen in TP39: nur noch `skip-name-resolve` und der wiederkehrende Sweep.**
|
||||
- 🆕 **TP39 angelegt (2026-09-01, User-Okay):** LXC-Hygiene auf pve — die zwei Restpunkte aus dem
|
||||
Wartungsfenster unten (`skip-name-resolve` auf der MariaDB, kaputtes `networking.service` in vier
|
||||
Containern) plus Resolver-Sweep als wiederkehrende Kontrolle. **Ein** TP, weil beides dieselbe Ecke
|
||||
betrifft und in einem Fenster erledigt wird.
|
||||
- ✅ **GELÖST 2026-09-01: der 10-Sekunden-Zoll auf `gitea`/`vw`/`hass` — es war Reverse-DNS auf einen
|
||||
toten Resolver.** MariaDB (LXC **302**, `10.40.20.23`) hatte `skip-name-resolve` **auskommentiert**
|
||||
und machte damit bei **jeder neuen Verbindung** einen Reverse-Lookup; ihr `/etc/resolv.conf` zeigte
|
||||
auf **`10.40.41.254`** — den toten VLAN1-Resolver aus dem alten Schema (`nc` bestätigt tot).
|
||||
**Gemessen im Container: 10.010 ms je Lookup.** Bestehende Pool-Verbindungen waren schnell, neue
|
||||
kosteten 10 s — daher wirkte es sporadisch und unerklärlich.
|
||||
🔑 **Die Blast-Radius war größer als gedacht:** an derselben DB hängen **Vaultwarden (`.127`),
|
||||
Home Assistant (`.105`) und gitea (`.157`)** — belegt über die Verbindungstabelle, nicht vermutet.
|
||||
*(Die erste Zuschreibung „vaultwarden nutzt SQLite, also nicht betroffen" war eine **Annahme** und
|
||||
falsch — Christoph hat sie korrigiert, die Verbindungsliste hat es bewiesen.)*
|
||||
**Fix (Wartungsfenster, User-Freigabe):** `pct set <id> --nameserver 10.40.20.1` + Reboot für
|
||||
**fünf** LXCs — `302 mariadb`, `102 mqtt5`, `106 influxdb2`, `107 redis`, `150 iob-exposed`.
|
||||
**Nachher: Lookup 21–24 ms statt 10.010 ms; gitea 0 von 25 Proben langsam** (vorher ~10 % bei
|
||||
10,1 s), langsamste Probe 75 ms. Alle drei Dienste wieder verbunden.
|
||||
⚠️ **Strukturell offen:** `skip-name-resolve` ist weiterhin aus — der Lookup ist jetzt nur schnell,
|
||||
nicht abgeschafft. Ihn zu setzen bricht alle Grants, die auf **Hostnamen** statt IPs lauten; das
|
||||
gehört vorher gegen `mysql.user` geprüft (DB-Zugang fehlt: `root@localhost` ohne Passwort verweigert).
|
||||
- 🔴 **Latenter Fund aus demselben Wartungsfenster: `networking.service` und
|
||||
`ifupdown-wait-online.service` sind in `101`, `102`, `106`, `107` fehlgeschlagen.** Folge:
|
||||
Dienste mit `After=network-online.target` starten nach einem Reboot **nicht von selbst** —
|
||||
**influxdb2 kam nach dem Neustart nicht hoch**, Unit `enabled`, aber **kein einziger Startversuch
|
||||
im Journal**. Von Hand gestartet lief es sofort.
|
||||
🔑 **Nicht durch diese Session verursacht:** `101 iob-master` wurde nie angefasst und zeigt dieselben
|
||||
fehlgeschlagenen Units — die Container liefen nur 13+ Tage ohne Neustart, deshalb fiel es niemandem
|
||||
auf. **Merkregel: diese vier Container sind einen Reboot davon entfernt, still Dienste zu verlieren.**
|
||||
- 🔴 **Befund am Rand des Flips, NICHT von ihm verursacht: `gitea` hängt sporadisch exakt ~10,2 s.**
|
||||
~10 % der Anfragen, quer über Web-UI, **`/api/healthz`** und den **Git-Pfad** (`info/refs`) — also
|
||||
kein Rendering-Problem. `vw` und `hass` über **dieselbe** Strecke: **0 von 30**. Damit sind CF-Edge,
|
||||
AP-Connector, Firewall und Caddy entlastet; übrig bleibt **Caddy → `10.40.20.157:3000`** oder gitea
|
||||
selbst. **Immer ~10,15–10,43 s** = Timeout-Signatur (klassisch: ein toter Resolver, s. den
|
||||
VLAN20-Docker-DNS-Gotcha in der CLAUDE.md), kein Lastproblem. Weiter nur mit LAN-Zugriff.
|
||||
- 📎 **Drei Messfallen, die diese Session gekostet haben** (alle drei sind Standort-, keine Netzfehler):
|
||||
1. **Split-Horizon macht Tests von zuhause wertlos:** der Haus-Resolver liefert für alle vier Namen
|
||||
direkt `10.40.20.40`, die Anfrage sieht Cloudflare **nie**. Prüfen geht nur mit festgenagelter
|
||||
CF-Edge-IP (`curl --resolve <name>:443:188.114.96.10`) oder von außerhalb.
|
||||
2. **Access antwortet je nach Standpunkt verschieden:** `n8n` gibt aus dem Haus **200** (die
|
||||
`bf4`-Policy trägt die WAN-IP), von außen **302** zum Login. Beides gesund. Für die drei
|
||||
`bypass`-Namen (`hass`/`vw`/`gitea`) gibt es gar keinen Login — die Access-App heisst zu Recht
|
||||
`hass, vw, gitea`, sie führt alle drei in `self_hosted_domains` (das Feld `domain` zeigt nur den
|
||||
ersten). **Ein Rename wäre eine Verschlechterung gewesen.**
|
||||
3. 🔴 **„Der Host ist ausgefallen" war der eigene Standortwechsel.** Ping/SSH/Caddy waren tot,
|
||||
weil die Session-Maschine auf einen **Hotspot** gewandert war (Default-GW `172.20.10.1`) —
|
||||
dieselbe Klasse wie die WARP-Falle von 2026-08-13, nur ohne WARP. Es wurde daraufhin ein bereits
|
||||
erfolgreicher `n8n`-Flip **grundlos zurückgerollt**. **Merkregel: bevor ein Ausfall gemeldet wird,
|
||||
`route -n get default` lesen** — ein Standortwechsel sieht exakt aus wie ein Totalausfall.
|
||||
- ⚠️ **Nebenbefund: Caddy führt gar kein Access-Log.** Im `Caddyfile` steht keine `log`-Direktive, im
|
||||
Journal landen nur `info`/`warn`. Der Abnahmepunkt „Caddy sieht den Request" (Runbook §5.3) **kann
|
||||
nicht funktionieren** — ein erfolgreicher Request hinterlässt dort nichts, und leere Ausgabe liest
|
||||
sich wie „kam nie an". Dass der öffentliche Reverse Proxy keine Requests protokolliert, ist für sich
|
||||
eine Lücke (gehört zu TP31/TP34).
|
||||
- 🆕 **TP16.3: die sechs AP-Drifts sind ins Soll gehoben (2026-08-31) — Compliance 139/140.**
|
||||
`veth` / `ip address` / `ip firewall nat` der drei cloudflared-Connectoren stehen jetzt im
|
||||
**AP-Template** (Block `[7]`) und im ConfigContext `ap-common` (`ap.container`), **nicht** in einer
|
||||
`gc-transaktion`: ein Dauerzustand gehört ins Soll, eine Transaktion ist ein befristeter Versuch.
|
||||
**Kein Re-Baseline, kein Rückbau, keine Zeile aufs Gerät** — nur die SoT nachgezogen.
|
||||
🆕 **28. ComplianceRule `interface-veth`** (vorher gab es für `/interface veth` keine ⇒ ein
|
||||
gelöschtes veth hätte **nichts** gemeldet). 🔴 **Zwei Funde dabei:** (a) der veth-Export trägt
|
||||
**zwei gerätevergebene MACs** — eine nackte Regel hätte Dauer-Drift auf allen drei APs erzeugt;
|
||||
gelöst mit zwei eng auf die veth-Zeile verankerten `ConfigReplace`-Regeln (`60-`/`61-veth-*`,
|
||||
`nautobot/ingest-veth-rule.py`), bewusst mit der vorhandenen Maske `<<REDACTED>>`, weil die im
|
||||
Repo zugleich die **Push-Sperre** ist. (b) **Der erste Compliance-Lauf meldete `SUCCESS` und war
|
||||
trotzdem unvollständig** (139 statt 140 Einträge, `crs326-core`/`interface-veth` fehlte;
|
||||
Job-Log: `RENDER COMPLIANCE TASK GROUP failed: Broken pipe`). Zweiter Lauf sauber.
|
||||
⚠️ **Merkregel: nach einem Refresh nicht nur die Konformitätsquote lesen, sondern die ANZAHL der
|
||||
Einträge** — sie muss `Regeln × Geräte` sein. Eine fehlende Kombination sieht aus wie „die neue
|
||||
Regel greift nicht" und ist in Wahrheit ein halb gelaufener Job.
|
||||
*(Die damals übrige `crs326-core`/`wifi-security`-Drift = Transaktion `TP25-fund-e-ft-over-ds` ist mit
|
||||
dem Rückbau 2026-09-01 gegenstandslos; Fund E am 2026-09-02 geschlossen, Compliance 140/140.)*
|
||||
- 🆕 **MCP-Bestand 2026-08-28 korrigiert:** **9 MCPs identisch** in `~/.claude.json` und `~/.config/opencode/opencode.jsonc` (vorher Asymmetrie 7 vs. 11 — `portainer-mcp`/`shelly-mcp` raus, `gitea-mcp`/`n8n-mcp` rein). **Mac mini = n8n-Host, NICHT zentraler MCP-Host** (Performance-Restriktion); zentraler MCP-Host = **DS923** (`nas-bf`, Portainer Endpoint 14). Inventur-Stand `mcp/INVENTORY.md` + Policy `docs/mcp-host-policy.md`.
|
||||
- 🆕 **MCP-Migration FQDN→IP (2026-08-29, TP35 Schicht 1):** `gitea-mcp` und `n8n-mcp` in `~/.config/opencode/opencode.jsonc` von CF-FQDN auf LAN-IP umgestellt — `gitea-mcp` → `http://10.40.20.167:3000/mcp` (macmini, **ohne** Bearer, intern), `n8n-mcp` → `http://10.40.20.167:3001/mcp` (mit `Bearer {env:N8N_MCP_BEARER}`). **Befund dabei:** der Handoff 1.2 nannte für gitea-mcp die falsche IP `.157:3000` (das ist die Gitea-Web-UI, `400 Invalid CSRF`); der echte gitea-mcp läuft auf `.167:3000` (`serverInfo: gitea-mcp 0.0.10`). `cloudflare`-MCP bleibt auf dem offiziellen FQDN (User-Direktive).
|
||||
- 🆕 **TP16.3 §2/§3 durchgezogen (2026-08-31, Vor-Ort):** je ein `cloudflared`-Container auf
|
||||
**ap1/ap2/ap3**, `bf-public` **healthy mit 12 Connectoren** — der Tunnel hat damit zum ersten Mal
|
||||
überhaupt Connectoren. 🔴 **Zwei Runbook-Fehler dabei aufgedeckt:** die Flash-Rechnung war um Faktor
|
||||
2,5 zu optimistisch (entpacktes cloudflared = **69,5 MiB**, nicht 28 MB ⇒ das offizielle Image passt
|
||||
auf keinen der drei APs; Ersatz ist ein selbst gebautes `FROM scratch`-Image mit 35,3 MiB, Binary-SHA
|
||||
`e93824f6…` gepinnt), und `/container/envs` heisst `list=`, nicht `name=`.
|
||||
🔴 **Offen: Compliance 128/135.** §3.4 verlangt Direkt-Writes für `ip address`/`nat`, die unter
|
||||
GC-Kontrolle stehen ⇒ **sechs neue `extra`-Drifts** (zwei je AP), inhaltlich richtig, aber nicht im
|
||||
Soll. Braucht eine Entscheidung (ins Template heben), **kein Re-Baseline**. Details im TP16.3-Abschnitt.
|
||||
- 🆕 **TP37 angelegt (2026-08-30, User-Okay):** Nautobot soll durchgehende Netzwerkdoku werden, erster
|
||||
Anwendungsfall die CF-Connectoren. 🔴 **Befund dabei, live gegen die API:** Nautobot kennt heute
|
||||
**nur die 8 Netzgeräte** (4 aktiv, 4 decommissioned) — `ipam.Service`, `CustomField`, `Relationship`,
|
||||
`VirtualMachine` und `Tag` sind **alle 0**, installiert ist nur `golden-config`. Kein Host, kein
|
||||
Container, kein Dienst ist erfasst. **TP37 ist damit zuerst eine Datenmodell-Frage** und liefert das
|
||||
Fundament, das **TP34** (Caddyfile via GC) und **TP36** (SSoT für diese Datei) ebenfalls brauchen —
|
||||
Reihenfolge: **TP37 vor TP34**.
|
||||
- 🆕 **CF-Public-Strang eingedampft (2026-08-30, TP16.3 grosser Schnitt):** **12 Hostnames** aus Ingress
|
||||
**und** DNS entfernt — `pve`/`portainer`/`pve-mcp`/`ha-mcp`/`ds-bf`/`ccu`/`iobroker`/`z2m25`/`pbs`
|
||||
plus die drei CF-Access-SSH-Ziele. Ingress: `bf-mm` 6→4, `bf-nas` 6→3, `bf-pve` 9→2. **Öffentlich
|
||||
bleiben nur `vw`/`hass`/`n8n`/`gitea`** (weiterhin HTTP 200). Intern unverändert über AdGuard→Caddy
|
||||
(9× HTTP 200). ✅ **Nachtrag 2026-08-30 (F1 durch): `gitea-mcp`/`n8n-mcp` sind jetzt AUCH
|
||||
geschnitten** — damit **14 Hostnames** gesamt und `bf-mm` **4→2** (nur noch `n8n` + Catch-all).
|
||||
Möglich wurde das, weil zuvor **12 MCP-Client-Configs** auf LAN-IP migriert wurden (nicht 10 wie
|
||||
geschätzt: zwei Worktree-Kopien in `bf-network-r6{,.opencode}` kamen dazu). 🔴 **`bf-public` hat weiterhin 0 Connectoren** — er gehört
|
||||
laut Entscheid auf **ap1/ap2**, dort fehlen aber `container`-Paket **und** `device-mode container=yes`
|
||||
(physische Bestätigung nötig). Bis dahin stehen `bf-mm`/`bf-nas`/`bf-pve` mit **6 Containern**.
|
||||
Backups: `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json`.
|
||||
- 🆕 **TP16.3/TP31 Schritt 5 vorbereitet (2026-08-30):** Runbook für die Vor-Ort-Session geschrieben
|
||||
([`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)), CF-Rollback-Snapshots
|
||||
unter `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json` **neu angelegt** (es gab sie vorher nicht).
|
||||
`docs/tp31-schritt5-runbook.md` ist damit **abgelöst** (read-only-Header): es kannte ap3 nicht,
|
||||
unterstellte einen Registry-Pull (`fetch: no` auf ap1/ap2) und enthielt eine **falsche**
|
||||
Firewall-Freigabe-Behauptung. **CF-Stand:** `bf-public` `inactive` / 0 Connectoren; die vier
|
||||
öffentlichen Namen laufen noch über bf-mm/bf-nas/bf-pve **an Caddy vorbei** direkt aufs Backend.
|
||||
- 🆕 **TP38 angelegt (2026-08-31):** OOB-Management — je ein untagged-VLAN99-Port auf beiden Switches.
|
||||
Auslöser: beim Core-Neustart war `crs326-branch` nicht erreichbar, weil sein Mgmt **ausschließlich**
|
||||
über den Core läuft (`vlan99` dort `tagged=ether1,ether24,bridge`, kein untagged-Member).
|
||||
🔴 **Nebenbefund mit eigenem Gewicht: `crs326-branch` hat gar keine `input`-Firewallregeln** — sein
|
||||
Schutz ist heute allein die Erreichbarkeit über den gehärteten Core. Das ist die Hauptauflage des TPs.
|
||||
- 🆕 **ROS-Upgrade 7.24.1 auf allen fünf (2026-08-31, User).** In Nautobot nachgezogen: **ap1** stand
|
||||
als einziges Gerät noch auf `7.24` (die anderen vier waren schon richtig) ⇒ auf `7.24.1` gesetzt.
|
||||
⚠️ **Korrektur (2026-08-31, nachträglich):** die dabei gesehene `wifi-security`-Drift auf
|
||||
`crs326-core` (`sec-main` `ft-over-ds=no` statt `yes`) hatte **nichts mit dem Upgrade zu tun** — sie
|
||||
ist **TP25 Fund E, Hebel 1**, um 08:09 bewusst gesetzt (Core-Upgrade war erst 14:36) und als
|
||||
**offene Transaktion `TP25-fund-e-ft-over-ds`** geführt (Scope `wifi-security`, Ablauf 2026-09-03).
|
||||
**Die 134/135 sind Absicht**, nicht Schaden: grün machen würde den laufenden Versuch unsichtbar
|
||||
machen, und `gc-transaktion.py` verweigert den Commit ohnehin, solange der Scope driftet.
|
||||
🔑 **Lehre:** vor jeder Drift-Bewertung `nautobot/gc-transaktion.py status` lesen — Drift aus einem
|
||||
laufenden Versuch sieht identisch aus wie ein Versehen, und genau dafür gibt es das Werkzeug.
|
||||
- 🆕 **TP16.3 §1-Blocker remote erledigt (2026-08-31):** Ingress von `bf-public` auf
|
||||
`https://10.40.20.40:443` + `originServerName`; Firewall-Freigabe `vlan99 → vlan20:443` **über
|
||||
Nautobot GC** (Plan `Completed`, Re-Baseline `ingest-core-firewall.py --apply`, danach
|
||||
**135/135 compliant / 0 Drifts**); `ROUTER_AP3_PASS` in `~/.secrets`. **Offen bleibt nur die
|
||||
Vor-Ort-Session** (§2/§3: `container.npk`, `device-mode`, Container) und danach Flip + Abbau.
|
||||
- 🆕 **Globale Verhaltensregeln konsolidiert (2026-08-29, TP35 Schicht 3):** `~/.config/opencode/AGENTS.md` ist jetzt die **SSoT** für globale Verhaltensregeln (Secrets, VLAN-Schema, 5 Lessons, SoC, Übergabe-Mechanik, Mid-Action-Checkpoint, Commit-Hinweis, Backup-Guard). `~/.claude/CLAUDE.md` hält denselben Block als Claude-Code-Spiegelung mit Verweis auf die SSoT. opencode liest AGENTS.md mit Vorrang vor `~/.claude/CLAUDE.md`.
|
||||
|
||||
- **Alle fünf MikroTik laufen auf RouterOS `7.24.1`** (🆕 **Angleichung am 2026-08-31 von Christoph
|
||||
selbst durchgezogen, TP27 damit erledigt** — ap2 08:56, core 14:36, ap1 14:50, branch 14:52;
|
||||
ap3 kam bereits so. RouterBOOT jeweils = `upgrade-firmware` = `7.24.1`, live gegengelesen).
|
||||
Vorher: vier auf 7.24 (08-17), nur ap3 auf 7.24.1. Neu in der Flotte **`ap3`**
|
||||
(cAP ax `cAPGi-5HaxD2HaxD`, **`10.40.99.13`**, Uplink core `ether21`).
|
||||
✅ **Folge für TP16.3:** die versionsgebundene `container.npk` ist damit **eine** Datei für alle drei
|
||||
APs (alle `arm64`) — die Zwei-Dateien-Regel vom 08-30 war rein versionsbedingt und ist erledigt.
|
||||
*(Hier stand bis 08-29 „Alle vier MikroTik … 7.24" — durch ap3 und das Upgrade zweifach überholt.)*
|
||||
- **L3 + WAN-Edge = `crs326-core`** (CRS326-24G-2S+, Technik UG): Routing, DHCP (7 Server,
|
||||
54 Reservierungen), DNS, NAT, **NTP-Server für den Standort** und der WG-Hub (TP10, Peers `fm`/`dw`)
|
||||
laufen hier. Mgmt-SVI **`10.40.99.1`** (vlan99), Gateway `.1` je VLAN. **WAN = `vlan100`** — DHCP-Client
|
||||
hält die öffentliche Liwest-IP **`90.146.33.177/24`** direkt vom Arris (Bridge-Mode) → kein Double-NAT.
|
||||
**DNS-Upstream = DoH zum CF-Gateway mit `verify-doh-cert=yes`** (TP23 Stufe 1 ✅ 08-18);
|
||||
`servers=1.1.1.1` steht nur noch als Bootstrap für den DoH-Hostnamen.
|
||||
- 🔥 **Die Firewall ist deny-by-default — in BEIDEN Ketten** (TP5 E3/E4 ✅ 08-18). `TP10-seal-lan` ist
|
||||
`disabled`; Mgmt-Input kommt nur noch aus VLAN10, von HA (`8728`), von Nautobot (`22`) und über
|
||||
`cf-mesh`. Ein neuer Dienst über eine VLAN-Grenze braucht ab jetzt eine explizite Regel.
|
||||
- **L2-Branch = `crs326-branch`** (Technik OG): reiner L2-Switch, Mgmt **`10.40.99.2`**,
|
||||
Uplink `ether1` → core `ether8`.
|
||||
- **WLAN = CAPsMAN auf dem Core** (TP28 ✅ 08-17, GC-Modell nachgezogen 08-18): ap1 **`10.40.99.11`**
|
||||
(an core `ether15`), ap2 **`10.40.99.12`** (an branch `ether24`), beide **reine CAPs** —
|
||||
`cap enabled=yes`, `discovery-interfaces=vlan99`. Die APs halten **keine eigene WLAN-Config** mehr;
|
||||
Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, `ft-over-ds=yes`.
|
||||
🆕 **Dritter AP `ap3` (cAP ax) ✅ LIVE seit 2026-08-30** (TP28 dafür wieder geöffnet):
|
||||
`10.40.99.13`, Uplink `ether1` → **core `ether21`** (Trunk), reiner CAP, **RouterOS 7.24.1**
|
||||
(🆕 **seit 2026-08-31 die ganze Flotte** — die frühere Versionsdifferenz ist aufgelöst). Kanäle
|
||||
**`5180/ax/Ce/I`** + **`2437/ax`** (ch6) nach dem TP25-Plan vom 2026-08-24, am Gerät nachgemessen.
|
||||
🔴 **ap1 ist funkstill** — es steht bis zu seinem Umzug ans Sideboard direkt neben ap3 und würde
|
||||
auf 5180 kollidieren; es hat deshalb bewusst **keine Provisioning-Regel** am Manager, bleibt aber
|
||||
erreichbar. Beim Umzug: ap1 → `5260/Ce` (Band B, DFS, **Frequenz-Liste statt Einzelwert**),
|
||||
ap2 → `5300/Ce`, Uplink ap1 → `core ether3`.
|
||||
**Compliance jetzt 130/130 / 0 Drifts über 5 Geräte** (vorher 104/104 über 4).
|
||||
- **SSDP-Relay** (TP7.1, **nachgebessert 08-25**): LXC **108 `mcast-relay`** auf pve, zweibeinig
|
||||
`10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs; mDNS macht
|
||||
weiterhin der Core. **Läuft seit 08-25 ohne `--ssdpUnicastAddr`** — die Flagge verwarf die M-SEARCH
|
||||
der App auf dem Weg nach VLAN40 (Messung: **0 Pakete `via eth1` gegen 308 `via eth0`** in 20 min,
|
||||
**0×** `Received SSDP Unicast` im ganzen Journal seit 08-18). Der Rückweg läuft jetzt **nativ geroutet**
|
||||
über die längst offene Regel `TP5: MEDIA->MAIN`; nachgemessen kommen `HTTP/1.1 200 OK` von
|
||||
`10.40.40.193/.195` an beiden MacBook-Beinen an. Unit liegt im Repo: **`mcast-relay/multicast-relay.service`**,
|
||||
Details als Gotcha in `CLAUDE.md`. ⚠️ **Der Fall hatte zwei unabhängige Ursachen** — nach der
|
||||
Relay-Reparatur suchte die **App** immer noch nicht (macOS-Local-Network-Gate stand auf „an" und
|
||||
griff nicht; Fix war Schalter aus/ein + Neustart, User-bestätigt 08-25). **Beide sind erledigt.**
|
||||
- **Reverse Proxy = `caddy-bf1`** (LXC 130 auf pve, **`10.40.20.40`**, TP31 ✅ Schritt 1–3 seit
|
||||
2026-08-25, **`hass` 400 behoben 2026-08-26, Split-Horizon seit 2026-08-26**): Caddy 2.11.4,
|
||||
**16 Sites** (4 public, 12 intern mit `.bf.`-Suffix), Zertifikate ausschliesslich
|
||||
per **DNS-01** mit einem eng geschnittenen `Zone:DNS:Edit`-Token (`CF_DNS_API_TOKEN`, **nicht** das
|
||||
account-weite). Abnahme über den Host-Header von einem zweiten Host: 11× 200, `nautobot` 302.
|
||||
Offen: **Schritt 5–6** (Connector für `bf-public`, CNAME-Flip, Abbau der sechs Container) —
|
||||
**Runbook steht: [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)**,
|
||||
Vor-Ort-Session an ap1/ap2/ap3. ✅ **Die drei §1-Blocker sind am 2026-08-31 remote erledigt** —
|
||||
**(a)** Ingress auf `https://10.40.20.40:443` + `originServerName` je Regel umgestellt (die alte
|
||||
`:80`-Zeile hätte über CF eine Redirect-Schleife gebaut, weil Caddy dort 308 auf https antwortet);
|
||||
**(b)** Freigabe `vlan99 → vlan20:443` **über Nautobot GC** deployt (`ip-firewall-filter` steht unter
|
||||
Drift-Kontrolle, TP26 ⇒ kein Direkt-Write), Position vor `TP5: log-only` verifiziert, **135/135
|
||||
compliant, 0 Drifts**; **(c)** `ROUTER_AP3_PASS` in `~/.secrets` nachgetragen (REST-Auth 200).
|
||||
⇒ **Vor Ort startet die Arbeit bei §2/§3** (Pakete, `device-mode`, Container).
|
||||
- **DNS/Resolver = AdGuard-Paar `agh-bf1`/`agh-bf2`** (LXC 110/111 auf pve, **`10.40.20.36`/`.37`**,
|
||||
TP23 Stufe 2 + TP31 Schritt 4 ✅ seit 2026-08-26): Upstream DoH zum CF-Gateway, conditional
|
||||
`[/bf.emsenhuber.net/] → 10.40.20.1`, 47 Rewrites (16 Service → Caddy `.20.40` + 10 Host-Namen
|
||||
mit `.bf.`-Suffix + alte suffixlose parallel). DHCP liefert `.36`/`.37` als DNS in allen 7 VLANs
|
||||
(`domain=emsenhuber.net`), Firewall forward UDP+TCP/53 auf beide. Der Core-Resolver (`10.40.X.1`)
|
||||
bleibt als Bootstrap unverändert (DoH, `allow-remote-requests=yes`, `mdns-repeat-ifaces`).
|
||||
- **Ausgebaut / gelöscht:** ALLNET-`sw1` und Omada-`sw2` sind **physisch weg** (2026-08-08 bzw. 2026-07-23,
|
||||
in Nautobot *Decommissioned*); Omada-Controller + `omada-mcp` gelöscht (2026-07-16); FritzBox 6660 raus
|
||||
seit 2026-07-01 (Liwest-Tausch auf Arris). **VLAN1 ist leer** (`/ip arp` auf `bridge` = 0 Einträge,
|
||||
08-18) — TP19 ist geschlossen; `cudy1` hängt stromlos an core `ether11`, physischer Ausbau ist hands-on.
|
||||
- 🆕 **WLAN-Logging persistent seit 2026-08-30 (TP25 Fund E):** `wireless` + `caps` gehen vom Core
|
||||
in den TP5-Kollektor (`10.40.20.18:514`); `/system logging` steht unter der **27. ComplianceRule**
|
||||
`system-logging`. Vorher nur Ringpuffer → ein seit Wochen gemeldeter iPhone-Abbruch war rückwirkend
|
||||
nicht diagnostizierbar.
|
||||
- ⚖️ **TP25 Fund E — Hebel 1 ZURÜCKGEBAUT, Entscheidung faellt am 2026-09-02 (Stand 09-01 09:2x):**
|
||||
Zwei Sessions haben denselben Fall bewertet und kamen zu entgegengesetzten Schluessen. **Aufgeloest
|
||||
zugunsten der Kollektor-Messung:**
|
||||
- Die Nacht-Session las `get_log … sinceMinutes=1440` → `total 0` und schloss auf „geheilt". Das ist
|
||||
der **fluechtige Ringpuffer des Routers**: live nachgemessen **1000 Zeilen, aeltester Eintrag
|
||||
01.09. 00:38:58** — keine 9 h Tiefe. Der Core-Reboot 14:36 hatte ihn geleert, der 31.08. Vormittag
|
||||
war darin **nicht sichtbar**. 🔴 Genau die Falle, wegen der am 30.08. der persistente Kollektor
|
||||
eingerichtet wurde.
|
||||
- Im **Kollektor** stehen fuer 08:09–14:36 (Hebel 1 **allein**, vor dem Upgrade) **124×
|
||||
`reauthenticating`**, Stundenverteilung 09h=1 · 11h=2 · 12h=20 · 13h=31 · 14h=70, **alle vom
|
||||
iPhone allein** (= Client-Befund nach der eigenen Merkregel „viele MACs = Infrastruktur"), dazu
|
||||
Christophs **zwei erlebte Ausfaelle** um 13:16 und 14:23. Zwischen 09:00 und 14:36 gab es
|
||||
**keinen Reboot**, der sie erklaeren koennte.
|
||||
- ⇒ Hebel 1 hat im upgrade-freien Fenster **nichts** bewirkt; der Einbruch faellt exakt auf 7.24.1.
|
||||
**Deshalb am 2026-09-01 zurueckgebaut** (`ft-over-ds=yes`, Provisionierung nachgezogen, frische
|
||||
DHCP-Lease abgenommen, Transaktion **`zurueckgebaut`**, **Compliance 140/140**). Der Rueckbau **ist
|
||||
das Experiment**: bleibt es ruhig, war 7.24.1 die Ursache; kehrt das Haemmern zurueck, hatte die
|
||||
Nacht-Session recht und Hebel 1 wird als neue Transaktion erneut gezogen. **Bis zur Messung am
|
||||
2026-09-02 frueh committet niemand.** Handoff:
|
||||
[`handoff-20260901-fund-e-hebel1-zurueckgebaut.md`](handoff-20260901-fund-e-hebel1-zurueckgebaut.md).
|
||||
- 🔴 **ABER: neuer Teilbefund am 23:02 (2026-08-31) — `ap2` unterperformt, NICHT FT.** Christoph am
|
||||
Schreibtisch, 5G, kein Telefonat. Log: iPhone `F2:62:4C:E1:AF:09` ping-pongt im Sekundentakt
|
||||
`cap-wifi5 ↔ cap-wifi6` = **beide Radios von ap2** (5G 5220 ↔ 2,4G ch11/2462, Band-Steering-Thrash
|
||||
auf demselben AP), `disconnected, not responding` um 22:37:40 und 23:01:00. Feldstärke **−75…−88 dBm
|
||||
— bei laut Christoph 2 m Abstand** (ap2 auf 2,3 m, gleicher Raum). Bei 2 m wären −35…−45 normal.
|
||||
Aktuell **0 Clients auf ap2**, alle 29 auf ap3. ⇒ kein Coverage-/FT-Problem, sondern **ap2s Funk
|
||||
selbst** (RF/Antenne/HW) und/oder die **DTU-Störung auf ap2/ch11** (Transaktion `TP17.1-dtu-pin-ap2`,
|
||||
−59 dBm auf ap2s 2,4-Radio). Als eigener 🔬-Unterpunkt geführt, In-situ zu messen. Handoff:
|
||||
[`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-verdacht.md).
|
||||
- 🔴 **TP25 Fund E — Hebel 1 ist GESCHEITERT (Stand 2026-08-31 nachmittags):** `sec-main` steht am
|
||||
Core auf **`ft=yes ft-over-ds=no`** (seit 08:09), aber das Symptom blieb: **124× `reauthenticating`
|
||||
nach dem Eingriff** und **zwei von Christoph erlebte Ausfälle** — 13:16 (Videocall bricht ab,
|
||||
7 min ohne WLAN) und 14:23 (5,5 min, nur durch WLAN-Toggle beendet). Stundenvergleich auf
|
||||
gleicher Firmware: **07:00–08:00 = 79 · 14:00–15:00 = 70**. Erfolgskriterium war „~0" ⇒ nicht
|
||||
eingetreten. Gewertet wird bewusst nur **08:09–14:36** (danach kommt das TP27-Upgrade als zweite
|
||||
Variable dazu; Christoph-Entscheid: „Vormittag als Ergebnis werten").
|
||||
⇒ Transaktion **`TP25-fund-e-ft-over-ds` bleibt OFFEN** (Ablauf **2026-09-03**), **Compliance
|
||||
weiter bewusst 134/135**; Rollback oder Commit ist eine Entscheidung, kein Aufräumen.
|
||||
Geräte-Backup `pre-tp25-funde-ftoverds.backup` liegt auf dem Core.
|
||||
**Nächste Entscheidung:** Hebel 2 (`ft=no`) **oder** `authentication-types=wpa2-psk,wpa3-psk` —
|
||||
`sec-main` ist auch die einzige SSID ohne `wpa2-psk`, und `association rejected, no cached PMK`
|
||||
(157× am 31.08., 0× am 30.08.) ist eine SAE/PMKSA-Meldung. **Vorher prüfen, ob 7.24.1 auf dem
|
||||
Manager das Verhalten schon verändert hat** — zwischen 14:37 und 15:12 stand nur **1**
|
||||
`reauthenticating` im Log, keines vom iPhone (35 min = noch keine Aussage).
|
||||
Details: [`handoff-20260831-tp27-upgrade-und-hebel1-befund.md`](handoff-20260831-tp27-upgrade-und-hebel1-befund.md).
|
||||
- ⚠️ **Messfalle, die diese Session fast erwischt hat:** die 13 CAP-`joined`/`activity timeout` vom
|
||||
31.08. sahen nach CAPWAP-Instabilität aus — es waren die **Reboots des TP27-Upgrades**. Und die
|
||||
**`cap-wifiN`-Karte hat sich an einem Tag zweimal verschoben** (08:09, 14:36), womit die im
|
||||
Vorgänger-Handoff festgehaltene Tabelle nach sechs Stunden falsch war. Interface-Namen in
|
||||
Logzeilen sind ohne zeitnahen Karten-Snapshot **nicht zuschreibbar**.
|
||||
- **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 5 Geräte in der Registry),
|
||||
**Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf,
|
||||
**135/135 Compliance-Checks grün, 0 Drifts** bei **27 Rules × 5 Geräten** (Stand 2026-08-30;
|
||||
**seit 2026-08-31 vorübergehend 134/135 — der eine Drift ist die offene Transaktion
|
||||
`TP25-fund-e-ft-over-ds`, s. oben, kein Fehler**) — live gegen die API
|
||||
geprüft am **2026-08-23**; zuletzt dazugekommen: **`wifi-access-list`** (08-22), **`queue`**
|
||||
(08-23, TP6) und **`ip-firewall-mangle`** (08-23, TP6.1), s. TP22-Nachtrag im Archiv).
|
||||
*(Hier stand bis zum 08-23 „100/100 bei 25 Rules" — eine Zählung, die TP6.1 am selben Tag überholt
|
||||
hat. Die Merkregel gilt: der Ist-Stand-Block wird bei jeder Änderung mitgezogen, nicht nur der TP.)*
|
||||
|
||||
<details><summary>📜 Historische Fassung (Stand bis 2026-08-08, Topologie <b>vor</b> dem Core-Cutover — bewusst erhalten, nicht als Ist lesen)</summary>
|
||||
|
||||
> ALLNET-Core `.11` (FW2.2.1, VLANs 10/20/30/40/50/90/99/100; GE1/GE2/GE8 Trunk; **TP1 ✅ 2026-06-15**). Omada-Switch `.12` (adopted, **TP2 ✅ 2026-06-15**; Trunk-ALL auf P1+P2, Access-Profile auf P3-P8; Profile noch PVID=1 bis Omada-PVID-Fix).
|
||||
> Omada-Controller **`.175`** (DS923/macvlan, mbentley 6.2, 🟢 live). omada-mcp **`.176`** (pve-LXC, StreamableHTTP, 🟢 live, 84 read-Tools; Controller-API v2 intern via christoph@emsenhuber.com).
|
||||
> 2× hAP ax² (7.23.1): ap1 `.1` (ALLNET P1) — **TP4 ✅ 2026-07-01: Router + WAN-Edge** (VLAN-Subinterfaces, DHCP, DNS, NAT, Public-Edge). ap2 `.2` (Omada P2 — Trunk).
|
||||
> **Edge-Cutover ✅ 2026-07-01:** FB6660 physisch entfernt (Liwest-Kabelmodemtausch, kein Zurück), Arris-Kabelmodem im Bridge-Mode; WAN = `sw1 gi16 Access VLAN100 → gi15 Trunk → ap1 ether2 → vlan100 auf bridge` (**NICHT** ether1); ap1 hält Public-IP direkt (Liwest, kein CGNAT), Double-NAT weg. Telefonie war auf der FB und wird nicht weitergeführt. Vorarbeit: TP10-Kern durch (2026-06-27, WG-Hub live) → Bridge-Gate vorher gelöst. Runbook: `routeros/wan-cutover-runbook.md`.
|
||||
> Kontrolle: MikroMCP (MikroTik) live · ALLNET = Klickanleitungen · Omada → Controller + omada-mcp 🟢.
|
||||
|
||||
</details>
|
||||
|
||||
**Nächster Schritt (Stand 2026-08-30): Vor-Ort-Session ap1/ap2** — sie ist der einzige Weg, den
|
||||
Public-Strang auf `bf-public` zu heben und die drei Alt-Tunnel samt sechs Containern abzubauen
|
||||
(TP16.3 Follow-up F2). ✅ **F1 ist am 2026-08-30 durch** (12 Client-Configs auf LAN-IP, danach
|
||||
`gitea-mcp`/`n8n-mcp` aus Ingress+DNS geschnitten) — davor unabhängig machbar bleibt nur noch die
|
||||
**Announcement-Doku (TP35 Schicht 4)**, die den anderen Projekten die neuen MCP-URLs mitteilt.
|
||||
|
||||
**Vorherige Fassung (Stand 2026-08-28): drei Folge-Sessions parallel** (siehe `docs/handoff-20260828-mcp-bestand-und-performance.md`):
|
||||
> - ✅ **Session A** — `/projektstatus`-Skill global für opencode (TP35 Schicht 6). Funktionsbeweis erbracht 2026-08-28.
|
||||
> - 🔜 **Session B** — shelly-mcp-Container auf DS923 (`10.40.20.10`) deployen (live, ssh). Repo-Materialien committed (`mcp/shelly/{Dockerfile,BUILD.md,requirements.txt,.gitignore}`); `.env` und Tunnel-Secret lokal — kein MCP-Client (User-Direktive).
|
||||
> - 🔜 **Session C** — `caddy/cf-sync.py`-Drift-Check + Backup-Guard (`~/dev/.bin/backup-guard.sh`, TP35 Schicht 5).
|
||||
> 🧭 **Ist-Stand, kritischer Pfad und Quick-Index leben ab jetzt als Gitea-Issues**, nicht mehr in
|
||||
> dieser Datei: **[admin/bf-infra · Issues](https://gitea.emsenhuber.net/admin/bf-infra/issues)**.
|
||||
> **TP = Issue · Status = Label `status:*`** (todo/wip/paused/idea/concept/done/dropped) ·
|
||||
> **Gruppierung = Label `thema:*`**. Filtern über die Label-Chips der Issue-Liste, z.B.
|
||||
> [nur offene](https://gitea.emsenhuber.net/admin/bf-infra/issues?type=issue&state=open).
|
||||
>
|
||||
> 🆕 **Dazu seit 2026-08-30: TP16.3/TP31 Schritt 5 = Vor-Ort-Session** an ap1/ap2/**ap3**
|
||||
> ([Runbook](tp16.3-bf-public-ap-connector-runbook.md)). **Reihenfolge beachten:** die drei Blocker in
|
||||
> §1 sind **remote** zu erledigen, *bevor* jemand in den Keller geht — sonst steht man vor einem
|
||||
> Connector, der `healthy` meldet, während die Seite eine Redirect-Schleife dreht.
|
||||
> **Diese Datei führt nur noch die per-TP-Prosa** — die `### TPnn`-Abschnitte unten; Abgeschlossenes im
|
||||
> **[Archiv](teilprojekte-archiv.md)**. **Live-Gerätedaten** (Compliance-Zahl, RouterOS-Version …) kommen
|
||||
> aus **Nautobot**, nicht von hier. Sync-Werkzeug Datei/Archiv → Gitea (idempotent, Dry-Run default):
|
||||
> `bin/gitea-tp-sync.py`.
|
||||
>
|
||||
> 🆕 **Strukturelle Folge = TP36:** SSoT für `teilprojekte.md`-Live-Daten via Nautobot (eigene Session, nicht heute). Befund 2026-08-28: manuell gepflegte Datei verliert gegen die Realität, sobald sie lang wird. Quickref-Index, TP-Abschnitt und Ist-Stand-Block müssen bei jedem Edit mitgezogen werden — keine schlägt die andere. **Merkregel im Lessons-Block.**
|
||||
> *(TP36-Cutover 2026-09-07: die frühere Quick-Index-Tabelle und der chronologische „Ist-Stand (Kurz)"-Block
|
||||
> sind aus dieser Datei entfernt, damit TP-Branches nicht mehr auf der Steuerungs-Doku kollidieren — Status-
|
||||
> Updates gehen nach Gitea statt in diese Datei. Die alte Ist-Stand-Historie steht in der Git-Historie.)*
|
||||
|
||||
> 🆕 **Neu am 2026-08-24/25:** [TP31](#tp31) löst **TP16** und **TP16.2** ab und dampft cloudflared
|
||||
> von **sechs Containern auf einen** ein; [TP32](#tp32) gibt die Nummern- und Adress-Systematik vor
|
||||
> (und hat nebenbei die **falsche Startreihenfolge auf pve repariert** — HA startete vor seiner
|
||||
> Datenbank). **TP16 ✅, TP25 ✅ und TP16.3 (Pilot) ✅** sind am 24./25. durchgezogen worden.
|
||||
|
||||
**Vorherige Fassung (Stand 2026-08-23): Christoph entscheidet — der kritische Pfad ist LEER.**
|
||||
> 🎯 **Das Projekt ist in seinem Kern fertig.** deny-by-default steht (TP5), Golden Config steht und
|
||||
> meldet **104/104 compliant bei 0 Drifts** (live geprüft 2026-08-23), WLAN steht über CAPsMAN auf dem
|
||||
> Core (TP28), VLAN1 ist leer (TP19) — und **TP6 ist beantwortet statt offengelassen**: CAKE gebaut,
|
||||
> gemessen, wieder ausgebaut. **Nichts blockiert mehr etwas.**
|
||||
> 🟢 **Was zuletzt wirklich etwas gebracht hat, war TP6.1** (2026-08-23): nicht Shapen, sondern
|
||||
> **Klassentrennung** — NAS-Bulk `priority=8` gegen interaktiv `priority=1` drückt dieselbe Strecke
|
||||
> unter derselben Sättigung von **40,8 auf 11,0 ms (−73 %)**. Die zuerst notierte DOCSIS-Erklärung aus
|
||||
> TP6 ist damit **widerlegt**; der Fehler lag in der Messmethode (Last und Messung vom selben Host).
|
||||
> 📋 **Offen und ohne Blockadewirkung:** **TP25** (Fund F = 5:05-Takt bei IoT, plus die Entscheidung
|
||||
> „5 GHz auf 20 MHz") · **TP16** (CF-Tunnel-HA degradiert, vier tote Alt-Tunnel) · **TP16.2/16.3** ·
|
||||
> **TP23 Stufe 2** · **TP24** (DC-USV) · **TP13/TP14.4** (beide mit fertigem Runbook) ·
|
||||
> ❌ **TP17.1 VERWORFEN 2026-08-23** (Zigbee-Weg bewiesen, Ziel damit nicht erreichbar — das Gerät
|
||||
> bleibt WLAN-Client; `s9` zurückgebaut) · ✅ **TP17.3 ERLEDIGT** (Ursache **`eco_mode`** bewiesen;
|
||||
> `s23` + `s26` repariert; **`eco_mode` flottenweit aus**, 22/22 außer `s24`) ·
|
||||
> ➡️ **TP17.2 ist am 2026-08-24 in TP30 aufgegangen** (Shelly-Gerätepasswort = Erstvergabe eines
|
||||
> Credentials; TP30 ist das Sammelbecken dafür) ·
|
||||
> 🔑 **Alle Passwort-/Token-Rotationen sind seit 2026-08-24 TP30** (Sammel-TP) — **bewusst
|
||||
> gebündelt gegen Projektende**, User-Entscheid. Damit ist die ältere Einreihung „hinter TP17.1"
|
||||
> gegenstandslos (TP17.1 ist verworfen). **Etappe 1 — die Service-Accounts — ist durch;**
|
||||
> das `admin`-Passwort selbst ist **nicht rotiert** und bleibt es bis dahin bewusst.
|
||||
> 📌 **Einstieg für die nächste Session:
|
||||
> [`handoff-20260825-agh-dns-cutover.md`](handoff-20260825-agh-dns-cutover.md)** — AdGuard als
|
||||
> Resolver + Split-Horizon (TP23 Stufe 2 ∥ TP31 Schritt 4). **Bewusst eigene Session:** der Schritt
|
||||
> verschiebt die DNS-Auflösung des ganzen Standorts in einen Container.
|
||||
> 📜 Älter: [`handoff-20260824-session.md`](handoff-20260824-session.md)
|
||||
> — ⚠️ dessen Empfehlung „Start bei der Passwort-Rotation Etappe 2" ist durch den TP30-Entscheid
|
||||
> **überholt**; der Rest der Übergabe gilt. Ältere Sitzungsvorschläge:
|
||||
> [`handoff-20260822-naechste-schritte.md`](handoff-20260822-naechste-schritte.md).
|
||||
> ✅ **`crs326-core` ist transaktionsfrei** (seit 2026-08-22, geprüft): ein gemeldeter Drift ist ab
|
||||
> jetzt **echt**, nicht beabsichtigt — `python3 nautobot/gc-transaktion.py status`.
|
||||
|
||||
<details><summary>📜 Vorherige Fassungen dieses Absatzes (2026-07-22 → 2026-08-21, bewusst erhalten)</summary>
|
||||
|
||||
> *Fassung 2026-08-21 abends (erledigt — TP25s Dead Spot ist am 08-22 repariert, TP6 am 08-23 geschlossen):*
|
||||
> **Nächster Schritt: TP25 — Steering-Test in den Schlafräumen.**
|
||||
> 📋 **Einstieg: [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md)** — dort steht
|
||||
> der verifizierte Ist-Stand, die Messfallen und der nächste Test.
|
||||
> ✅ **`crs326-core` ist transaktionsfrei (2026-08-22).** `TEST-ether3-trunk-ap1-sideboard` und
|
||||
> `TEST-infra-ssid-matter-pairing` sind beide zurückgerollt und geschlossen; ap1 hängt wieder auf
|
||||
> `ether15`, `ether3` ist wieder untagged VLAN40. Ein gemeldeter Drift ist ab jetzt **echt**, nicht
|
||||
> beabsichtigt — `python3 nautobot/gc-transaktion.py status`.
|
||||
> 🆕 **TP29** (Anwesenheitserkennung fürs Energiesparen) ist als Idee angelegt, nicht auf dem Pfad.
|
||||
> 📌 **Die TP6-Prämisse ist am 2026-08-20/21 gemessen** (Runbook §5): formal Fall B (+28…+47 ms unter
|
||||
> Dauer-Upload), aber im Videocall **nicht wahrnehmbar** — kein einziger Problem-Klick über sechs
|
||||
> Lastphasen. Laut war stattdessen der Funk: **Sticky Client + Band-Ping-Pong, zwölf Wechsel in neun
|
||||
> Minuten**, je Wechsel bis zu 4 s ohne Durchsatz →
|
||||
> [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md). Deshalb steht jetzt
|
||||
> **TP25 vor TP6**; TP6 bleibt offen und begründet, aber nachrangig.
|
||||
>
|
||||
> *Vorherige Fassung (Stand 2026-08-18, erledigt):*
|
||||
TP6 wird hier seit Monaten als „DAS Ziel" geführt, aber `docs/call-quality-diagnosis.md` hat den
|
||||
Bufferbloat 2026-06-16 bereits gemessen (**A/A+**, „CAKE wäre marginal, nicht deployt"). Die Messung
|
||||
stammt aus der FritzBox-Ära, der Edge ist seither getauscht — also **einmal gegen den heutigen Edge
|
||||
nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Nacht auf den 18. abgeschlossen worden (Etappe 3 + 4: deny-by-default in `forward` **und** `input`, MGMT isoliert, `seal-lan` abgeschaltet), TP23 Stufe 1 (DoH auf dem Core) ebenfalls. Nebenher offen, blockiert nichts: **TP25** (Signal-Boden + Re-Walk), **TP28-Rest** (die fünf `wifi-*`-Features vom AP-Scope auf den Core umhängen — die einzigen verbliebenen Compliance-Drifts), **TP7.1** (SSDP-Relay für die macOS-Sonos-App), **TP16.3**, **TP24**.
|
||||
> *Vorherige Fassung (Stand 2026-08-14, überholt): „TP5 — Deny-by-default am Ende von `forward`." Alles Davorliegende ist durch:* CRS326-Bring-up + L3-Umzug ap1→`crs326-core` (TP15/TP21.1 ✅ 2026-08-08), WLAN-Neuprovisionierung ohne CAPsMAN (TP3 ✅ 2026-08-08), Nautobot-Plattform + Fleet-Adoption + Deploy-Pipeline (TP21/TP22 ✅ 2026-08-13). Danach **TP6 (CAKE) = das Projektziel**. Kleine Follow-ups nebenher: DoH-Cloudflare auf core, DC-USV (**TP24**, seit 2026-08-16 eigener TP). ⚪ **Omada-PVID-Fix ist obsolet** (User-Entscheid 2026-07-22 — Omada spielt seit der TP15-Korrektur keine Rolle mehr im Netz, nie mehr aufgreifen).
|
||||
> *Vorherige Fassung (Stand 2026-07-22, erledigt): „TP15 CRS326-Bring-up als neuer L3-Core, danach WLAN-Neuprovisionierung ohne CAPsMAN; TP5 folgt nach TP21/CRS326-Adoption."*
|
||||
|
||||
</details>
|
||||
## Governance & genehmigte Ausnahmen (stehende Regeln — kein Status)
|
||||
|
||||
> 🎯 **Ziel: sauberes System „from scratch", keine Leichen/Altlasten.** Migrations-Session-Endstand = **VLAN1 + altes cudy-WLAN komplett weg** (VLAN1 nur noch Mgmt, `defconf`-Client-DHCP zurückgezogen, cudy-Uplinks aufs Ziel-Client-VLAN getaggt oder cudy abgebaut). **Unterpunkte/Aufräum-Liste bei Migration:** (a) die Interim-Härtung ap2 `wifi1/wifi2 frame-types=admit-only-vlan-tagged` (TP3.1, 2026-07-06) bleibt gültig — verschwindet automatisch mit VLAN1; (b) ~~`st-main rrm/wnm` = reines Roaming-Provisorium~~ → **kein Provisorium mehr (2026-08-13, TP3.3): Steering ist fester Bestandteil des Golden-Config-Templates + ComplianceFeature `wifi-steering`** — es war die Neuprovisionierung, die es stillschweigend verloren hatte; (c) ~~**cudy1 = verbleibender aktiver VLAN1-Leak**~~ → **ERLEDIGT (User-bestätigt + verifiziert 2026-08-14): cudy1 ist offline.** `core ether11` trägt **kein Link** (kein `R`-Flag), Nautobot-Status *Offline* (TP22 Schritt 5). Damit existiert kein aktiver VLAN1-Leak mehr; der Dual-DHCP-Race aus `project_vlan1_dhcp_incident` ist Historie. Der formale Teardown (Gerät ausbauen, Doku/Inventar) bleibt TP19.
|
||||
> ⚠️ **HW-Constraint hAP ax²:** 1 GB RAM, aber nur **128 MB Flash + KEIN USB** → **Container auf dem Router NICHT praktikabel**.
|
||||
@@ -486,43 +67,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
|
||||
> ~~**TP17.2** ↔ TP17~~ *(TP17.2 ist am 2026-08-24 in **TP30** aufgegangen und kein eigenes TP mehr)*
|
||||
> und **TP20.1** ↔ TP20).
|
||||
>
|
||||
> ### 📊 Quick-Index (nur dieser Block ist für den Statusbaum nˆtig — 1 Read)
|
||||
>
|
||||
> | # | Status | Kurztitel | Abschluss |
|
||||
> |---|---|---|---|
|
||||
> | TP5.1 | ⏸️ | DNS-Visibility Airfryer | — |
|
||||
> | TP8 | ⏸️ | Identity / 802.1X | — |
|
||||
> | TP11 | ⏸️ | Monitoring | — |
|
||||
> | TP12 | ⏸️ | IPv6 (3 Standorte) | — |
|
||||
> | TP12.1 | 🔜 | IPv6 ULA + v6-Firewall | — |
|
||||
> | TP13 | 🔜 | Botschafts-WLANs | — |
|
||||
> | TP14.4 | ✅ | CF-Access-Token — **in TP41 T4 aufgegangen** (`claude-mcp`→`zt-svc`, `.env` geschrieben) | 2026-09-01 |
|
||||
> | TP16 | ✅ | CF-Tunnel HA wieder voll | 2026-08-24 |
|
||||
> | TP16.2 | 🔜 | Härtung bypass-Dienste | — |
|
||||
> | TP16.3 | ✅ | cloudflared-Reduktion — Flip + Abbau durch; nur noch `bf-public`+`fm`, 7 Container weg | 2026-09-01 |
|
||||
> | TP17.1 | ❌ | Shelly Gen4 → Zigbee | 2026-08-23 |
|
||||
> | TP17.3 | ✅ | Shelly-Regression behoben | 2026-08-23 |
|
||||
> | TP18 | 🔜 | Garage-Objektspeicher | — |
|
||||
> | TP20.1 | 🔜 | Vaultwarden → Platform-PG | — |
|
||||
> | TP23 | ✅ | DNS-Policy (Stufe 1+2 ✅) | 2026-08-26 |
|
||||
> | TP24 | 🔜 | DC-USV | — |
|
||||
> | TP25 | ⏳ | WLAN-Stabilisierung (Kern ✅) — **Fund E ✅ geschlossen 09-02: 7.24.1 war die Ursache, nicht `ft-over-ds` (4/8,6 h, 0 Ausfälle)**; offen nur ap2 unterperformt (2 m/−80, In-situ) | 2026-09-02 |
|
||||
> | TP28 | ✅ | WLAN/CAPsMAN — dritter AP `ap3` (cAP ax) **live** | 2026-08-30 |
|
||||
> | TP29 | 💡 | Anwesenheitserkennung (Idee) | — |
|
||||
> | TP30 | 🔜 | Credentials (Etappe 1 ✅, Rest gebündelt) | — |
|
||||
> | TP31 | ✅ | Reverse Proxy Caddy — 1–4 ✅ (08-25/26), **5+6 via TP16.3 (09-01)**; Abnahme-Tool `cf-sync.py` grün | 2026-09-01 |
|
||||
> | TP32 | 🟢 | Nummern-Systematik (Konzept) | — |
|
||||
> | TP33 | 💡 | Sonos fern (Idee, zurückgestellt) | — |
|
||||
> | TP34 | 🔜 | Caddyfile via Nautobot/GC (Stub, Inventur-Handoff 2026-08-27) | — |
|
||||
> | TP35 | ⏳ | MCP-Schichtung + 3-Schicht-CLAUDE.md + Backup-Guard (Schicht 1+3+6 ✅) | — |
|
||||
> | TP36 | 🔜 | SSoT via Nautobot für teilprojekte.md-Live-Daten + Datei-Aufteilung | — |
|
||||
> | TP37 | 🔜 | Nautobot als Netzwerkdoku: Hosts/Container/Dienste + CF-Connectoren (Okay 08-30) | — |
|
||||
> | TP38 | 🔜 | OOB-Mgmt: untagged-VLAN99-Port je Switch | — |
|
||||
> | TP40 | 🔜 | HA-Recorder-Retention: 41 GB `states` beschneiden (Okay 09-01) | — |
|
||||
> | TP41 | ⏸️ | CF-Access-Reparatur **zurückgestellt 2026-09-01** (User): **T2/T3/T4/T5 ✅** (GitHub+Google-Login live); **Facebook abgesagt**; **Geli CF-seitig fertig** (zt-humans+geli-Policy) → offen nur noch WARP-Client auf Gelis Gerät, wartet auf User-Info | — |
|
||||
> | TP42 | ✅ | Namens-Vereinheitlichung: ein interner Suffix **`bf.emsenhuber.net`** — DHCP-domain via GC geflippt (140/140), `.16` abgebaut (`.17`=FM bleibt); Nautobot-Doku-Teil an TP37 | 2026-09-02 |
|
||||
> | TP43 | 🔜 | dw-Standort-DNS-Uplink: eigener Resolver mit site-Overrides (`420.teilzeitladen.xyz`→BF-Caddy/WG), WG-Disconnect bedacht; Geli sofort per WARP | — |
|
||||
> | — | — | **35 archivierte TPs** in `teilprojekte-archiv.md` (inkl. TP39 ✅ 2026-09-01) | — |
|
||||
> 📊 **Index/Statustabelle → [Gitea-Board](https://gitea.emsenhuber.net/admin/bf-infra/issues)** (siehe Kopf dieser Datei). Hier unten folgt nur noch die per-TP-Prosa.
|
||||
|
||||
### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt
|
||||
- **Kontext (2026-07-08/09):** Airfryer „airschreier" (Philips/Versuni, ESP, `10.40.30.180`) bekam per DHCP-Option 6 `dns 1.1.1.1` als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — **kein** Content-Block, via Gateway-Logs bewiesen, s. `memory/ref_esp_ota_doh_not_block`). Nebenwirkung: public **1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH** (`px35ynlol3.cloudflare-gateway.com`) → **DNS-Sicht auf das Gerät weg**, Gerät umgeht zudem die Filterung.
|
||||
@@ -1429,7 +974,26 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
- **Verwandt/Abgrenzung:** vw ist auch in **TP16** offen (CF-Access: `bypass`→WARP-geschützt) — **DB-HA ≠ Access**, getrennt behandeln. Reziprokes 2-Site-Muster analog **TP16**/**TP18**.
|
||||
- Abh.: **TP20** (Tier-1 Postgres muss stehen), **TP10** (WG-Transport für Phase-2), **TP18** (Backup-Schirm). Kein Blocker für TP21.
|
||||
|
||||
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 1 DURCH, STUFE 2 VORBEREITET (2026-08-18)**
|
||||
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 1–5 DURCH (2026-09-04); offen nur Stufe 6 (Bypass-Sperre, in TP5)**
|
||||
> 🟢 **Update 2026-09-04 — Stufe 4 + 5 gebaut, live verifiziert. Der `.16`-Text unten ist überholt**
|
||||
> (Stufe 2 lebt seit 08-26 auf dem Paar `agh-bf1`/`agh-bf2` = `.36`/`.37`, LXC 110/111 — s. Ist-Stand-Block):
|
||||
> - **Stufe 4 (CIDR-Substrat):** 7 VLAN-Gruppen + `dns-bypass-exempt` in `clients.persistent` auf beiden
|
||||
> Instanzen; Policy-Matrix (INFRA/MEDIA/MGMT `filtering=aus`, GUEST safe-search+parental an). Neuer
|
||||
> AGH-API-User `automation` (PW in `~/.secrets` → TP30). Sync-Werkzeug `adguard/agh-sync.py`
|
||||
> (`check`/`push`), Repo-SSoT `adguard/AdGuardHome.yaml` mit **redigierten** Hashes. Invariante `.36==.37`.
|
||||
> - **Stufe 5 (HA-Self-Service):** `agh-exception.py` (RMW-CLI, `allow-domain`/`unallow`/`unfilter`/`refilter`)
|
||||
> auf HAOS deployt; `shell_command` + 5 Helper + Timer + 2 Automations (`ha-mcp`). Button → Domain für
|
||||
> 15 min/1 h/4 h entsperren, nativer `timer` revertiert; RouterOS-`jit-dns-bypass`-Backstop erst mit Stufe 6.
|
||||
> Doku: `adguard/README.md` + `adguard/ha-self-service.md`. End-to-End getestet (Start + Ablauf).
|
||||
> - **Offen:** nur **Stufe 6** (DNS-Bypass-Sperre) — bewusst NICHT geschaltet. Runbook für eine
|
||||
> **separate Session**: [`docs/tp23-stufe6-bypass-sperre-runbook.md`](tp23-stufe6-bypass-sperre-runbook.md).
|
||||
> 🔴🔴 **HARTES VOR-ORT-GATE:** macht AGH `.36/.37` zum harten DNS-SPOF, aus der Ferne nicht
|
||||
> reparierbar → **NUR vor Ort, NICHT während Christoph abwesend ist** (2026-09-06: ~1 Woche weg →
|
||||
> in dem Fenster gar nicht anfangen). Methodik: log-only-Vorstufe zuerst, Drop mit Auto-Revert zuletzt.
|
||||
> - **WARP-Managed-Network (Stufe 2 im alten Plan)** bleibt offen (TP41-nah). Der `.16`-basierte
|
||||
> Abschnitt darunter ist die historische Fassung (2026-08-18), bewusst erhalten.
|
||||
|
||||
**[Historische Fassung 2026-08-18 — `.16`-basiert, durch den Block oben überholt:]**
|
||||
- ✅ **Stufe 1 — DoH auf dem Core scharf** (`routeros/tp23-stufe1-doh-core.rsc`), schliesst zugleich
|
||||
**TP21.1 TODO 1**. `use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query`,
|
||||
**`verify-doh-cert=yes`**. `servers=1.1.1.1` bleibt als Bootstrap fuer den DoH-Hostnamen stehen.
|
||||
@@ -1766,7 +1330,7 @@ die alte Liste führte `2g-probe-delay` an, das aber **Band-Steering** adressier
|
||||
| # | Hebel | Begründung der Reihenfolge | Risiko | Stand |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **`ft-over-ds=no`** auf `sec-main` | nimmt die Änderung vom 08-17 zurück; FT bleibt über `ft=yes` (over-the-air) erhalten. Kleinster Eingriff, der die verdächtige Achse trifft | gering | ❌ **widerlegt** — lief 6,4 h allein → 124× (≈19/h) + 2 Ausfälle, **zurückgebaut 2026-09-01** |
|
||||
| 2 | `ft=no` auf `sec-main` | falls (1) nicht reicht: FT komplett aus. Kostet Roaming-*Geschwindigkeit*, nicht Roaming | mittel | ⏹️ entfällt — 7.24.1 löste Fund E (Messung 09-02) |
|
||||
| 2 | `ft=no` auf `sec-main` | falls (1) nicht reicht: FT komplett aus. Kostet Roaming-*Geschwindigkeit*, nicht Roaming | mittel | ✅ **gezogen 2026-09-04** (User-Entscheid, Dauerzustand — Template + bf-golden-config + Nautobot-Sync) |
|
||||
| 3 | `2g-probe-delay=yes` (2,4-GHz-Configs) | MikroTiks *dafür gebautes* Werkzeug; verzögert nur Probe-Antworten und **kann keinen Client stranden lassen**. Adressiert die **Band-Oszillation** — ein anderes Symptom | sehr gering | ⏹️ entfällt (Fund E geschlossen) |
|
||||
| 4 | `signal-range`-Boden per `access-list` (Fund E im Original) | **kann Clients aussperren** — genau das war 08-22 das Gegenargument; nur mit gemessenem Wert | hoch | ⏹️ entfällt (Fund E geschlossen) |
|
||||
|
||||
@@ -1774,7 +1338,8 @@ die alte Liste führte `2g-probe-delay` an, das aber **Band-Steering** adressier
|
||||
> `ft-over-ds=yes` **und** 7.24.1 verschwunden — 09-02 nur **4 reauth in 8,6 h** (alle iPhone `F2:62`, isolierte
|
||||
> Einzel, 0 Ausfälle), Verlauf **218 → 40 → 4**. Vorbedingung geprüft (Core-Uptime 1d18h, `caps joined=0`).
|
||||
> ⇒ **7.24.1 war die Ursache/der Fix, nicht `ft-over-ds`.** Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen,
|
||||
> **kein Geräte-Write** (Soll steht: `ft=yes ft-over-ds=yes`, Compliance 140/140). Der folgende Detailblock zu
|
||||
> **kein Geräte-Write** (Soll stand: `ft=yes ft-over-ds=yes`, Compliance 140/140). **Am 2026-09-04 hat
|
||||
> Christoph `ft=no` auf `sec-main` als Dauerzustand entschieden** (Hebel 2 doch gezogen). Der folgende Detailblock zu
|
||||
> Hebel 1 ist historisch.
|
||||
|
||||
#### 🔬 Hebel 1 — gezogen am 2026-08-31 08:09, Messung läuft bis 2026-09-01
|
||||
@@ -1858,16 +1423,20 @@ während eines Calls deployen, `/system backup` je AP vorher.
|
||||
- 🔴 **Ein vom Pin abgewiesener Verbindungsversuch ist in Ping-Daten nicht von einem Funkloch zu
|
||||
unterscheiden** — und der Manager protokolliert ihn auf `info`-Ebene **gar nicht**. Android quittiert
|
||||
die Abweisung mit „Verbindung fehlgeschlagen" und **wählt das Netz ab**; danach misst der Rest des
|
||||
Rundgangs Stille. Die Klick-Konsole schlägt deshalb Alarm, sobald das gepinnte Gerät nirgends hängt,
|
||||
und schreibt nach 45 s `UNERREICHBAR=apX` — denn „der Gegen-AP trägt hier nicht" **ist das Messergebnis**.
|
||||
- 📐 **Eine Runde, zwei Messpunkte** (Christophs Aufbau 2026-08-22): das frei roamende iPhone zeigt, was
|
||||
ein normaler Client wählt — interessant ist der **andere**. Das S22 wird per `access-list` auf den
|
||||
Gegen-AP gezwungen ([`tp25-pin-s22.sh`](tp25-pin-s22.sh)). Gute Referenz + schlechte Sonde = die
|
||||
Abdeckung steht, die Rückfallebene nicht. Memory `ref_gegen_ap_messung`.
|
||||
Rundgangs Stille. Die Klick-Konsole schlägt deshalb Alarm, sobald ein **gepinnter** Client nirgends
|
||||
hängt (`sta_iface=absent` in `sampler-<name>.csv`) — denn „der Gegen-AP trägt hier nicht" **ist das
|
||||
Messergebnis**.
|
||||
- 📐 **Eine Runde, N Messpunkte** (seit 2026-09-04): die frei roamenden Clients zeigen, was ein normaler
|
||||
Client wählt — interessant sind die **gepinnten**. Jeder Client wird **band-genau** auf einen AP
|
||||
gezwungen ([`tp25-pin-multi.sh`](tp25-pin-multi.sh), `TP25_PINSPEC="name|mac|ap|band;…"`), direkt aus
|
||||
der Klick-Konsole. Gute Referenz + schlechte Sonde = die Abdeckung steht, die Rückfallebene nicht.
|
||||
Memory `ref_gegen_ap_messung`. *(Die alte 2-Client-`access-list`-Automatik über `tp25-pin-s22.sh` ist
|
||||
mit dem Multi-Pin-Umbau entfallen; `tp25-pin-s22.sh` liefert nur noch die `map`.)*
|
||||
- ▶️ **Start ohne Werkzeugkette: `bash docs/tp25-runde.sh [name]`** — legt den Lauf unter
|
||||
`docs/messdaten/tp25-runde-<name>/` an, friert die AP-Karte ein, startet Konsole (Port 3031) und
|
||||
Messung, sagt die **vom Interface gelesene** Adresse fürs Telefon an und löst beim Beenden den Pin.
|
||||
Auswertung: `tp25-gehweg-auswertung.py --dir …`.
|
||||
Messung, sagt die **vom Interface gelesene** Adresse fürs Telefon an. In der Konsole: **pinnen**
|
||||
(N Clients je AP/Band) → **gehen** (🚶/📍) → **Räume antippen** → **stoppen**; beim Beenden löst der
|
||||
Wrapper den Multi-Pin. Auswertung: `tp25-gehweg-auswertung.py --dir …`.
|
||||
- ℹ️ **Und das alles passiert TROTZ aktivem 802.11k/v und FT** (`st-main` mit `rrm=yes`/`wnm=yes`,
|
||||
`ft-over-ds=yes`, live geprüft). Die Merkregel `ref_ft_is_not_roaming` („11k/v fehlen") ist
|
||||
**historisch**. Bei −48…−55 dBm kostet eine Übergabe 0–2,6 s, meist unter einer halben Sekunde —
|
||||
@@ -2785,16 +2354,6 @@ kann in Ruhe nachziehen.
|
||||
|
||||
---
|
||||
|
||||
### TP34 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ▸ 🔜 STUB
|
||||
> **Konzept + Architektur-Skizze:** [`docs/handoff-20260827-inventur-und-architektur.md`](handoff-20260827-inventur-und-architektur.md) — bitte dort weiterlesen (Architektur-Abschnitt).
|
||||
- **Scope:** Reverse-Proxy-Hosts werden im **Nautobot-Datenmodell** gepflegt (Site, Host, Backend-Spec). **Golden Config** rendert den Caddyfile aus den Daten, Diff-Check gegen die Live-Version auf `caddy-bf1`, Compliance-Checks + Transaktionen identisch zum bestehenden Workflow für Router-Configs.
|
||||
- **Auslöser (Inventur 2026-08-27):** der Caddyfile (`bf-infra/caddy/Caddyfile`) ist heute **manuell gepflegt** — kein Drift-Check, keine Transaktionen, andere Projekte können keine Sites anlegen ohne Christoph. Lösung: **Nautobot/GC**, kein Caddy-API-Sidecar (kein zweites SSoT).
|
||||
- **Erster Anwendungsfall:** `terpene.teilzeitladen.xyz → http://10.40.20.167:8890` (Mac-mini-Backend, dort PostgREST + sync + web via lokalem Caddy `:8890`). Vor TP34: manueller Quick-Win-Caddy-Block.
|
||||
- **Abh.:** TP22 (GC-Pipeline) ✅ vorhanden · TP23 (DNS-Policy + Caddy-Voraussetzungen) ✅ Stufe 1. Hängt an **nichts**, eigene Implementierung im Runbook nach dem Quick-Win.
|
||||
- **Status:** 🔜 Stub; Konzept + Architektur-Skizze vollständig in Handoff-Datei. Implementierung in eigenem Runbook nach terpene-Caddy-Quick-Win.
|
||||
|
||||
---
|
||||
|
||||
### TP35 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard ▸ ⏳ **ANGELEGT 2026-08-28, Schicht 1 (FQDN→IP) + Schicht 3 (globale Regeln) + Schicht 6 (Skill) ✅**
|
||||
- **Auslöser:** Bestandsaufnahme Session 2026-08-28 (Handoff `docs/handoff-20260828-pre-opencode-migration.md`). Erkenntnis: MCP-Configs sind heute über `~/.config/opencode/opencode.jsonc` UND `~/.claude.json` verstreut, ohne klares Schema.
|
||||
- **Befund (geprüft):** 11/12 Backups im Verzeichnis `~/dev/.pre-opencode-migration-backups/20260828-150335/` gzip-valid; `uwe-job-bewerbung.tar.gz` korrupt (Source unverändert da). Repos und globale Configs md5-/HEAD-identisch zum Vor-Session-Stand. Synology-Drive-Sync aktiv (Anwender, manuell).
|
||||
@@ -2812,17 +2371,17 @@ kann in Ruhe nachziehen.
|
||||
|
||||
---
|
||||
|
||||
### TP36 — SSoT via Nautobot für `teilprojekte.md`-Live-Daten + Datei-Aufteilung ▸ 🔜 ANGELEGT 2026-08-28, Scope-Ausarbeitung eigene Session
|
||||
- **Auslöser:** Skill-Run 2026-08-28 (`docs/handoff-20260828-session-a-projektstatus.md`) lieferte „Nächster Schritt (2026-08-26): TP31 Schritt 5" als Output, obwohl seit dem 26. drei Folge-Sessions parallel offen waren. **Manuell gepflegte Doku mit Live-Daten verliert gegen die Realität, sobald sie lang wird.**
|
||||
- **Befund:** Drei Stellen pro Edit berücksichtigen müssen — Quick-Index (Tabelle), TP-Abschnitt, Ist-Stand-Block. Keine schlägt die andere. Wer eine ändert, **muss** die anderen mitlesen. Die Datei hat aktuell 1940 Zeilen, der Ist-Stand-Block steht bei Z. 28, das Archiv-Cover bei Z. 1771. Strukturelles Risiko, kein Einzelfall.
|
||||
- **Scope (Skizze, eigene Session):**
|
||||
1. **Nautobot Custom Fields für Live-Daten** — RouterOS-Version, Compliance-Zahl (104/104, 26 Rules × 4 Geräte), DHCP-Server-Anzahl, MGMT-IPs, DNS-Upstream-Modus. Script `nautobot/gc-snapshot.py` rendert `docs/teilprojekte.md` (oder einen schmaleren Ersatz) als **read-only Snapshot**.
|
||||
2. **Datei-Aufteilung** — `docs/teilprojekte-index.md` (1 Seite: nur aktive TPs, Titel + Status + Datum), `docs/teilprojekte-aktuell.md` (Live-Snapshot aus Nautobot), `docs/teilprojekte-archiv.md` (bleibt Volltext). `/projektstatus`-Skill liest Index + Archiv, nicht die Snapshot-Datei.
|
||||
3. **Archiv-Anzeige im Quick-Index** — die heute 34 Namen stehen heute in *einer* Zeile am Ende. Sichtbarkeit = „Geschichte zählt". Quick-Index-Block bekommt eine zweite Tabelle mit „abgeschlossen seit …" und Link aufs Archiv.
|
||||
4. **Migrationspfad** — kompatibel mit bestehendem Workflow (`/projektstatus`-Skill konsumieren, niemand außer mir editiert Live-Markdown). Testphase = beide Dateien parallel führen.
|
||||
- **Abgrenzung:** TP36 ist Querschnitt, nicht Kern-BF. Eigene Session, eigener Handoff. Nicht heute.
|
||||
- **Verzahnt mit:** TP22 (Nautobot-GC als Vorbild), TP35 (geht Hand in Hand — beide sind „SSoT + Konsolidierung"), `bf-infra/CLAUDE.md`-Lessons.
|
||||
- **Referenzen:** Lessons-Block-Eintrag „Manuell gepflegte Doku mit Live-Daten verliert gegen die Realität" · Handoff Session A 2026-08-28.
|
||||
### TP36 — Index/Status als SSoT nach Gitea (Steuerung aus der branchbaren Datei herauslösen) ▸ ⏳ **CUTOVER 2026-09-07** — Index/Status = Gitea-Issues, Prosa bleibt Datei
|
||||
- **Auslöser:** Skill-Run 2026-08-28 (`docs/handoff-20260828-session-a-projektstatus.md`) lieferte einen überholten „Nächster Schritt"-Output; **manuell gepflegte Doku mit Live-Daten verliert gegen die Realität, sobald sie lang wird.** Verschärft 2026-09-06: `docs/teilprojekte.md` wurde in **40/40** letzten Commits angefasst — jeder TP editierte Quick-Index **und** Ist-Stand-Block → **Merge-Konflikt-Magnet**, der Branch-pro-TP verhinderte (ein Parallel-Branch hatte so die TP37-Doppelvergabe verursacht).
|
||||
- 🔄 **Reframe 2026-09-06 (Christoph):** von „SSoT via **Nautobot**" auf „SSoT via **Gitea**". Grund: Index+Status sind **kuratierte Steuerung** (Reihenfolge, Marker, Abhängigkeiten), kein Geräte-Live-Zustand — die gehören auf ein **Issue-Board**, nicht in Custom Fields. **Nautobot bleibt nur für Live-Gerätedaten** relevant (Compliance-Zahl, RouterOS-Version …) → als optionaler Rest unten, entkoppelt von diesem TP.
|
||||
- **Modell (umgesetzt):** TP = **Gitea-Issue** in [`admin/bf-infra`](https://gitea.emsenhuber.net/admin/bf-infra/issues) · Status = Label `status:*` (todo/wip/paused/idea/concept/done/dropped) · Gruppierung = Label `thema:*`. Seeder `bin/gitea-tp-sync.py` (idempotent, Dry-Run default, `--commit` schreibt; Token aus `~/.hermes/.env`, **nie ausgeben**). Erstlauf: **68 Issues (25 open/43 closed) + 15 Labels**.
|
||||
- ✅ **Erledigt (A1–A3):** Labels/Modell · 68 TPs → Issues · Board = Index (Label-Filter statt Handtabelle).
|
||||
- ⏳ **Cutover 2026-09-07 (diese Session):**
|
||||
- **A4** — Quick-Index-Tabelle + „Ist-Stand (Kurz)"-Block aus dieser Datei entfernt; Header zeigt aufs Gitea-Board; die vier **Genehmigten Ausnahmen** unter eigenem Governance-Header behalten. Alle `### TPnn`-Prosa bleibt.
|
||||
- **A5** — `/projektstatus` liest Baum+Status aus der **Gitea-API** (Issues + `status:*`/`thema:*`), Transaktionen weiter aus `nautobot/gc-transaktion.py status`, Prosa-Detail bei Bedarf aus dem Repo. Beide Skill-Kopien (`~/.claude/skills/…` **und** `~/.config/opencode/skills/…`, `cp`, kein Symlink).
|
||||
- **A6** — Projekt-`CLAUDE.md`: Pflichtlektüre = Gitea-Board (Status) + Prosa-Datei (Detail); Branch-Konvention (`tpNN-kurz` jederzeit, Merge nach `main` wenn Code/Config steht, Steuerungs-Updates nach Gitea); SSoT-Notiz.
|
||||
- 🔜 **Optionaler Rest (entkoppelt, „TP36-Live"):** Live-Daten-CustomFields auf den **4 `dcim.Device`** (`ros_version`, `compliance_pct`, `dhcp_server_count`, `mgmt_ip`, `dns_upstream_mode`) + Snapshot-Skript `nautobot/gc-snapshot.py` (existiert noch nicht). Unabhängig von diesem Cutover; nur relevant, wenn die Doku Live-Zahlen tragen soll. ⚠️ **Namensfalle bereinigt:** `nautobot/tp36-cf-estate-seed.py` gehörte **nie** zu TP36 → seit 2026-09-06 `tp44-cf-estate-seed.py`.
|
||||
- **Verzahnt mit:** TP35 (SSoT + Konsolidierung), TP44 (native Nautobot-Modelle), `bf-infra/CLAUDE.md`-Lessons. Plan: `~/.claude/plans/geht-das-los-auch-reflective-lark.md`.
|
||||
|
||||
<a id="tp37"></a>
|
||||
### TP38 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch ▸ 🔜 **ANGELEGT 2026-08-31**
|
||||
@@ -2942,157 +2501,6 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
- 🔴 **Kein eigenmächtiger Datenlöschvorgang** — `purge` wirft echte Historie weg; die
|
||||
Aufbewahrungsdauer ist eine Nutzerentscheidung. Diese Session hat **nur** vergrössert und gemessen.
|
||||
|
||||
### TP41 — CF-Access-Reparatur + Zero-Trust-Konsolidierung ▸ ⏸️ **ZURÜCKGESTELLT 2026-09-01 (User-Entscheid)** — Auth-Kern durch; offen nur Gelis WARP-Client (nicht aus der Ferne machbar), User gibt ggf. später Info
|
||||
|
||||
**Auslöser:** CF-Access zwang Christoph **und Geli** täglich zum OTP-Login (soll: 30 Tage); GitHub/Google
|
||||
„funktionieren nicht". Root-Causes: Team-Domain-Rename `bf4`→`emsenhuber` (Provider-Callback veraltet),
|
||||
App-Session 24h, diverse Ordnungs-/Sicherheitslecks. Vollständiger Verlauf + offene Schritte:
|
||||
**[`docs/handoff-20260901-tp41.md`](handoff-20260901-tp41.md)**. Branch `tp41-cf-access-repair`.
|
||||
|
||||
**✅ Live erledigt:** Session 720h (Wildcard/vw/Warp-Login) · login_method-Loch zu (reusable Policy
|
||||
`bf-users` = nur `email:christoph`+`email:geli`) · `8e44f5d1` von 4 Apps auf **nur Terpenfinder**
|
||||
entkoppelt · Access-**Gruppen** `zt-humans`/`zt-home`/`zt-automation`, Policies zeigen darauf · **vw voll
|
||||
Bypass** (Lebensader offen) + **`vw/admin` per WAF-Regel öffentlich geblockt** (CF-Edge + Mobilfunk
|
||||
verifiziert) · API-Token auf das vollständige `…4148` konsolidiert (User hat Service-Tokens- + Zero-Trust-
|
||||
Write ergänzt), altes `…2174` revoken.
|
||||
|
||||
**✅ T4 DURCH (2026-09-01, Variante B):** ein Access-Service-Token **`zt-svc`** (`65f1821a`, rotiert) über
|
||||
Gruppe `zt-automation`; Terpene-Worker per `wrangler secret put TERPENE_SVC_CLIENT_ID` auf zt-svc
|
||||
umgestellt (Worker-Pin `index.js:44`); `terpene-verify`+`-v2` **gelöscht**; `test_whoami` grün.
|
||||
**Token-Altlast geklärt:** `b00722` = LIVE Caddy-DNS-Token (LXC130 caddy.env, SSoT), `d47435` = stale
|
||||
Kopie (kein Consumer) → **User revoket `d47435`**; `credentials.env`-Kopie auf b00722 realignt.
|
||||
|
||||
**✅ T3 APPLIED (2026-09-01):** Default-Profil (`0197d1b3`; das custom „Cloudflare Mesh" matcht nur den
|
||||
`warp_connector`) `fallback_domains += emsenhuber.net → [10.40.20.36, 10.40.20.37]`; Tunnel-Route
|
||||
`10.40.0.0/16` (`d1da7c8a`) da, kein `10/8`-Exclude → routbar. ⏳ Client-Verify: WARP neu verbinden.
|
||||
**✅ T2 DURCH (2026-09-01):** WAF-Regel `f26744eb` gelöscht; Caddy `vw`-Block `respond @cf_admin 404` bei
|
||||
Header `CF-Connecting-IP` (LXC130). Verify: Edge `/admin`=404, lokal/WARP `/admin`=200. ✅
|
||||
|
||||
**✅ T5 DURCH (2026-09-01, User-bestätigt):** GitHub-Login läuft; Google-Login läuft nach dem
|
||||
Consent-Screen-Fix (§A des Follow-up-Handoffs: User Type *Internal*→*External*/veröffentlicht).
|
||||
Callback für beide `https://emsenhuber.cloudflareaccess.com/cdn-cgi/access/callback`.
|
||||
**❌ Facebook/Meta für TP41 abgesagt (2026-09-01, User-Entscheid):** optional, kein Bedarf — Geli ist
|
||||
über OTP `angelikahy@outlook.de` + Google `pillepalle670@gmail.com` voll gedeckt. **Keine Facebook-App,
|
||||
kein CF-Facebook-IdP angelegt → nichts aufzuräumen.** Instagram hat ohnehin keinen CF-IdP.
|
||||
**✅ App-Launcher (D1):** je eine Bookmark-Kachel vw/hass/terpenfinder, Wildcard-Kachel raus, Launcher
|
||||
gestylt (Follow-up-Handoff §D, Policies unverändert).
|
||||
|
||||
**✅ Geli in WARP — CF-seitig nichts zu tun (2026-09-01 live geprüft):** Device-Enrollment läuft über
|
||||
die App **„Warp Login App"** (`6c32fe23…`, type `warp`) → Policy `bf-users` → Gruppe **`zt-humans`**, die
|
||||
Gelis beide Mails **schon enthält** → sie **darf bereits enrollen** (deshalb war „Device enrollment
|
||||
permissions" nicht als fehlendes Setting zu finden). Terpenfinder-`geli`-Policy lässt sie per E-Mail durch.
|
||||
⇒ verbleibt **nur ein Handgriff auf Gelis Gerät** (WARP installieren, Team `emsenhuber`, Login OTP
|
||||
`angelikahy@outlook.de` / Google `pillepalle670@gmail.com`) — nicht aus der Ferne machbar.
|
||||
🔑 **Permission-Frage beantwortet:** CF-seitig **keine** nötig. Für generelle WARP/Enrollment/User-Verwaltung
|
||||
per Account-Token: *Access: Apps and Policies → Edit* + *Access: Organizations, IdPs, and Groups → Edit*
|
||||
(User erteilt 2026-09-01). **Seat-Verwaltung (`/access/seats`) bleibt per MCP gesperrt** — `10405 Method
|
||||
not allowed for this authentication scheme` (Endpoint will User-Token, nicht Account-Token; kein Scope-Fix,
|
||||
für Enroll irrelevant).
|
||||
|
||||
**🔜 Offen — nur noch außerhalb CF:** **Gelis WARP-Client** auf ihrem Gerät (s.o.) · **T6** optional
|
||||
Rate-Limit · 🔴 `TERPENE_ANDROID_KEYSTORE_PW`-Rotation erwägen (TP30 Posten #7, Transkript-Leak).
|
||||
**d47435-Revoke ✅ · T3-WARP-Client-Verify ✅** (User, 2026-09-01).
|
||||
|
||||
**🔻 Optionaler Security-Cleanup (eigener Punkt, nicht in dieser Runde):** die `geli`-Terpenfinder-Policy
|
||||
hat **OR-Semantik** und enthält den breiten CGNAT-Bereich `94.154.138.0/24` als IP-Include → jeder aus
|
||||
dieser geteilten Range käme **ohne Login** rein. Über WARP ist der Egress ohnehin Cloudflare (nicht diese
|
||||
IP) → der IP-Include wäre dann überflüssig und entfernbar.
|
||||
|
||||
**Abgrenzung:** hass/gitea bleiben (diese Runde) öffentlich; Terpenfinder-Human-Policies nicht angefasst;
|
||||
**Gitea-Least-Privilege** = eigener künftiger TP (jedes Projekt repo-scoped Login, Admin nur Claude/Christoph).
|
||||
- Bezug **TP11** (Monitoring): eine Plattenfüllstand-Warnung hätte das Wochen früher gezeigt.
|
||||
|
||||
### TP42 — Namens-Vereinheitlichung bf.lan → emsenhuber.net ▸ 🔜 ANGELEGT 2026-09-01 (User-Okay erteilt)
|
||||
|
||||
**Auslöser (2026-09-01, TP41-Randbefund):** live gemessen liefern die realen Client-Resolver
|
||||
`10.40.20.36`/`.37` `*.emsenhuber.net → 10.40.20.40` (Caddy) — korrekt. Daneben existiert aber ein
|
||||
zweiter, gedrifteter interner Namensraum: der **Core** trägt in `/ip dns static` 11 Einträge auf
|
||||
`*.bf.lan`, der **Caddyfile** doppelt jeden internen Namen als `*.bf.emsenhuber.net`, und die alten
|
||||
AdGuard `.16`/`.17` antworten noch mit `nginx*.bf.emsenhuber.net` bzw. totem VLAN1 `10.40.41.123`.
|
||||
Christoph: „aus emsenhuber.net wurde schon wieder bf.lan — das müssen wir ein für alle mal aus allen
|
||||
Geräten rausprügeln und in Nautobot festhalten."
|
||||
|
||||
**Ziel:** genau **ein** interner Namensraum `emsenhuber.net` (Split-Horizon `.36/.37 → .40`). `.bf.lan`
|
||||
und `.bf.emsenhuber.net` werden entfernt; Sollzustand in Nautobot dokumentiert.
|
||||
|
||||
**Scope (Reihenfolge = eigene Session):**
|
||||
1. **Bestandsaufnahme** aller Fundstellen: Core `/ip dns static` (`*.bf.lan`), DHCP-Option
|
||||
`domain`/Search-Domain je Netz, Caddyfile-Aliase (`*.bf.emsenhuber.net`), AdGuard-Rewrites `.36/.37`,
|
||||
stale `.16/.17`, Geräte-`identity`.
|
||||
2. **Entscheidung Zielschema** (nur `emsenhuber.net`, ggf. `bf.emsenhuber.net` als *einzige* interne
|
||||
Zone — Christoph-Entscheid) und Migrationsreihenfolge ohne DNS-Loch.
|
||||
3. **Umsetzen:** Core-DNS + DHCP + Caddyfile angleichen; stale `.16/.17` klären/abbauen.
|
||||
4. **Nautobot:** interne Namen/Records als Sollzustand erfassen (Bezug TP37 Datenmodell).
|
||||
|
||||
**Abgrenzung:** kein CF-Public-DNS-Umbau (Zone `emsenhuber.net` öffentlich bleibt); kein Resolver-Swap
|
||||
(`.36/.37` bleiben); `.1`-als-DNS-Reste auf Clients sind separat/tolerierbar (User: „bis jetzt kein
|
||||
Problem"). **Abh.:** TP37 (Nautobot-Datenmodell) für Punkt 4; sonst nichts Offenes.
|
||||
|
||||
#### ✅ Bestandsaufnahme LIVE (2026-09-02) — Drift hat 80 % schon erledigt
|
||||
Gegen die Geräte gemessen (MikroMCP + dig), nicht aus dem Handoff:
|
||||
|
||||
| Fundstelle | Handoff 09-01 | Live 09-02 |
|
||||
|---|---|---|
|
||||
| Core `/ip dns static` | 11× `*.bf.lan` | ✅ **0× `.bf.lan`** — 10× `*.bf.emsenhuber.net` (deckt sich mit GC-Template Z.284–293) |
|
||||
| DHCP `domain` (7 Netze) | „prüfen" | `emsenhuber.net` (GC-Template Z.133 = gleich ⇒ **keine Drift**), DNS `.36/.37` |
|
||||
| `.36/.37` (Soll) | aktiv | `vw.emsenhuber.net→.40` ✅ · `macmini.bf.emsenhuber.net→.20.167` ✅ · **`macmini.emsenhuber.net→leer`** ❌ |
|
||||
| Caddyfile | `*.bf.emsenhuber.net`-Aliase | unverändert, **konsistent** mit Core-Static |
|
||||
| `.16`/`.17` | tot | laufen (:53), liefern totes VLAN1; **kein Core-Objekt** (Firewall/NAT/DNS-Upstream/Addr-List) hängt an `.16` |
|
||||
|
||||
**Kernkonflikt:** DHCP verteilt `emsenhuber.net` (bare), Records leben unter `bf.emsenhuber.net` ⇒ bare `macmini` löst nicht.
|
||||
|
||||
#### ✅ Entscheidungen (User, 2026-09-02)
|
||||
1. **Zielschema = `bf.emsenhuber.net`** (der wenig-Arbeit-Weg): DHCP-domain → `bf.emsenhuber.net`, dann
|
||||
löst `macmini` bare korrekt; Core-Static + Caddyfile bleiben (tragen schon `.bf.`). Öffentliche Zone
|
||||
`emsenhuber.net` bleibt getrennt.
|
||||
2. **`.16` (adguard_bf1) abbauen**, **`.17` (adguard_fm2 = Standort FM) NICHT anfassen.**
|
||||
|
||||
#### ✅ Umsetzung DURCH (2026-09-02)
|
||||
- **(A) DHCP-domain `emsenhuber.net → bf.emsenhuber.net` über GC ✅** — kein Direkt-Write:
|
||||
`mikrotik_routeros.j2` Z.133 gehoben → `push-gc-template.py` (Commit `7a4068c4`, **Nebenbefund:** das
|
||||
Remote-Template hing dem lokalen hinterher — es trug noch `*.bf.lan`+`router.lan`, obwohl Gerät+lokal
|
||||
längst `.bf.emsenhuber.net` fuhren; der Push hat das GC-Repo an den Live-Stand angeglichen) →
|
||||
GitRepo-Sync (SUCCESS) → manueller ConfigPlan **`tp42-dhcp-domain`** (`set [find] domain=…`, weil
|
||||
`add` das Netz doppelt anlegen würde) → Deploy an **crs326-core** → Live **7/7 = bf.emsenhuber.net**,
|
||||
Compliance **140/140, keine Drifts**. Der Job steht dauerhaft in `nautobot/gc-configplan.py`.
|
||||
- **(B) `.16`-Teardown ✅** — `adguard_bf1` lag im geteilten Portainer-Stack **195 `adguard`** (Docker-LXC
|
||||
`.126`, **ep2** — *nicht* ep14) **zusammen mit `adguard_fm2` (`.17`, Standort FM!)**. Deshalb **nicht**
|
||||
Stack löschen, sondern compose auf nur `.17` getrimmt + Stack redeployt (Prune): `.16`-Container weg,
|
||||
`.16:53` tot; **`.17` läuft** (⚠️ ~40 s mit-neugestartet durch den Redeploy — kurzer FM-DNS-Blip,
|
||||
danach sauber). Rollback-compose gesichert. 🧹 Daten-Dirs `/opt/docker/adguard_bf1/*` bleiben als
|
||||
harmlose Leichen auf dem LXC.
|
||||
- **(C) Nautobot-Soll** (Scope-Punkt 4) bleibt an **TP37** gekoppelt (Datenmodell), separat — kein Blocker.
|
||||
|
||||
**Status:** ✅ **Namensraum vereinheitlicht (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`,
|
||||
`.bf.lan` raus, `.16` abgebaut, Compliance 140/140. Offen nur der TP37-gekoppelte Nautobot-Doku-Teil.
|
||||
Handoff: [`docs/handoff-20260901-tp42-naming.md`](handoff-20260901-tp42-naming.md).
|
||||
|
||||
### TP43 — dw-Standort-DNS-Uplink (site-spezifische Overrides) ▸ 🔜 ANGELEGT 2026-09-01 (User-Okay erteilt)
|
||||
|
||||
**Auslöser (2026-09-01, TP41-Follow-up):** Geli soll `420.teilzeitladen.xyz` am dw-Standort erreichen.
|
||||
Split-Horizon am dw scheitert an der **FritzBox** (`10.95.10.1`): kein Per-Host-A-Override im Standard
|
||||
(nur DNS-Rebind-Ausnahmen ohne IP). Christoph: *„keine Chance im FritzBox-Standard? … lieber ein TP als
|
||||
uns verfranzen. Geli kommt rein, das zählt."*
|
||||
|
||||
**Ziel:** dem dw-Standort einen **eigenen Resolver/DNS-Uplink** geben, der **site-spezifische Overrides**
|
||||
hält (erster Fall `420.teilzeitladen.xyz` → BF-Caddy über WG), als DNS der FritzBox/Clients gesetzt.
|
||||
|
||||
**Kernfrage (explizit):** Verhalten bei **WG-Disconnect.** Ein Override auf eine BF-interne IP
|
||||
(`10.40.20.40`) fällt aus, wenn der Tunnel unten ist. Für einen Login-gegateten Dienst gibt es kein
|
||||
„kein Login **und** funktioniert bei WG-down" — Optionen: (a) Override nur bei WG-up (Failover-Resolver),
|
||||
(b) öffentlicher Pfad (CF, mit Login) als Fallback. Zu entwerfen.
|
||||
|
||||
**Sofortlösung (nicht Teil von TP43):** Geli per **WARP** (Session statt täglichem Login, CGNAT egal,
|
||||
degradiert sauber: WG-up = nahtlos, WG-down = öffentliche URL + Login).
|
||||
|
||||
**BF-seitige Prereqs:** Caddy-vhost `420.teilzeitladen.xyz` → macmini-Worker (`10.40.20.167:8890`) mit
|
||||
Cert (DNS-01, `…4148` hat teilzeitladen.xyz-DNS) · WG-Route dw→`10.40.20.40` · Split-Horizon für
|
||||
`420.teilzeitladen.xyz` (heute liefern auch `.36/.37` public). **Vorher verifizieren:** FritzOS-Version +
|
||||
Dauerläufer am dw. Cross-Projekt (teilzeitladen.xyz). Handoff:
|
||||
[`docs/handoff-20260901-tp41-followup.md`](handoff-20260901-tp41-followup.md).
|
||||
|
||||
**Status:** 🔜 angelegt 2026-09-01, eigene Session.
|
||||
|
||||
### TP37 — Nautobot als durchgehende Netzwerkdoku: Hosts/Container/Dienste, erster Anwendungsfall CF-Connectoren ▸ 🔜 ANGELEGT 2026-08-30 (User-Okay erteilt)
|
||||
> **Christophs Formulierung (2026-08-30):** *„können wir das setup mit den cf-connectoren dann auch in
|
||||
> nautobot abbilden. quasi eine durchgehende netzwerkdoku stück für stück. muss eh fast sein, da du auf
|
||||
@@ -3117,10 +2525,10 @@ Dauerläufer am dw. Cross-Projekt (teilzeitladen.xyz). Handoff:
|
||||
„wo läuft was" steht.
|
||||
2. **Dienste abbilden** — `ipam.Service` je exponiertem Port (Caddy `:80/:443`, AdGuard `:53`,
|
||||
Nautobot `:8080`, n8n `:5678`, …). Das ist Nautobot-Kernmodell, **keine eigene App nötig**.
|
||||
3. **CF-Connectoren als erster Anwendungsfall** — Tunnel (`bf-public`, historisch `bf-mm`/`bf-nas`/`bf-pve`)
|
||||
als Objekt, Connector als Relation Tunnel↔Host, Ingress-Regel als Hostname→Backend-Paar.
|
||||
Modellwahl offen: Custom Fields + Relationships auf Bestandsmodellen **oder** eine kleine eigene App.
|
||||
🔴 **Erst Punkt 1–2 entscheiden, dann diese Frage** — umgekehrt entsteht ein Modell für einen Sonderfall.
|
||||
3. ✅ **CF-Connectoren — ERLEDIGT via [TP44](teilprojekte-archiv.md#tp44) (Etappe 1, 2026-09-02).** Die Modellwahl ist damit
|
||||
entschieden: **native `nautobot.cloud`-Modelle** (Tunnel/WARP = `CloudNetwork`, VPC/Hyperdrive/Worker/
|
||||
Access-App = `CloudService`, IDs/Ports/Origins in `extra_config`), **keine eigene App**. Offen nur noch
|
||||
TP44 Etappe 2 (CF-API→Nautobot-Sync). ⇒ **Punkt 1+2 unten sind der echte Rest von TP37.**
|
||||
4. **Drift-Check** — Skript liest CF-API (`cfd_tunnel` + `configurations`) und vergleicht gegen Nautobot,
|
||||
analog `nautobot/gc-configplan.py --refresh-compliance`. **Read-only zuerst**; ob Nautobot je nach
|
||||
Cloudflare *schreibt*, ist eine spätere, eigene Entscheidung.
|
||||
@@ -3138,7 +2546,11 @@ Dauerläufer am dw. Cross-Projekt (teilzeitladen.xyz). Handoff:
|
||||
die Pipeline ist stabil und **104/104 compliant**. TP37 vergrößert die Angriffsfläche dieser Pipeline.
|
||||
Deshalb Punkt 1–2 als eigener, abnehmbarer Schritt **mit Compliance-Gegenprobe danach**, nicht als
|
||||
Nebeneffekt von Punkt 3.
|
||||
- **Status:** 🔜 angelegt 2026-08-30, Scope-Ausarbeitung in eigener Session. Kein Runbook.
|
||||
- **Status:** ⏳ **Punkt 3 (CF) ✅ via TP44; Punkt 1+2 (Substrat + Dienste) offen** — das ist die
|
||||
fehlende Voraussetzung für TP34. Datenmodell **entschieden 2026-09-06 (native Modelle:
|
||||
`virtualization.Cluster/VirtualMachine` + `ipam.Service`, keine App)**. Runbook liegt jetzt vor:
|
||||
[`handoff-20260906-tp37-34-36-plan.md`](handoff-20260906-tp37-34-36-plan.md). Live-Ausgangslage:
|
||||
VM 0 · Cluster 0 · `ipam.Service` 0 · CustomField 0 (2026-09-06 gegen die API gelesen).
|
||||
|
||||
---
|
||||
|
||||
@@ -3158,7 +2570,7 @@ Kurzorientierung, wo was zu finden ist:
|
||||
| **Firewall / QoS** | [TP5](teilprojekte-archiv.md#tp5) deny-by-default · [TP26](teilprojekte-archiv.md#tp26) `ip-firewall-filter` unter Drift-Kontrolle · [TP6](teilprojekte-archiv.md#tp6) CAKE *(geschlossen statt gebaut)* · [TP6.1](teilprojekte-archiv.md#tp6-1) Upload-Priorisierung |
|
||||
| **Discovery / Media** | [TP7](teilprojekte-archiv.md#tp7) mDNS · [TP7.1](teilprojekte-archiv.md#tp7-1) SSDP-Relay · [TP7.2](teilprojekte-archiv.md#tp7-2) HA↔Sonos |
|
||||
| **Hosts / Migration** | [TP14](teilprojekte-archiv.md#tp14) (+[.1](teilprojekte-archiv.md#tp14-1)/[.2](teilprojekte-archiv.md#tp14-2)/[.2a](teilprojekte-archiv.md#tp14-2a)/[.3](teilprojekte-archiv.md#tp14-3)/[.5](teilprojekte-archiv.md#tp14-5)) VLAN20-Cutover · [TP17](teilprojekte-archiv.md#tp17) Shelly→VLAN30 · [TP19](teilprojekte-archiv.md#tp19) VLAN1-Retirement |
|
||||
| **Control-Plane** | [TP20](teilprojekte-archiv.md#tp20) Platform-Postgres · [TP21](teilprojekte-archiv.md#tp21) Nautobot GC · [TP22](teilprojekte-archiv.md#tp22) Adoption + Compliance-Push · [TP27](teilprojekte-archiv.md#tp27) RouterBOOT 7.24 · [TP-R](teilprojekte-archiv.md#tp-r) Capabilities-Sweep |
|
||||
| **Control-Plane** | [TP20](teilprojekte-archiv.md#tp20) Platform-Postgres · [TP21](teilprojekte-archiv.md#tp21) Nautobot GC · [TP22](teilprojekte-archiv.md#tp22) Adoption + Compliance-Push · [TP27](teilprojekte-archiv.md#tp27) RouterBOOT 7.24 · [TP-R](teilprojekte-archiv.md#tp-r) Capabilities-Sweep · [TP44](teilprojekte-archiv.md#tp44) CF-Estate Doku-SSoT |
|
||||
| **Zugang** | [TP10](teilprojekte-archiv.md#tp10) Multi-Site-VPN *(offen nur der DDNS-Updater, s. Follow-ups)* · [TP16.1](teilprojekte-archiv.md#tp16-1) natives CF-Mesh |
|
||||
| **Verworfen** | [TP9](teilprojekte-archiv.md#tp9) Gateway-HA (User-Entscheid 2026-08-08) |
|
||||
|
||||
@@ -3313,3 +2725,78 @@ TP22 · TP-R · **TP3.2** (08-17) · **TP5** (08-18) · **TP7.1** (08-18) · **T
|
||||
7. **Foundation (2026-07-14):** **TP15** (MikroTik-Switching / Omada-WLAN, 2× CRS326 bestellt) ∥ **TP20 (Platform-Postgres, single-site replication-ready)** → **TP21 (Nautobot Golden Config)** ∥ **TP20.1 (Vaultwarden→PG + 2-Site-HA)**. TP20 hängt an **TP18 ①** (Garage-Bucket für pgBackRest).
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### TP43 — dw-Standort: interne BF-DNS-Namen über WG auflösbar machen ▸ ✅ **UMGESETZT 2026-09-11** — dw-FritzBox: Upstream `.36`+`.37`, DNS-Rebind-Ausnahme `bf.emsenhuber.net`, Fallback-Haken „öffentliche NS bei Unreachbarkeit" **gesetzt** (Ideal-Variante: `.37`-Redundanz bleibt, BF-down → public). (verworfene 420-Historie am Ende dieses Abschnitts)
|
||||
|
||||
**Live-Auslöser (2026-09-11):** Christoph am dw-Standort, WG-Tunnel zu BF up, kann `portainer.bf.emsenhuber.net`
|
||||
nicht erreichen. Direkte IP `https://10.40.20.126:9443` geht ⇒ **reines DNS-, kein Routing-Problem.** Der
|
||||
dw-Client nutzt die FritzBox (`10.95.10.1`) als Resolver; die kennt die interne Zone `bf.emsenhuber.net`
|
||||
(Split-Horizon `.36/.37 → 10.40.20.40` Caddy) nicht.
|
||||
|
||||
**Korrektur gegenüber dem verworfenen Scope:** Der ursprüngliche Fall (`420.teilzeitladen.xyz`) ist ein
|
||||
**öffentlicher** CF-Worker und war nie das Problem — der ist via TP41/WARP + CF Access gelöst. Das echte,
|
||||
offene Problem sind **interne** BF-Namen (`*.bf.emsenhuber.net`) über den WG-Tunnel.
|
||||
|
||||
**Live verifiziert (2026-09-11, `dig @10.40.20.36` über den Tunnel):** `portainer.bf.emsenhuber.net
|
||||
→ 10.40.20.40` (Caddy), `macmini.bf.emsenhuber.net → 10.40.20.167` — alle internen bf-Namen landen
|
||||
in `10.40.0.0/16`, über WG erreichbar. `ds-fm`/`gateway-fm.emsenhuber.net → 188.114.96.10/.97.10`
|
||||
= **Cloudflare-Anycast** (public), **nie `10.101.0.x`**. ⇒ **Der fm-Einwand (10.101.x intern ohne
|
||||
Pfad von dw) trifft für `.36/.37` nicht zu** — fm läuft über den public `fm.emsenhuber.net`-Tunnel,
|
||||
nicht über interne Records. (WG-Topologie TP10: dw-Peer AllowedIPs BF-seitig = `10.40.0.0/16`, dw
|
||||
routet **nicht** nach `10.101.0.0/24`.)
|
||||
|
||||
**Constraint am dw:** die 24/7-Geräte `10.95.10.246/.247` sind **Cudy-APs (kein DNS-Host)**; die
|
||||
FritzBox kann **kein Per-Zone-Forwarding** (nur einen globalen Upstream) **und per DHCP nur EINE
|
||||
DNS-IP ausliefern** (⇒ „mehrere NS an die Clients" scheidet aus). Ein echter conditional forwarder
|
||||
(dnsmasq `server=/bf.emsenhuber.net/.36`) bräuchte einen Zusatz-Dauerläufer — abgelehnt.
|
||||
|
||||
**Entscheidung (Christoph, 2026-09-11): FB-Upstream + Rebind-Ausnahme.** Clients bekommen per DHCP
|
||||
`10.95.10.1` (die FB selbst, Default); die **FB forwardet an die BF-Resolver**: *DNS-Server → „Andere
|
||||
DNSv4-Server"* Bevorzugt **`10.40.20.36`**, Alternativ **`10.40.20.37`** (beide BF, beide
|
||||
Voll-Resolver → Redundanz bleibt). Sauberer als der zwischenzeitliche DHCP-Multi-Gedanke: die FB ist
|
||||
der **einzige** Client-Resolver und `.36` löst **intern und public** auf → alles geht zuerst an `.36`,
|
||||
public wird nur als Unreachable-Fallback konsultiert, **nie „zuerst"** → kein NXDOMAIN-Leak im
|
||||
Normalbetrieb.
|
||||
|
||||
🔴 **DNS-Rebind-Schutz-Ausnahme `bf.emsenhuber.net`** (*Heimnetz → Netzwerk → Netzwerkeinstellungen →
|
||||
DNS-Rebind-Schutz*) ist **zwingend**: sonst filtert die FB die Privat-IP-Antworten (`10.40.x`) als
|
||||
Rebind-Angriff weg → bf-Namen liefern nichts, obwohl `.36` korrekt antwortet. Die Ausnahme ist ein
|
||||
reines Namens-Whitelist (keine IP).
|
||||
|
||||
**BF-down-Fallback (Verzweigung am FRITZ!OS-Stand):** Christoph erinnert ein Setting „öffentliche NS
|
||||
verwenden, wenn die primären nicht erreichbar sind". Vorhanden → aktivieren, Upstream bleibt
|
||||
`.36`+`.37`, BF-down geht auf public. Fehlt → Alternativ-Slot statt `.37` auf **public `9.9.9.9`**
|
||||
(kostet `.37`-Redundanz); dann zusätzlich prüfen, dass die FB **strikt primär** fragt (nicht
|
||||
round-robbt), sonst NXDOMAIN-Leak für `bf.*` bei BF-up.
|
||||
|
||||
- **BF-up:** FB → `.36` → interne `bf.*` (→ `.40`, dank Rebind-Ausnahme durchgereicht) **und** public.
|
||||
- **BF-down:** `.36`/`.37` unreachable → public-Fallback → public lebt, `bf.*` = NXDOMAIN (erwartet).
|
||||
|
||||
**Bewusst akzeptierter Tradeoff:** bei BF-up läuft Geli's **gesamter** Haushalt-DNS über Linz
|
||||
(`.36`/AdGuard-Filter) — vermeidbar nur mit conditional forwarder (Zusatz-HW, abgelehnt).
|
||||
|
||||
**On-site-Verifikationen (Christoph, am dw):** (1) Rebind-Ausnahme greift — `nslookup
|
||||
portainer.bf.emsenhuber.net` über die FB → stabil `10.40.20.40`; (2) BF-down-Fallback vorhanden
|
||||
(Setting) oder via public-Alternativ-Slot gesetzt; (3) nur falls BF+public gemischt: FB fragt strikt
|
||||
primär. Rückfall pro Gerät: `/etc/hosts` `10.40.20.40 <name>` oder WARP `fallback_domains
|
||||
bf.emsenhuber.net → [.36,.37]` (TP41-Präzedenz).
|
||||
|
||||
**Sofort-Workaround (läuft unabhängig):** `https://10.40.20.126:9443` direkt bzw.
|
||||
`/etc/hosts`-Zeile `10.40.20.40 portainer.bf.emsenhuber.net`.
|
||||
|
||||
**Abhängigkeiten:** WG-Route dw→`10.40.0.0/16` (steht). Grenzt an TP42 (interner Namensraum
|
||||
`bf.emsenhuber.net`) und TP10 (Multi-Site-VPN). **Umsetzung = eigene On-site-Session** (FritzBox-DHCP
|
||||
ist Christophs manueller Schritt, kein Tool-Zugriff).
|
||||
|
||||
---
|
||||
|
||||
**Historie — verworfener 420-Scope (angelegt 2026-09-01, verworfen 2026-09-11):**
|
||||
|
||||
**Auslöser (2026-09-01, TP41-Follow-up):** Geli soll `420.teilzeitladen.xyz` am dw-Standort erreichen.
|
||||
Split-Horizon am dw scheitert an der **FritzBox** (`10.95.10.1`): kein Per-Host-A-Override im Standard
|
||||
(nur DNS-Rebind-Ausnahmen ohne IP). Christoph: *„keine Chance im FritzBox-Standard? … lieber ein TP als
|
||||
uns verfranzen. Geli kommt rein, das zählt."* — Diese Prämisse fiel weg: `420.teilzeitladen.xyz` ist ein
|
||||
public CF-Worker hinter CF Access (löst auf CF-Anycast), kein BF-Origin ⇒ dafür braucht es kein
|
||||
Split-Horizon; Zugang via TP41-**WARP** gelöst (Geli 2026-09-10 via Android). Der Override-Bedarf für
|
||||
**interne** Namen blieb dabei übersehen — deshalb reopened, s.o.
|
||||
|
||||
82
docs/tp23-stufe6-bypass-sperre-runbook.md
Normal file
82
docs/tp23-stufe6-bypass-sperre-runbook.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# TP23 Stufe 6 — DNS-Bypass-Sperre (Runbook für eine SEPARATE, VOR-ORT-Session)
|
||||
|
||||
> 🔴🔴 **HARTES GATE: NUR VOR ORT ausführen. NICHT aus der Ferne, nicht während Christoph
|
||||
> abwesend ist.** (Notiert 2026-09-06: Christoph ist ~1 Woche nicht vor Ort → in diesem Fenster
|
||||
> **gar nicht anfangen**, auch nicht die log-only-Vorstufe scharf lassen.)
|
||||
>
|
||||
> Grund: Die Sperre macht das AGH-Paar `.36`/`.37` zum **harten DNS-SPOF**. Bis heute kann jeder
|
||||
> Client bei einem AGH-Ausfall auf einen externen Resolver ausweichen; **nach** der Sperre nicht
|
||||
> mehr — ein AGH-Ausfall = **hausweiter DNS-Ausfall** (betrifft die Familie), und ohne physischen
|
||||
> Zugriff gibt es keinen schnellen Rollback. Eine falsch platzierte Drop-Regel kann außerdem den
|
||||
> **Fernzugang selbst** (WG/cf-mesh) aussperren, mit dem man den Fehler zurücknehmen wollte.
|
||||
|
||||
## Ziel
|
||||
Clients dürfen plain **DNS (udp/tcp 53)** und **DoT (853)** nur noch zu den erlaubten Resolvern
|
||||
(`10.40.20.36`, `10.40.20.37`, Core `10.40.X.1`) schicken. Externe DNS-Server (Smart-TV mit
|
||||
`8.8.8.8` hartkodiert, App-eigene Resolver) werden geblockt → **kein Umgehen des Filters**.
|
||||
Das ist der letzte Baustein von TP23 und lebt in der **TP5-Firewall** (`forward`-Kette).
|
||||
|
||||
## Voraussetzungen
|
||||
**Erfüllt (Stand 2026-09-06):**
|
||||
- Self-Service-Oberfläche steht und ist getestet (HA-Dashboard `dns-ausnahmen`, Stufe 5) → ein
|
||||
geblockter Dienst kann **ohne Admin** temporär freigegeben werden. Das war die harte
|
||||
Reihenfolge-Regel („erst der Ersatz, dann die Sperre").
|
||||
- `dns-bypass-exempt`-Gruppe existiert auf dem Resolver-Paar (AGH-Client-Seite).
|
||||
|
||||
**Entwurf liegt bereit (freigegeben 2026-09-11, NOCH NICHT deployt — Christoph war remote):**
|
||||
- Phase A (Substrat + Exempt/JIT-Accepts + log-only): `routeros/tp23-stufe6-phaseA-exempt-log.rsc`
|
||||
- Phase B (reject + Auto-Revert): `routeros/tp23-stufe6-phaseB-reject.rsc`
|
||||
- 🔴 **Vor dem Deploy live verifizieren** (im Dateikopf markiert): (1) der Egress-Accept-Anker in
|
||||
der forward-Kette, vor dem die Regeln stehen müssen; (2) die Exempt-IPs des dev-MacBooks.
|
||||
|
||||
**Noch zu bauen (Teil dieser Session, VOR dem Drop):**
|
||||
- **Firewall-Exempt** ist noch NICHT da: eine Address-List `dns-bypass-exempt` (permanent) mit dem
|
||||
dev-MacBook (`10.40.10.183` **und** `10.40.10.82`, dual-homed) + Agent-Toolchain, plus eine
|
||||
`accept`-Regel **vor** dem Drop. Ohne die verliert der Entwicklungsrechner seinen Ausweg.
|
||||
- Ad-hoc-JIT: Address-List `jit-dns-bypass` mit `timeout` nach dem Muster `jit-admin`
|
||||
(`routeros/tp5-etappe4-input-mgmt.rsc`) — selbstverfallende Freigabe.
|
||||
|
||||
## Bekannte Grenze (offen benennen, nicht „lösen")
|
||||
Die Sperre greift auf **Port** (53/853). **DoH läuft über 443** und ist damit portgleich zu HTTPS —
|
||||
ein Gerät, das DoH gegen `8.8.8.8:443` fährt, umgeht die Sperre. Das per SNI/IP zu schließen ist
|
||||
ein eigenes, größeres Thema (nicht Teil von TP23). Für den Alltag (TVs/IoT mit Plain-DNS) reicht die
|
||||
Port-Sperre.
|
||||
|
||||
## Sichere Methodik — log-only zuerst, Drop zuletzt
|
||||
1. **Exempt + JIT bauen** (via Nautobot GC, `ip-firewall-filter` steht unter Drift-Kontrolle → **kein
|
||||
Direkt-Write**): `accept` für `dns-bypass-exempt`/`jit-dns-bypass` → 53/853, platziert **vor** der
|
||||
künftigen Drop-Regel. Erst hier abnehmen, dass die Exempts wirklich durchkommen.
|
||||
2. **Log-only-Vorstufe**: eine Regel `TP5-WOULD-DROP-DNS`, die LAN→extern auf `dst-port 53,853`
|
||||
(udp+tcp), **nicht** zu `.36/.37/Core`, **nur loggt** (in den Kollektor `10.40.20.18:514`, wie die
|
||||
bestehende `TP5-WOULD-DROP`). **Mehrere Tage laufen lassen** und den Drop-Log lesen: *welche*
|
||||
Geräte reden mit *welchen* externen Resolvern? Jedes davon ist ein Kandidat, der nach dem Drop
|
||||
bricht — vorab entscheiden (Reservierung auf AGH, Exempt, oder bewusst brechen lassen).
|
||||
🔑 Merkregel aus dem E5-Drop-Log: Geräte fallen oft mit **udp/161/andere** nebenbei auf — nur die
|
||||
53/853-Zeilen zählen hier.
|
||||
3. **Erst nach ruhigem/verstandenem Log**: die Log-only-Regel auf `action=drop` (oder `reject` mit
|
||||
`icmp-port-unreachable`, damit Clients schneller auf den nächsten Resolver fallen) heben —
|
||||
**mit Auto-Revert-Absicherung** (Scheduler/Netwatch, der die Regel nach X Minuten deaktiviert,
|
||||
falls nicht bestätigt), analog zum TP5-E3/E4-Vorgehen. **Vor Ort**, mit offener Konsole.
|
||||
|
||||
## Was NICHT gesperrt werden darf (sonst Selbst-Aussperrung)
|
||||
- `.36`/`.37` → CF-DoH-Upstream (443) und Core-Resolver-Egress: die Resolver müssen raus.
|
||||
- Core `10.40.X.1` DoH-Egress.
|
||||
- **cf-mesh / WG-Pfad** (Fernzugang) — die Drop-Regel matcht nur `dst-port 53,853`; trotzdem vor dem
|
||||
Scharfschalten gegenprüfen, dass Management-Input (`cf-mesh`, HA `8728`, Nautobot `22`) unberührt
|
||||
bleibt.
|
||||
|
||||
## Rollback
|
||||
- GC-Deploy der vorherigen Version (Regel zurück auf `disabled`/entfernt) — bzw. der Auto-Revert.
|
||||
- Fällt das AGH-Paar bei aktiver Sperre aus: Drop-Regel deaktivieren (Vor-Ort-Konsole) → Clients
|
||||
können sofort wieder externe Resolver nutzen.
|
||||
|
||||
## Abnahme
|
||||
- Exempt-Host (dev-MacBook) löst extern weiter auf; ein Test-Gerät mit hartkodiertem `8.8.8.8` löst
|
||||
**nicht** mehr (aber über AGH schon).
|
||||
- Self-Service: eine geblockte Domain per HA-Button freigeben → wirkt; nach Ablauf wieder gefiltert.
|
||||
- Compliance nach dem GC-Deploy grün (`python3 nautobot/gc-transaktion.py status`, GC 140/140).
|
||||
|
||||
## Verweise
|
||||
- Stufe 4/5 (fertig): `adguard/README.md`, `adguard/ha-self-service.md`, `adguard/agh-exception.py`.
|
||||
- JIT-/Firewall-Muster: `routeros/tp5-etappe4-input-mgmt.rsc`; GC-Pipeline: `nautobot/gc-configplan.py`.
|
||||
- Kontext/Reihenfolge-Regel: TP23-Abschnitt in `docs/teilprojekte.md`.
|
||||
@@ -19,9 +19,9 @@ cd "$HERE"
|
||||
|
||||
NAME="${1:-$(date +%Y%m%d-%H%M)}"
|
||||
export TP6_RUN_DIR="$HERE/docs/messdaten/tp25-runde-$NAME"
|
||||
export TP25_PHONE_IP="${TP25_PHONE_IP:-10.40.10.178}" # S22 -- wird gepingt
|
||||
export TP25_PHONE_MAC="${TP25_PHONE_MAC:-2A:0D:B0:5D:AD:A7}"
|
||||
export TP25_IPHONE_MAC="${TP25_IPHONE_MAC:-F2:62:4C:E1:AF:09}"
|
||||
# N-Geraete-Liste (name|ip|mac;...). Default = die bisherigen zwei, damit alte Aufrufe
|
||||
# unveraendert weiterlaufen. Wird an tp25-walk-messung.sh durchgereicht.
|
||||
export TP25_PHONES="${TP25_PHONES:-iphone|10.40.10.196|F2:62:4C:E1:AF:09;s22|10.40.10.178|2A:0D:B0:5D:AD:A7}"
|
||||
|
||||
mkdir -p "$TP6_RUN_DIR"
|
||||
|
||||
@@ -36,14 +36,6 @@ else
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# 🔴 Gegen-AP-Automatik gleich einschalten. Sie war bisher ein Klick auf der Konsole -- und
|
||||
# wer ihn vergisst, geht die ganze Runde mit beiden Telefonen auf DEMSELBEN AP und merkt
|
||||
# es erst bei der Auswertung (im Test am 2026-08-22 genau so passiert). Abschalten geht
|
||||
# weiter jederzeit ueber den Knopf.
|
||||
curl -s -X POST --max-time 10 "http://127.0.0.1:3031/auto?an=1" >/dev/null 2>&1 \
|
||||
&& echo "Gegen-AP-Automatik: EIN" \
|
||||
|| echo "⚠️ Automatik konnte nicht eingeschaltet werden -- auf der Konsole nachholen!"
|
||||
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " Konsole am Telefon oeffnen:"
|
||||
@@ -53,8 +45,8 @@ ifconfig | awk '/^[a-z0-9]+:/{i=substr($1,1,length($1)-1)}
|
||||
/inet /{if ($2 !~ /^127\.|^169\.254\./) printf " http://%s:3031/ (%s)\n", $2, i}'
|
||||
echo
|
||||
echo " 1) Auf dem Telefon einmal irgendwo tippen -> schaltet den Signalton frei"
|
||||
echo " 2) Gegen-AP-Automatik laeuft bereits -> S22 folgt dem iPhone spiegelverkehrt"
|
||||
echo " 3) Losgehen. Doppelton = beide angemeldet -> Raum antippen, weiter"
|
||||
echo " 2) In der Konsole pinnen (Multi-Pin) -> je Client AP/Band waehlen, 'Pin setzen'"
|
||||
echo " 3) Losgehen. Raum antippen, weiter -> Alarm = gepinnter Client haengt nirgends"
|
||||
echo
|
||||
echo " Lauf: $TP6_RUN_DIR"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
@@ -63,18 +55,17 @@ echo
|
||||
aufraeumen() {
|
||||
echo
|
||||
echo "Runde beendet. Aufraeumen …"
|
||||
# 🔴 REIHENFOLGE: erst die Konsole toeten, dann den Pin loesen. Andersherum ist es ein
|
||||
# Wettlauf, den man verliert -- die Automatik der noch laufenden Konsole sieht den
|
||||
# geloesten Pin und setzt ihn binnen zwei Sekunden wieder. Am 2026-08-22 meldete der
|
||||
# Aufraeumer brav "AP-Pin geloest", und danach standen die Regeln trotzdem am Core:
|
||||
# das Telefon waere nach jeder Runde gepinnt geblieben.
|
||||
# 🔴 REIHENFOLGE: erst die Konsole toeten, dann den Pin loesen. Die Konsole pinnt nicht
|
||||
# mehr automatisch (die alte Gegen-AP-Automatik ist mit dem Multi-Pin-Umbau 2026-09-04
|
||||
# entfallen), aber sie haelt ggf. noch SSH-Sitzungen zum Manager offen -- die erst
|
||||
# beenden, bevor der Pin geloest wird.
|
||||
[[ -n "$KONSOLE" ]] && kill "$KONSOLE" 2>/dev/null && echo " Konsole beendet."
|
||||
# Muster bewusst ohne -e/-p: es gab beide Varianten, und die enge Fassung liess den
|
||||
# Sampler stehen.
|
||||
pkill -f 'sshpass.*10.40.99.1' 2>/dev/null
|
||||
pkill -f 'tp6-monitor-core.sh' 2>/dev/null
|
||||
sleep 1
|
||||
bash docs/tp25-pin-s22.sh off >/dev/null 2>&1 && echo " AP-Pin geloest."
|
||||
bash docs/tp25-pin-multi.sh off >/dev/null 2>&1 && echo " Multi-Pin geloest."
|
||||
echo
|
||||
echo "Auswertung:"
|
||||
echo " python3 docs/tp25-gehweg-auswertung.py --dir $TP6_RUN_DIR"
|
||||
|
||||
@@ -44,6 +44,14 @@ HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
mkdir -p "$RUN_DIR"
|
||||
[[ -f "$MARKERS" ]] || echo "ts_epoch,ts_local,marker" > "$MARKERS"
|
||||
|
||||
# Client-Liste fuer die Klick-Konsole (docs/tp6-marker/server.py). Die Konsole liest sie
|
||||
# ueber den .current-run-Zeiger -- so kennt sie die N Geraete auch, wenn sie als
|
||||
# Dauer-Preview schon vor dem Messlauf lief. Format: name|ip|mac je Zeile.
|
||||
: > "$RUN_DIR/geraete.txt"
|
||||
for i in "${!PH_NAME[@]}"; do
|
||||
printf '%s|%s|%s\n' "${PH_NAME[$i]}" "${PH_IP[$i]}" "${PH_MAC[$i]}" >> "$RUN_DIR/geraete.txt"
|
||||
done
|
||||
|
||||
# Zeiger fuer die Klick-Konsole (docs/tp6-marker/server.py). Die laeuft als Dauer-Preview
|
||||
# auf Port 3031 und kennt dieses Verzeichnis sonst nicht -- ihre Marker landeten dann im
|
||||
# Lauf von gestern, waehrend der Sampler hierher schreibt. Beides fiel erst bei der
|
||||
|
||||
@@ -16,12 +16,12 @@ Zwei Aufgaben:
|
||||
- Die Seite hat ein <meta refresh>. Ohne JS aktualisiert sich die Anleitung dadurch
|
||||
trotzdem; laeuft JS, entfernt es das Meta-Tag und pollt stattdessen leise.
|
||||
|
||||
🔴 SEIT 2026-08-22 AENDERT DIESE SEITE DOCH ETWAS AM NETZ: der AP-Pin (TP25) schreibt
|
||||
🔴 SEIT 2026-08-22 AENDERT DIESE SEITE DOCH ETWAS AM NETZ: der Multi-Pin (TP25) schreibt
|
||||
access-list-Regeln auf den WLAN-Manager. Die Seite bindet auf 0.0.0.0 und kennt keine
|
||||
Authentifizierung — wer in VLAN10 steht, kann den Pin umschalten. Das ist fuer die Dauer
|
||||
einer Messung vertretbar und sonst nicht: den Server nach dem Rundgang beenden. Der Pin
|
||||
selbst ist harmlos genug (er betrifft eine MAC), aber die Regel ist es nicht, die als
|
||||
naechstes hier landen koennte.
|
||||
Authentifizierung — wer in VLAN10 steht, kann pinnen. Das ist fuer die Dauer einer Messung
|
||||
vertretbar und sonst nicht: den Server nach dem Rundgang beenden. Der Pin selbst ist
|
||||
harmlos genug (er betrifft MACs), aber die Regel ist es nicht, die als naechstes hier
|
||||
landen koennte.
|
||||
|
||||
Bindet auf 0.0.0.0 (Bedienung vom iPhone/iPad).
|
||||
"""
|
||||
@@ -164,6 +164,82 @@ def sampler_tail():
|
||||
return {}
|
||||
|
||||
|
||||
def geraete():
|
||||
"""Liste (name, ip, mac) aus geraete.txt im Laufverzeichnis.
|
||||
|
||||
🔴 Die Datei schreibt tp25-walk-messung.sh beim Start des Messlaufs (eine Zeile
|
||||
name|ip|mac je Client). Fehlt sie — Alt-Lauf oder Dauer-Preview, die schon vor dem
|
||||
ersten Lauf lief — fallen wir auf die zwei bisherigen Geraete zurueck, damit die
|
||||
Anzeige nicht leer ist. Die Namen muessen zu den sampler-<name>.csv /
|
||||
walk-ping-<name>.csv passen, die die Harness schreibt.
|
||||
"""
|
||||
try:
|
||||
zeilen = [z.strip() for z in open(_p("geraete.txt")) if z.strip()]
|
||||
gs = []
|
||||
for z in zeilen:
|
||||
teile = z.split("|")
|
||||
if len(teile) >= 3 and teile[0]:
|
||||
gs.append((teile[0], teile[1], teile[2]))
|
||||
if gs:
|
||||
return gs
|
||||
except Exception:
|
||||
pass
|
||||
return [("iphone", "", "F2:62:4C:E1:AF:09"), ("s22", "", "2A:0D:B0:5D:AD:A7")]
|
||||
|
||||
|
||||
def client_tail(name):
|
||||
"""Letzte Sampler-Zeile EINES Geraets (sampler-<name>.csv). Fehlt sie, ist das kein Fehler.
|
||||
|
||||
Gleiche Spalten wie sampler_tail() — die Harness schreibt pro Geraet einen eigenen
|
||||
Sampler mit demselben Header (tp6-monitor-core.sh). Die Konsole braucht deshalb KEINE
|
||||
eigene SSH-Sitzung mehr: sie liest nur, was die Sampler schon auf die Platte legen.
|
||||
"""
|
||||
try:
|
||||
pfad = _p("sampler-%s.csv" % name)
|
||||
rows = [r.strip() for r in open(pfad) if r.strip()]
|
||||
if len(rows) < 2:
|
||||
return {}
|
||||
row = dict(zip(rows[0].split(","), rows[-1].split(",")))
|
||||
return {
|
||||
"age": int(time.time() - int(row.get("ts_epoch", "0"))),
|
||||
"cpu": row.get("cpu", ""), "wan_rx": row.get("wan_rx_bps", ""),
|
||||
"wan_tx": row.get("wan_tx_bps", ""), "iface": row.get("sta_iface", ""),
|
||||
"signal": row.get("sta_signal", ""), "tx_rate": row.get("sta_tx_rate", ""),
|
||||
"sta_tx_bps": row.get("sta_tx_bps", ""), "sta_rx_bps": row.get("sta_rx_bps", ""),
|
||||
}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def ping_loss(name, n=150):
|
||||
"""Ping-Verlust der letzten ~n Zeilen von walk-ping-<name>.csv (5 Pings/s -> n=150 ~ 30 s).
|
||||
|
||||
Leeres ms-Feld = Verlust. Liefert (verloren, gesamt) oder None, wenn noch keine Daten da
|
||||
sind. Der Header (ts_local,seq,ms) wird ueber die nicht-numerische seq-Spalte uebersprungen.
|
||||
"""
|
||||
try:
|
||||
rows = [r.strip() for r in open(_p("walk-ping-%s.csv" % name)) if r.strip()]
|
||||
verloren = gesamt = 0
|
||||
for z in rows[-n:]:
|
||||
teile = z.split(",")
|
||||
if len(teile) >= 3 and teile[1].isdigit():
|
||||
gesamt += 1
|
||||
if not teile[2].strip():
|
||||
verloren += 1
|
||||
return (verloren, gesamt) if gesamt else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wan_tail():
|
||||
"""WAN/CPU-Anzeige: aus sampler.csv (Alt-Lauf) oder dem ersten Geraet-Sampler."""
|
||||
w = sampler_tail()
|
||||
if w.get("cpu") or w.get("wan_rx"):
|
||||
return w
|
||||
gs = geraete()
|
||||
return client_tail(gs[0][0]) if gs else {}
|
||||
|
||||
|
||||
def kill_flows():
|
||||
"""Not-Aus: Saettigungs-Flows sofort beenden."""
|
||||
killed = 0
|
||||
@@ -214,153 +290,104 @@ def ap_map():
|
||||
return _apmap["karte"]
|
||||
|
||||
|
||||
# 🔎 Die Messidee (Christoph, 2026-08-22): das iPhone roamt frei und zeigt damit, welchen AP
|
||||
# ein normaler Client an dieser Stelle waehlt -- das wissen wir also ohnehin. Interessant
|
||||
# ist der ANDERE. Deshalb wird das S22 automatisch auf den Gegen-AP gezwungen: pro Standort
|
||||
# gibt es dann zwei Messpunkte statt einem, ohne zweimal zu gehen.
|
||||
IPHONE_MAC = os.environ.get("TP25_IPHONE_MAC", "F2:62:4C:E1:AF:09")
|
||||
S22_MAC = os.environ.get("TP25_PIN_MAC", "2A:0D:B0:5D:AD:A7")
|
||||
# 🔎 Die Messidee (Christoph, 2026-08-22, generalisiert 2026-09-04): die Clients roamen frei
|
||||
# und zeigen damit, welchen AP ein normaler Client an dieser Stelle waehlt. Fuer die
|
||||
# Abdeckungsfrage wird je Client per access-list GENAU EIN AP+Band erlaubt
|
||||
# (tp25-pin-multi.sh). Die alte Gegen-AP-Automatik (S22 folgt dem iPhone) ist damit
|
||||
# ueberholt: gepinnt wird vorab band-genau, nicht mehr automatisch waehrend des Gehens.
|
||||
|
||||
# Was der Beobachter sieht. "" = nicht angemeldet.
|
||||
_live = {"iphone": "", "iphone_sig": "", "s22": "", "s22_sig": "", "stand": 0.0}
|
||||
_auto = {"an": False, "seit_weg": 0.0, "aufgegeben": ""}
|
||||
PINM_SH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tp25-pin-multi.sh")
|
||||
|
||||
# Pin-Zustand. Das Setzen dauert ~8 s (Regeln + Kick), deshalb laeuft es in einem Thread und
|
||||
# die Seite zeigt solange "setze Pin …". pins = {name: "ap|band"} der zuletzt gesetzten Auswahl.
|
||||
_pinm = {"busy": False, "msg": "", "pins": {}, "stand": 0.0}
|
||||
|
||||
|
||||
def core_passwort():
|
||||
"""Aus dem Credential-Store. Wird nie geloggt und nie in die Seite gerendert."""
|
||||
with open(os.path.expanduser("~/.secrets/credentials.env")) as fh:
|
||||
for zeile in fh:
|
||||
if zeile.startswith("CRS326_CORE_PASS="):
|
||||
return zeile.split("=", 1)[1].strip().strip("\"'")
|
||||
raise RuntimeError("CRS326_CORE_PASS nicht gefunden")
|
||||
def pin_targets():
|
||||
"""Eindeutige (ap, band)-Paare aus der Live-Karte, band-normalisiert fuer tp25-pin-multi.sh.
|
||||
|
||||
|
||||
def _ap_von(iface):
|
||||
return ap_map().get(iface, "").split(" /")[0]
|
||||
|
||||
|
||||
def beobachter():
|
||||
"""Haelt EINE SSH-Sitzung offen und liest beide Telefone im 2-s-Takt.
|
||||
|
||||
🔴 Bewusst kein Poll mit je einem ssh-Aufruf: das waeren ueber einen Rundgang mehrere
|
||||
hundert Verbindungsaufbauten, jeder ~1 s. Dasselbe Muster wie tp6-monitor-core.sh --
|
||||
inklusive Reconnect-Schleife, weil die Sitzung am 2026-08-21 mitten in einer Messung
|
||||
weggebrochen ist und die Daten still endeten.
|
||||
ap_map() liefert 'apX / 5 GHz' bzw. 'apX / 2,4 GHz'; das Skript erwartet Band '5' oder '24'.
|
||||
"""
|
||||
# 🔴 KEINE Klammern um die :while-Schleife -- "(:while ...)" laesst RouterOS die
|
||||
# Sitzung wortlos beenden, und mit stderr auf DEVNULL sieht man nur, dass alle 3 s
|
||||
# ein neuer ssh startet. Genau so lag der Beobachter am 2026-08-22 tot.
|
||||
ros = (':while (true) do={'
|
||||
':local a [/interface/wifi/registration-table find where mac-address="%s"];'
|
||||
':local b [/interface/wifi/registration-table find where mac-address="%s"];'
|
||||
':local ai ""; :local as2 ""; :local bi ""; :local bs "";'
|
||||
':if ([:len $a] > 0) do={:set ai [/interface/wifi/registration-table get [:pick $a 0] interface];'
|
||||
':set as2 [/interface/wifi/registration-table get [:pick $a 0] signal]};'
|
||||
':if ([:len $b] > 0) do={:set bi [/interface/wifi/registration-table get [:pick $b 0] interface];'
|
||||
':set bs [/interface/wifi/registration-table get [:pick $b 0] signal]};'
|
||||
':put ($ai . "," . $as2 . "," . $bi . "," . $bs); :delay 2s;}'
|
||||
% (IPHONE_MAC, S22_MAC))
|
||||
while True:
|
||||
try:
|
||||
pw = core_passwort()
|
||||
# 🔴 sshpass -e statt -p: bei -p steht das Router-Passwort in der KOMMANDOZEILE
|
||||
# und damit in `ps`, in jedem Prozess-Trace und in jeder Debug-Ausgabe, die
|
||||
# argv anfasst. Am 2026-08-22 ist es genau so im Klartext in einem Trace
|
||||
# gelandet. Mit -e liegt es nur in der Umgebung dieses einen Kindprozesses.
|
||||
umgebung = dict(os.environ, SSHPASS=pw)
|
||||
proc = subprocess.Popen(
|
||||
["sshpass", "-e", "ssh", "-o", "StrictHostKeyChecking=no",
|
||||
"-o", "ConnectTimeout=8", "-o", "ServerAliveInterval=15",
|
||||
"admin@10.40.99.1", ros],
|
||||
stdout=subprocess.PIPE, stderr=open(_p("beobachter.log"), "a"),
|
||||
text=True, bufsize=1, env=umgebung)
|
||||
# 🔴 NICHT `for zeile in proc.stdout`: die Iteration eines Pipes liest blockweise
|
||||
# vor, die Zeilen kommen also erst an, wenn ~4 KB beisammen sind -- bei einer
|
||||
# Zeile alle 2 s waere das eine Stunde. readline() liefert sofort.
|
||||
for zeile in iter(proc.stdout.readline, ""):
|
||||
teile = zeile.strip().split(",")
|
||||
if len(teile) != 4:
|
||||
continue
|
||||
_live.update(iphone=teile[0], iphone_sig=teile[1],
|
||||
s22=teile[2], s22_sig=teile[3], stand=time.time())
|
||||
# 🔴 Bis 2026-08-22 blieben diese Zahlen NUR im Speicher, fuer die Anzeige.
|
||||
# Nach dem Rundgang fehlte deshalb die halbe Messung: das S22 sitzt per
|
||||
# Automatik immer auf dem Gegen-AP, die Alltagsabdeckung zeigt das iPhone
|
||||
# -- und genau dessen Zeitreihe war weg. Nachtraeglich liess sich nur das
|
||||
# Roaming-Log am Manager retten: Ereignisse, keine Kurve.
|
||||
mitschreiben(teile)
|
||||
entscheide()
|
||||
except Exception as e:
|
||||
# Ein Beobachter, der still stirbt, ist schlimmer als keiner (Lehre aus dem
|
||||
# Sampler-Abbruch am 2026-08-21). Also mitschreiben statt schlucken.
|
||||
try:
|
||||
with open(_p("beobachter.log"), "a") as fh:
|
||||
fh.write("%s %s: %s\n" % (time.strftime("%H:%M:%S"), type(e).__name__, e))
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(3)
|
||||
ziele, gesehen = [], set()
|
||||
for wert in ap_map().values():
|
||||
ap, _, band = wert.partition(" / ")
|
||||
band = band.replace(" GHz", "").replace(",", ".")
|
||||
if band.startswith("5"):
|
||||
band = "5"
|
||||
elif band.startswith("2"):
|
||||
band = "24"
|
||||
else:
|
||||
continue
|
||||
if (ap, band) not in gesehen:
|
||||
gesehen.add((ap, band))
|
||||
ziele.append((ap, band))
|
||||
return sorted(ziele)
|
||||
|
||||
|
||||
def mitschreiben(teile):
|
||||
"""Beide Telefone im 2-s-Takt in den Lauf schreiben: iface + Signal je Geraet."""
|
||||
try:
|
||||
pfad = _p("beide-telefone.csv")
|
||||
frisch = not os.path.exists(pfad) or os.path.getsize(pfad) == 0
|
||||
with open(pfad, "a") as fh:
|
||||
if frisch:
|
||||
fh.write("ts_epoch,ts_local,iphone_iface,iphone_sig,s22_iface,s22_sig\n")
|
||||
fh.write("%d,%s,%s\n" % (time.time(), time.strftime("%H:%M:%S"), ",".join(teile)))
|
||||
except Exception:
|
||||
pass
|
||||
def build_spec(form):
|
||||
"""Aus den Formular-Auswahlen (pin_<name>=ap|band oder frei) den TP25_PINSPEC-String bauen.
|
||||
|
||||
Clients ohne Ziel (frei) bleiben frei — tp25-pin-multi.sh set raeumt vorher alle
|
||||
TP25-PINM-*-Regeln ab und setzt nur die hier genannten.
|
||||
"""
|
||||
eintraege = []
|
||||
for name, _ip, mac in geraete():
|
||||
wert = (form.get("pin_%s" % name) or ["frei"])[0]
|
||||
if wert and wert != "frei" and "|" in wert:
|
||||
ap, band = wert.split("|", 1)
|
||||
eintraege.append("%s|%s|%s|%s" % (name, mac, ap, band))
|
||||
return ";".join(eintraege)
|
||||
|
||||
|
||||
def entscheide():
|
||||
"""Gegen-AP setzen, sobald das iPhone den AP wechselt."""
|
||||
if not _auto["an"] or _pin["busy"]:
|
||||
return
|
||||
ip_ap = _ap_von(_live["iphone"])
|
||||
if ip_ap not in ("ap1", "ap2"):
|
||||
return # iPhone selbst nicht angemeldet -> nichts zu spiegeln
|
||||
gegen = "ap1" if ip_ap == "ap2" else "ap2"
|
||||
if pin_lesen() != gegen:
|
||||
_auto["aufgegeben"] = ""
|
||||
_auto["seit_weg"] = time.time()
|
||||
pin_setzen(gegen)
|
||||
def pinm_aktion(spec):
|
||||
"""Pin setzen (spec = 'name|mac|ap|band;...') oder loesen (spec = ''). Laeuft im Thread."""
|
||||
pins = {}
|
||||
for e in (spec.split(";") if spec else []):
|
||||
teile = e.split("|")
|
||||
if len(teile) >= 4:
|
||||
pins[teile[0]] = "%s|%s" % (teile[2], teile[3])
|
||||
|
||||
|
||||
# Pin-Zustand. Das Umschalten dauert ~8 s (Regeln + Kick + Nachlesen), deshalb laeuft es
|
||||
# in einem Thread und die Seite zeigt solange "schaltet um".
|
||||
_pin = {"ziel": None, "busy": False, "msg": "", "stand": 0.0}
|
||||
|
||||
|
||||
def pin_lesen():
|
||||
if _pin["busy"] or time.time() - _pin["stand"] < 20:
|
||||
return _pin["ziel"]
|
||||
try:
|
||||
_pin["ziel"] = subprocess.run(["bash", PIN_SH, "which"], capture_output=True,
|
||||
text=True, timeout=20).stdout.strip() or None
|
||||
_pin["stand"] = time.time()
|
||||
except Exception:
|
||||
pass
|
||||
return _pin["ziel"]
|
||||
|
||||
|
||||
def pin_setzen(ziel):
|
||||
def lauf():
|
||||
try:
|
||||
r = subprocess.run(["bash", PIN_SH, ziel], capture_output=True, text=True, timeout=60)
|
||||
_pin["msg"] = (r.stdout.strip().splitlines() or [""])[0]
|
||||
umgebung = dict(os.environ)
|
||||
if spec:
|
||||
umgebung["TP25_PINSPEC"] = spec
|
||||
r = subprocess.run(["bash", PINM_SH, "set"], capture_output=True, text=True,
|
||||
timeout=90, env=umgebung)
|
||||
else:
|
||||
r = subprocess.run(["bash", PINM_SH, "off"], capture_output=True, text=True,
|
||||
timeout=90)
|
||||
_pinm["msg"] = r.stdout.strip() or r.stderr.strip() or "ok"
|
||||
except Exception as e:
|
||||
_pin["msg"] = "Fehler: %s" % e
|
||||
_pinm["msg"] = "Fehler: %s" % e
|
||||
finally:
|
||||
_pin["busy"] = False
|
||||
_pin["stand"] = 0.0
|
||||
pin_lesen()
|
||||
_pin["busy"] = True
|
||||
_pin["msg"] = "schaltet um …"
|
||||
_pinm["busy"] = False
|
||||
_pinm["stand"] = time.time()
|
||||
|
||||
_pinm["busy"] = True
|
||||
_pinm["msg"] = "setze Pin …" if spec else "löse Pin …"
|
||||
_pinm["pins"] = pins
|
||||
# Der Klick gehoert in dieselbe Zeitachse wie alles andere — sonst laesst sich hinterher
|
||||
# nicht sagen, welche Messpunkte zu welchem AP gehoeren.
|
||||
write_marker("PIN=%s" % ziel)
|
||||
# nicht sagen, welche Messpunkte zu welchem Pin gehoeren.
|
||||
write_marker("PINM=%s" % (spec or "off"))
|
||||
threading.Thread(target=lauf, daemon=True).start()
|
||||
|
||||
|
||||
def pinm_verify():
|
||||
"""tp25-pin-multi.sh verify im Thread — zeigt Regeln + aktuelle Anmeldungen."""
|
||||
def lauf():
|
||||
try:
|
||||
r = subprocess.run(["bash", PINM_SH, "verify"], capture_output=True, text=True,
|
||||
timeout=60)
|
||||
_pinm["msg"] = r.stdout.strip() or r.stderr.strip() or "ok"
|
||||
except Exception as e:
|
||||
_pinm["msg"] = "Fehler: %s" % e
|
||||
finally:
|
||||
_pinm["busy"] = False
|
||||
_pinm["stand"] = time.time()
|
||||
|
||||
_pinm["busy"] = True
|
||||
_pinm["msg"] = "prüfe …"
|
||||
threading.Thread(target=lauf, daemon=True).start()
|
||||
|
||||
|
||||
@@ -391,27 +418,42 @@ def fmt_bps(v):
|
||||
|
||||
|
||||
def render(st):
|
||||
w = sampler_tail()
|
||||
w = wan_tail()
|
||||
rest = max(0, int(st.get("ends", 0) - time.time())) if st.get("ends") else 0
|
||||
mode = st.get("mode", "wartet")
|
||||
|
||||
banner = ""
|
||||
alarm = False # nur DIESER Zustand loest Ton + Vibration aus, nicht jeder Banner
|
||||
# 🔴 Der wichtigste Alarm der Seite, seit es den AP-Pin gibt (2026-08-22): ein vom Pin
|
||||
# ABGEWIESENER Verbindungsversuch ist in den Ping-Daten nicht von einem Funkloch zu
|
||||
# unterscheiden -- und der Manager protokolliert die Abweisung auf info-Ebene nicht.
|
||||
# Android quittiert sie mit "Verbindung fehlgeschlagen" und WAEHLT DAS NETZ AB, danach
|
||||
# misst der restliche Rundgang nur noch Stille. Deshalb wird "haengt nirgends" hier
|
||||
# nicht klein in der WLAN-Zeile gezeigt, sondern schreit.
|
||||
if pin_lesen() in ("ap1", "ap2") and w.get("iface") == "absent" and w.get("age", 999) < 30:
|
||||
alarm = True
|
||||
banner = ('<div class="banner warn">📵 S22 HÄNGT NIRGENDS — Display an und wieder auf '
|
||||
'dirtyvader-main verbinden. Der Pin wirft es vom gesperrten AP; Android '
|
||||
'wählt das Netz dann von selbst ab. <b>Nicht weitergehen</b>, sonst misst '
|
||||
'du Stille statt Abdeckung.</div>')
|
||||
haengende = []
|
||||
|
||||
# 🔴 Der wichtigste Alarm der Seite, seit es den Pin gibt (2026-08-22, generalisiert
|
||||
# 2026-09-04): ein vom Pin ABGEWIESENER Verbindungsversuch ist in den Ping-Daten nicht
|
||||
# von einem Funkloch zu unterscheiden -- und der Manager protokolliert die Abweisung auf
|
||||
# info-Ebene nicht. Android quittiert sie mit "Verbindung fehlgeschlagen" und WAEHLT DAS
|
||||
# NETZ AB, danach misst der restliche Rundgang nur noch Stille. Deshalb wird "haengt
|
||||
# nirgends" hier nicht klein in der Client-Zeile gezeigt, sondern schreit.
|
||||
clients = []
|
||||
for name, ip, mac in geraete():
|
||||
t = client_tail(name)
|
||||
pl = ping_loss(name)
|
||||
iface = t.get("iface", "")
|
||||
ap = ap_map().get(iface, iface) if iface else ""
|
||||
signal = t.get("signal", "")
|
||||
frisch = t.get("age", 999) < 30
|
||||
gepinnt = name in _pinm["pins"]
|
||||
if gepinnt and iface == "absent" and frisch:
|
||||
alarm = True
|
||||
haengende.append(name)
|
||||
clients.append((name, ip, mac, iface, ap, signal, pl, frisch, gepinnt))
|
||||
|
||||
if haengende:
|
||||
banner = ('<div class="banner warn">📵 <b>%s</b> HÄNGT NIRGENDS — der Pin wirft das '
|
||||
'Geraet vom gesperrten AP; Android waehlt das Netz dann von selbst ab. '
|
||||
'<b>Nicht weitergehen</b>, sonst misst du Stille statt Abdeckung.</div>'
|
||||
% ", ".join(haengende))
|
||||
elif not st.get("driver"):
|
||||
banner = ('<div class="banner done">ℹ️ Keine Ping-Aufzeichnung — Räume markieren und '
|
||||
'AP-Pin gehen trotzdem. Für die Messkurve <b>tp25-walk-messung.sh</b> starten.</div>')
|
||||
'pinnen geht trotzdem. Für die Messkurve <b>tp25-walk-messung.sh</b> starten.</div>')
|
||||
elif mode == "langzeit":
|
||||
banner = ('<div class="banner done">✅ MESSUNG ABGESCHLOSSEN — die Zahlen liegen vor. '
|
||||
'Ab jetzt läuft nur noch die Langzeit-Aufzeichnung.</div>')
|
||||
@@ -423,86 +465,65 @@ def render(st):
|
||||
countdown = ('<div class="cd" id="cd" data-ends="%d">noch ~%d s</div>' % (st.get("ends", 0), rest)
|
||||
if rest else '<div class="cd" id="cd" data-ends="0"></div>')
|
||||
|
||||
wlan = "—"
|
||||
if w.get("iface"):
|
||||
wlan = "%s · %s dBm · tx %s / rx %s" % (
|
||||
ap_map().get(w["iface"], w["iface"]), w.get("signal", "?"),
|
||||
fmt_bps(w.get("sta_tx_bps")), fmt_bps(w.get("sta_rx_bps")))
|
||||
# Client-Karten: je Geraet Name, AP+Signal, Ping-Verlust der letzten ~30 s.
|
||||
karten = ""
|
||||
for name, ip, mac, iface, ap, signal, pl, frisch, gepinnt in clients:
|
||||
if iface and iface != "absent":
|
||||
status = ("%s · %s dBm" % (ap, signal)) if signal else ap
|
||||
elif iface == "absent":
|
||||
status = "<b>nirgends</b>"
|
||||
else:
|
||||
status = "—"
|
||||
if pl:
|
||||
verloren, gesamt = pl
|
||||
ping = "%d/%d verloren" % (verloren, gesamt)
|
||||
else:
|
||||
ping = "—"
|
||||
karten += ('<div class="card"><div class="phase">%s</div>'
|
||||
'<div class="inst"><b>%s</b></div>'
|
||||
'<div class="cval">%s</div>'
|
||||
'<div class="cval">Ping: %s</div></div>'
|
||||
% ("📌 gepinnt" if gepinnt else "Client", name, status, ping))
|
||||
|
||||
ziel = pin_lesen()
|
||||
ip_ap, s22_ap = _ap_von(_live["iphone"]), _ap_von(_live["s22"])
|
||||
frisch = time.time() - _live["stand"] < 15
|
||||
# Pin-UI: je Client ein Ziel-Dropdown, dazu setzen/loesen/verify.
|
||||
ziele = pin_targets()
|
||||
|
||||
# Drei Zustaende, nicht zwei. "S22 kommt nicht rein" ist ein ERGEBNIS (der Gegen-AP
|
||||
# traegt hier nicht), kein Defekt -- es darf deshalb nicht wie ein Fehler aussehen und
|
||||
# nicht ewig Alarm schlagen.
|
||||
# 🔴 "beide angemeldet" ist NICHT das Kriterium -- der Messpunkt taugt nur, wenn die
|
||||
# beiden Telefone auf VERSCHIEDENEN APs sitzen. Am 2026-08-22 meldete die Seite
|
||||
# "✅ beide angemeldet, auf verschiedenen APs", waehrend beide auf ap2 hingen: ein
|
||||
# gruenes Licht fuer eine Messung, die es gar nicht gab. Wieder derselbe Fehler --
|
||||
# die Anzeige geprueft statt der Wirkung.
|
||||
getrennt = bool(ip_ap and s22_ap and ip_ap != s22_ap)
|
||||
bereit = bool(frisch and getrennt)
|
||||
gleich = bool(frisch and ip_ap and s22_ap and ip_ap == s22_ap)
|
||||
wartet = bool(frisch and ip_ap and not s22_ap and not _auto["aufgegeben"])
|
||||
if wartet and _auto["seit_weg"] and time.time() - _auto["seit_weg"] > 45:
|
||||
_auto["aufgegeben"] = ziel or "?"
|
||||
write_marker("UNERREICHBAR=%s" % (ziel or "?"))
|
||||
wartet = False
|
||||
def optionen(aktuell):
|
||||
out = ['<option value="frei"%s>frei</option>' % (" selected" if aktuell == "frei" else "")]
|
||||
for ap, band in ziele:
|
||||
wert = "%s|%s" % (ap, band)
|
||||
label = "%s / %s GHz" % (ap, "5" if band == "5" else "2,4")
|
||||
out.append('<option value="%s"%s>%s</option>'
|
||||
% (wert, " selected" if wert == aktuell else "", label))
|
||||
return "".join(out)
|
||||
|
||||
def z(ap, sig, mac_name):
|
||||
if not frisch:
|
||||
return "%s: —" % mac_name
|
||||
return ("%s: <b>%s</b> %s dBm" % (mac_name, ap, sig)) if ap else ("%s: <b>nirgends</b>" % mac_name)
|
||||
zeilen = ""
|
||||
for name, ip, mac in geraete():
|
||||
zeilen += ('<div class="pinzeile"><label>%s</label>'
|
||||
'<select name="pin_%s">%s</select></div>'
|
||||
% (name, name, optionen(_pinm["pins"].get(name, "frei"))))
|
||||
|
||||
doppel = "%s · %s" % (z(ip_ap, _live["iphone_sig"], "iPhone"),
|
||||
z(s22_ap, _live["s22_sig"], "S22"))
|
||||
|
||||
if _pin["busy"]:
|
||||
pinstatus = "⏳ %s<br>%s" % (_pin["msg"], doppel)
|
||||
elif _auto["aufgegeben"]:
|
||||
pinstatus = ("🚫 S22 bekommt <b>%s</b> hier nicht — <b>das ist das Messergebnis</b>, "
|
||||
"Raum markieren und weitergehen.<br>%s" % (_auto["aufgegeben"], doppel))
|
||||
elif bereit:
|
||||
pinstatus = "✅ beide angemeldet, <b>verschiedene APs</b> — Messpunkt zählt<br>%s" % doppel
|
||||
elif gleich and _auto["an"]:
|
||||
pinstatus = ("⏳ beide auf <b>%s</b> — Gegen-AP wird gerade nachgezogen, "
|
||||
"noch nicht weitergehen<br>%s" % (ip_ap, doppel))
|
||||
elif gleich:
|
||||
pinstatus = ("⚠️ beide auf <b>%s</b> — <b>kein Gegen-AP-Messpunkt.</b> "
|
||||
"Automatik einschalten oder S22 von Hand pinnen.<br>%s" % (ip_ap, doppel))
|
||||
elif wartet:
|
||||
pinstatus = "⏳ S22 sucht <b>%s</b> …<br>%s" % (ziel or "?", doppel)
|
||||
elif ziel in ("ap1", "ap2"):
|
||||
pinstatus = "S22 gepinnt auf <b>%s</b><br>%s" % (ziel, doppel)
|
||||
elif ziel == "off":
|
||||
pinstatus = "kein Pin — S22 roamt frei<br>%s" % doppel
|
||||
if _pinm["busy"]:
|
||||
pinstatus = "⏳ %s" % _pinm["msg"].replace("\n", "<br>")
|
||||
elif _pinm["msg"]:
|
||||
pinstatus = _pinm["msg"].replace("\n", "<br>")
|
||||
elif not ziele:
|
||||
pinstatus = "Manager nicht erreichbar — keine Pin-Ziele."
|
||||
else:
|
||||
pinstatus = "Zustand unbekannt (Manager nicht erreichbar?)"
|
||||
pinkarte = (
|
||||
'<div class="card"><span id="sig" data-alarm="%d" data-busy="%d" data-pin="%s" '
|
||||
'data-bereit="%d"></span>'
|
||||
'<div class="phase">AP-Pin S22</div>'
|
||||
'<div class="inst" id="pininst">%s</div><div class="raster">'
|
||||
'<form method="post" action="/pin?ap=ap1" onsubmit="return sendpin(event,\'ap1\')">'
|
||||
'<button class="raum%s" type="submit">ap1</button></form>'
|
||||
'<form method="post" action="/pin?ap=ap2" onsubmit="return sendpin(event,\'ap2\')">'
|
||||
'<button class="raum%s" type="submit">ap2</button></form>'
|
||||
'</div>'
|
||||
'<form method="post" action="/auto" onsubmit="return sendauto(event)">'
|
||||
'<button class="%s" style="margin-top:8px" type="submit">%s</button></form>'
|
||||
'<form method="post" action="/pin?ap=off" onsubmit="return sendpin(event,\'off\')">'
|
||||
'<button class="cam" style="margin-top:8px" type="submit">Pin lösen</button></form>'
|
||||
'</div>' % (1 if alarm else 0, 1 if _pin["busy"] else 0, ziel or "?",
|
||||
1 if bereit else 0,
|
||||
pinstatus,
|
||||
" hier" if ziel == "ap1" else "",
|
||||
" hier" if ziel == "ap2" else "",
|
||||
"ok" if _auto["an"] else "cam",
|
||||
"🔁 Gegen-AP: AN (folgt dem iPhone)" if _auto["an"]
|
||||
else "🔁 Gegen-AP automatisch",
|
||||
)
|
||||
)
|
||||
pinstatus = "Je Client AP/Band wählen, dann „Pin setzen“."
|
||||
|
||||
pinm_karte = (
|
||||
'<div class="card"><span id="sig" data-alarm="%d" data-busy="%d"></span>'
|
||||
'<div class="phase">Pinnen (Multi-Pin)</div>'
|
||||
'<div class="inst" id="pininst">%s</div>'
|
||||
'<form method="post" action="/pinmulti" onsubmit="return sendpinm(event)">'
|
||||
'%s'
|
||||
'<button class="ok" type="submit">📌 Pin setzen</button></form>'
|
||||
'<form method="post" action="/pinmulti?off=1" onsubmit="return sendpinmoff(event)">'
|
||||
'<button class="cam" type="submit">Pin lösen</button></form>'
|
||||
'<form method="post" action="/pinmulti?verify=1" onsubmit="return sendpinmoff(event)">'
|
||||
'<button class="cam" type="submit">verify</button></form>'
|
||||
'</div>' % (1 if alarm else 0, 1 if _pinm["busy"] else 0, pinstatus, zeilen))
|
||||
|
||||
raum, seit = current_room()
|
||||
# 🔴 Die Kacheln hingen bis 2026-08-22 am Treiber-Zustand -- ohne laufenden TP6-Treiber
|
||||
@@ -547,8 +568,9 @@ def render(st):
|
||||
return PAGE.format(
|
||||
banner=banner, title=st.get("title", "—"), phase=st.get("phase", "—"),
|
||||
instruction=st.get("instruction", ""), prog=prog, countdown=countdown,
|
||||
raumzeile=raumzeile, pinkarte=pinkarte, wlan=wlan, wan="rx %s / tx %s · CPU %s%%" % (fmt_bps(w.get("wan_rx")),
|
||||
fmt_bps(w.get("wan_tx")), w.get("cpu", "?")),
|
||||
raumzeile=raumzeile, clients=karten, pinm_karte=pinm_karte,
|
||||
wan="rx %s / tx %s · CPU %s%%" % (fmt_bps(w.get("wan_rx")),
|
||||
fmt_bps(w.get("wan_tx")), w.get("cpu", "?")),
|
||||
last=last_marker(), buttons=btns, notaus=notaus,
|
||||
)
|
||||
|
||||
@@ -579,6 +601,11 @@ button:active{{filter:brightness(1.3)}}
|
||||
.raster form{{margin:0}}
|
||||
.raum{{margin:0;padding:16px 8px;font-size:16px;background:#1e3a5f}}
|
||||
.raum.hier{{background:#0ea5e9;color:#06202e;box-shadow:0 0 0 3px #7dd3fc inset}}
|
||||
.cval{{font:14px/1.6 ui-monospace,Menlo,monospace;color:#9aa4b2;margin-top:2px}}
|
||||
.pinzeile{{display:flex;align-items:center;gap:10px;margin:8px 0}}
|
||||
.pinzeile label{{flex:0 0 90px;font-weight:600}}
|
||||
.pinzeile select{{flex:1;padding:10px;font-size:16px;background:#262b34;color:#e8eaed;
|
||||
border:1px solid #2a2f3a;border-radius:8px}}
|
||||
form{{margin:0}}
|
||||
</style>
|
||||
{banner}
|
||||
@@ -589,10 +616,10 @@ form{{margin:0}}
|
||||
{countdown}
|
||||
{prog}
|
||||
</div>
|
||||
{pinkarte}
|
||||
{clients}
|
||||
{pinm_karte}
|
||||
{raumzeile}
|
||||
<div class="live">WLAN {wlan}
|
||||
WAN {wan}
|
||||
<div class="live">WAN {wan}
|
||||
Marker {last}</div>
|
||||
{buttons}
|
||||
{notaus}
|
||||
@@ -638,18 +665,10 @@ function alarmton(){{ ton(880,180,0); ton(660,180,0.24); ton(880,260,0.48);
|
||||
function okton(){{ ton(1320,90,0); ton(1760,120,0.11);
|
||||
try{{ navigator.vibrate && navigator.vibrate(90); }}catch(e){{}} }}
|
||||
|
||||
function bereitton(){{ ton(1180,110,0); ton(1580,150,0.13);
|
||||
try{{ navigator.vibrate && navigator.vibrate([60,60,60]); }}catch(e){{}} }}
|
||||
|
||||
var letzterAlarm = 0, letzterBusy = 0, letzterBereit = 0, alarmTimer = null;
|
||||
var letzterAlarm = 0, letzterBusy = 0, alarmTimer = null;
|
||||
function signale(){{
|
||||
var sig = document.getElementById('sig'); if(!sig) return;
|
||||
var alarm = sig.dataset.alarm === '1', busy = sig.dataset.busy === '1';
|
||||
var bereit = sig.dataset.bereit === '1';
|
||||
// Der Piep, auf den Christoph wartet: beide Telefone haengen, auf verschiedenen APs --
|
||||
// der Messpunkt ist vollstaendig, weitergehen. Nur bei ZUSTANDSWECHSEL, nicht dauernd.
|
||||
if(bereit && !letzterBereit) bereitton();
|
||||
letzterBereit = bereit ? 1 : 0;
|
||||
if(alarm && !letzterAlarm){{
|
||||
alarmton();
|
||||
// Solange nichts geschieht, alle 12 s erinnern -- ein einmaliger Piep in der Hosentasche
|
||||
@@ -657,7 +676,7 @@ function signale(){{
|
||||
if(!alarmTimer) alarmTimer = setInterval(alarmton, 12000);
|
||||
}}
|
||||
if(!alarm && alarmTimer){{ clearInterval(alarmTimer); alarmTimer = null; }}
|
||||
if(!busy && letzterBusy) okton(); // Umschalten fertig
|
||||
if(!busy && letzterBusy) okton(); // Pin-Aktion fertig
|
||||
letzterAlarm = alarm ? 1 : 0; letzterBusy = busy ? 1 : 0;
|
||||
}}
|
||||
|
||||
@@ -672,28 +691,22 @@ function sendstop(ev){{
|
||||
return false;
|
||||
}}catch(e){{ return true; }}
|
||||
}}
|
||||
function sendauto(ev){{
|
||||
function sendpinm(ev){{
|
||||
try{{
|
||||
ev.preventDefault(); unlock(); okton();
|
||||
fetch('/auto', {{method:'POST'}}).catch(function(){{}});
|
||||
setTimeout(refresh, 400); setTimeout(refresh, 3000);
|
||||
var inst = document.getElementById('pininst');
|
||||
if(inst) inst.innerHTML = '⏳ setze Pin …<br>Gepinnte Geräte fliegen kurz aus dem WLAN und verbinden sich neu.';
|
||||
var fd = new FormData(ev.target);
|
||||
fetch('/pinmulti', {{method:'POST', body: new URLSearchParams(fd)}}).catch(function(){{}});
|
||||
setTimeout(refresh, 3000); setTimeout(refresh, 9000); setTimeout(refresh, 20000);
|
||||
return false;
|
||||
}}catch(e){{ return true; }}
|
||||
}}
|
||||
function sendpin(ev, ap){{
|
||||
function sendpinmoff(ev){{
|
||||
try{{
|
||||
ev.preventDefault(); unlock();
|
||||
// 🔴 Der Klick kommt vom S22 SELBST -- also von genau dem Geraet, das der Pin gleich vom
|
||||
// WLAN wirft. Die Antwort auf dieses fetch() kommt deshalb womoeglich nie an, und die
|
||||
// Seite kann sich nicht mehr nachladen. Die Anweisung muss JETZT auf dem Schirm
|
||||
// stehen, solange die Verbindung noch steht.
|
||||
var inst = document.getElementById('pininst');
|
||||
if(inst) inst.innerHTML = '⏳ schalte auf <b>' + ap + '</b> …<br>' +
|
||||
'<b>Dieses Telefon fliegt jetzt aus dem WLAN.</b> Wenn die Seite haengt: in den ' +
|
||||
'WLAN-Einstellungen wieder auf <b>dirtyvader-main</b> verbinden, dann laedt sie von selbst.';
|
||||
okton();
|
||||
fetch('/pin?ap=' + encodeURIComponent(ap), {{method:'POST'}}).catch(function(){{}});
|
||||
setTimeout(refresh, 3000); setTimeout(refresh, 9000); setTimeout(refresh, 20000);
|
||||
ev.preventDefault(); unlock(); okton();
|
||||
fetch(ev.target.action, {{method:'POST'}}).catch(function(){{}});
|
||||
setTimeout(refresh, 3000); setTimeout(refresh, 9000);
|
||||
return false;
|
||||
}}catch(e){{ return true; }}
|
||||
}}
|
||||
@@ -767,37 +780,27 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._send(200, json.dumps({"ok": True})) if "text/html" not in (
|
||||
self.headers.get("Accept") or "") else self._back()
|
||||
return
|
||||
if self.path.startswith("/auto"):
|
||||
# Ohne Parameter umschalten (das macht der Knopf), mit ?an=0|1 gezielt setzen --
|
||||
# tp25-runde.sh schaltet damit beim Start scharf. Ein Toggle waere dort falsch:
|
||||
# laeuft die Konsole schon mit Automatik, haette der Start sie AUSgeschaltet.
|
||||
if "an=" in self.path:
|
||||
_auto["an"] = self.path.split("an=", 1)[1].split("&")[0] in ("1", "true", "an")
|
||||
if self.path.startswith("/pinmulti"):
|
||||
if _pinm["busy"]:
|
||||
if "text/html" in (self.headers.get("Accept") or ""):
|
||||
self._back()
|
||||
else:
|
||||
self._send(409, json.dumps({"error": "laeuft schon"}))
|
||||
return
|
||||
qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
||||
if qs.get("off"):
|
||||
pinm_aktion("")
|
||||
elif qs.get("verify"):
|
||||
pinm_verify()
|
||||
else:
|
||||
_auto["an"] = not _auto["an"]
|
||||
_auto["aufgegeben"] = ""
|
||||
_auto["seit_weg"] = time.time()
|
||||
write_marker("AUTO=%s" % ("an" if _auto["an"] else "aus"))
|
||||
if _auto["an"]:
|
||||
entscheide()
|
||||
laenge = int(self.headers.get("Content-Length") or 0)
|
||||
body = self.rfile.read(laenge).decode("utf-8") if laenge else ""
|
||||
form = urllib.parse.parse_qs(body)
|
||||
pinm_aktion(build_spec(form))
|
||||
if "text/html" in (self.headers.get("Accept") or ""):
|
||||
self._back()
|
||||
else:
|
||||
self._send(200, json.dumps({"ok": True, "auto": _auto["an"]}))
|
||||
return
|
||||
if self.path.startswith("/pin?"):
|
||||
ap = urllib.parse.unquote(self.path.split("ap=", 1)[1].split("&")[0])
|
||||
if ap not in ("ap1", "ap2", "off"):
|
||||
self._send(400, json.dumps({"error": "ap muss ap1, ap2 oder off sein"}))
|
||||
return
|
||||
if _pin["busy"]:
|
||||
self._send(409, json.dumps({"error": "laeuft schon"}))
|
||||
return
|
||||
pin_setzen(ap)
|
||||
if "text/html" in (self.headers.get("Accept") or ""):
|
||||
self._back()
|
||||
else:
|
||||
self._send(200, json.dumps({"ok": True, "ziel": ap}))
|
||||
self._send(200, json.dumps({"ok": True}))
|
||||
return
|
||||
if self.path.startswith("/m?"):
|
||||
# 🔴 Erst dekodieren, dann NICHT hochschreiben: "RAUM%3DDiele%20OG" wurde sonst zu
|
||||
@@ -836,8 +839,7 @@ if __name__ == "__main__":
|
||||
print("Marker -> %s" % markers_pfad())
|
||||
for iface, wo in sorted(ap_map().items()):
|
||||
print(" %-14s %s" % (iface, wo))
|
||||
threading.Thread(target=beobachter, daemon=True).start()
|
||||
# 🔴 Threading ist Pflicht, seit der Pin dazugekommen ist: das Umschalten redet ueber SSH
|
||||
# 🔴 Threading ist Pflicht, seit der Pin dazugekommen ist: das Setzen redet ueber SSH
|
||||
# mit dem Manager und braucht ~8 s. Mit dem alten HTTPServer (single-threaded) haette
|
||||
# das die ganze Konsole blockiert -- inklusive Not-Aus.
|
||||
ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"reason": "TP25-Kanalplan live: ap1=5260/5280, ap2=5300/5320 (DFS Band B). Testet den Zielzustand nach TP25 (dritter AP + ap2-Umverteilung) am tatsaechlichen Aufstellort. Bei ok: Template auf Frequenz-Listen umstellen und committen. Bei nicht ok: zurueck auf 5180/5220 (Snapshot).",
|
||||
"started": "2026-09-03",
|
||||
"expires": "2026-09-10",
|
||||
"state": "open",
|
||||
"state": "committed",
|
||||
"snapshot": "docs/transaktionen/TP25-kanalplan-ap1-live.snapshot.rsc",
|
||||
"device_backup": ""
|
||||
"device_backup": "",
|
||||
"closed": "2026-09-04"
|
||||
}
|
||||
|
||||
14
jobs/__init__.py
Normal file
14
jobs/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""bf-infra Nautobot-Jobs (Git-Datenquelle).
|
||||
|
||||
Nautobot importiert nur dieses Paket; damit die enthaltenen Jobs sich
|
||||
registrieren, muss jedes Job-Modul hier importiert werden. Neue periodische
|
||||
externe->Nautobot-Syncs kommen als weiteres Modul dazu (kein per-Task-Container).
|
||||
"""
|
||||
from nautobot.apps.jobs import register_jobs
|
||||
|
||||
from .cf_estate_sync import CFEstateSync
|
||||
|
||||
# Nautobot importiert nur dieses Paket (bf_infra.jobs) und erwartet register_jobs()
|
||||
# HIER im __init__ (nicht im Submodul) — sonst: "No jobs were registered on loading
|
||||
# the bf_infra.jobs submodule". Neue Jobs: Klasse importieren + hier mitregistrieren.
|
||||
register_jobs(CFEstateSync)
|
||||
267
jobs/cf_estate_sync.py
Normal file
267
jobs/cf_estate_sync.py
Normal file
@@ -0,0 +1,267 @@
|
||||
"""
|
||||
TP44 Teil B — CF-Estate → Nautobot, als geplanter Nautobot-Job (ORM).
|
||||
|
||||
Kanonische Runtime des CF→Nautobot-Sync. Laeuft im nautobot-worker (kein extra
|
||||
Container), schreibt per ORM (keine Nautobot-Exposure), zieht die CF-API outbound.
|
||||
Portierung der Reconcile-Logik aus nautobot/tp44-cf-sync.py (REST) auf das ORM.
|
||||
|
||||
Governance (User 2026-09-09): periodische externe->Nautobot-Syncs leben als
|
||||
Nautobot-Jobs in bf-infra/jobs/ und werden in Nautobot geplant — KEIN per-Task-
|
||||
Container/-Cron. Kuenftige Syncs = weitere Jobs hier.
|
||||
|
||||
Deckung: CloudNetwork (cfd_tunnel + warp_connector), CloudService (workers,
|
||||
access-apps, hyperdrive, VPC), Prefix-Assignments (teamnet/routes), Verwaisung
|
||||
(stale-Markierung statt Loeschen). ID-Matching + extra_config-Merge wie im Skript.
|
||||
|
||||
Token: Nautobot-Secret "cf-api-token" (Provider environment-variable -> CF_API_TOKEN
|
||||
am Worker). CF-Account-ID: Secret "cf-account-id" ODER Konstante unten.
|
||||
|
||||
CF bleibt operativer Master; Nautobot ist Dokumentations-SSoT (read-only Pull).
|
||||
"""
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from nautobot.apps.jobs import BooleanVar, Job
|
||||
from nautobot.cloud.models import (
|
||||
CloudAccount,
|
||||
CloudNetwork,
|
||||
CloudNetworkPrefixAssignment,
|
||||
CloudResourceType,
|
||||
CloudService,
|
||||
)
|
||||
from nautobot.dcim.models import Manufacturer
|
||||
from nautobot.extras.models import Secret
|
||||
from nautobot.ipam.models import Prefix
|
||||
|
||||
CF_API = "https://api.cloudflare.com/client/v4"
|
||||
CF_ACCOUNT_ID = "393c051f3accf51881d6c98fc5b66c36"
|
||||
|
||||
RESOURCE_TYPES = [
|
||||
("Cloudflare cloudflared Tunnel", "cloud", "cloudnetwork"),
|
||||
("Cloudflare WARP Connector", "cloud", "cloudnetwork"),
|
||||
("Cloudflare VPC Service", "cloud", "cloudservice"),
|
||||
("Cloudflare Hyperdrive", "cloud", "cloudservice"),
|
||||
("Cloudflare Worker", "cloud", "cloudservice"),
|
||||
("Cloudflare Access Application", "cloud", "cloudservice"),
|
||||
]
|
||||
|
||||
|
||||
class CFEstateSync(Job):
|
||||
dryrun = BooleanVar(
|
||||
default=True,
|
||||
description="Nur berichten, nichts schreiben (empfohlen fuer den ersten Lauf).",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
name = "CF Estate Sync"
|
||||
description = "Cloudflare-Estate (Tunnel/Worker/Access/Hyperdrive/VPC) nach Nautobot syncen (Doku-SSoT)."
|
||||
grouping = "TP44 / Cloudflare"
|
||||
# Nur ein Boolean (dryrun) als Eingabe; das CF-Token kommt aus dem Nautobot-Secret,
|
||||
# nicht aus einer Job-Variable. Ohne diese Zeile (Default True) verweigert Nautobot
|
||||
# das Scheduling: "Job may have sensitive input variables".
|
||||
has_sensitive_variables = False
|
||||
|
||||
# ---------------------------------------------------------------- CF-Pull
|
||||
def _cf_get(self, path):
|
||||
"""(ok, result) — 403/404 werden abgefangen, damit fehlende Scopes den Sync nicht kippen."""
|
||||
req = urllib.request.Request(CF_API + path, headers={"Authorization": "Bearer " + self._tok})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return True, json.load(r).get("result")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (403, 404):
|
||||
return False, None
|
||||
raise
|
||||
|
||||
def _ingress(self, tunnel_id):
|
||||
ok, res = self._cf_get(f"/accounts/{CF_ACCOUNT_ID}/cfd_tunnel/{tunnel_id}/configurations")
|
||||
rules = ((res or {}).get("config") or {}).get("ingress") or [] if ok else []
|
||||
return [r["hostname"] for r in rules if r.get("hostname")]
|
||||
|
||||
def _load_cf(self):
|
||||
"""networks/services: (name, rt_name, desc, extra, match_key, match_val).
|
||||
routes: {tunnel_id: cidr}. skipped: Liste nicht lesbarer Endpoints."""
|
||||
A = CF_ACCOUNT_ID
|
||||
networks, services, routes, skipped = [], [], {}, []
|
||||
|
||||
_, tuns = self._cf_get(f"/accounts/{A}/cfd_tunnel?is_deleted=false&per_page=50")
|
||||
for t in (tuns or []):
|
||||
networks.append((t["name"], "Cloudflare cloudflared Tunnel",
|
||||
f"cloudflared-Tunnel ({len(t.get('connections') or [])} Connectoren)",
|
||||
{"tunnel_id": t["id"], "kind": "cloudflared",
|
||||
"connector_count": len(t.get("connections") or []),
|
||||
"ingress_hostnames": self._ingress(t["id"])},
|
||||
"tunnel_id", t["id"]))
|
||||
_, warps = self._cf_get(f"/accounts/{A}/warp_connector?per_page=50")
|
||||
for w in (warps or []):
|
||||
networks.append((w["name"], "Cloudflare WARP Connector",
|
||||
f"WARP-Connector ({len(w.get('connections') or [])} Connectoren)",
|
||||
{"tunnel_id": w["id"], "kind": "warp_connector",
|
||||
"connector_count": len(w.get("connections") or [])},
|
||||
"tunnel_id", w["id"]))
|
||||
|
||||
_, scripts = self._cf_get(f"/accounts/{A}/workers/scripts")
|
||||
for s in (scripts or []):
|
||||
services.append((s["id"], "Cloudflare Worker", "Cloudflare Worker",
|
||||
{"script": s["id"]}, "script", s["id"]))
|
||||
|
||||
ok, apps = self._cf_get(f"/accounts/{A}/access/apps?per_page=100")
|
||||
if not ok:
|
||||
skipped.append("access/apps")
|
||||
for a in (apps or []):
|
||||
services.append((a.get("name") or a.get("id"), "Cloudflare Access Application",
|
||||
f"Access-App ({a.get('type')})",
|
||||
{"id": a.get("id"), "type": a.get("type"), "domain": a.get("domain")},
|
||||
"id", a.get("id")))
|
||||
|
||||
ok, hyper = self._cf_get(f"/accounts/{A}/hyperdrive/configs")
|
||||
if not ok:
|
||||
skipped.append("hyperdrive/configs")
|
||||
for h in (hyper or []):
|
||||
origin = h.get("origin") or {}
|
||||
services.append((h.get("name") or h.get("id"), "Cloudflare Hyperdrive", "Hyperdrive-Config",
|
||||
{"id": h.get("id"), "database": origin.get("database"),
|
||||
"user": origin.get("user")}, "id", h.get("id")))
|
||||
|
||||
ok, vpc = self._cf_get(f"/accounts/{A}/connectivity/directory/services")
|
||||
if not ok:
|
||||
skipped.append("connectivity/directory/services")
|
||||
for v in (vpc or []):
|
||||
# VPC-Directory-Services tragen ihren Identifier in "service_id" (NICHT "id").
|
||||
sid = v.get("service_id") or v.get("id")
|
||||
services.append((v.get("name") or sid, "Cloudflare VPC Service", "VPC-Service",
|
||||
{"service_id": sid, "type": v.get("type")}, "service_id", sid))
|
||||
|
||||
ok, rts = self._cf_get(f"/accounts/{A}/teamnet/routes?per_page=50")
|
||||
if not ok:
|
||||
skipped.append("teamnet/routes")
|
||||
for r in (rts or []):
|
||||
if r.get("network") and r.get("tunnel_id"):
|
||||
routes[r["tunnel_id"]] = r["network"]
|
||||
|
||||
return networks, services, routes, skipped
|
||||
|
||||
# ---------------------------------------------------------------- ORM-Upsert
|
||||
def _upsert(self, model, cache, name, rt, acct, desc, extra, mk, mv, label, seen, dryrun):
|
||||
# mv=None nie matchen: sonst greift .get(mk)==None auf das erste Objekt OHNE mk
|
||||
# und mischt fremde extra_config zusammen (z.B. VPC ohne id -> App-Launcher).
|
||||
if mv is None:
|
||||
self.logger.warning("~ %s '%s' ohne Match-Wert (%s) — uebersprungen", label, name, mk)
|
||||
return None
|
||||
ex = next((o for o in cache if (o.extra_config or {}).get(mk) == mv), None)
|
||||
if ex:
|
||||
seen.add(ex.id)
|
||||
merged = dict(ex.extra_config or {})
|
||||
merged.update(extra)
|
||||
if merged == (ex.extra_config or {}):
|
||||
return ex
|
||||
if dryrun:
|
||||
self.logger.info("~ %s '%s': extra_config-Update [DRY-RUN]", label, ex.name)
|
||||
return ex
|
||||
ex.extra_config = merged
|
||||
ex.validated_save()
|
||||
self.logger.info("~ %s '%s' aktualisiert", label, ex.name)
|
||||
return ex
|
||||
if dryrun:
|
||||
self.logger.info("+ %s '%s' [DRY-RUN]", label, name)
|
||||
return None
|
||||
obj = model(name=name, cloud_resource_type=rt, cloud_account=acct,
|
||||
description=desc, extra_config=extra)
|
||||
obj.validated_save()
|
||||
cache.append(obj)
|
||||
seen.add(obj.id)
|
||||
self.logger.info("+ %s '%s' angelegt", label, name)
|
||||
return obj
|
||||
|
||||
def _mark_stale(self, cache, seen, label, dryrun):
|
||||
for o in cache:
|
||||
extra = o.extra_config or {}
|
||||
if o.id in seen:
|
||||
if extra.get("stale") and not dryrun:
|
||||
o.extra_config = {k: v for k, v in extra.items() if k != "stale"}
|
||||
o.validated_save()
|
||||
self.logger.info("↩ %s '%s': stale entfernt (wieder da)", label, o.name)
|
||||
continue
|
||||
if extra.get("stale"):
|
||||
continue
|
||||
if dryrun:
|
||||
self.logger.warning("⚠ %s '%s' fehlt im Live-Pull → wuerde stale markiert [DRY-RUN]", label, o.name)
|
||||
continue
|
||||
o.extra_config = dict(extra, stale=True)
|
||||
o.validated_save()
|
||||
self.logger.warning("⚠ %s '%s' als stale markiert (nicht mehr in CF)", label, o.name)
|
||||
|
||||
# ---------------------------------------------------------------- run
|
||||
def run(self, dryrun=True):
|
||||
try:
|
||||
self._tok = Secret.objects.get(name="cf-api-token").get_value()
|
||||
except Secret.DoesNotExist:
|
||||
raise RuntimeError("Nautobot-Secret 'cf-api-token' fehlt — bitte anlegen (Provider env, CF_API_TOKEN).")
|
||||
|
||||
networks, services, routes, skipped = self._load_cf()
|
||||
self.logger.info("Live-Pull: %d CloudNetworks · %d CloudServices%s",
|
||||
len(networks), len(services),
|
||||
f" · SKIP: {', '.join(skipped)}" if skipped else " · alle Endpoints lesbar")
|
||||
|
||||
mfr, _ = Manufacturer.objects.get_or_create(name="Cloudflare")
|
||||
acct, created = CloudAccount.objects.get_or_create(
|
||||
name="Cloudflare", defaults={"account_number": CF_ACCOUNT_ID, "provider": mfr})
|
||||
if created and dryrun:
|
||||
pass
|
||||
|
||||
rt_by_name = {}
|
||||
for name, app, model_name in RESOURCE_TYPES:
|
||||
ct = ContentType.objects.get(app_label=app, model=model_name)
|
||||
rt, _ = CloudResourceType.objects.get_or_create(name=name, defaults={"provider": mfr})
|
||||
rt.content_types.add(ct)
|
||||
rt_by_name[name] = rt
|
||||
|
||||
net_cache = list(CloudNetwork.objects.filter(cloud_account=acct))
|
||||
svc_cache = list(CloudService.objects.filter(cloud_account=acct))
|
||||
seen_net, seen_svc = set(), set()
|
||||
|
||||
self.logger.info("[CloudNetworks]")
|
||||
for name, rt_name, desc, extra, mk, mv in networks:
|
||||
self._upsert(CloudNetwork, net_cache, name, rt_by_name.get(rt_name), acct,
|
||||
desc, extra, mk, mv, "CloudNetwork", seen_net, dryrun)
|
||||
|
||||
self.logger.info("[CloudServices]")
|
||||
for name, rt_name, desc, extra, mk, mv in services:
|
||||
self._upsert(CloudService, svc_cache, name, rt_by_name.get(rt_name), acct,
|
||||
desc, extra, mk, mv, "CloudService", seen_svc, dryrun)
|
||||
|
||||
self.logger.info("[Prefix-Assignments]")
|
||||
for tunnel_id, cidr in routes.items():
|
||||
net = next((n for n in net_cache if (n.extra_config or {}).get("tunnel_id") == tunnel_id), None)
|
||||
if not net or getattr(net, "id", None) is None:
|
||||
continue
|
||||
pfx = Prefix.objects.filter(prefix=cidr).first()
|
||||
if not pfx:
|
||||
self.logger.info(" ~ Route %s: Prefix nicht in IPAM → uebersprungen", cidr)
|
||||
continue
|
||||
if CloudNetworkPrefixAssignment.objects.filter(cloud_network=net, prefix=pfx).exists():
|
||||
continue
|
||||
if dryrun:
|
||||
self.logger.info(" + %s ↔ %s [DRY-RUN]", net.name, cidr)
|
||||
continue
|
||||
CloudNetworkPrefixAssignment(cloud_network=net, prefix=pfx).validated_save()
|
||||
self.logger.info(" + %s ↔ %s angelegt", net.name, cidr)
|
||||
|
||||
self.logger.info("[Verwaisung]")
|
||||
# CloudServices nur markieren, wenn ALLE Service-Endpoints lesbar waren — sonst
|
||||
# wuerde ein fehlender Scope echte Objekte faelschlich als verwaist markieren.
|
||||
svc_safe = not ({"access/apps", "hyperdrive/configs", "connectivity/directory/services"} & set(skipped))
|
||||
if svc_safe:
|
||||
self._mark_stale(svc_cache, seen_svc, "CloudService", dryrun)
|
||||
else:
|
||||
self.logger.info(" · CloudService-Verwaisung uebersprungen (Token-Scope unvollstaendig)")
|
||||
self._mark_stale(net_cache, seen_net, "CloudNetwork", dryrun)
|
||||
|
||||
self.logger.info("Fertig (%s).", "DRY-RUN — nichts geschrieben" if dryrun else "COMMIT")
|
||||
|
||||
|
||||
# Registrierung zentral in jobs/__init__.py (Nautobot importiert nur das Paket).
|
||||
@@ -203,8 +203,11 @@
|
||||
# Literal-Block wie die L2-Fabric: Radio-MACs und Kanalzuordnung sind
|
||||
# geraetegebunden und in Nautobot nicht modelliert. Aenderungen daran sind
|
||||
# bewusst sichtbare Template-Aenderungen.
|
||||
/interface wifi channel add band=5ghz-ax frequency=5180 name=ch-5g-ap1 width=20/40mhz
|
||||
/interface wifi channel add band=5ghz-ax frequency=5220 name=ch-5g-ap2 width=20/40mhz
|
||||
# ap1/ap2 auf Band B (DFS) seit dem TP25-Kanalplan-Umzug (committed 2026-09-04). Frequenz-LISTE
|
||||
# statt Einzelwert: bei einer Radar-Erkennung muss das Radio auf einen anderen Wert der Liste
|
||||
# ausweichen koennen -- ein Einzelwert waere ein SPOF. ap1=52+56 (5260/Ce), ap2=60+64 (5300/Ce).
|
||||
/interface wifi channel add band=5ghz-ax frequency=5260,5280 name=ch-5g-ap1 width=20/40mhz
|
||||
/interface wifi channel add band=5ghz-ax frequency=5300,5320 name=ch-5g-ap2 width=20/40mhz
|
||||
/interface wifi channel add band=2ghz-ax frequency=2412 name=ch-2g-ap1 width=20mhz
|
||||
/interface wifi channel add band=2ghz-ax frequency=2462 name=ch-2g-ap2 width=20mhz
|
||||
# ap3 (cAP ax, Technik UG) seit 2026-08-30. Kanaele aus dem TP25-Plan vom 2026-08-24:
|
||||
@@ -219,7 +222,10 @@
|
||||
/interface wifi datapath add bridge=bridge name=dp-media vlan-id=40
|
||||
/interface wifi datapath add bridge=bridge name=dp-untrusted vlan-id=50
|
||||
/interface wifi datapath add bridge=bridge name=dp-guest vlan-id=90
|
||||
/interface wifi security add authentication-types=wpa3-psk ft=yes ft-over-ds=yes name=sec-main passphrase="<<REDACTED>>"
|
||||
# sec-main: ft=no seit 2026-09-04 (User-Entscheid nach TP25-Messung). Fast-Transition
|
||||
# komplett aus; ft-over-ds ist damit gegenstandslos (gilt nur bei ft=yes) und steht auf no.
|
||||
# Fund E hatte ft-over-ds=no BEI ft=yes widerlegt -- das hier ist ein anderer Hebel (FT aus).
|
||||
/interface wifi security add authentication-types=wpa3-psk ft=no ft-over-ds=no name=sec-main passphrase="<<REDACTED>>"
|
||||
/interface wifi security add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=sec-infra passphrase="<<REDACTED>>"
|
||||
/interface wifi security add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=sec-iot passphrase="<<REDACTED>>"
|
||||
/interface wifi security add authentication-types=wpa2-psk,wpa3-psk ft=no name=sec-media passphrase="<<REDACTED>>"
|
||||
@@ -241,18 +247,16 @@
|
||||
/interface wifi configuration add channel=ch-2g-ap3 country=Austria datapath=dp-main mode=ap name=cfg-main-2g-ap3 security=sec-main ssid=dirtyvader-main steering=st-main
|
||||
/interface wifi configuration add country=Austria datapath=dp-guest mode=ap name=cfg-guest security=sec-guest ssid=dirtyvader-guest steering=st-guest
|
||||
/interface wifi capsman set ca-certificate=auto certificate=auto enabled=yes interfaces=vlan99 upgrade-policy=none
|
||||
# 🔴 ap1 hat BEWUSST keine Provisioning-Zeile (2026-08-30). ap3 hat in Technik UG seinen
|
||||
# Platz uebernommen und funkt dort auf 5180; ap1 steht bis zu seinem Umzug ans Sideboard
|
||||
# direkt daneben und wuerde auf demselben Kanal kollidieren. Ohne passende Regel legt
|
||||
# CAPsMAN fuer ein Radio gar nichts an -> es bleibt still, das Geraet aber erreichbar.
|
||||
# Die Objekte `ch-5g-ap1`/`ch-2g-ap1` und `cfg-main-*-ap1` bleiben absichtlich stehen:
|
||||
# beim Umzug bekommt ap1 laut TP25 `5260/Ce` (Band B, DFS) -- dann werden nur diese
|
||||
# zwei Zeilen wieder eingesetzt und das Channel-Objekt auf eine Frequenz-LISTE gestellt
|
||||
# (mit nur einem Wert waere eine Radar-Erkennung ein SPOF).
|
||||
# ap1 ist ans Sideboard umgezogen (TP25-Kanalplan committed 2026-09-04) und hat wieder eine
|
||||
# Provisioning-Zeile: Band B `5260/Ce` (5G) + ch1 (2,4G). Die frueher bewusst weggelassenen
|
||||
# Zeilen (2026-08-30, ap1 stand neben ap3 auf 5180 und haette kollidiert) sind damit
|
||||
# gegenstandslos. Reihenfolge wie am Geraet: ap2, ap3, ap1.
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 5GHz" master-configuration=cfg-main-5g-ap2 radio-mac=D0:EA:11:4D:40:3E slave-configurations=cfg-media,cfg-untrusted,cfg-guest
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 2.4GHz" master-configuration=cfg-main-2g-ap2 radio-mac=D0:EA:11:4D:40:3F slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap3 5GHz" master-configuration=cfg-main-5g-ap3 radio-mac=D0:EA:11:FF:16:9F slave-configurations=cfg-media,cfg-untrusted,cfg-guest
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap3 2.4GHz" master-configuration=cfg-main-2g-ap3 radio-mac=D0:EA:11:FF:16:A0 slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap1 5GHz" master-configuration=cfg-main-5g-ap1 radio-mac=D0:EA:11:4D:40:E5 slave-configurations=cfg-media,cfg-untrusted,cfg-guest
|
||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap1 2.4GHz" master-configuration=cfg-main-2g-ap1 radio-mac=D0:EA:11:4D:40:E6 slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
|
||||
|
||||
# [6c] WLAN ACCESS-LIST — der Soll-Zustand ist LEER, und das ist eine Aussage.
|
||||
#
|
||||
|
||||
178
nautobot/tp37-dienste-seed.py
Normal file
178
nautobot/tp37-dienste-seed.py
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP37 Punkt 2 (Dienste) — exponierte Ports als native ipam.Service-Objekte.
|
||||
|
||||
Idempotenter Seeder (get-or-create) fuer ipam.Service:
|
||||
je VM-Name + Port + Protokoll ein Service-Eintrag.
|
||||
|
||||
Vorbedingung: TP37 P1 (tp37-substrat-seed.py --commit) muss vorher gelaufen sein,
|
||||
da die VirtualMachine-Objekte existieren muessen.
|
||||
|
||||
Datenmodell-Entscheid (User 2026-09-06): native Modelle, KEINE eigene App.
|
||||
Muster: nautobot/tp37-substrat-seed.py.
|
||||
|
||||
Umfang (User-Default 2026-09-07):
|
||||
Caddy (:80/:443), Caddy-Backends (haos:8123, vaultwarden:80, gitea:3000, n8n:5678),
|
||||
Nautobot:8080, AdGuard:53 (bf1+bf2, tcp+udp), platform-pg:5432, mariadb:3306,
|
||||
influxdb2:8086, mqtt5:1883, portainer:9443,
|
||||
MCP-Dienste (pve-mcp:3000, shelly-mcp:3030, n8n-mcp:3001, gitea-mcp:3000).
|
||||
+ Smart-Home (vervollstaendigt 2026-09-08, Ports per Socket-Probe verifiziert):
|
||||
RaspberryMatic/CCU (80/443/2001/2010), iob-master:8081, z2m25:8080.
|
||||
iob-exposed/iob_slave BEWUSST ohne Service (kein exponierter Port gefunden).
|
||||
|
||||
Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored),
|
||||
Schluessel NAUTOBOT_SUPERUSER_API_TOKEN.
|
||||
|
||||
Aufruf:
|
||||
python3 nautobot/tp37-dienste-seed.py # Dry-Run (default)
|
||||
python3 nautobot/tp37-dienste-seed.py --commit # legt an
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
|
||||
def load_token() -> str:
|
||||
for line in YML.read_text().splitlines():
|
||||
m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden")
|
||||
|
||||
|
||||
TOKEN = load_token()
|
||||
HDRS = {"Authorization": f"Token {TOKEN}", "Accept": "application/json",
|
||||
"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def req(method: str, path: str, body=None):
|
||||
url = f"{NB}{path}"
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
r = urllib.request.Request(url, data=data, headers=HDRS, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=20) as resp:
|
||||
return resp.status, json.loads(resp.read().decode() or "{}")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode() or "{}")
|
||||
|
||||
|
||||
def find(endpoint: str, params: dict, match_key: str, match_val: str):
|
||||
q = urllib.parse.urlencode(dict(params, depth=0))
|
||||
st, d = req("GET", f"/api/{endpoint}/?{q}")
|
||||
for row in d.get("results", []):
|
||||
if row.get(match_key) == match_val:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def get_or_create(endpoint: str, find_params: dict, match_key: str,
|
||||
match_val: str, payload: dict, label: str):
|
||||
existing = find(endpoint, find_params, match_key, match_val)
|
||||
if existing:
|
||||
print(f" = {label}: '{match_val}' existiert ({existing['id']})")
|
||||
return existing
|
||||
if not COMMIT:
|
||||
print(f" + {label}: '{match_val}' [DRY-RUN]")
|
||||
return {"id": f"<dry:{match_val}>"}
|
||||
st, d = req("POST", f"/api/{endpoint}/", payload)
|
||||
if st in (200, 201):
|
||||
print(f" + {label}: '{match_val}' angelegt ({d.get('id')})")
|
||||
return d
|
||||
print(f" ! {label}: '{match_val}' FEHLER {st}: {json.dumps(d)[:300]}")
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Inventar
|
||||
# (vm_name, svc_name, ports, protocol, description)
|
||||
SERVICES = [
|
||||
# Caddy Reverse-Proxy (TP34 critical)
|
||||
("caddy-bf1", "caddy-http", [80], "tcp", "Caddy HTTP"),
|
||||
("caddy-bf1", "caddy-https", [443], "tcp", "Caddy HTTPS Reverse Proxy"),
|
||||
# Caddy-Backends via bf-public Tunnel
|
||||
("haos", "home-assistant", [8123], "tcp", "Home Assistant Web"),
|
||||
("vaultwarden", "vaultwarden-http", [80], "tcp", "Vaultwarden (HTTP, TLS via Caddy)"),
|
||||
("gitea", "gitea-http", [3000], "tcp", "Gitea Web"),
|
||||
("n8n", "n8n-web", [5678], "tcp", "n8n Workflow Engine"),
|
||||
# Nautobot
|
||||
("nautobot", "nautobot-web", [8080], "tcp", "Nautobot API/Web"),
|
||||
# DNS (TCP + UDP je Resolver)
|
||||
("agh-bf1", "adguard-dns-tcp", [53], "tcp", "AdGuard Home DNS/TCP"),
|
||||
("agh-bf1", "adguard-dns-udp", [53], "udp", "AdGuard Home DNS/UDP"),
|
||||
("agh-bf2", "adguard-dns-tcp", [53], "tcp", "AdGuard Home DNS/TCP"),
|
||||
("agh-bf2", "adguard-dns-udp", [53], "udp", "AdGuard Home DNS/UDP"),
|
||||
# Infrastruktur
|
||||
("platform-pg", "postgresql", [5432], "tcp", "PostgreSQL 16 (Nautobot-DB)"),
|
||||
("mariadb", "mariadb", [3306], "tcp", "MariaDB (vw/hass/gitea)"),
|
||||
("influxdb2", "influxdb", [8086], "tcp", "InfluxDB 2"),
|
||||
("mqtt5", "mqtt", [1883], "tcp", "MQTT Broker (Mosquitto)"),
|
||||
("docker", "portainer", [9443], "tcp", "Portainer CE"),
|
||||
# MCP-Dienste
|
||||
("pve-mcp", "pve-mcp", [3000], "tcp", "Proxmox MCP (StreamableHTTP)"),
|
||||
("shelly-mcp", "shelly-mcp", [3030], "tcp", "Shelly MCP"),
|
||||
("n8n-mcp", "n8n-mcp", [3001], "tcp", "n8n MCP"),
|
||||
("gitea-mcp", "gitea-mcp", [3000], "tcp", "Gitea MCP"),
|
||||
# Smart-Home / IoT — Ports live per Socket-Probe verifiziert 2026-09-08 (NICHT raten!)
|
||||
("RaspberryMatic","ccu-http", [80], "tcp", "Homematic CCU Web (HTTP)"),
|
||||
("RaspberryMatic","ccu-https", [443], "tcp", "Homematic CCU Web (HTTPS)"),
|
||||
("RaspberryMatic","ccu-rpc-bidcos", [2001], "tcp", "Homematic BidCos-RF XML-RPC"),
|
||||
("RaspberryMatic","ccu-rpc-hmip", [2010], "tcp", "Homematic-IP XML-RPC"),
|
||||
("iob-master", "iobroker-admin", [8081], "tcp", "ioBroker Admin"),
|
||||
("z2m25", "zigbee2mqtt", [8080], "tcp", "Zigbee2MQTT Frontend"),
|
||||
# iob-exposed (.19) + iob_slave (.33): kein exponierter Port gefunden (13 Ports, alle Timeout)
|
||||
# -> BEWUSST kein Service, bis ein Port live belegt ist. Nicht "hilfreich" einen erfinden.
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
print(f"== TP37 Dienste-Seed ({'COMMIT' if COMMIT else 'DRY-RUN'}) @ {NB} ==")
|
||||
|
||||
# VM-IDs vorab holen (unique Namen, Batch)
|
||||
vm_names = sorted({vm for vm, *_ in SERVICES})
|
||||
print(f"\n[0] VM-IDs holen ({len(vm_names)} unique VMs)")
|
||||
vm_ids = {}
|
||||
for name in vm_names:
|
||||
row = find("virtualization/virtual-machines", {"name": name}, "name", name)
|
||||
if row:
|
||||
vm_ids[name] = row["id"]
|
||||
print(f" = {name}: {row['id']}")
|
||||
else:
|
||||
vm_ids[name] = None
|
||||
print(f" ! {name}: NICHT gefunden — Service-Eintraege werden uebersprungen")
|
||||
|
||||
print(f"\n[1] ipam.Services ({len(SERVICES)} Eintraege)")
|
||||
n_ok = n_skip = 0
|
||||
for vm_name, svc_name, ports, protocol, desc in SERVICES:
|
||||
vm_id = vm_ids.get(vm_name)
|
||||
if not vm_id and COMMIT:
|
||||
print(f" ~ {vm_name}/{svc_name}: VM fehlt, uebersprungen")
|
||||
n_skip += 1
|
||||
continue
|
||||
svc = get_or_create(
|
||||
"ipam/services",
|
||||
{"name": svc_name, "virtual_machine": vm_name},
|
||||
"name", svc_name,
|
||||
{"virtual_machine": vm_id, "name": svc_name,
|
||||
"ports": ports, "protocol": protocol, "description": desc},
|
||||
f"{vm_name}/{protocol}/{ports[0]}",
|
||||
)
|
||||
if svc:
|
||||
n_ok += 1
|
||||
else:
|
||||
n_skip += 1
|
||||
|
||||
print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN — nichts geschrieben'}) "
|
||||
f"| Services={n_ok} uebersprungen={n_skip} ==")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
256
nautobot/tp37-substrat-seed.py
Normal file
256
nautobot/tp37-substrat-seed.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP37 Punkt 1 (Substrat) — Hosts/Container als native Nautobot-Objekte.
|
||||
|
||||
Idempotenter Seeder (get-or-create) fuer die native virtualization-Modellwelt:
|
||||
ClusterType (3x: Proxmox VE / Synology Docker / Docker Desktop)
|
||||
-> Cluster (pve / nas-bf / macmini)
|
||||
-> VirtualMachine je Workload (Status Active)
|
||||
+ VMInterface "eth0" + ipam.IPAddress (Namespace Global) + primary_ip4
|
||||
|
||||
Datenmodell-Entscheid (User 2026-09-06): native Modelle, KEINE eigene App
|
||||
(konsistent zu TP44). Muster: nautobot/tp44-cf-estate-seed.py.
|
||||
|
||||
Umfang Etappe 1 (User-Default 2026-09-07):
|
||||
- komplettes Substrat: alle 15 pve-Gaeste (inkl. Smart-Home) + alle running
|
||||
Container auf nas-bf (Portainer ep14) und macmini (ep20).
|
||||
- macmini-Container teilen sich die Host-IP 10.40.20.167 (Bridge/Port-Mapping)
|
||||
-> KEIN primary_ip4; Port kommt in TP37 P2 (ipam.Service).
|
||||
- portainer_agent (Bridge 172.17.0.2, kein Workload) weggelassen.
|
||||
- gestoppte (exited) NAS-Container weggelassen (nur running).
|
||||
|
||||
Datenstand: Live-Inventur 2026-09-07 (pve-mcp pct config, Portainer ep14,
|
||||
ssh macmini docker ps, ARP fuer die 2 QEMU-IPs).
|
||||
|
||||
Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored),
|
||||
Schluessel NAUTOBOT_SUPERUSER_API_TOKEN.
|
||||
|
||||
Aufruf:
|
||||
python3 nautobot/tp37-substrat-seed.py # Dry-Run (default)
|
||||
python3 nautobot/tp37-substrat-seed.py --commit # legt an
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
|
||||
def load_token() -> str:
|
||||
for line in YML.read_text().splitlines():
|
||||
m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden")
|
||||
|
||||
|
||||
TOKEN = load_token()
|
||||
HDRS = {"Authorization": f"Token {TOKEN}", "Accept": "application/json",
|
||||
"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def req(method: str, path: str, body=None):
|
||||
url = f"{NB}{path}"
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
r = urllib.request.Request(url, data=data, headers=HDRS, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=20) as resp:
|
||||
return resp.status, json.loads(resp.read().decode() or "{}")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode() or "{}")
|
||||
|
||||
|
||||
def is_dry(v) -> bool:
|
||||
return isinstance(v, str) and v.startswith("<dry:")
|
||||
|
||||
|
||||
def find(endpoint: str, params: dict, match_key: str, match_val: str):
|
||||
q = urllib.parse.urlencode(dict(params, depth=0))
|
||||
st, d = req("GET", f"/api/{endpoint}/?{q}")
|
||||
for row in d.get("results", []):
|
||||
if row.get(match_key) == match_val:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def get_or_create(endpoint: str, find_params: dict, match_key: str,
|
||||
match_val: str, payload: dict, label: str):
|
||||
existing = find(endpoint, find_params, match_key, match_val)
|
||||
if existing:
|
||||
print(f" = {label}: '{match_val}' existiert ({existing['id']})")
|
||||
return existing
|
||||
if not COMMIT:
|
||||
print(f" + {label}: '{match_val}' [DRY-RUN]")
|
||||
return {"id": f"<dry:{match_val}>"}
|
||||
st, d = req("POST", f"/api/{endpoint}/", payload)
|
||||
if st in (200, 201):
|
||||
print(f" + {label}: '{match_val}' angelegt ({d.get('id')})")
|
||||
return d
|
||||
print(f" ! {label}: '{match_val}' FEHLER {st}: {json.dumps(d)[:300]}")
|
||||
return None
|
||||
|
||||
|
||||
def status_id(name: str):
|
||||
return find("extras/statuses", {"name": name}, "name", name)["id"]
|
||||
|
||||
|
||||
def namespace_id(name: str):
|
||||
return find("ipam/namespaces", {"name": name}, "name", name)["id"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Inventar
|
||||
CLUSTER_TYPES = ["Proxmox VE", "Synology Docker", "Docker Desktop"]
|
||||
|
||||
# cluster -> clustertype
|
||||
CLUSTERS = [("pve", "Proxmox VE"),
|
||||
("nas-bf", "Synology Docker"),
|
||||
("macmini", "Docker Desktop")]
|
||||
|
||||
# cluster -> [ (vm_name, primary_ip_with_mask_or_None, comment) ]
|
||||
VMS = {
|
||||
"pve": [
|
||||
("haos", "10.40.20.105/24", "Home Assistant OS (QEMU 100)"),
|
||||
("iob-master", "10.40.20.107/24", "ioBroker Master (LXC 101)"),
|
||||
("mqtt5", "10.40.20.108/24", "MQTT-Broker Mosquitto (LXC 102)"),
|
||||
("platform-pg", "10.40.20.20/24", "PostgreSQL 16 - Nautobot-DB (LXC 105, TP20)"),
|
||||
("influxdb2", "10.40.20.8/24", "InfluxDB 2 (LXC 106)"),
|
||||
("redis", "10.40.20.111/24", "Redis (LXC 107)"),
|
||||
("mcast-relay", "10.40.10.21/24", "SSDP-Relay VLAN10<->40 (LXC 108); 2. Bein 10.40.40.21"),
|
||||
("agh-bf1", "10.40.20.36/24", "AdGuard Home Resolver (LXC 110, TP23)"),
|
||||
("agh-bf2", "10.40.20.37/24", "AdGuard Home Resolver (LXC 111, TP23)"),
|
||||
("z2m25", "10.40.20.106/24", "Zigbee2MQTT (LXC 112)"),
|
||||
("caddy-bf1", "10.40.20.40/24", "Caddy Reverse-Proxy (LXC 130, TP31)"),
|
||||
("iob-exposed", "10.40.20.19/24", "ioBroker exposed (LXC 150)"),
|
||||
("mariadb", "10.40.20.23/24", "MariaDB - vw/hass/gitea (LXC 302)"),
|
||||
("RaspberryMatic","10.40.20.232/24", "Homematic CCU (QEMU 330)"),
|
||||
("docker", "10.40.20.126/24", "Docker-Host nested (LXC 400)"),
|
||||
],
|
||||
"nas-bf": [
|
||||
("nautobot", "10.40.20.30/24", "Nautobot Web (DS923 ep14)"),
|
||||
("nautobot-worker", "10.40.20.31/24", "Nautobot Golden-Config Worker"),
|
||||
("nautobot-scheduler", "10.40.20.32/24", "Nautobot Scheduler"),
|
||||
("nautobot-redis", "10.40.20.34/24", "Nautobot Redis"),
|
||||
("gitea", "10.40.20.157/24", "Gitea (git-SSoT)"),
|
||||
("vaultwarden", "10.40.20.127/24", "Vaultwarden (DB=mariadb LXC302)"),
|
||||
("shelly-mcp", "10.40.20.180/24", "Shelly-MCP"),
|
||||
("pve-mcp", "10.40.20.173/24", "Proxmox-MCP (mcp-pve)"),
|
||||
("iob_slave", "10.40.20.33/24", "ioBroker Slave"),
|
||||
],
|
||||
"macmini": [
|
||||
# Bridge/Port-Mapping auf Host 10.40.20.167 -> kein eigenes primary_ip4
|
||||
("n8n", None, "n8n (Docker Desktop, Host 10.40.20.167:5678)"),
|
||||
("n8n-redis", None, "n8n Redis (intern, kein Host-Port)"),
|
||||
("n8n-mcp", None, "n8n-MCP (Host 10.40.20.167:3001)"),
|
||||
("gitea-mcp", None, "Gitea-MCP (Host 10.40.20.167:3000)"),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print(f"== TP37 Substrat-Seed ({'COMMIT' if COMMIT else 'DRY-RUN'}) @ {NB} ==")
|
||||
|
||||
st_active = status_id("Active")
|
||||
ns_global = namespace_id("Global")
|
||||
print(f" (Status Active={st_active} Namespace Global={ns_global})")
|
||||
|
||||
print("\n[1] ClusterTypes")
|
||||
for ct in CLUSTER_TYPES:
|
||||
get_or_create("virtualization/cluster-types", {"name": ct}, "name", ct,
|
||||
{"name": ct}, "ClusterType")
|
||||
|
||||
print("\n[2] Clusters")
|
||||
ct_lookup = {ct: find("virtualization/cluster-types", {"name": ct}, "name", ct)
|
||||
for ct in CLUSTER_TYPES}
|
||||
cluster_ids = {}
|
||||
for cname, ctname in CLUSTERS:
|
||||
ctrow = ct_lookup.get(ctname)
|
||||
ct_id = ctrow["id"] if ctrow else None
|
||||
cl = get_or_create("virtualization/clusters", {"name": cname}, "name", cname,
|
||||
{"name": cname, "cluster_type": ct_id}, "Cluster")
|
||||
cluster_ids[cname] = cl["id"] if cl else None
|
||||
|
||||
print("\n[3] VirtualMachines (+ eth0 + IPAddress + primary_ip4)")
|
||||
n_vm = n_ip = 0
|
||||
for cname, _ in CLUSTERS:
|
||||
cl_id = cluster_ids.get(cname)
|
||||
print(f" -- Cluster {cname} --")
|
||||
for name, ip, comment in VMS[cname]:
|
||||
vm = get_or_create(
|
||||
"virtualization/virtual-machines", {"name": name}, "name", name,
|
||||
{"name": name, "cluster": cl_id, "status": st_active,
|
||||
"comments": comment}, "VM")
|
||||
if not vm:
|
||||
continue
|
||||
n_vm += 1
|
||||
if not ip:
|
||||
print(f" (ohne primary_ip4 - Host-geteilt)")
|
||||
continue
|
||||
# VMInterface eth0
|
||||
vmif = None
|
||||
if is_dry(vm["id"]):
|
||||
print(f" + eth0 @ {name} [DRY-RUN]")
|
||||
vmif = {"id": f"<dry:{name}/eth0>"}
|
||||
else:
|
||||
vmif = find("virtualization/interfaces",
|
||||
{"virtual_machine": name, "name": "eth0"}, "name", "eth0")
|
||||
if vmif:
|
||||
print(f" = eth0 @ {name} ({vmif['id']})")
|
||||
else:
|
||||
s, d = req("POST", "/api/virtualization/interfaces/",
|
||||
{"virtual_machine": vm["id"], "name": "eth0",
|
||||
"status": st_active})
|
||||
if s in (200, 201):
|
||||
vmif = d
|
||||
print(f" + eth0 @ {name} ({d.get('id')})")
|
||||
else:
|
||||
print(f" ! eth0 @ {name} FEHLER {s}: {json.dumps(d)[:200]}")
|
||||
continue
|
||||
# IPAddress
|
||||
ipobj = get_or_create("ipam/ip-addresses", {"address": ip}, "address", ip,
|
||||
{"address": ip, "status": st_active,
|
||||
"namespace": ns_global, "dns_name": ""},
|
||||
"IP")
|
||||
if not ipobj:
|
||||
continue
|
||||
n_ip += 1
|
||||
if is_dry(vm["id"]) or is_dry(ipobj["id"]) or is_dry(vmif["id"]):
|
||||
print(f" + assign {ip} -> {name}/eth0 + primary_ip4 [DRY-RUN]")
|
||||
continue
|
||||
# assign IP <-> interface (idempotent)
|
||||
q = urllib.parse.urlencode({"vm_interface": vmif["id"], "depth": 0})
|
||||
s, d = req("GET", f"/api/ipam/ip-address-to-interface/?{q}")
|
||||
have = any((r.get("ip_address") or {}).get("id") == ipobj["id"]
|
||||
for r in d.get("results", []))
|
||||
if have:
|
||||
print(f" = assign {ip} -> {name}/eth0")
|
||||
else:
|
||||
s, d = req("POST", "/api/ipam/ip-address-to-interface/",
|
||||
{"ip_address": ipobj["id"], "vm_interface": vmif["id"]})
|
||||
print(f" {'+' if s in (200,201) else '!'} assign {ip} -> {name}/eth0"
|
||||
+ ("" if s in (200, 201) else f" FEHLER {s}: {json.dumps(d)[:150]}"))
|
||||
# primary_ip4 (idempotent)
|
||||
s, vmnow = req("GET", f"/api/virtualization/virtual-machines/{vm['id']}/?depth=0")
|
||||
cur = vmnow.get("primary_ip4")
|
||||
cur = cur.get("id") if isinstance(cur, dict) else cur
|
||||
if cur == ipobj["id"]:
|
||||
print(f" = primary_ip4 {ip} @ {name}")
|
||||
else:
|
||||
s, d = req("PATCH", f"/api/virtualization/virtual-machines/{vm['id']}/",
|
||||
{"primary_ip4": ipobj["id"]})
|
||||
print(f" {'+' if s == 200 else '!'} primary_ip4 {ip} @ {name}"
|
||||
+ ("" if s == 200 else f" FEHLER {s}: {json.dumps(d)[:150]}"))
|
||||
|
||||
print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN - nichts geschrieben'}) "
|
||||
f"| VMs={n_vm} IPs={n_ip} ==")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP36 — Cloudflare-Estate als SSoT in Nautobot (Etappe 1: manuelle Erstbefuellung).
|
||||
TP44 — Cloudflare-Estate als SSoT in Nautobot (Etappe 1: manuelle Erstbefuellung).
|
||||
|
||||
Idempotenter Seeder (get-or-create) fuer die native nautobot.cloud-Modellwelt:
|
||||
Manufacturer "Cloudflare"
|
||||
@@ -18,8 +18,8 @@ Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored),
|
||||
Schluessel NAUTOBOT_SUPERUSER_API_TOKEN.
|
||||
|
||||
Aufruf:
|
||||
python3 nautobot/tp36-cf-estate-seed.py # Dry-Run (default)
|
||||
python3 nautobot/tp36-cf-estate-seed.py --commit # legt an
|
||||
python3 nautobot/tp44-cf-estate-seed.py # Dry-Run (default)
|
||||
python3 nautobot/tp44-cf-estate-seed.py --commit # legt an
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
315
nautobot/tp44-cf-sync.py
Normal file
315
nautobot/tp44-cf-sync.py
Normal file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP44 Etappe 2 Teil B — CF-Estate LIVE aus der Cloudflare-API nach Nautobot syncen.
|
||||
|
||||
Ersetzt den Inline-Snapshot von tp44-cf-estate-seed.py durch einen periodischen
|
||||
Live-Pull (get-or-create + Merge). Idempotent, Dry-Run default. Kein Push nach CF —
|
||||
read-only Pull, Nautobot bleibt Dokumentations-SSoT, CF operativer Master.
|
||||
|
||||
Deckung (Live-Pull, je Endpoint 403-tolerant):
|
||||
CloudNetwork cfd_tunnel + warp_connector (4)
|
||||
CloudService workers/scripts + access/apps (11) [Repo-Token OK]
|
||||
CloudService hyperdrive/configs + VPC directory (3) [braucht Token-Scope]
|
||||
|
||||
Verwaisung: ein CF-Objekt, das in Nautobot steht, aber im Live-Pull fehlt, wird
|
||||
NICHT geloescht, sondern extra_config.stale=true gesetzt (Doku-SSoT bewahrt Historie).
|
||||
Ein Objekt, das wieder auftaucht, verliert die Markierung.
|
||||
|
||||
Tokens:
|
||||
CF : CF_API_TOKEN + CF_ACCOUNT_ID aus ~/.secrets/credentials.env (nie im Output)
|
||||
Nautobot: NAUTOBOT_SUPERUSER_API_TOKEN aus docs/nas-vlan20/stacks/210-nautobot.yml (gitignored)
|
||||
|
||||
Aufruf:
|
||||
python3 nautobot/tp44-cf-sync.py # Dry-Run (default) — zeigt Diff
|
||||
python3 nautobot/tp44-cf-sync.py --commit # schreibt
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
# ------------------------------------------------------------ Cloudflare-Seite
|
||||
def cf_secrets():
|
||||
env = {}
|
||||
with open(os.path.expanduser("~/.secrets/credentials.env")) as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
env[k.strip()] = v.strip().strip("'\"")
|
||||
return env
|
||||
|
||||
|
||||
_CF = cf_secrets()
|
||||
CF_TOK, CF_ACCT = _CF.get("CF_API_TOKEN"), _CF.get("CF_ACCOUNT_ID")
|
||||
if not CF_TOK or not CF_ACCT:
|
||||
sys.exit("CF_API_TOKEN/CF_ACCOUNT_ID fehlen in ~/.secrets/credentials.env")
|
||||
|
||||
|
||||
def cf_get(path):
|
||||
"""(ok, result) — faengt 403/404 ab, damit fehlende Token-Scopes den Sync nicht kippen."""
|
||||
req = urllib.request.Request("https://api.cloudflare.com/client/v4" + path,
|
||||
headers={"Authorization": "Bearer " + CF_TOK})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return True, json.load(r).get("result")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (403, 404):
|
||||
return False, None
|
||||
raise
|
||||
|
||||
|
||||
def cf_ingress(tunnel_id):
|
||||
ok, res = cf_get(f"/accounts/{CF_ACCT}/cfd_tunnel/{tunnel_id}/configurations")
|
||||
rules = ((res or {}).get("config") or {}).get("ingress") or [] if ok else []
|
||||
return [r["hostname"] for r in rules if r.get("hostname")]
|
||||
|
||||
|
||||
def load_cf_estate():
|
||||
"""Baut (networks, services) aus dem Live-Pull. Jedes Objekt:
|
||||
(name, resource_type, description, extra_config, match_key, match_val).
|
||||
match_key/-val = stabile CF-ID → idempotentes Matching trotz kuratierter Namen.
|
||||
Live-Zaehl-/Statusfelder liegen unter eigenen Keys (connector_count, ingress_hostnames),
|
||||
damit der Merge kuratierte Strings (z.B. connectors='3x @ ap1/ap2/ap3') nicht ueberschreibt."""
|
||||
networks, services = [], []
|
||||
skipped = []
|
||||
|
||||
# --- CloudNetworks: cloudflared-Tunnel (+ Connector-Zahl, Ingress-Hostnames)
|
||||
ok, tuns = cf_get(f"/accounts/{CF_ACCT}/cfd_tunnel?is_deleted=false&per_page=50")
|
||||
for t in (tuns or []):
|
||||
networks.append((t["name"], "Cloudflare cloudflared Tunnel",
|
||||
f"cloudflared-Tunnel ({len(t.get('connections') or [])} Connectoren)",
|
||||
{"tunnel_id": t["id"], "kind": "cloudflared",
|
||||
"connector_count": len(t.get("connections") or []),
|
||||
"ingress_hostnames": cf_ingress(t["id"])},
|
||||
"tunnel_id", t["id"]))
|
||||
# --- CloudNetworks: WARP-Connector
|
||||
ok, warps = cf_get(f"/accounts/{CF_ACCT}/warp_connector?per_page=50")
|
||||
for w in (warps or []):
|
||||
networks.append((w["name"], "Cloudflare WARP Connector",
|
||||
f"WARP-Connector ({len(w.get('connections') or [])} Connectoren)",
|
||||
{"tunnel_id": w["id"], "kind": "warp_connector",
|
||||
"connector_count": len(w.get("connections") or [])},
|
||||
"tunnel_id", w["id"]))
|
||||
|
||||
# --- CloudServices: Workers
|
||||
ok, scripts = cf_get(f"/accounts/{CF_ACCT}/workers/scripts")
|
||||
for s in (scripts or []):
|
||||
services.append((s["id"], "Cloudflare Worker", "Cloudflare Worker",
|
||||
{"script": s["id"]}, "script", s["id"]))
|
||||
|
||||
# --- CloudServices: Access-Apps
|
||||
ok, apps = cf_get(f"/accounts/{CF_ACCT}/access/apps?per_page=100")
|
||||
if not ok:
|
||||
skipped.append("access/apps")
|
||||
for a in (apps or []):
|
||||
services.append((a.get("name") or a.get("id"), "Cloudflare Access Application",
|
||||
f"Access-App ({a.get('type')})",
|
||||
{"id": a.get("id"), "type": a.get("type"), "domain": a.get("domain")},
|
||||
"id", a.get("id")))
|
||||
|
||||
# --- CloudServices: Hyperdrive (braucht Scope)
|
||||
ok, hyper = cf_get(f"/accounts/{CF_ACCT}/hyperdrive/configs")
|
||||
if not ok:
|
||||
skipped.append("hyperdrive/configs")
|
||||
for h in (hyper or []):
|
||||
origin = h.get("origin") or {}
|
||||
services.append((h.get("name") or h.get("id"), "Cloudflare Hyperdrive",
|
||||
"Hyperdrive-Config",
|
||||
{"id": h.get("id"), "database": origin.get("database"),
|
||||
"user": origin.get("user")}, "id", h.get("id")))
|
||||
|
||||
# --- CloudServices: VPC-Service (braucht Scope)
|
||||
ok, vpc = cf_get(f"/accounts/{CF_ACCT}/connectivity/directory/services")
|
||||
if not ok:
|
||||
skipped.append("connectivity/directory/services")
|
||||
for v in (vpc or []):
|
||||
services.append((v.get("name") or v.get("id"), "Cloudflare VPC Service",
|
||||
"VPC-Service",
|
||||
{"service_id": v.get("id"), "type": v.get("type")},
|
||||
"service_id", v.get("id")))
|
||||
|
||||
return networks, services, skipped
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Nautobot-Seite
|
||||
def nb_token():
|
||||
for line in YML.read_text().splitlines():
|
||||
m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden")
|
||||
|
||||
|
||||
NB_HDRS = {"Authorization": f"Token {nb_token()}", "Accept": "application/json",
|
||||
"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def nb(method, path, body=None):
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
r = urllib.request.Request(f"{NB}{path}", data=data, headers=NB_HDRS, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=20) as resp:
|
||||
return resp.status, json.loads(resp.read().decode() or "{}")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode() or "{}")
|
||||
|
||||
|
||||
def nb_all(endpoint, params):
|
||||
"""Alle Rows eines Endpoints (paginiert)."""
|
||||
out, url = [], f"/api/{endpoint}/?{urllib.parse.urlencode(dict(params, depth=0, limit=200))}"
|
||||
while url:
|
||||
st, d = nb("GET", url)
|
||||
out += d.get("results", [])
|
||||
nxt = d.get("next")
|
||||
url = nxt.replace(NB, "") if nxt else None
|
||||
return out
|
||||
|
||||
|
||||
_INDEX = {} # endpoint -> [rows] (einmal je Account geladen)
|
||||
|
||||
|
||||
def index(endpoint, acct_id):
|
||||
if endpoint not in _INDEX:
|
||||
_INDEX[endpoint] = nb_all(endpoint, {"cloud_account": acct_id})
|
||||
return _INDEX[endpoint]
|
||||
|
||||
|
||||
def upsert(endpoint, name, payload, label, seen, acct_id=None,
|
||||
match_key=None, match_val=None):
|
||||
"""get-or-create + Merge von extra_config.
|
||||
Matching: bei (match_key,match_val) nach der stabilen CF-ID in extra_config
|
||||
(Rename-fest), sonst nach Name. Trackt die getroffene id in seen[endpoint]."""
|
||||
ex = None
|
||||
if match_val is not None:
|
||||
for row in index(endpoint, acct_id):
|
||||
if (row.get("extra_config") or {}).get(match_key) == match_val:
|
||||
ex = row
|
||||
break
|
||||
else:
|
||||
for row in (index(endpoint, acct_id) if acct_id else
|
||||
nb_all(endpoint, {"name": name})):
|
||||
if row.get("name") == name:
|
||||
ex = row
|
||||
break
|
||||
if ex:
|
||||
seen.setdefault(endpoint, set()).add(ex["id"])
|
||||
merged = dict(ex.get("extra_config") or {})
|
||||
merged.update(payload.get("extra_config") or {})
|
||||
if merged == (ex.get("extra_config") or {}):
|
||||
print(f" = {label}: '{name}' unveraendert")
|
||||
return ex
|
||||
if not COMMIT:
|
||||
print(f" ~ {label}: '{name}' extra_config-Update [DRY-RUN]")
|
||||
return ex
|
||||
st, d = nb("PATCH", f"/api/{endpoint}/{ex['id']}/", {"extra_config": merged})
|
||||
print(f" ~ {label}: '{name}' aktualisiert" if st == 200
|
||||
else f" ! {label}: '{name}' PATCH {st}: {json.dumps(d)[:200]}")
|
||||
return d if st == 200 else ex
|
||||
if not COMMIT:
|
||||
print(f" + {label}: '{name}' [DRY-RUN]")
|
||||
return {"id": f"<dry:{name}>"}
|
||||
st, d = nb("POST", f"/api/{endpoint}/", payload)
|
||||
if st in (200, 201):
|
||||
seen.setdefault(endpoint, set()).add(d["id"])
|
||||
print(f" + {label}: '{name}' angelegt")
|
||||
return d
|
||||
print(f" ! {label}: '{name}' FEHLER {st}: {json.dumps(d)[:200]}")
|
||||
return None
|
||||
|
||||
|
||||
def mark_stale(endpoint, acct_id, seen_ids, label):
|
||||
"""CF-Objekte in Nautobot (per id), die im Live-Pull fehlen → extra_config.stale=true.
|
||||
Ein wieder aufgetauchtes Objekt verliert die Markierung."""
|
||||
for row in index(endpoint, acct_id):
|
||||
extra = row.get("extra_config") or {}
|
||||
nm = row.get("name")
|
||||
if row["id"] in seen_ids:
|
||||
if extra.get("stale") and COMMIT:
|
||||
nb("PATCH", f"/api/{endpoint}/{row['id']}/",
|
||||
{"extra_config": {k: v for k, v in extra.items() if k != "stale"}})
|
||||
print(f" ↩ {label}: '{nm}' stale-Markierung entfernt (wieder da)")
|
||||
continue
|
||||
if extra.get("stale"):
|
||||
continue
|
||||
if not COMMIT:
|
||||
print(f" ⚠ {label}: '{nm}' fehlt im Live-Pull → wuerde stale markiert [DRY-RUN]")
|
||||
continue
|
||||
nb("PATCH", f"/api/{endpoint}/{row['id']}/", {"extra_config": dict(extra, stale=True)})
|
||||
print(f" ⚠ {label}: '{nm}' als stale markiert (nicht mehr in CF)")
|
||||
|
||||
|
||||
RESOURCE_TYPES = [
|
||||
("Cloudflare cloudflared Tunnel", "cloud.cloudnetwork"),
|
||||
("Cloudflare WARP Connector", "cloud.cloudnetwork"),
|
||||
("Cloudflare VPC Service", "cloud.cloudservice"),
|
||||
("Cloudflare Hyperdrive", "cloud.cloudservice"),
|
||||
("Cloudflare Worker", "cloud.cloudservice"),
|
||||
("Cloudflare Access Application", "cloud.cloudservice"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
print(f"== TP44 CF-Sync ({'COMMIT' if COMMIT else 'DRY-RUN'}) CF→{NB} ==")
|
||||
networks, services, skipped = load_cf_estate()
|
||||
print(f"Live-Pull: {len(networks)} CloudNetworks · {len(services)} CloudServices"
|
||||
+ (f" · SKIP (Token-Scope): {', '.join(skipped)}" if skipped else " · alle Endpoints lesbar"))
|
||||
|
||||
print("\n[1] Manufacturer + CloudAccount")
|
||||
mfr = upsert("dcim/manufacturers", "Cloudflare", {"name": "Cloudflare"}, "Manufacturer", {})
|
||||
mfr_id = mfr["id"]
|
||||
acct = upsert("cloud/cloud-accounts", "Cloudflare",
|
||||
{"name": "Cloudflare", "account_number": CF_ACCT, "provider": mfr_id},
|
||||
"CloudAccount", {})
|
||||
acct_id = acct["id"]
|
||||
|
||||
print("\n[2] CloudResourceTypes")
|
||||
rt_ids = {}
|
||||
for name, ct in RESOURCE_TYPES:
|
||||
rt = upsert("cloud/cloud-resource-types", name,
|
||||
{"name": name, "provider": mfr_id, "content_types": [ct]}, "ResourceType", {})
|
||||
rt_ids[name] = rt["id"]
|
||||
|
||||
seen = {}
|
||||
|
||||
print("\n[3] CloudNetworks")
|
||||
for name, rt_name, desc, extra, mk, mv in networks:
|
||||
upsert("cloud/cloud-networks", name,
|
||||
{"name": name, "cloud_resource_type": rt_ids.get(rt_name), "cloud_account": acct_id,
|
||||
"description": desc, "extra_config": extra}, "CloudNetwork", seen,
|
||||
acct_id=acct_id, match_key=mk, match_val=mv)
|
||||
|
||||
print("\n[4] CloudServices")
|
||||
for name, rt_name, desc, extra, mk, mv in services:
|
||||
upsert("cloud/cloud-services", name,
|
||||
{"name": name, "cloud_resource_type": rt_ids.get(rt_name), "cloud_account": acct_id,
|
||||
"description": desc, "extra_config": extra}, "CloudService", seen,
|
||||
acct_id=acct_id, match_key=mk, match_val=mv)
|
||||
|
||||
print("\n[5] Verwaisung (stale-Markierung; nur Endpoints, die gelesen wurden)")
|
||||
# Nur markieren, wenn der zugehoerige Endpoint erreichbar war — sonst wuerde ein
|
||||
# fehlender Token-Scope echte Objekte faelschlich als verwaist markieren.
|
||||
if "access/apps" not in skipped and "hyperdrive/configs" not in skipped \
|
||||
and "connectivity/directory/services" not in skipped:
|
||||
mark_stale("cloud/cloud-services", acct_id, seen.get("cloud/cloud-services", set()), "CloudService")
|
||||
else:
|
||||
print(" · CloudService-Verwaisung uebersprungen (Token-Scope unvollstaendig — kein sicherer Vergleich)")
|
||||
mark_stale("cloud/cloud-networks", acct_id, seen.get("cloud/cloud-networks", set()), "CloudNetwork")
|
||||
|
||||
print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN — nichts geschrieben'}) ==")
|
||||
if skipped:
|
||||
print(f"Hinweis: {', '.join(skipped)} nicht lesbar → Token-Scope erweitern, dann syncen sie mit.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
119
nautobot/tp44-ipam-prefixes.py
Normal file
119
nautobot/tp44-ipam-prefixes.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP44 Etappe 2 Teil A — die zwei fehlenden IPAM-Prefixe der CF-CloudNetworks anlegen.
|
||||
|
||||
Zwei CloudNetworks (tp44-cf-estate-seed.py) tragen eine Route, die der Seeder als
|
||||
cloud-network-prefix-assignment verknuepfen wuerde -- aber nur, wenn der Prefix in
|
||||
IPAM existiert (dort Schritt [7], sonst "~ Prefix ... nicht in IPAM"):
|
||||
|
||||
10.40.0.0/16 -> bf-core-mesh (WARP-Connector auf crs326-core, TP16.1) [container]
|
||||
10.101.0.0/24 -> fm.emsenhuber.net (Standort FM, cloudflared-Tunnel) [network]
|
||||
|
||||
Idempotenter get-or-create (Dry-Run default). Nach dem --commit den Seeder erneut
|
||||
laufen lassen -- er haengt die zwei Assignments dann automatisch ein.
|
||||
|
||||
10.40.0.0/16 als 'container', weil die VLAN-/24 (10.40.10/20/30/...) aus
|
||||
ingest-baseline.py bereits als Prefixe existieren; Nautobot nistet sie
|
||||
automatisch darunter (rein hierarchisch, nicht-destruktiv).
|
||||
|
||||
Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored),
|
||||
Schluessel NAUTOBOT_SUPERUSER_API_TOKEN.
|
||||
|
||||
Aufruf:
|
||||
python3 nautobot/tp44-ipam-prefixes.py # Dry-Run (default)
|
||||
python3 nautobot/tp44-ipam-prefixes.py --commit # legt an
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
|
||||
def load_token() -> str:
|
||||
for line in YML.read_text().splitlines():
|
||||
m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden")
|
||||
|
||||
|
||||
TOKEN = load_token()
|
||||
HDRS = {"Authorization": f"Token {TOKEN}", "Accept": "application/json",
|
||||
"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def req(method: str, path: str, body=None):
|
||||
url = f"{NB}{path}"
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
r = urllib.request.Request(url, data=data, headers=HDRS, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=20) as resp:
|
||||
return resp.status, json.loads(resp.read().decode() or "{}")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode() or "{}")
|
||||
|
||||
|
||||
def find(endpoint: str, params: dict, match_key: str, match_val: str):
|
||||
q = urllib.parse.urlencode(dict(params, depth=0))
|
||||
st, d = req("GET", f"/api/{endpoint}/?{q}")
|
||||
for row in d.get("results", []):
|
||||
if row.get(match_key) == match_val:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def status_id(name: str):
|
||||
return find("extras/statuses", {"name": name}, "name", name)["id"]
|
||||
|
||||
|
||||
def namespace_id(name: str):
|
||||
return find("ipam/namespaces", {"name": name}, "name", name)["id"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Inventar
|
||||
# (prefix, type, description)
|
||||
PREFIXES = [
|
||||
("10.40.0.0/16", "container",
|
||||
"BF-Supernet; Route des CF WARP-Connectors bf-core-mesh (crs326-core, TP16.1)"),
|
||||
("10.101.0.0/24", "network",
|
||||
"Standort FM; Route des CF cloudflared-Tunnels fm.emsenhuber.net"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
print(f"== TP44 IPAM-Prefixe ({'COMMIT' if COMMIT else 'DRY-RUN'}) @ {NB} ==")
|
||||
st_active = status_id("Active")
|
||||
ns_global = namespace_id("Global")
|
||||
|
||||
for prefix, ptype, desc in PREFIXES:
|
||||
existing = find("ipam/prefixes", {"prefix": prefix}, "prefix", prefix)
|
||||
if existing:
|
||||
print(f" = Prefix: '{prefix}' existiert ({existing['id']})")
|
||||
continue
|
||||
if not COMMIT:
|
||||
print(f" + Prefix: '{prefix}' [{ptype}] [DRY-RUN]")
|
||||
continue
|
||||
payload = {"prefix": prefix, "status": st_active, "namespace": ns_global,
|
||||
"type": ptype, "description": desc}
|
||||
st, d = req("POST", "/api/ipam/prefixes/", payload)
|
||||
if st in (200, 201):
|
||||
print(f" + Prefix: '{prefix}' [{ptype}] angelegt ({d.get('id')})")
|
||||
else:
|
||||
print(f" ! Prefix: '{prefix}' FEHLER {st}: {json.dumps(d)[:300]}")
|
||||
|
||||
print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN — nichts geschrieben'}) ==")
|
||||
if not COMMIT:
|
||||
print("Danach: python3 nautobot/tp44-cf-estate-seed.py --commit (haengt die 2 Assignments ein)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
59
routeros/tp23-stufe6-phaseA-exempt-log.rsc
Normal file
59
routeros/tp23-stufe6-phaseA-exempt-log.rsc
Normal file
@@ -0,0 +1,59 @@
|
||||
# TP23 Stufe 6 — PHASE A: Substrat + Exempt/JIT-Accepts + log-only-Vorstufe.
|
||||
#
|
||||
# 🔴🔴 NUR VOR ORT. Dieser Deploy allein bricht noch nichts (kein drop/reject),
|
||||
# aber Phase B (der reject) macht .36/.37 zum DNS-SPOF ohne Fern-Rollback.
|
||||
# Runbook: docs/tp23-stufe6-bypass-sperre-runbook.md.
|
||||
#
|
||||
# STATUS: ENTWURF, freigegeben 2026-09-11, NOCH NICHT DEPLOYT (Christoph war remote).
|
||||
#
|
||||
# ┌─ LIVE-VERIFY VOR DEM DEPLOY (nicht raten — MikroMCP war beim Schreiben tot) ─┐
|
||||
# │ (1) ANKER <<EGRESS-ACCEPT>>: die forward-Regel, die HEUTE LAN->WAN-DNS zu │
|
||||
# │ 8.8.8.8:53 durchlaesst (der generische Internet-Egress-Accept). Meine │
|
||||
# │ Regeln muessen DAVOR stehen, sonst greift der Egress-Accept zuerst und │
|
||||
# │ die log-only-Regel zaehlt 0. Anker aus der Live-Kette holen: │
|
||||
# │ /ip firewall filter print where chain=forward │
|
||||
# │ und unten das place-before-Kommentar ersetzen. Faellt externer DNS heute │
|
||||
# │ schon in deny-by-default (TVs mit 8.8.8.8 loesen JETZT nicht auf), ist │
|
||||
# │ Stufe 6 gegenstandslos — dann Runbook neu bewerten. │
|
||||
# │ (2) EXEMPT-IPs: dev-MacBook ist dual-homed. Beide Beine gegen die Realitaet │
|
||||
# │ pruefen (en5/en0 koennen per DHCP gewechselt haben): │
|
||||
# │ /ip dhcp-server lease print where comment~"macbook" │
|
||||
# │ und mit der AGH-Seite (clients.persistent 'dns-bypass-exempt') abgleichen.│
|
||||
# └───────────────────────────────────────────────────────────────────────────┘
|
||||
#
|
||||
# Weg (KEIN mikromcp-Direkt-Write — ip-firewall-filter steht unter Drift-Kontrolle):
|
||||
# Backup davor: create_backup routerId=crs326-core (core-pre-tp23-s6-<datum>.backup)
|
||||
# Deploy: python3 nautobot/gc-manual-plan.py --device crs326-core \
|
||||
# --commands-file routeros/tp23-stufe6-phaseA-exempt-log.rsc \
|
||||
# --change-id TP23-S6-PhaseA --deploy
|
||||
# log-only als befristeter Versuch fuehren (NICHT als Versehen lesbar machen):
|
||||
# python3 nautobot/gc-transaktion.py (Transaktion 'TP23-S6-would-drop-dns')
|
||||
# Soll-Sync (Substrat + Accepts sind Dauerzustand -> ins intended):
|
||||
# python3 nautobot/ingest-core-firewall.py --apply
|
||||
# python3 nautobot/gc-configplan.py --refresh-compliance
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 0) Substrat: erlaubte Resolver (dst), permanente Exempts (src), leere JIT-Liste.
|
||||
:do { :if ([:len [/ip firewall address-list find list="dns-resolvers" address="10.40.20.36"]] = 0) do={ /ip firewall address-list add list=dns-resolvers address=10.40.20.36 comment="TP23-S6: AGH agh-bf1" } } on-error={ :log info "tp23s6A: 00a warf" }
|
||||
:do { :if ([:len [/ip firewall address-list find list="dns-resolvers" address="10.40.20.37"]] = 0) do={ /ip firewall address-list add list=dns-resolvers address=10.40.20.37 comment="TP23-S6: AGH agh-bf2" } } on-error={ :log info "tp23s6A: 00b warf" }
|
||||
:do { :if ([:len [/ip firewall address-list find list="dns-bypass-exempt" address="10.40.10.183"]] = 0) do={ /ip firewall address-list add list=dns-bypass-exempt address=10.40.10.183 comment="TP23-S6: dev-MacBook en5 (VERIFY)" } } on-error={ :log info "tp23s6A: 00c warf" }
|
||||
:do { :if ([:len [/ip firewall address-list find list="dns-bypass-exempt" address="10.40.10.82"]] = 0) do={ /ip firewall address-list add list=dns-bypass-exempt address=10.40.10.82 comment="TP23-S6: dev-MacBook en0 (VERIFY)" } } on-error={ :log info "tp23s6A: 00d warf" }
|
||||
|
||||
# 1) Exempt-Accepts. dev-MacBook + Agent-Toolchain behalten ihren DNS-Ausweg.
|
||||
# MUSS vor der log-only-Regel stehen (action=accept terminiert -> Exempt-Traffic
|
||||
# erreicht die Log-Regel nie und taucht im WOULD-DROP-Log korrekt NICHT auf).
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: dns-exempt udp"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=dns-bypass-exempt protocol=udp dst-port=53,853 comment="TP23-S6: dns-exempt udp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 01a warf" }
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: dns-exempt tcp"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=dns-bypass-exempt protocol=tcp dst-port=53,853 comment="TP23-S6: dns-exempt tcp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 01b warf" }
|
||||
|
||||
# 2) JIT-Accepts. Leere, selbstverfallende Liste (Muster jit-admin, tp5-etappe4):
|
||||
# /ip firewall address-list add list=jit-dns-bypass address=<ip> timeout=1h
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: dns-jit udp"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=jit-dns-bypass protocol=udp dst-port=53,853 comment="TP23-S6: dns-jit udp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 02a warf" }
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: dns-jit tcp"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=jit-dns-bypass protocol=tcp dst-port=53,853 comment="TP23-S6: dns-jit tcp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 02b warf" }
|
||||
|
||||
# 3) log-only-Vorstufe. Loggt LAN->extern DNS, das NICHT zu einem erlaubten
|
||||
# Resolver geht. dst-address-list=!dns-resolvers negiert die Liste. Mehrere
|
||||
# Tage lesen (Kollektor 10.40.20.18:514), dann Phase B. NUR 53/853-Zeilen zaehlen.
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: WOULD-DROP-DNS udp"]] = 0) do={ /ip firewall filter add chain=forward action=log log-prefix="TP5-WOULD-DROP-DNS" protocol=udp dst-port=53,853 dst-address-list=!dns-resolvers comment="TP23-S6: WOULD-DROP-DNS udp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 03a warf" }
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: WOULD-DROP-DNS tcp"]] = 0) do={ /ip firewall filter add chain=forward action=log log-prefix="TP5-WOULD-DROP-DNS" protocol=tcp dst-port=53,853 dst-address-list=!dns-resolvers comment="TP23-S6: WOULD-DROP-DNS tcp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6A: 03b warf" }
|
||||
|
||||
:log info ("tp23s6A: ENDE exempt-udp=" . [:tostr [:len [/ip firewall filter find comment="TP23-S6: dns-exempt udp"]]] . " log-udp=" . [:tostr [:len [/ip firewall filter find comment="TP23-S6: WOULD-DROP-DNS udp"]]] . " resolvers=" . [:tostr [:len [/ip firewall address-list find list="dns-resolvers"]]] . " exempt=" . [:tostr [:len [/ip firewall address-list find list="dns-bypass-exempt"]]])
|
||||
40
routeros/tp23-stufe6-phaseB-reject.rsc
Normal file
40
routeros/tp23-stufe6-phaseB-reject.rsc
Normal file
@@ -0,0 +1,40 @@
|
||||
# TP23 Stufe 6 — PHASE B: der scharfe Schnitt (reject) + Auto-Revert.
|
||||
#
|
||||
# 🔴🔴 NUR VOR ORT, mit offener Konsole. Erst NACHDEM das WOULD-DROP-DNS-Log
|
||||
# (Phase A) mehrere Tage ruhig/verstanden ist und jeder externe Resolver
|
||||
# entschieden wurde (Reservierung auf AGH / Exempt / bewusst brechen lassen).
|
||||
# AB HIER ist .36/.37 ein DNS-SPOF ohne Fern-Rollback (Runbook, Gate).
|
||||
#
|
||||
# STATUS: ENTWURF, freigegeben 2026-09-11, NOCH NICHT DEPLOYT.
|
||||
#
|
||||
# WARUM reject STATT drop: reject icmp-port-unreachable laesst Clients schneller
|
||||
# auf den naechsten (erlaubten) Resolver fallen, statt in einen Timeout zu laufen.
|
||||
#
|
||||
# WARUM die Log-Regel STEHEN BLEIBT: action=log terminiert NICHT — das Paket wird
|
||||
# geloggt und faellt dann in die reject-Regel darunter. Gleiche Doktrin wie
|
||||
# tp5-etappe3-drop (Log VOR Drop), der Kollektor zeigt weiter, was reject frisst.
|
||||
#
|
||||
# Weg (wie Phase A, ueber Nautobot GC):
|
||||
# Deploy: python3 nautobot/gc-manual-plan.py --device crs326-core \
|
||||
# --commands-file routeros/tp23-stufe6-phaseB-reject.rsc \
|
||||
# --change-id TP23-S6-PhaseB --deploy
|
||||
# Danach (reject ist jetzt Dauerzustand -> Soll, log-only-Transaktion schliessen):
|
||||
# python3 nautobot/ingest-core-firewall.py --apply
|
||||
# python3 nautobot/gc-configplan.py --refresh-compliance # Ziel gruen
|
||||
#
|
||||
# Rollback sofort (Vor-Ort-Konsole): /ip firewall filter set [find comment~"TP23-S6: reject"] disabled=yes
|
||||
# -> Clients koennen sofort wieder externe Resolver nutzen. Bzw. Auto-Revert unten.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 0) Auto-Revert ZUERST — deaktiviert die reject-Regeln nach 10m, falls nicht
|
||||
# bestaetigt (analog tp5-etappe3/4). Nach der Abnahme abbestellen:
|
||||
# /system scheduler remove [find name="tp23s6autorevert"]
|
||||
:do { :if ([:len [/system scheduler find name="tp23s6autorevert"]] = 0) do={ /system scheduler add name="tp23s6autorevert" interval=10m on-event="/ip firewall filter set [find comment~\"TP23-S6: reject\"] disabled=yes; /system scheduler remove [find name=\"tp23s6autorevert\"]; :log warning \"tp23s6: AUTO-REVERT hat die DNS-reject-Regeln abgeschaltet\"" } } on-error={ :log info "tp23s6B: 00 scheduler warf" }
|
||||
|
||||
# 1) reject. Gleiche Match-Tupel wie die WOULD-DROP-DNS-Log-Regeln, direkt
|
||||
# dahinter (place-before denselben Egress-Anker -> landet nach den Log-Regeln).
|
||||
# <<EGRESS-ACCEPT>>: derselbe live-verifizierte Anker wie in Phase A.
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: reject dns udp"]] = 0) do={ /ip firewall filter add chain=forward action=reject reject-with=icmp-port-unreachable protocol=udp dst-port=53,853 dst-address-list=!dns-resolvers comment="TP23-S6: reject dns udp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6B: 01a warf" }
|
||||
:do { :if ([:len [/ip firewall filter find comment="TP23-S6: reject dns tcp"]] = 0) do={ /ip firewall filter add chain=forward action=reject reject-with=icmp-port-unreachable protocol=tcp dst-port=53,853 dst-address-list=!dns-resolvers comment="TP23-S6: reject dns tcp" place-before=[/ip firewall filter find comment="<<EGRESS-ACCEPT — LIVE VERIFY>>"] } } on-error={ :log info "tp23s6B: 01b warf" }
|
||||
|
||||
:log info ("tp23s6B: ENDE reject-udp=" . [:tostr [:len [/ip firewall filter find comment="TP23-S6: reject dns udp"]]] . " reject-tcp=" . [:tostr [:len [/ip firewall filter find comment="TP23-S6: reject dns tcp"]]] . " autorevert=" . [:tostr [:len [/system scheduler find name="tp23s6autorevert"]]])
|
||||
@@ -22,8 +22,13 @@
|
||||
/interface wifi datapath remove [find]
|
||||
/interface wifi channel remove [find]
|
||||
# Werks-Inline-Overrides auf den Master-Radios loeschen, sonst gewinnen sie gegen den Manager.
|
||||
/interface wifi set [find name=wifi1] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds
|
||||
/interface wifi set [find name=wifi2] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds
|
||||
# 🔴 `!security.ft` MUSS mit dabei sein: die Werks-cAP-Radios kommen mit ft=yes; ohne diesen
|
||||
# unset bleibt er stehen und sendet dirtyvader-main mit FT, waehrend ap1/ap2 (ft geerbt=no)
|
||||
# es nicht tun -> gemischtes FT-ESS, Strong-Signal-Abbrueche NUR auf ap3. Genau das war der
|
||||
# Fall am 2026-09-04: die Abnahme unten zaehlt nur BENANNTE security-Objekte (=0) und sieht
|
||||
# den Inline-Wert auf wifi1/wifi2 nicht. Nachtraeglich behoben 2026-09-04.
|
||||
/interface wifi set [find name=wifi1] !configuration.ssid !security.authentication-types !security.passphrase !security.ft !security.ft-over-ds
|
||||
/interface wifi set [find name=wifi2] !configuration.ssid !security.authentication-types !security.passphrase !security.ft !security.ft-over-ds
|
||||
|
||||
# --- (2) L3-Reste des Werks-Routers weg (jetzt gefahrlos: die WLAN-Sitzung ist ohnehin tot) ---
|
||||
/ip dhcp-server remove [find]
|
||||
|
||||
Reference in New Issue
Block a user