- Migrations 010 (content_hash) + 011 (search tsvector column) - new: internal/knowledge/seed.go — knowledge seed ingest engine - new: internal/httpapi/knowledge.go — SearchKnowledge + GetEntityKnowledge - wire knowledge ingest into oikos seed pipeline - convert all 36 wiki docs + 6 investigations + 12 runbooks → seeds/knowledge.yaml - archive: knowledge/wiki/→archive/, oikos/cards/→archive/, .hermes/plans/→archive/ - delete: 9 superseded Python kernel files, ledger/, mcp/build_host_files.py - remove empty knowledge/ directory tree
247 lines
10 KiB
Markdown
247 lines
10 KiB
Markdown
# Plan: Narrow Technitium DHCP Pool to Avoid Static-IP Conflicts
|
||
|
||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
||
|
||
**Goal:** Eliminate the IP conflict risk created by the Technitium DHCP pool (`.100–.240`) overlapping with all static LXC/VM IPs (`.101–.239`).
|
||
|
||
**Architecture:** Shrink the DHCP pool range on Technitium so it only covers IPs that no static host uses. No LXC/VM IPs change. Single server-side change (Technitium API), plus documentation updates.
|
||
|
||
**Tech Stack:** Technitium DNS API (`/api/dhcp/scopes/set`), bash/curl, homelab-context repo for docs.
|
||
|
||
---
|
||
|
||
## Problem statement
|
||
|
||
The Technitium DHCP server on [CT 107](../../knowledge/wiki/containers/107-dns.md) serves `192.168.8.100–192.168.8.240`. **Every static homelab IP except hubris (`.77`) sits inside that range:**
|
||
|
||
| Host | IP | Inside pool? |
|
||
|---|---|---|
|
||
| hubris (Proxmox) | .77 | No — below `.100` |
|
||
| haos (VM 108) | .101 | YES |
|
||
| gitea (104) | .121 | YES |
|
||
| paperless (103) | .130 | YES |
|
||
| arriman (122) | .132 | YES |
|
||
| mule-images (120) | .136 | YES |
|
||
| sophia (119) | .157 | YES |
|
||
| mac-mini | .174 | YES |
|
||
| caddy (121) | .175 | YES |
|
||
| authentik (124) | .180 | YES |
|
||
| plato (126) | .190 | YES |
|
||
| zimaos (VM 100) | .195 | YES |
|
||
| nfs-export (102) | .200 | YES |
|
||
| apps (105) | .205 | YES |
|
||
| jellyfin (101) | .206 | YES |
|
||
| nextcloud (114) | .224 | YES |
|
||
| claudio-bot (123) | .230 | YES |
|
||
| elementsynapse (118) | .239 | YES |
|
||
|
||
The docs claim "Static-IP LXCs (below `.100`) are unaffected" — this is **false**. Static IPs span `.101–.239`, the DHCP pool spans `.100–.240`. They overlap almost entirely.
|
||
|
||
If the DHCP server hands out `.121/.136/.224` (or any of the above) to a new dynamic client before the static LXC claims it on boot, the static service will fail to bind and the service goes dark.
|
||
|
||
---
|
||
|
||
## Proposed approach: Shrink the pool
|
||
|
||
**Move the DHCP pool start from `.100` to `.241`**, resulting in:
|
||
- **New pool:** `192.168.8.241 – 192.168.8.254` (14 dynamic IPs)
|
||
- **Reserved:** `.100–.240` stays for static hosts, `.2` for Technitium, `.1` for gateway
|
||
- **Zero changes to any LXC, VM, Caddy, or Proxmox config.**
|
||
|
||
Why `.241–.254`:
|
||
- Highest static IP is `.239` (elementsynapse) — `.241` gives a 1-IP gap
|
||
- `.255` is the broadcast address (unusable)
|
||
- 14 IPs is plenty for truly dynamic clients (new transient containers, test VMs)
|
||
- If more are ever needed, the pool can easily be widened back down
|
||
|
||
---
|
||
|
||
## Tasks
|
||
|
||
### Task 1: Verify current Technitium DHCP scope from the API
|
||
|
||
**Objective:** Confirm the active pool range matches what's documented.
|
||
|
||
**Step 1: Log in to Technitium API and get a token**
|
||
|
||
```bash
|
||
TOKEN=$(curl -sk -X POST http://192.168.8.2:5380/api/user/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"user":"admin","pass":"'$(cat /opt/technitium/admin_password.txt)'","includeInfo":false}' \
|
||
| jq -r '.token')
|
||
echo "Token: ${TOKEN:0:10}..."
|
||
```
|
||
|
||
**Step 2: Fetch current DHCP scopes**
|
||
|
||
```bash
|
||
curl -sk "http://192.168.8.2:5380/api/dhcp/scopes/list?token=$TOKEN" | jq .
|
||
```
|
||
|
||
**Expected:** One scope named `homelab` with `startingAddress: "192.168.8.100"` and `endingAddress: "192.168.8.240"`.
|
||
|
||
**Verification:** If the scope is NOT `.100–.240`, note the actual range and adjust the plan.
|
||
|
||
---
|
||
|
||
### Task 2: Update the DHCP scope to `.241–.254`
|
||
|
||
**Objective:** Shrink the pool so it no longer overlaps static IPs.
|
||
|
||
**Step 1: Update the scope via API**
|
||
|
||
```bash
|
||
curl -sk -X POST "http://192.168.8.2:5380/api/dhcp/scopes/set?token=$TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"name": "homelab",
|
||
"startingAddress": "192.168.8.241",
|
||
"endingAddress": "192.168.8.254",
|
||
"subnetMask": "255.255.255.0",
|
||
"gatewayAddress": "192.168.8.1",
|
||
"dnsServerAddresses": ["192.168.8.2"],
|
||
"leaseTime": 86400
|
||
}'
|
||
```
|
||
|
||
**Step 2: Verify the change took effect**
|
||
|
||
```bash
|
||
curl -sk "http://192.168.8.2:5380/api/dhcp/scopes/list?token=$TOKEN" | jq '.response.scopes[0] | {startingAddress, endingAddress}'
|
||
```
|
||
|
||
**Expected:**
|
||
```json
|
||
{
|
||
"startingAddress": "192.168.8.241",
|
||
"endingAddress": "192.168.8.254"
|
||
}
|
||
```
|
||
|
||
**Pitfall:** If the API returns `{"status":"error"}`, the scope name or parameter format may differ. Inspect the response body. Technitium's API might use `rangeStart`/`rangeEnd` instead of `startingAddress`/`endingAddress`. Adjust if needed (check the full scope object from Task 1 step 2 for exact key names).
|
||
|
||
---
|
||
|
||
### Task 3: Check for active DHCP leases in the old pool that would be stranded
|
||
|
||
**Objective:** Ensure no DHCP client is currently holding an IP in `.100–.240` that it will lose when its lease expires.
|
||
|
||
**Step 1: List active DHCP leases**
|
||
|
||
```bash
|
||
curl -sk "http://192.168.8.2:5380/api/dhcp/leases/list?token=$TOKEN" | jq '.response.leases[] | {ip: .ipAddress, client: .clientHostname, mac: .hardwareAddress, expires: .leaseExpires}'
|
||
```
|
||
|
||
**Step 2: Interpret results**
|
||
|
||
- If the only leases are from static LXCs that configured themselves before the DHCP move (e.g., old leases from before the 2026-06-02 static-IP migration), these leases are stale and harmless.
|
||
- If a *dynamic* client (e.g., a test laptop, transient VM) holds `.195` or similar, note it — it will lose its IP on next renew and should be moved to a static assignment or into the `.241+` pool.
|
||
- **ZimaOS (VM 100) at `.195` is a DHCP lease, not static** — this is the one host that needs attention. Either:
|
||
- Set a static IP inside ZimaOS (preferred), or
|
||
- Add a DHCP reservation for MAC in Technitium to pin `.195`
|
||
|
||
**Verification:** No "surprise" dynamic clients that would break on lease expiry.
|
||
|
||
---
|
||
|
||
### Task 4: Fix ZimaOS IP stability (if needed)
|
||
|
||
**Objective:** Ensure ZimaOS at `.195` won't float or break when the pool shrinks.
|
||
|
||
**If ZimaOS already has a static IP configured inside the VM:** Nothing to do.
|
||
|
||
**If ZimaOS is DHCP-only (likely — doc says "DHCP lease, not a reservation"):**
|
||
|
||
Option A (preferred): Set a static IP inside ZimaOS via its web UI at `http://192.168.8.195` → Settings → Network → Static IP → `192.168.8.195/24`, gateway `192.168.8.1`, DNS `192.168.8.2`.
|
||
|
||
Option B: Add a DHCP reservation in Technitium for ZimaOS's MAC address:
|
||
```bash
|
||
ZIMAMAC=$(ssh root@hubris "qm config 100 | grep net0 | grep -oE '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}'")
|
||
curl -sk -X POST "http://192.168.8.2:5380/api/dhcp/reservations/add?token=$TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"hardwareAddress\":\"$ZIMAMAC\",\"ipAddress\":\"192.168.8.195\"}"
|
||
```
|
||
|
||
**Pitfall:** The `/api/dhcp/reservations/add` endpoint signature is unverified — confirm the exact endpoint name from Technitium's API docs or the web UI before running it. The web console at `http://192.168.8.2:5380` → DHCP → Reservations can be used as a manual fallback.
|
||
|
||
---
|
||
|
||
### Task 5: Update documentation in homelab-context
|
||
|
||
**Objective:** Fix the now-wrong claims about static IPs being "below .100".
|
||
|
||
**Files to edit:**
|
||
|
||
1. **`infrastructure/network.md`** — Line 53
|
||
- Old: `Most homelab LXCs use static IPs below \`.100\`. DHCP only covers new/transient containers.`
|
||
- New: `Static IPs span \`.101–.239\` (all LXCs + VMs + workstations). DHCP pool narrowed to \`.241–.254\` to avoid overlap.`
|
||
|
||
2. **`containers/107-dns.md`** — Lines 37, 42, 55
|
||
- Line 37: Update pool range: `192.168.8.241 – 192.168.8.254`
|
||
- Line 42: `Static-IP LXCs (below \`.100\`)` → `Static-IP LXCs (\`.101–.239\`) are excluded from the pool.`
|
||
- Line 55: Add changelog entry for the pool shrink
|
||
|
||
3. **`containers/107-dns.md`** — Add changelog entry:
|
||
```markdown
|
||
### 2026-06-03 — DHCP pool narrowed to `.241–.254` to exclude static IPs
|
||
Previous pool `.100–.240` overlapped with all static LXCs/VMs (\`.101–.239\`), creating IP conflict risk. Shrunk pool to `.241–.254`. No services re-IP'd. See [plan](../plans/2026-06-03-dhcp-pool-exclude-static-ips.md).
|
||
```
|
||
|
||
4. **`infrastructure/network.md`** — Line 51: Update pool range in the DHCP table row.
|
||
|
||
5. **`plans/2026-06-01-slate-ax-to-sodola-migration.md`** — Line 60: Optionally update the pool range in the config table (or add a post-migration note). This is the historical migration plan, so a footnote rather than an edit may be better.
|
||
|
||
**Commit:**
|
||
```bash
|
||
cd /opt/homelab-context
|
||
git add infrastructure/network.md containers/107-dns.md plans/
|
||
git commit -m "docs: DHCP pool narrowed to .241-.254 to exclude static IPs"
|
||
git push
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: Verify no regressions
|
||
|
||
**Objective:** Smoke-test that DNS and key services still work after the scope change.
|
||
|
||
```bash
|
||
# 1. DNS resolution via Technitium
|
||
dig @192.168.8.2 +short git.hubris.network
|
||
# Expected: 192.168.8.175
|
||
|
||
# 2. Caddy reverse-proxy chain
|
||
curl -sI https://git.hubris.network | head -1
|
||
# Expected: HTTP/2 200
|
||
|
||
# 3. All app names resolve
|
||
for name in git cloud media paperless photos matrix auth plato artifacto; do
|
||
result=$(dig @192.168.8.2 +short ${name}.hubris.network)
|
||
printf "%-20s → %s\n" "${name}.hubris.network" "$result"
|
||
done
|
||
|
||
# 4. Technitium DHCP scope is correct
|
||
curl -sk "http://192.168.8.2:5380/api/dhcp/scopes/list?token=$TOKEN" | jq '.response.scopes[0] | {startingAddress, endingAddress}'
|
||
```
|
||
|
||
---
|
||
|
||
## Risk assessment
|
||
|
||
| Risk | Likelihood | Impact | Mitigation |
|
||
|---|---|---|---|
|
||
| API call fails (wrong field names) | Medium | Low | Inspect live scope object first (Task 1); adjust payload |
|
||
| ZimaOS loses IP on next boot | Low | Medium | Task 4 makes ZimaOS static or reserved |
|
||
| Active DHCP client in `.100–.240` gets stranded | Low | Low | Task 3 surfaces this; client just requests a new IP from `.241+` |
|
||
| Technitium admin password file missing | Low | Medium | `/opt/technitium/admin_password.txt` was created during setup; verify existence |
|
||
|
||
## Open questions
|
||
|
||
1. Is zimaos (VM 100) currently DHCP or static? The doc says DHCP lease, but it's listed as `lan_ip: 192.168.8.195` in inventory. If it's actually DHCP, it's the one host that needs a static assignment before the pool shrinks.
|
||
2. Are there any transient DHCP clients (test laptops, phones) on the homelab subnet that hold `.100–.240` addresses? Check leases before cutting over.
|
||
3. Should we widen the pool slightly (e.g., `.230–.254`) for more headroom? Currently 14 IPs. If 3+ transient devices are expected, `.230–.254` = 25 IPs — still safe since the highest static is `.239` and `.230–.239` could be excluded.
|
||
|
||
## Execution preference
|
||
|
||
All changes are on the Technitium API + homelab-context repo. No LXC/VM restarts needed. The pool shrink takes effect immediately for NEW DHCP requests; existing leases in the old range continue until expiry (24h max).
|