Initial import from bf-network-r6.opencode — opencode migration
This commit is contained in:
77
docs/nas-vlan20/RUNBOOK.md
Normal file
77
docs/nas-vlan20/RUNBOOK.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# NAS (DS923) → VLAN20 Docker-Migration — Runbook
|
||||
|
||||
> Stand: 2026-07-09. Ziel: NAS-NIC ist auf VLAN20; **ein** Docker-Macvlan `vlan20`
|
||||
> (10.40.20.0/24, gw .1, parent `ovs_eth0`), **alle** Container darauf, `default_external`
|
||||
> (10.40.41.0/24) weg. Mapping-Regel: **letztes Oktett behalten**, Subnetz .41→.20.
|
||||
|
||||
## Root-Cause der Dauer-Fehler (diagnostiziert)
|
||||
1. `vlan20` war ein **Config-Only-Macvlan** (`ConfigOnly:true`, `Driver:null`) — Portainers
|
||||
zweistufiger Assistent, nur Schritt 1. An Config-Only-Netze kann Docker **keine** Container
|
||||
hängen → jeder Container auf `vlan20` blieb `created`/`exited`. **(gelöscht am 2026-07-09)**
|
||||
2. Macvlan belegt seinen Parent (`ovs_eth0`) **exklusiv** → `vlan20` kann erst existieren,
|
||||
wenn `default_external` weg ist. Deshalb ist „alle LANs raus" technisch zwingend.
|
||||
3. `default_internal` (172.18er Bridge) **existiert nicht mehr** (404); der Range ist von
|
||||
`gitea_default` (172.18.0.0/16) belegt. Darum sind Multi-Netz-Stacks doppelt kaputt.
|
||||
→ Lösung „nur vlan20": interne Bridge wird nicht neu gebaut, jeder Service nur auf `vlan20`
|
||||
(Container am selben Macvlan erreichen sich gegenseitig; DB-/Sidecar-Refs waren fast alle
|
||||
auskommentiert).
|
||||
|
||||
## IP-Mapping (default_external .41.X → vlan20 .20.X)
|
||||
| Container | Stack (ep14) | alt | neu |
|
||||
|---|---|---|---|
|
||||
| omada-controller | omada (198) | 10.40.41.175 | 10.40.20.175 |
|
||||
| prometheus | oc-stack (112) | .41.151 | .20.151 |
|
||||
| grafana | oc-stack (112) | .41.109 | .20.109 |
|
||||
| alertmanager | oc-stack (112) | .41.153 | .20.153 |
|
||||
| node-exporter | oc-stack (112) | .41.154 | .20.154 |
|
||||
| iob_slave | iob_slave (64) | .41.33 | .20.33 |
|
||||
| vaultwarden | vaultwarden (73) | .41.127 | .20.127 |
|
||||
| oc-tzcharlie-gateway | oc-charlie (158) | .41.162 | .20.162 |
|
||||
| oc-johnny2-gateway | oc-johnny2 (150) | .41.161 | .20.161 |
|
||||
| oc-qdrant-central | oc-central (160) | .41.159 | .20.159 |
|
||||
| dashboard-web | nginx (101) | .41.148 | .20.148 |
|
||||
| hms-mqtt | default (72) | .41.124 | .20.124 |
|
||||
| gitea | gitea (111) | .41.157 | .20.157 |
|
||||
| drawio-server | **standalone** | .41/.20.139 | .20.139 |
|
||||
| pve-mcp | **standalone** | .41.173 | .20.173 |
|
||||
|
||||
Unangetastet: `cloudflared` (host-Netz), `portainer_agent` (bridge).
|
||||
Transformierte Compose-Files: `docs/nas-vlan20/stacks/*.yml` (Netz-Block auf `vlan20`,
|
||||
IPs .41→.20, `dns:`→10.40.20.1). Standalone-Specs: `docs/nas-vlan20/inspect_*.json`.
|
||||
|
||||
## Phase A — Netz-Cutover (irreversibel für laufende Container-States; Volumes bleiben)
|
||||
Container-Prozesse werden neu gestartet, **Volumes werden NICHT angefasst** (`v=false`).
|
||||
Skript: `docs/nas-vlan20/phaseA-...py` (bzw. /tmp/nasmig/phaseA.py). Es:
|
||||
1. entfernt alle Container auf `default_external` + die 2 auf dem toten `vlan20`,
|
||||
2. löscht `default_external`,
|
||||
3. legt `vlan20` an: `-d macvlan --subnet 10.40.20.0/24 --gateway 10.40.20.1 -o parent=ovs_eth0`.
|
||||
|
||||
Manuell (falls per Hand): in Portainer → NAS(ep14) → Networks → `default_external`:
|
||||
alle Container disconnecten/stoppen → Netz löschen → „Add network": macvlan, parent `ovs_eth0`,
|
||||
Subnet 10.40.20.0/24, GW 10.40.20.1, Name `vlan20`.
|
||||
|
||||
## Phase B — Redeploy (macht Christoph selbst in Portainer)
|
||||
Je Stack den Inhalt aus `docs/nas-vlan20/stacks/<id>-*.yml` einspielen → Update/Redeploy.
|
||||
Standalone neu bauen (drawio-server, pve-mcp) siehe inspect_*.json (image/env/binds), Netz `vlan20`.
|
||||
|
||||
## Post-Migration (erst NACH Redeploy, wenn Container real auf .20.x)
|
||||
- **ap1 DNS (bf.lan A-Records):** alle gewanderten Hosts .41.X→.20.X nachziehen (MikroMCP `manage_dns_entry`).
|
||||
- **CF-Tunnel-Ingress** (`70d6a4cf…`, remote-managed): NAS-Origins .41→.20 repointen —
|
||||
hass(→schon .20.105?), gitea .41.157→.20.157, grafana .109, pve-mcp .173, vw .127, ds-bf, portainer …
|
||||
SSoT: `docs/cf-tunnel-mapping.md`. Vorher pve→VLAN20-Reachability smoke-testen.
|
||||
- **omada-mcp** (`~/.claude.json` + Stack ep2/ep14): `OMADA_URL` .41.175→.20.175.
|
||||
- ⚠️ **omada-controller Inform-Adresse:** adoptierter Omada-Switch sw2 kennt Controller unter
|
||||
.41.175 → nach Umzug im Controller die Controller/Inform-Adresse auf .20.175 setzen, sonst sw2
|
||||
„Disconnected". UI wandert auf https://10.40.20.175:8043.
|
||||
- **CLAUDE.md / memory:** .41→.20 Referenzen nachziehen.
|
||||
|
||||
## CF-Access-Profil für Claude (Thread „ich komm schwer durch den Tunnel")
|
||||
- `*.emsenhuber.net` = CF-Access-App. Policies: `christoph` (E-Mail-Login), `bf4`
|
||||
(non_identity, **IP-Bypass** = BF-Egress → darum on-LAN offen), **`claude-mcp`
|
||||
(allow, service_token)** = mein Profil, **policy-seitig bereits vorhanden**.
|
||||
- Fehlt nur das **Secret** des `claude-mcp`-Tokens (liegt nirgends lokal). Ohne
|
||||
`CF-Access-Client-Id/Secret`-Header komme ich nur über den BF-IP-Bypass durch.
|
||||
- **Offen (braucht Freigabe):** Service-Token `claude-mcp` rotieren → neues Secret →
|
||||
ablegen in `mcp/cloudflare/.env` (chmod 600) als `CF_ACCESS_CLIENT_ID` / `CF_ACCESS_CLIENT_SECRET`.
|
||||
Rotieren invalidiert das alte Secret (das aber ohnehin verloren ist).
|
||||
- `hass, vw`-App = `bypass everyone` → hass/vw sind **öffentlich** (nur HA-Login davor). Separat bewerten.
|
||||
102
docs/nas-vlan20/handoff/README.md
Normal file
102
docs/nas-vlan20/handoff/README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# TP14 — Übergabestatements für neue Sessions
|
||||
|
||||
Steuerung: `docs/teilprojekte.md` → **TP14**. Runbook: `docs/nas-vlan20/RUNBOOK.md`.
|
||||
Reihenfolge: **A → B → C → (D jederzeit nach Token-Scope) → E zuletzt.**
|
||||
Entscheidung: **sequenzielle Sessions, keine parallelen Branches** (geteilte Live-Infra: 1× NAS,
|
||||
1× pve-LXC, 1× CF, 1× ap1 → Branches isolieren nur Repo-Files, nicht Infra-State → Kollisionsrisiko).
|
||||
|
||||
Portainer-API `https://10.40.41.126:9443` (nach TP14-Session-B → `.20.126`), Key `mcp/portainer/.env`.
|
||||
Transform-Regeln + Scratch-Skripte: Runbook §„Transform-Regeln". Stack-Files (mit Secrets, gitignored):
|
||||
`docs/nas-vlan20/stacks/`. Mapping-Regel durchgehend: **letztes Oktett behalten, `.41→.20`.**
|
||||
|
||||
---
|
||||
|
||||
## TP14.1 — Session A: NAS fertigstellen ✅ DURCH (2026-07-10, außer Omada-Inform)
|
||||
**Ergebnis:** alle ep14-Container laufen auf vlan20/.20.x (prometheus .151, node-exporter .154,
|
||||
grafana .109, hms-mqtt .124, dashboard-web .148, qdrant .159, gitea .157 [DB .20.23 ✓],
|
||||
drawio .139 [intern], oc-tzcharlie .162+cf, oc-johnny2 .161+cf). pve-mcp NEU als Stack id 202
|
||||
`.20.173` (⚠️ StreamableHTTP am **Root `/`**, nicht `/sse`); `~/.claude.json` `mcpServers.pve-mcp`
|
||||
neu = `{type:http,url:http://10.40.20.173:3000/}`. Tunnel-Smoke gitea/grafana/vw = 200/302/200.
|
||||
**Rest-/Nacharbeiten:** (a) ⏸️ **Omada-Inform-Adresse VERTAGT** (2026-07-10): Controller-UI-Login an
|
||||
`.20.175` schlägt fehl → blockiert, verschoben. sw2 „Disconnected" aber switcht weiter (management-only,
|
||||
unkritisch). Vor Recovery zuerst **Controller-Login klären** (Passwort-Reset, Creds nicht lokal). (b) ⚠️ alertmanager .153 crash-loopt auf
|
||||
**pre-existing** Config-Bug (`${TELEGRAM_CHAT_ID}`-Literal), nicht migrationsbedingt. (c) ⚠️ openclaw
|
||||
shared-DB `.41.122:3306` down (pre-existing). (d) Merke: `OLLAMA_BASE_URL=.41.167`=Mac-mini bleibt
|
||||
`.41` — **kein Blanket-Env-.41→.20**. Helper: `/private/tmp/nasmig/{redeploy.py,pt.py,omada.py}`.
|
||||
**Omada-Inform-Fix ⏸️ VERTAGT (2026-07-10):** Controller-UI-Login an `https://10.40.20.175:8043`
|
||||
klappt nicht → erst **Login/Passwort-Recovery** klären (Creds nicht lokal). Danach: UI → Settings →
|
||||
Controller → „Controller Hostname/IP" = `10.40.20.175`; dann sw2 forget + re-adopt (L3-Discovery).
|
||||
Kein Auto-Fix: Open API kein Inform-Setting, kein Controller-Admin-Login lokal, sw2 = statische IP (kein DHCP-138).
|
||||
|
||||
<details><summary>Original-Aufgabe (erledigt)</summary>
|
||||
|
||||
Kontext: NAS(ep14) ist auf `vlan20`-Macvlan migriert (DSM `.20.10`). Container teils exited.
|
||||
Aufgabe:
|
||||
1. Restcontainer starten/redeployen aus `docs/nas-vlan20/stacks/`: **oc-stack(112)** (prometheus/
|
||||
alertmanager/node-exporter), **default(72)** (hms-mqtt), **nginx(101)** (dashboard-web),
|
||||
**oc-central(160)**, **oc-charlie(158)**, **oc-johnny2(150)**.
|
||||
2. **gitea(111)** deployen → `.20.157`; ⚠️ `GITEA__database__HOST` `.20.23` verifizieren (Rewrite-Annahme).
|
||||
3. **drawio(107)** mit `transform.py` auf vlan20 bringen (`.20.139`) → deployen.
|
||||
4. **pve-mcp NEU als Stack** (vlan20-only): Spec `docs/nas-vlan20/inspect_pve-mcp.json` — image
|
||||
`ghcr.io/samik081/mcp-pve:latest`, `PVE_BASE_URL=https://10.40.41.100:8006`, `MCP_PORT=3000`,
|
||||
`MCP_HOST=0.0.0.0`, `MCP_TRANSPORT=http`; Netz `vlan20` `ipv4_address: 10.40.20.173`. Danach
|
||||
`~/.claude.json` pve-mcp-URL `.41.173→.20.173`.
|
||||
5. **omada-controller Inform-Adresse** (UI `https://10.40.20.175:8043`) → `10.40.20.175`, sonst sw2 down.
|
||||
Verifikation: `pt.py containers` alle `running`/`.20.x`; von `.41`-Host (gw ap1) `curl`/`ping` auf `.20`-IP;
|
||||
Tunnel `curl -m8 -w %{http_code} https://<host>.emsenhuber.net/` → 200/302.
|
||||
</details>
|
||||
|
||||
## TP14.2 — Session B: pve-Docker-LXC (.126) → VLAN20 ✅ DURCH (2026-07-10)
|
||||
**Ergebnis:** LXC 400 (`docker`) net0 `tag=20 / 10.40.20.126/24 / gw .20.1`; Docker `vlan20`=ipvlan
|
||||
**`parent=veth20`** (⚠️ in-Container-Iface heißt jetzt `veth20`, nicht eth0 — UI-Namensfeld). Container:
|
||||
omada-mcp `.20.176` (OMADA_BASE_URL→`.20.175`), adguard_bf1 `.20.16`, adguard_fm2 `.20.17` — alle running/reachable.
|
||||
CF-Connector `db713b30` jetzt VLAN20 → **#1033-Zielbild gelöst** (Tunnel healthy; portainer/gitea/vw 200).
|
||||
Refs nachgezogen: `mcp/portainer/.env`, `~/.claude.json` omada-mcp, CLAUDE.md, ip-plan, cf-tunnel-mapping.
|
||||
**⚠️ Gotcha:** Proxmox-Token `claude@pam!claude` hat **kein `VM.Config.Network`** → Retag muss Christoph
|
||||
(UI) machen; Reboot danach via API (`VM.PowerMgmt` vorhanden). Transf. Files: `stacks/{195-adguard,200-omada-mcp}.yml`.
|
||||
|
||||
<details><summary>Original-Aufgabe (erledigt)</summary>
|
||||
|
||||
Kontext: pve-LXC(ep2) hängt auf `.41`, Netz `default_external` = **ipvlan** parent `eth0`:
|
||||
omada-mcp `.176`, adguard_bf1 `.16`, adguard_fm2 `.17`. **Portainer-Server + aktiver CF-Connector laufen
|
||||
hier.** ⚠️ Reihenfolge kritisch.
|
||||
Aufgabe:
|
||||
1. ep2-Stacks (`adguard`, `omada-mcp`) transformieren `.41→.20` (omada-mcp `OMADA_URL` `.41.175→.20.175`).
|
||||
2. Portainer-Zugangspfad für den Move sichern (URL wandert auf `https://10.40.20.126:9443`).
|
||||
3. LXC in Proxmox auf VLAN20 taggen + IP `.126→.20.126`.
|
||||
4. `default_external`(ipvlan) weg → `vlan20`(ipvlan) neu: `-d ipvlan --subnet 10.40.20.0/24
|
||||
--gateway 10.40.20.1 -o parent=eth0`. Container erst entfernen (`v=false`), dann Netz, dann Stacks redeploy.
|
||||
5. `mcp/portainer/.env` + `PORTAINER_URL` + CF-Ingress (portainer `.20.126`) nachziehen.
|
||||
Ergebnis: CF-Connector in VLAN20 = **#1033-Zielbild** (TP11-Track-B/C erledigt). Verifikation: Portainer
|
||||
unter neuer URL, ep2-Container `.20.x`, Tunnel healthy, pve-lokale Origins (pve-Host `.41.100`) noch erreichbar.
|
||||
</details>
|
||||
|
||||
## TP14.3 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep
|
||||
Aufgabe:
|
||||
1. `transform.py` auf dormante ep14-Stacks: core(43), openclaw(92), openclaw2(97), oc20260307(119),
|
||||
openspeedtest(74), speedtest_tracker(80), technikscheiss(42) → `.new`-Files (nicht zwingend deployen;
|
||||
42/43 waren `default_internal`→wird vlan20-only).
|
||||
2. Referenz-Sweep `.41→.20` für gewanderte Hosts: **ap1 bf.lan-DNS** (MikroMCP `manage_dns_entry`,
|
||||
ap1-Mgmt via `10.40.10.1` — s. Memory `ref_ap1_mgmt_access_vlan10`), **CLAUDE.md**, **`~/.claude.json`**
|
||||
(omada-mcp `.20.176`, pve-mcp `.20.173`), **`docs/ip-plan.md`**, **CF-Ingress-Rest**. SSoT `docs/cf-tunnel-mapping.md`.
|
||||
|
||||
## TP14.4 — Session D: CF-Access-Profil für Claude (nicht cutoff-kritisch)
|
||||
Kontext: `*.emsenhuber.net`-Access-App hat Policy `claude-mcp` (service_token) bereits; Secret fehlt lokal.
|
||||
CF-Token „claude code mcp" hat jetzt **`Access: Service Tokens: Write`+`Read`** (User bestätigt 2026-07-10).
|
||||
Aufgabe: `claude-mcp` rotieren (`POST /accounts/{acct}/access/service_tokens/{id}/rotate`) → `CF_ACCESS_CLIENT_ID`/
|
||||
`CF_ACCESS_CLIENT_SECRET` in `mcp/cloudflare/.env` (chmod600) → dokumentieren. Details: Memory `ref-cf-access-claude-profile`.
|
||||
|
||||
## TP14.5 — Session E: Freigabe-Gate → TP5-Firewall (ZULETZT)
|
||||
Erst wenn ALLE Origins/Connectoren in VLAN20 (nach A+B+C). Least-Privilege je VLAN; Deny-by-default fertigziehen.
|
||||
|
||||
---
|
||||
|
||||
## Kritische Gotchas (gelten für alle Sessions)
|
||||
- Macvlan/ipvlan-Parent **exklusiv** → altes `default_external` MUSS weg, bevor `vlan20` anlegbar.
|
||||
- **Host↔eigene-Macvlan-Isolation:** ein Connector auf demselben Host erreicht dessen Macvlan-Container NICHT
|
||||
→ Connector muss auf anderem Host in VLAN20 laufen (darum pve-LXC → VLAN20).
|
||||
- **Portainer lebt im pve-LXC** → dessen Move ändert die Mgmt-URL; Zugang vorher sichern.
|
||||
- **Container-Removal unkritisch** (Stack-Def + Ownership bleiben); **Recreate via API** macht admin-owned →
|
||||
besser via Stack-Redeploy (bleibt User-owned). `v=false` = Volumes bleiben.
|
||||
- Portainer-Macvlan-Assistent legt bei nur Schritt 1 ein **Config-Only-Netz** an (`Driver:null`) → echtes
|
||||
Netz nur per API in einem Schritt anlegen.
|
||||
29
docs/nas-vlan20/nautobot-image/Dockerfile
Normal file
29
docs/nas-vlan20/nautobot-image/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# Nautobot + Golden Config + Nornir, extending the official image in place
|
||||
# (not a satellite/side image). Reconstructed 2026-07-22 from the running
|
||||
# container's actual `docker image history` after the original build context
|
||||
# (referenced in git history as "scratch/nautobot-gc-build") turned out to
|
||||
# never have been committed to this repo -- this file replaces that dangling
|
||||
# reference as the real, versioned source of truth going forward.
|
||||
#
|
||||
# Rebuild: docker build -t bf-nautobot-golden-config:<tag> docs/nas-vlan20/nautobot-image/
|
||||
# then point docs/nas-vlan20/stacks/210-nautobot.yml's 3 `image:` lines at <tag>
|
||||
# and redeploy the Portainer stack (id 203, endpoint 14).
|
||||
# Rollback: point those 3 image lines back at ghcr.io/nautobot/nautobot:3.1.7
|
||||
# (drops the golden-config/nornir plugins; Nautobot core itself is unmodified).
|
||||
FROM ghcr.io/nautobot/nautobot:3.1.7
|
||||
|
||||
USER root
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends procps && rm -rf /var/lib/apt/lists/*
|
||||
RUN pip install --no-cache-dir \
|
||||
nautobot-golden-config \
|
||||
nautobot-plugin-nornir \
|
||||
nornir-netmiko \
|
||||
nornir-napalm \
|
||||
napalm-ros \
|
||||
librouteros \
|
||||
"setuptools<81"
|
||||
|
||||
COPY --chown=nautobot:nautobot nautobot_config.py /opt/nautobot/nautobot_config.py
|
||||
COPY --chown=nautobot:nautobot allnet_sg8316m_driver.py /opt/nautobot/allnet_sg8316m_driver.py
|
||||
|
||||
USER nautobot
|
||||
89
docs/nas-vlan20/nautobot-image/allnet_sg8316m_driver.py
Normal file
89
docs/nas-vlan20/nautobot-image/allnet_sg8316m_driver.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Custom Netmiko driver for the ALL-SG8316M (sw1) -- Telnet-only, IOS-like but
|
||||
non-standard CLI (no `terminal length`, `--More--` paging, `end` from
|
||||
config-vlan skips straight to the top level). No stock Netmiko/NAPALM driver
|
||||
matches this device.
|
||||
|
||||
NOT YET WIRED IN: this module is imported nowhere yet. The `claudecode`
|
||||
Telnet account on sw1 is currently rejected ("Incorrect User Name!!", found
|
||||
2026-07-16) so this driver is unverified against the real device. Once
|
||||
working credentials exist, activate it from nautobot_config.py:
|
||||
|
||||
import importlib
|
||||
import allnet_sg8316m_driver # noqa: F401 (registers itself on import)
|
||||
|
||||
and add to PLUGINS_CONFIG["nautobot_plugin_nornir"]["dispatcher_mapping"]:
|
||||
|
||||
"allnet_sg8316m": "allnet_sg8316m_driver.GoldenConfigAllnetSg8316m"
|
||||
|
||||
(dispatcher class still needs writing once the connection class below is
|
||||
confirmed to log in cleanly -- see project_nautobot_allnet_cudy_scope_20260716
|
||||
memory for the full status and the two netmiko gotchas that cost the most
|
||||
time: device_type must contain the literal substring "_telnet" for Netmiko to
|
||||
pick the Telnet transport at all (BaseConnection.__init__ checks this by
|
||||
string, not by class hierarchy), and `netmiko.ssh_dispatcher` at package level
|
||||
is the dispatcher *function*, not the submodule -- reach CLASS_MAPPER via
|
||||
`importlib.import_module("netmiko.ssh_dispatcher")`.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import time
|
||||
|
||||
from netmiko.cisco_base_connection import CiscoBaseConnection
|
||||
|
||||
_nm_dispatch = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
|
||||
|
||||
class AllnetSg8316mTelnet(CiscoBaseConnection):
|
||||
def session_preparation(self):
|
||||
self._test_channel_read(pattern=r"[>#]")
|
||||
self.set_base_prompt()
|
||||
self.ansi_escape_codes = True
|
||||
|
||||
def disable_paging(self, *args, **kwargs):
|
||||
# No config knob for this on this firmware; paging is handled
|
||||
# per-command in send_command_paged() below instead.
|
||||
return ""
|
||||
|
||||
def telnet_login(
|
||||
self,
|
||||
pri_prompt_terminator=r"#\s*$",
|
||||
alt_prompt_terminator=r">\s*$",
|
||||
username_pattern=r"Username",
|
||||
pwd_pattern=r"Password",
|
||||
delay_factor=1,
|
||||
max_loops=20,
|
||||
):
|
||||
return super().telnet_login(
|
||||
pri_prompt_terminator=pri_prompt_terminator,
|
||||
alt_prompt_terminator=alt_prompt_terminator,
|
||||
username_pattern=username_pattern,
|
||||
pwd_pattern=pwd_pattern,
|
||||
delay_factor=delay_factor,
|
||||
max_loops=max_loops,
|
||||
)
|
||||
|
||||
def send_command_paged(self, command_string, max_pages=200, delay=0.3):
|
||||
"""sg8316m paginates with '--More--' and has no length-0 setting;
|
||||
page through by sending a space each time it appears."""
|
||||
self.write_channel(command_string + self.RETURN)
|
||||
time.sleep(delay)
|
||||
output = ""
|
||||
for _ in range(max_pages):
|
||||
chunk = self.read_channel()
|
||||
output += chunk
|
||||
if "--More--" in chunk:
|
||||
self.write_channel(" ")
|
||||
time.sleep(delay)
|
||||
continue
|
||||
if any(p in chunk for p in ["sw1#", "sw1>"]) and "--More--" not in chunk:
|
||||
break
|
||||
if chunk == "":
|
||||
time.sleep(delay)
|
||||
return output.replace("--More--", "")
|
||||
|
||||
|
||||
# Registered under the "_telnet" suffix required by Netmiko's own transport
|
||||
# selection (see module docstring) -- registration is safe/inert on its own,
|
||||
# nothing calls ConnectHandler(device_type="allnet_sg8316m_telnet") yet.
|
||||
_nm_dispatch.CLASS_MAPPER["allnet_sg8316m_telnet"] = AllnetSg8316mTelnet
|
||||
_nm_dispatch.platforms = list(_nm_dispatch.CLASS_MAPPER.keys())
|
||||
91
docs/nas-vlan20/nautobot-image/nautobot_config.py
Normal file
91
docs/nas-vlan20/nautobot-image/nautobot_config.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from nautobot.core.settings import * # noqa F401,F403
|
||||
from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
|
||||
|
||||
if DATABASES["default"]["ENGINE"].endswith("mysql"):
|
||||
DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
|
||||
|
||||
SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "05^)=zgn3ytglmp_iv4@era^e23u3#_wtop-@yeaeazempdy^=")
|
||||
|
||||
INSTALLATION_METRICS_ENABLED = is_truthy(os.getenv("NAUTOBOT_INSTALLATION_METRICS_ENABLED", "True"))
|
||||
|
||||
# --- BF-Netzwerk additions: Golden Config + Nornir (TP21) ---
|
||||
#
|
||||
# Extends the stock config shipped in the base ghcr.io/nautobot/nautobot image
|
||||
# (unchanged above this point) instead of replacing it, so a rollback is just
|
||||
# "point the compose file's image tag back at ghcr.io/nautobot/nautobot:3.1.7".
|
||||
PLUGINS = [
|
||||
"nautobot_plugin_nornir",
|
||||
"nautobot_golden_config",
|
||||
]
|
||||
|
||||
PLUGINS_CONFIG = {
|
||||
"nautobot_plugin_nornir": {
|
||||
"nornir_settings": {
|
||||
# Per-device creds via each Device's SecretsGroup (ap1/ap2 -> "RouterOS
|
||||
# Nornir" group, access_type=Generic to match this class's default --
|
||||
# see get_device_creds()/_get_access_type_value() in nautobot_secrets.py).
|
||||
# Switched 2026-07-22 from env_vars.CredentialsEnvVars (a single global
|
||||
# NAPALM_USERNAME/PASSWORD pair for the whole fleet) so future devices
|
||||
# (CRS326 etc.) can carry their own credentials.
|
||||
"credentials": "nautobot_plugin_nornir.plugins.credentials.nautobot_secrets.CredentialsNautobotSecrets",
|
||||
"runner": {"plugin": "threaded", "options": {"num_workers": 5}},
|
||||
},
|
||||
# nautobot-plugin-nornir 2.0+ dropped PLUGINS_CONFIG["dispatcher_mapping"]
|
||||
# (it now errors on boot if present) -- per-platform dispatcher overrides
|
||||
# moved onto the Platform model itself (network_driver_mappings). The
|
||||
# ALLNET sg8316m custom driver will be wired in there once credentials
|
||||
# are sorted, not here.
|
||||
},
|
||||
"nautobot_golden_config": {
|
||||
# Backup + Intended-Config + Compliance enabled 2026-07-22 (TP21 follow-up
|
||||
# session) once a Jinja template + Git repo target existed.
|
||||
#
|
||||
# Plan + Deploy enabled 2026-08-13 (TP22 Schritt 3), after backup scrubbing
|
||||
# (ConfigReplace) and compliance were green on all four RouterOS devices.
|
||||
# Two binding governance rules apply -- see docs/teilprojekte.md TP22:
|
||||
# 1. Config Plans ONLY feature-/manual-scoped, never "full intended":
|
||||
# a full push would write the literal `passphrase="<<REDACTED>>"` to the
|
||||
# APs (kicking every WLAN client) and `private-key="<<REDACTED>>"` to the
|
||||
# core (killing the WireGuard hub). Sections containing the mask are
|
||||
# excluded from any push.
|
||||
# 2. The AP template is never pushed line-by-line to a live device: it
|
||||
# transiently sets vlan-filtering=no, which severs the vlan99 management
|
||||
# path mid-session. Full re-provisioning goes via file upload + /import.
|
||||
#
|
||||
# enable_postprocessing stays OFF on purpose: render_secrets would not fix the
|
||||
# compliance side (the stored intended would keep the un-rendered Jinja
|
||||
# expression -> permanent false positive) and pushing PSKs has no use here.
|
||||
"enable_backup": True,
|
||||
"enable_compliance": True,
|
||||
"enable_intended": True,
|
||||
"enable_sotagg": True,
|
||||
"enable_plan": True,
|
||||
"enable_deploy": True,
|
||||
"enable_postprocessing": False,
|
||||
# REQUIRED for deploy on RouterOS (found the hard way, 2026-08-13):
|
||||
# the default dispatcher resolves to NAPALM because Platform.network_driver_mappings
|
||||
# carries `napalm: ros` -- but napalm-ros is a READ-ONLY driver. Backup (get_config)
|
||||
# works, merge_config does not and fails with an empty error:
|
||||
# NornirNautobotException: E1015: The method merge_config failed ... ``
|
||||
# nornir-nautobot ships a RouterOS-specific Netmiko dispatcher whose merge_config
|
||||
# pushes via netmiko_send_config -- pin it explicitly. Key = network_driver name.
|
||||
"custom_dispatcher": {
|
||||
"mikrotik_routeros": "nornir_nautobot.plugins.tasks.dispatcher.mikrotik_routeros.NetmikoMikrotikRouteros",
|
||||
},
|
||||
"postprocessing_callables": [],
|
||||
"postprocessing_subscribed": [],
|
||||
"per_feature_bar": True,
|
||||
"per_feature_width": 0.13,
|
||||
"per_feature_height": 4,
|
||||
"default_deploy_status": "Not Approved",
|
||||
"get_custom_compliance": None,
|
||||
"sot_agg_transposer": None,
|
||||
},
|
||||
}
|
||||
|
||||
NAPALM_USERNAME = os.getenv("NAUTOBOT_NAPALM_USERNAME", "")
|
||||
NAPALM_PASSWORD = os.getenv("NAUTOBOT_NAPALM_PASSWORD", "")
|
||||
NAPALM_TIMEOUT = int(os.getenv("NAUTOBOT_NAPALM_TIMEOUT", "30"))
|
||||
Reference in New Issue
Block a user