Compare commits

112 Commits

Author SHA1 Message Date
fkrebs c94d88da34 fix: services-overview host/IP columns
Pocket-ID moved CT 110 → CT 109 on 2026-05-26 (per ct-inventory).
Gitea/Coder moved CT 111 → CT 104 on 2026-05-26.
Infisical moved CT 112 → CT 109 on 2026-05-26.
Zoraxy note in the intro updated to Traefik.
2026-07-21 15:46:49 +02:00
fkrebs 40a02f33ef docs: reconcile storage.md + volumes.md with live 2026-07-15 audit
Fixes contradictions found during the CT 104 disk-full incident:
- Arcane: was DECOMMISSIONED 2026-05-26 + local, not "migrated to UNAS"
  (volumes.md and storage.md disagreed).
- arr-stack: fully on UNAS incl. SQLite configs on services/arr-stack/;
  media/downloads on media/ share. Radarr/Sonarr not deployed. Removed
  stale "plan to migrate" rows pointing at local /opt/stacks/arr-stack.
- n8n: local (DB on CT 113), not UNAS.
- Garage: metadata is SQLite (not LMDB); keep off NFS; refreshed bucket
  table with real sizes (82 GB, mostly WAL-G PG backups).
- Marked sunsets: karakeep, memos, litellm, daytona.
- crawl4ai: correct name mcp-crawl4ai, no mounts, cache-growth TODO.
- Added ZFS snapshot note: refquota hides snapshot bloat from container df;
  pre-tier1-backups snapshot held 97.9 GB.
2026-07-15 11:15:53 +02:00
fkrebs ac55f25201 incident: CT 104 root full → Immich down + follow-ups
CT 104 root FS hit 200/200 GB. Postgres + Valkey couldn't write,
Immich down. Recovered with docker prune (89 GB freed) + daemon
restart + compose recreate. Added filesystem alerts on CT 109
(Prometheus rules + Grafana alert routing to existing Gotify contact
point). Top remaining disk consumers documented: mcp-crawl4ai
writable layer 40.7 GB, Garage 82 GB, arr-stack 27 GB.
2026-07-15 09:08:38 +02:00
fkrebs 449e549a39 sunset: karakeep + memos wiped end-to-end
Both services no longer in use. Compose files renamed, all data
(CT 104 local + UNAS + Postgres memos DB) deleted. Restic snapshots
rewritten to strip both paths (13 modified, ~7 GiB freed on JottaCloud).
Traefik routes removed, Pocket-ID OIDC clients deleted. Garage S3
memos bucket key revoked; bucket deletion blocked by quorum error
and is flagged for a separate Garage repair pass.
2026-07-10 08:16:39 +02:00
fkrebs 7dd501abe6 services: add Music Assistant (ma.nuclide.systems)
Traefik router + service added on CT 109 pointing to HAOS :8095. Uses the
Cloudflare wildcard cert (*.nuclide.systems), no per-host CF record. LAN
resolution via existing AdGuard wildcard rewrite → 192.168.1.8.
2026-07-03 09:35:40 +02:00
fkrebs ae20382cbe ops: sunset LiteLLM, add owui to Backrest, drop stale DB+role
- LiteLLM sunset 2026-05-28: container removed, config dir renamed
  .DECOMMISSIONED-2026-05-28, prometheus scrape job deleted, CT 113
  litellm DB + role dropped. Bifrost now connects directly to upstream
  LLM providers (verified: openai → SAIA chat-ai.academiccloud.de).
- Open WebUI data (CT 104 uploads + vector_db, 192K) now staged to
  UNAS /services/open-webui via openwebui-prestage.sh and included in
  Backrest services-backup-plan. Open WebUI chats/users live in CT 113
  Postgres owui DB (already in WAL-G).
- Miniflux confirmed DB-only — already covered by WAL-G.
- backrest.md and homelab-architecture.md updated; services-overview.md
  marks LiteLLM row strikethrough.
2026-05-28 21:47:50 +02:00
fkrebs 9cbd02e3ff docs: 2026-05-28 ops coverage audit + WAL-G monitor fix
- homelab-architecture.md: new "Ops coverage matrix" + "Recent ops debt
  cleared" + "Still open" sections. Verifies log aggregation (8/8 hosts),
  Postgres archiving (CT 113 + Immich), Backrest (16 paths exist, all 3
  plans have hooks), Diun, config-to-git, offsite sync, Gotify apps,
  timers and crons across the fleet.
- backrest.md: 16:00 false-positive postmortem (CT 103 had no SSH access
  to CT 113; 2>/dev/null hid the host-key error). Documented retry loop +
  SSH key deployment. Refreshed CT 113 DB list (added owui, miniflux;
  noted litellm decommissioned but DB retained).
2026-05-28 21:20:35 +02:00
fkrebs b26796e30c ops: Loki/Alloy fleet enabled; Backrest+WAL-G+Gitea Gotify hooks; Immich WAL fix
- Alloy now active on CT 102, 103, 104, 109, 101, 105, 113, nuc (mix of
  Docker container + systemd binary); all hosts shipping journal/docker
  logs to Loki on CT 109. CT 103 alloy newly enabled.
- Backrest plans: actionGotify hooks on SUCCESS (prio 3) + ERROR/WARNING
  (prio 8). Gotify app 'Backrest', token AtIMGOYcZsb5HfO.
- WAL-G staleness monitor on CT 103 (hourly): pg_stat_archiver SSH/exec
  against CT 113 + CT 104 immich_postgres. Found Immich WAL push failing
  for 42h — root cause: garage container on bridge network not
  shared_backend (Garage 2026-05-28 incident aftermath). Reconnected to
  shared_backend; image pinned v0.9.4 in compose to match running.
- Gitea daily digest at 08:00 on PVE host nuc — yesterday's commits.
- Closes fkrebs/n8n-flows #2, #5, #6.
2026-05-28 15:19:40 +02:00
fkrebs c05f38d008 docs: Diun deployed on CT 109; doc-sync Phase 1a/3 actual state
- Diun (Docker image update notifier) on CT 109, watching CT 104 via
  socket-proxy, Mondays 08:00 → Gotify app "Diun" (token A34U_7k3biVI_po)
- backrest.md: Phase 1a (rclone sync) confirmed ACTIVE since 2026-05-22
  (Saturdays 01:00). Phase 3 (config-to-git) confirmed ACTIVE across
  CT 102 / CT 103 / PVE host; sync-config-repos rebuilt to drop dead
  CT 110/111/112 refs, auto-discover CT 104 stacks, add CT 109 stacks
- Coverage map updated to reflect actual state (no longer "not deployed")
- Open Phase 3 gap: HAOS Git Pull addon — requires HAOS UI install
- services-overview.md: Diun row added; Traefik replaces Zoraxy
2026-05-28 10:03:39 +02:00
fkrebs f1ed462c91 fix: resolve 7 of 8 backup blind spots (2026-05-28)
- Vaultwarden data moved to UNAS /services/vaultwarden (CT 104 local → NFS)
- Garage S3 pre-backup rsync hook (garage-prestage.sh) + path added to plan
- n8n pre-backup rsync hook (n8n-prestage.sh) + confirm path covered
- Paperless-ngx media path added to services-backup-plan
- Stale paths (arcane, arr-stack, gluetun) removed from plan
- Repo passwords confirmed rotated (not tapirnase); LiteLLM blind spot N/A
- CT 103 SSH key deployed to CT 104 + CT 109 for prestage hooks
- pocketid-prestage.sh updated from CT 110 (destroyed) → CT 109
- Coverage map updated with new entries
2026-05-28 01:35:31 +02:00
fkrebs 24c7d56a1b backup: update coverage map and blind spots (2026-05-28)
- CT 108 Zoraxy decommissioned -> Traefik on CT 109
- Add critical blind spots: Vaultwarden (local FS, not backed up),
  Garage S3 data (WAL-G archive store, not backed up), Paperless media
- Fix services plan path list to match actual Backrest config.json
- Mark stale plan paths: arcane, arr-stack, gluetun
- Note Garage config incident: garage.toml became a directory after
  Portainer redeploy; fixed 2026-05-28 by manual replace + layout apply
2026-05-28 01:15:29 +02:00
fkrebs 17b6fc6fcb CT 108 decommissioned: Zoraxy replaced by Traefik v3 on CT 109
Traefik v3 now handles all *.nuclide.systems traffic (ports 80/443).
ACME DNS-01 via Cloudflare, HTTP/2, Keep-Alive fix for Immich.
CT 108 backup: vzdump-lxc-108-2026_05_28-00_46_45.tar.zst on UNAS.
2026-05-28 00:49:28 +02:00
fkrebs a0f1707e33 decommission Wallabag: remove from portmap, databases, mcp-gateway docs
Wallabag replaced by crawl4ai in paywall-bypass n8n flow.
Container, DB, MCP server, and Bifrost client all removed 2026-05-28.
2026-05-28 00:31:13 +02:00
fkrebs 080fe21329 add wallabag: port 17004, DB entry, Bifrost MCP client 2026-05-27 23:39:58 +02:00
fkrebs 80fca56658 Mark HAOS SSH key done 2026-05-27 20:58:58 +02:00
fkrebs 97cce4cd83 Mark Obsidian drift plugins done; note HAOS SSH key 2026-05-27 20:55:33 +02:00
fkrebs 643ee2cd36 Add Miniflux: port 17002, OIDC client, DB entry, Zoraxy route; remove Obsidian drift plugins from RESUME 2026-05-27 20:55:08 +02:00
fkrebs 24d99d18fc backup: fix CT 109 coverage gaps; archive dead Gitea repos
- ops-backup.timer on CT 109: daily 01:30 tars Pocket-ID data +
  Infisical pg_dump → Garage S3 ct109-portainer-backup (ops-*.tar.gz)
  → already offsite via walg-offsite-sync
- backrest.md: coverage map updated for CT 110/111/112 decommission,
  CT 109 Pocket-ID + Infisical now marked covered
- services plan paths: removed services/pocketid (no longer on UNAS)
- Gitea: archived ct110-pocket-id, ct111-dev, mcp-shepard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:41:18 +02:00
fkrebs fc6507abd2 migrate id (Pocket-ID) CT 110 → CT 109; destroy CT 110
- Pocket-ID now on CT 109 ops at :11000 (was CT 110 :11000)
- Zoraxy id.nuclide.systems → 192.168.1.8:11000
- AdGuard id.nuclide.lan → 192.168.1.8
- Homepage docker.yaml: removed ct110/ct111/ct112 (all destroyed)
- Homepage services.yaml: pocket-id server: my-local
- pocket-id.md: updated host, client table CTs corrected
- portainer.md: CT 110 row struck through
- portmap, ct-inventory, zoraxy all updated
- CT 110 LXC destroyed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:17:03 +02:00
fkrebs cf2aad887b destroy CT 111 (dev) and CT 112 (secrets)
Both LXCs removed from Proxmox after confirming all services migrated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:14:12 +02:00
fkrebs a2f1d49170 migrate dev→CT104, secrets→CT109; add proton-bridge; sunset CT111/CT112
- CT 111 (dev): Gitea + Coder + act-runner migrated to CT 104
  - Gitea uses Redis for queue/cache/session (idmapped NFS LevelDB workaround)
  - Zoraxy routes dev/git updated to 192.168.1.40
  - AdGuard dev.nuclide.lan updated to 192.168.1.40
- CT 112 (secrets): Infisical migrated to CT 109
  - DB dump restored; SITE_URL updated to 192.168.1.8:8200
  - AdGuard secrets.nuclide.lan updated to 192.168.1.8
- Proton Mail Bridge deployed on CT 104 (SMTP :1025, IMAP :1143)
- Homepage updated: Dev group → ct104, Infisical → my-local, Background group added
- portmap, ct-inventory, zoraxy, dev-environment, secrets-manager all updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:08:08 +02:00
fkrebs bcbe9e7a88 portainer: add LXC names to agents table, fix CT 105→CT 110 2026-05-26 21:44:01 +02:00
fkrebs 0b4727ed58 homepage: move to port 10000 2026-05-26 21:39:18 +02:00
fkrebs 4f17d99f55 homepage: use ops.nuclide.lan (host alias) not homepage.nuclide.lan 2026-05-26 21:38:09 +02:00
fkrebs ff7bacdaf9 Homepage replaces Homarr: portmap, inventory, DNS docs updated (2026-05-26) 2026-05-26 21:32:14 +02:00
fkrebs 4429b37c49 zoraxy: remove arcane.nuclide.systems route (decommissioned 2026-05-26) 2026-05-26 21:13:57 +02:00
fkrebs 03fd23b37b Update pocket-id.md: full client table with IDs, API key, remove Arcane/lobehub 2026-05-26 21:08:51 +02:00
fkrebs 8d73ae3e30 portainer.md: add Observability section (Prometheus + Grafana)
Prometheus scrapes /api/metrics via X-API-Key, alert rules for
environment health, Grafana dashboard at uid=portainer-be.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:23:09 +02:00
fkrebs f65ceef90a Add Portainer BE doc; update backup coverage for CT 109
- New services/portainer.md: BE license key, agent inventory, backup
  setup (Garage S3 + JottaCloud offsite), OIDC pending steps
- backrest.md: add CT 109 Portainer to coverage map, remove stale
  services/arcane path from plan, note Arcane decommission

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:12:36 +02:00
fkrebs 07b21a7e2e docs: add SAIA rate limits, Bifrost governance, and admin auth notes to mcp-gateway.md
- SAIA rate limits: minute=30/1m active at provider level; hour/day rows exist but not linked
- VK governance: allow_all_keys SQL workaround, allowed_models must be non-null JSON text
- Fix stale sk-tapirnase reference in add-MCP-client example (now uses cookie auth)
- Add LLM provider table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:43:50 +02:00
fkrebs dcd8f36331 sunset LiteLLM in portmap.md (2026-05-26)
- Port 14000 row struck as SUNSET; replaced by Bifrost
- Prometheus scrape target for LiteLLM struck
- e73bb7b9 Pocket-ID client row struck as DELETED
- Removed litellm from "not publicly proxied" list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:57:40 +02:00
fkrebs 6c401f2376 docs: reconcile sunset of LiteLLM external, mcp-gateway, and LobeChat
- Bifrost replaces LiteLLM at ai.nuclide.systems (CT 104:14003); LiteLLM
  now internal-only backend at :14000
- mcp-gateway (FastAPI/DinD, mcp.nuclide.systems) decommissioned 2026-05-26;
  MCP now served by Bifrost at ai.nuclide.systems/mcp (29 clients, ~760 tools)
- LobeChat decommissioned 2026-05-26; chat.nuclide.systems now Open WebUI
  (CT 104:14002, stack ai/open-webui.yml)
- Update portmap, volumes, services-overview, homelab-architecture, zoraxy,
  mcp-servers, databases, README accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:35:29 +02:00
fkrebs 550b54085c docs: add open-webui VK to mcp-gateway virtual keys table 2026-05-26 13:11:22 +02:00
fkrebs b065c2b849 docs: mark mcp-gateway fully decommissioned 2026-05-26 2026-05-26 13:10:14 +02:00
fkrebs 0600032b75 docs: migrate MCP gateway docs from mcp-gateway to Bifrost
- mcp-gateway.md: full rewrite for Bifrost at ai.nuclide.systems/mcp;
  29 clients / ~760 tools; VK auth; n8n+shepard blocked on
  streamable-HTTP; legacy gateway decommission checklist added
- connection-hosts.md: Bifrost at port 14003 is now ai.nuclide.systems;
  LiteLLM demoted to internal-only; mcp.nuclide.systems marked pending
  decommission; OIDC client 78c78998 flagged for removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 12:55:33 +02:00
fkrebs fbc6469fc4 doc-ingestion: nomic embedding stack, Bifrost semantic cache, GPU passthrough status 2026-05-26 09:49:09 +02:00
fkrebs 14b21199ee docs: ingest-pipeline design doc; update doc-ingestion with nomic service 2026-05-26 09:20:38 +02:00
fkrebs 3d71824295 auto: doc-ingestion: Qdrant+TEI deployed; OWUI RAG config; Bifrost embed models 2026-05-26 08:56:00 +02:00
fkrebs defc788ab5 2026-05-26: LiteLLM decommissioned, consistency sweep complete
- zoraxy.md: ai.nuclide.systems note updated (LiteLLM decommissioned)
- LiteLLM stopped, commented out of docker-compose.yml
- Paperless-AI, Nextcloud updated to Bifrost VK
- mcp-gateway sync loops disabled
2026-05-26 08:07:09 +02:00
fkrebs 7e0082379e 2026-05-26: LobeChat→Open WebUI cutover, Bifrost deployed
- zoraxy.md: chat.nuclide.systems → Open WebUI :14002, ai.nuclide.systems → Bifrost :14003
- config-to-git.md: CT 104 fkrebs/ct104-conf row added
2026-05-26 07:50:25 +02:00
fkrebs 067e42c25d ops: cut ai.nuclide.systems over to Bifrost (port 14003)
LiteLLM remains on :14000 for internal services during migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 07:29:37 +02:00
fkrebs 3819794812 docs: add CT 104 to config-to-git fleet
ct104-conf repo tracks /opt/stacks/ compose files and configs daily.
Bifrost and Open WebUI now covered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 07:26:37 +02:00
fkrebs 13bc1ecc12 RESUME + pocket-id: reflect 2026-05-24 → 26 state
RESUME:
- Backrest 3-for-3 success on 2026-05-26 (video-projects-plan recovered)
- Vault dedup + 234 link rewrites + cleanup
- Plugin audit + Memos Sync + Obsidian Git + streamlined-ui
- Config-to-git fleet now 8 repos
- AdGuard *.nuclide.lan zone (30 A-records)
- 3 new Homarr boards (home/admin/command)
- Pocket-ID LAN callback URLs
- Immich HTTP/2 Keep-Alive diagnosed
- New constraints: use LAN aliases; daily-note merge policy

pocket-id.md: LAN callback URLs added for Homarr/Grafana/Infisical/Proxmox.
Filed earlier as part of 2026-05-24 work, committing now.
2026-05-26 06:03:28 +02:00
fkrebs 3638e368b1 adguard: document new *.nuclide.lan zone (30 A-records)
Per-host + per-service LAN-only aliases added 2026-05-24 on CT 102.
Stable names → host IPs; clients use names instead of bare IPs so
service moves only require an AdGuard edit, not N app updates.

Use case driver: Immich Android HTTP/2 Keep-Alive bug — per-SSID
'nuclide' local URL https://immich.nuclide.lan:12000 bypasses Zoraxy.
2026-05-24 15:42:10 +02:00
fkrebs 3fa4608266 config-to-git: add CT 103/109/113 + obsidian-vault + obsidian-config
CT 103/109/113-conf were deployed earlier today, table updated.
Add obsidian-vault (CT 103, daily 04:00, rolling 2-commit window) and
obsidian-config (manual zip-drop workflow, see README in repo).
2026-05-24 10:27:49 +02:00
fkrebs a422bbfb13 obsidian MCP: document vault paths across CT 104/105/UNAS 2026-05-24 08:51:04 +02:00
fkrebs aab7f3a6c6 services-overview: make internal IP:port entries clickable 2026-05-24 08:45:22 +02:00
fkrebs 688f3cac45 docker-internal-inventory: 3-tier model with per-container recommendation
Lists all containers without LAN-published ports across CT 104/105/109/110/111/112/113.
Recommends keep-internal vs expose for each. Confirms the public/LAN/docker-internal
tiering is clean across the fleet.
2026-05-24 08:41:46 +02:00
fkrebs d76d766c0a services-overview: full inventory with external + internal URLs
Living doc at http://192.168.1.8:13080/services-overview/.
Categorised by AI/files/identity/dev/home/ops/network with external
Zoraxy routes, internal LAN IP:port, host, and doc cross-links.
2026-05-24 08:39:29 +02:00
fkrebs 7af18a82f1 config-to-git: document fleet (PVE, Zoraxy, AdGuard, HAOS)
Add /docs/infra/config-to-git.md listing all 4 repos, schedules,
script paths, and HAOS-specific addon init_commands pattern.
RESUME updated to reflect HAOS bootstrap done 2026-05-24.
2026-05-24 08:21:52 +02:00
fkrebs 710c8afe3c RESUME: arcane auto-update notifications done 2026-05-24 08:14:52 +02:00
fkrebs 829dd8f339 homarr OIDC: correct env var to AUTH_PROVIDERS (plural)
Homarr v1 ignores singular AUTH_PROVIDER; OIDC button only appears
when AUTH_PROVIDERS=credentials,oidc is set. Fixed live on CT 109.
2026-05-24 07:47:13 +02:00
fkrebs 8aa807bb94 docs: AdGuard split-horizon DNS done
*.nuclide.systems → 192.168.1.4 rewrite active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:45:25 +02:00
fkrebs e160e3dba8 docs: dozzle is LAN-only, clean up route notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:10:57 +02:00
fkrebs 895658a115 docs: consistency pass 2026-05-23 (session ×4)
- zoraxy.md: fix arcane upstream to CT109 (192.168.1.8:10002), add
  SkipWebSocketOriginCheck column, note missing routes (dozzle, etc.)
- arcane.md: reflect CT109 migration complete, expand agent table to
  all 8 environments, fix MANAGER_API_URL and DB paths
- ct-inventory.md: CT103 RAM 512→4096+swap; CT109 footnote complete
- proxmox-memory-audit.md: CT103 bump, add CT109+CT113 rows, fix sum
- portmap.md: add Wetty row, fix arcane backend, WAL-G scrape target,
  homepage decommissioned, dozzle LAN-only note
- mcp-gateway.md: add gitea/paperless/proxmox, count 26→29 servers
- RESUME.md: check off WAL-G, Loki, Zoraxy audit; CT109 in key state
  table; session ×4 completed items block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 16:42:45 +02:00
fkrebs 14137c32a7 docs(mcp): add gitea, paperless, proxmox MCP servers (2026-05-23)
Wire three new community MCP servers into the gateway on CT104:
- gitea: official Gitea MCP (53 tools), static upstream gitea-mcp:8000
- paperless: @nloui/paperless-mcp via mcp-proxy (12 tools), static upstream
- proxmox: proxmox-mcp-plus PyPI (39 tools), read-only PVEAuditor token

Total: 27 → 30 servers. Skipped: karakeep (API key unset), audiobookshelf
(Go binary only), vaultwarden (master password exposure risk).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:02:26 +02:00
fkrebs 10e138b677 docs: document WAL-G monitoring setup on CT113/CT109
Add WAL-G monitoring section to databases.md covering the textfile
collector script, walg-metrics.timer, node_exporter config, and
Prometheus alert rules added to prevent silent backup stalls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:50:39 +02:00
fkrebs f8d8ee3ac0 docs: add Nexa service doc — synopsis, nuclide.systems mapping, phased roadmap
Covers what Nexa is, which existing services it depends on, what is
genuinely missing (TEI, Qdrant collection, GraphDB), and alternative
tool choices for embeddings, web-search, and the graph pillar.

Repo: https://git.nuclide.systems/fkrebs/nexa

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:32:02 +02:00
fkrebs 1a68be98e4 docs: session ×3 — Homarr board, Arcane environments, Infisical OIDC
- Homarr board 'nuclide' deployed (6 sections, 27 apps, correct SQLite schema)
- Arcane 8 environments online with unique agent tokens per host
- Infisical SITE_URL → internal IP; Pocket-ID OIDC client created
- RESUME: Arcane OIDC closed, new open items for Infisical config + Loki + Gotify
- Claude settings: SSH allowlist expanded

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 13:41:48 +02:00
fkrebs f441e8b762 2026-05-23: UNAS SSH key auth, unas-conf repo, NFSv4 status 2026-05-23 11:29:25 +02:00
fkrebs 3de4d5db63 2026-05-23: PVE optimizations, LXC watchdog, KSM, startup order 2026-05-23 11:19:21 +02:00
fkrebs 7927390359 docs: add all config-tracking repos to related repos table 2026-05-23 09:48:22 +02:00
fkrebs c3fd649f68 docs: 2026-05-23 session — CT109 ops, Arcane/Dozzle migration, OIDC wired, OOM fix, n8n tracking 2026-05-23 09:44:15 +02:00
fkrebs 4c97732720 docs: Homarr + Grafana OIDC wired via Pocket-ID API (2026-05-23)
Client IDs created programmatically via Pocket-ID /api/oidc/clients endpoint.
Env vars injected into both compose files and force-recreated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:14:20 +02:00
fkrebs ff398a592f docs: Arcane + Dozzle migrated to CT 109; agents on all Docker hosts
- Arcane server moved CT 104 → CT 109:10002; DB migrated; Zoraxy upstream updated
- Dozzle server moved CT 104 → CT 109:10001; remote agents on all 7 Docker hosts
- Headless agents (arcane-headless + dozzle agent) deployed to:
  CT 101, 104, 105, 110, 111, 112 via /opt/stacks/ops-agents/
  CT 113 updated in-place (was pointing at CT 104, now CT 109)
- homelab-configs sync script expanded: ops/{arcane,dozzle,monitoring} + all agent configs
- portmap: updated CT 104 entries as decommissioned, added CT 109 entries
- ct-inventory: CT 109 role description updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 08:53:37 +02:00
fkrebs 96df6e7bd0 docs: migrate docs-server CT 111 → CT 109; update nav and URLs
- docs-server now runs on ops (192.168.1.8:13080), not dev (192.168.1.42)
- mkdocs.yml: fix site_url (was pointing at .111 instead of .42, now .8)
- mkdocs.yml: add all services added since last nav update (pocket-id,
  backrest, databases, arcane, mcp-servers, llm-benchmark, RESUME, connection-hosts)
- portmap.md: move :13080 entry from CT 111 to CT 109 section; add Homarr :7575;
  fix stale Promtail note → Alloy add-on
- README.md: update rendered docs URL to 192.168.1.8:13080

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 08:16:09 +02:00
fkrebs dc7ceab724 docs: correct Zigbee integration — Zigbee2MQTT + Mosquitto, not ZHA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 08:05:25 +02:00
fkrebs 474af687af docs: 2026-05-23 session — observability stack, Pocket-ID guide, secrets scrub
- CHANGELOG: full 2026-05-23 backfill (Loki, Alloy×12 hosts, pve-exporter,
  HA prometheus integration, 5 dashboards, 3 alert rules, Homarr, homelab-configs repo)
- services/pocket-id.md: new — OIDC endpoints, client creation walkthrough,
  per-service env var patterns, current client registry, backup notes
- infra/proxmox-state.md: redact D-Link credentials from plaintext (pelican/pinkpanther)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 08:03:01 +02:00
fkrebs 3d114c6873 docs: expand README with monitoring links, service index, config repos
Add Homarr/Grafana/Loki/Prometheus quick links, full public service table,
operator-only LAN links, layout additions, and related repos table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 07:48:23 +02:00
fkrebs b2cb123414 monitoring: document Loki/Alloy/pve-exporter, dashboards and alerts
CT 109 ops stack now includes Loki (log agg), Grafana Alloy (log agent on
all 12 hosts), prometheus-pve-exporter. 5 Grafana dashboards imported;
3 alert rules wired to Gotify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 07:03:51 +02:00
fkrebs 80647c1a6b monitoring: add pve-exporter to portmap
Proxmox VE metrics via prometheus-pve-exporter on CT 109 :9221.
monitor@pve!prometheus token with Monitor (read-only) role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 04:32:37 +02:00
fkrebs 0979f71857 monitoring: add Loki + Alloy log aggregation to portmap
Loki (CT 109 :3100, 30d retention) and Grafana Alloy deployed across
all 12 hosts. Portmap updated with new services and agent deployment notes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 04:28:15 +02:00
fkrebs a8ed4350f0 ops: CT 109 provisioned — Prometheus + Grafana monitoring
- Debian 13, Docker 29.5, 4c/4G/32G at 192.168.1.8
- Prometheus :9090 (90d retention), Grafana :3000 (LAN only)
- Scrapes: litellm CT104:14000, node-ct104:9100, node-ct109:9100
- CT 104 standalone node-exporter retained at /opt/stacks/monitoring/
- All 4 targets confirmed up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 03:44:05 +02:00
fkrebs 644ba72d77 ops: add Prometheus + Grafana monitoring stack on CT 104
- Prometheus :9090, Grafana :9091, node-exporter :9100
- LiteLLM /metrics/ enabled via prometheus callback
- Scrapes: litellm (bearer auth), node-ct104, prometheus self
- 14 litellm_* metrics confirmed flowing
- Stack: /opt/stacks/monitoring; migrate to CT 109 ops when built

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 03:35:22 +02:00
fkrebs c7529bd867 docs: Nextcloud completion model → qwen3.5-122b-a10b (vision+tools)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 03:29:06 +02:00
fkrebs 6309286b96 docs: Nextcloud assistant model config — occ method, current assignments
integration_openai points to LiteLLM at ai.nuclide.systems/v1.
Fixed completion=llama-3.3-70b-instruct, T2I=saia-flux (was devstral).
occ snippet for future model changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 03:10:58 +02:00
fkrebs c3c40dc07b docs: add model management UI, catalog API, and 4K improvements to mcp-gateway
- New /ui/models page (sortable model catalog + service assignments)
- model_assignments.json: 35-model catalog with latency/cost/capability metadata
- API: GET/PATCH /api/model-assignments
- Model evaluator agent added to agents.json
- ui.html: widen max-width to 1600px, 4K breakpoints at 1920/2560/3840px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 02:19:28 +02:00
fkrebs 25da11b53a Add connection-hosts and mcp-servers maintained docs
- infra/connection-hosts.md: every host, LAN IP, port, and public URL
  across all Proxmox guests, Docker services, network infra, and
  external hardware; OIDC client registry; SSH cheat-sheet
- services/mcp-servers.md: all 27 MCP servers with full credentials,
  upstream URLs, transport, group, gateway management API examples,
  and gateway .env token inventory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 01:03:59 +02:00
fkrebs 396aa0e48e docs: remove claude high-effort variants, set temperature=0.7 on Claude models (58 total) 2026-05-23 00:41:26 +02:00
fkrebs 9ab9033ab0 docs: fix voxtral-mini proxy status + remove realtime model entry 2026-05-23 00:26:32 +02:00
fkrebs 6aa55fdf2d docs: add LLM model benchmark + service catalogue (60 models) 2026-05-23 00:23:04 +02:00
fkrebs 8ccf76b37c 2026-05-23: config-to-git deployed, syncstack consolidated, secrets backlog detailed
- Config-to-git crons live on CT 108/102/PVE; HA pending
- Syncstack retired; litellm_sync.py in MCP gateway loop
- Infisical migration phases 2-4 detailed in backlog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 00:09:59 +02:00
fkrebs 377f20a817 todo sync: update Backrest media-repo status, Vaultwarden OIDC status, UNAS off-host context
- video-projects-plan has never completed; media-backup-plan last OK 2026-05-21
- Both plans have orphaned runs since 19:10; Backrest restarted at 21:23
- Next runs scheduled 2026-05-25
- Vaultwarden OIDC: no env vars wired in .env as of 2026-05-22
- UNAS off-host: Backrest is the right vehicle; reliability fix is prerequisite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:50:38 +02:00
fkrebs dd806f86b8 crawl4ai: mark SSE StreamConsumed resolved — clean restart, 7 tools, 27/27 lobe-sync 2026-05-22 23:34:46 +02:00
fkrebs 05ad2b7d26 backlog: sync to truth — drop 4 already-done items, correct disk numbers 2026-05-22 23:27:07 +02:00
fkrebs b090b4a4a8 backlog: reorder by urgency (critical/high/medium/planned) 2026-05-22 23:25:39 +02:00
fkrebs 3881f95e28 backlog: add 20 items from docs scan (security, backups, db cleanup, infra, services) 2026-05-22 23:22:21 +02:00
fkrebs f86135f683 backlog: remove v5 import generalize (not a Claude task) 2026-05-22 23:19:36 +02:00
fkrebs 811bc1af94 backlog: add crawl4ai SSE fix, syncstack consolidation, v5 import generalize
- crawl4ai MCP (SSE transport) fixed and working — 27/27 servers in LobeChat
- syncstack cron fixed (missing cd prefix)
- Backlog section added for: syncstack→gateway consolidation, SSE StreamConsumed
  investigation, v5 collection import generalization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:18:44 +02:00
fkrebs 143343decf docs: session continuity file + claude-max-bridge /v1/responses fix
- Add RESUME.md for cross-session continuity (open items, key state, constraints)
- claude-max-bridge: implement /v1/responses (OpenAI Responses API) with
  previous_response_id chaining via server-side history injection into system prompt.
  Root cause of "session already in use": CLI leaves JSONL in un-resumable
  "dequeued" state after each --print run; fix avoids session reuse entirely.
  Also fixed: assistant content must be array-of-blocks not plain string (silent
  JS crash otherwise).
- LiteLLM: add pass_through_endpoints for /v1/responses → claude-max-bridge
- Storage, volumes, architecture docs reconciled (Vaultwarden → local zfs,
  Pocket-ID backup, WAL-G fix, apps/ decommission, Nextcloud CIFS→NFS)
- Add ideas/, proxmox-memory-audit.md, llm-benchmark.md (new docs this session)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 22:42:17 +02:00
fkrebs e0199042c1 Re-enable nextcloud MCP: single_user_basic + NEXTCLOUD_VERIFY_SSL=false
App-password generated via occ for fkrebs@nucli.de. Gateway patched
to persist enabled flag from config.json across restarts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:41:51 +02:00
fkrebs 6e46b894cb CT 101 shepard rootfs 100G → 500G
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:21:41 +02:00
fkrebs c2979f7c9d Rename CT 109 observe → ops across all docs
Also updates IP conflict note: .6=CT113, .7=CT112, so CT 109 gets .8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:38:57 +02:00
fkrebs b0160322b3 Mark CT 112 live; add to portmap; update secrets-manager
CT 112 "secrets" deployed 2026-05-22 — Infisical + Postgres 16 + Redis 7
running at http://192.168.1.7:8200 (LAN-only, no Zoraxy route).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:20:41 +02:00
fkrebs 0e50996b43 docs: CT 112 secrets → 192.168.1.7, 4G/20G; CT 109 IP note updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:54:47 +02:00
fkrebs 0d83f83bc0 docs: MCP gateway git/gitlab/paper-search; CT 109 plan; new backrest/db/arcane docs
- mcp-gateway.md: 26 servers (added git, gitlab, paper-search catalog bridge);
  document --pass-environment requirement for env var passthrough in DinD bridges
- ct-inventory.md + homelab-architecture.md: CT 109 IP conflict, CT 113 live
- proxmox-state.md §16: Loki, sshwifty, Alloy, IP conflict note, sidecar table
  expanded to CT 103/111/113; build order updated
- infra/portmap.md: CT 113 db section; QNAP TS-251D (Klipper/Mainsail)
- services/backrest.md: full backup strategy doc (new)
- services/databases.md: CT 113 postgres/WAL-G/pgAdmin doc (new)
- services/arcane.md: Docker management IDE doc (new)
- CHANGELOG.md: 2026-05-22 entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:35:07 +02:00
fkrebs 0def5c0be9 docs: rewrite zoraxy.md — full route table, audit status, config guide
Replaces outdated setup-only guide with current state:
- Full 22-route table with upstream IPs (audited 2026-05-21)
- All routes confirmed ACME+WS+Hop enabled
- JSON template for adding new routes
- Auth posture section with open items (Tinyauth pending CT 109)
- Backup/restore instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:52:25 +02:00
fkrebs 52db8ce9ed docs: portmap shepard-mcp decommissioned; add doc-ingestion pipeline design
- portmap: mark port 18010 (shepard-mcp) as DECOMMISSIONED 2026-05-21;
  remove mcp-shepard from Gitea repos note
- services/doc-ingestion.md: new — n8n → Docling → LiteLLM embeddings →
  LobeChat knowledge base pipeline architecture + converter comparison table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:33:35 +02:00
fkrebs 0baf283678 docs: add SSH key deployment section to CT inventory 2026-05-21 08:18:37 +02:00
fkrebs ad0fdd2beb docs: dissolve Stacks chapter → infra/; reconcile portmap, storage, changelog (2026-05-21)
- Move PORTMAP, storage, volumes, docker-networks from stacks/ → infra/
- Remove stacks/todo.md (content migrated to CHANGELOG + ideas)
- portmap: add new MCP servers (18005/07/09/10/11), fix s3.nuclide.systems entry,
  update Daytona OIDC row to DECOMMISSIONED, add Vaultwarden OIDC placeholder,
  add DevOps table row for MCP server Gitea repos
- storage: add Garage S3 buckets subsection (lobe-files + chat-artifacts)
- docker-networks: remove daytona-minimal_daytona-network row + daytona from
  "Stacks Keeping Own Networks" list
- ct-inventory: add CT 112 (secrets/Infisical, planned)
- CHANGELOG: prepend 2026-05-21 entry (docs, S3, MCP, dev env, Gitea repos, planned)
- ideas/stack-ideas: add §11 Renovate Bot, §12 egress firewall, §13 local LLM
- mkdocs.yml: replace nav — dissolve Stacks chapter, add infra/ entries
2026-05-21 07:54:21 +02:00
fkrebs 630c5e0ae5 docs: add secrets-manager design doc; fix mkdocs nav 2026-05-21 07:34:32 +02:00
fkrebs 333e3c99cd security: add data-leak audits (2 sessions + comparison + meta + transcript) and SAIA reclassification 2026-05-21 02:57:59 +02:00
fkrebs 9f6d0dd195 homelab-architecture: add Mermaid topology / auth / MCP / data plane diagrams 2026-05-21 00:35:24 +02:00
fkrebs 7ef5aea9db mcp-gateway: document new /mcp-config endpoint (Claude/Cursor config download) 2026-05-21 00:27:53 +02:00
fkrebs 061f09b2d2 docs: top-10 fixes (PORTMAP, storage truth, banners, AdGuard scrub, zoraxy UCI removed, CT 111 section, Daytona purge) 2026-05-21 00:20:18 +02:00
fkrebs d63e0df647 docs: README links docs site (LAN-only on CT 111:13080) 2026-05-20 23:58:50 +02:00
fkrebs 4728fd096f docs: mkdocs.yml lives in docs-server image, not repo 2026-05-20 23:57:14 +02:00
fkrebs c163a7905e docs: mkdocs use repo root as docs_dir 2026-05-20 23:56:15 +02:00
fkrebs e7e62d6bb3 docs: add mkdocs.yml (Material theme) 2026-05-20 23:55:43 +02:00
fkrebs caf133f4a7 docs: add CHANGELOG, ct-inventory, rewrite README as TOC 2026-05-20 23:45:51 +02:00
fkrebs 99f93b585f reorg: split into infra/ services/ history/ ideas/ 2026-05-20 23:25:20 +02:00
fkrebs 04d54ac9b0 baseline: docs as of pre-reorg audit (2026-05-20) 2026-05-20 23:23:31 +02:00
178 changed files with 11788 additions and 169071 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(pct list *)",
"Bash(pct status *)",
"Bash(zfs list *)",
"Bash(zpool status *)",
"Bash(systemctl status *)",
"Bash(systemctl list-timers *)",
"Bash(ssh root@192.168.1.*)"
]
}
}
-1817
View File
File diff suppressed because it is too large Load Diff
+244
View File
@@ -0,0 +1,244 @@
# Changelog
All notable infrastructure / service / doc changes. Newest first.
## 2026-07-15
### Immich outage — CT 104 root filesystem full (200 G / 200 G, 0 avail)
Cascading failure: Immich 502 from Traefik. Root cause CT 104 root FS at 100% — Postgres crash-looping ("Disk quota exceeded" on pg_stat_tmp), Valkey blocking writes ("MISCONF stop-writes-on-bgsave-error"), immich_server not serving on :12000.
- **Freed 89 GB** via `docker system prune -af --volumes` on CT 104. Post-prune: 183 G / 200 G (92%).
- Docker network state got wedged (orphan endpoint after prune blocked recreate); resolved with `systemctl restart docker` on CT 104 → all 79 containers auto-restored via `restart: unless-stopped`.
- Immich stack recreated via `docker compose up -d --force-recreate`; postgres data preserved on bind mount `/opt/stacks/immich/postgres/`. All 4 containers healthy; external route returns HTTP/2 200.
**Top disk consumers identified (post-cleanup):**
- `mcp-crawl4ai` container writable layer: **40.7 GB** — crawler cache not persisted to a volume. Needs remediation (bind mount or periodic reset).
- `/opt/stacks/shared-db/garage`: 82 GB (Garage S3 data on root FS).
- `/opt/stacks/arr-stack/media`: 27 GB (dormant per CLAUDE.md — safe to delete).
- Docker daemon logs are already rotated (10 MB × 3 per container in `/etc/docker/daemon.json`).
**Alerting added on CT 109:**
- Prometheus rules `filesystem.yml` (Warning >85% / Critical >95% / Full >99%) at `/opt/stacks/monitoring/prometheus/rules/`.
- Grafana-managed alert rule `Filesystem >85%` (uid `cfs5tiefwl3b4a`, folder `Alerts`), routes to existing Gotify contact point via root notification policy. First delivery expected in ~10 min while CT 104 sits at 92%.
## 2026-07-10
### Karakeep + Memos SUNSET (full wipe)
Both services no longer in use. Wiped end-to-end.
- **CT 104**: `docker-compose.yml` in `/opt/stacks/karakeep/` and `/opt/stacks/memos/` renamed to `.sunset-2026-07-10`. Containers stopped + removed (karakeep, karakeep_chrome, karakeep_meilisearch, memos). Local data dir `/opt/stacks/karakeep/localdata/` (279 MB) deleted.
- **CT 104 MCP**: `mcp-memos` (docker-run) container removed. `karakeep-mcp.yml` compose renamed to `.sunset-2026-07-10` (container was already stopped).
- **UNAS**: `/mnt/pve/unas/services/karakeep` (472 MB) and `/mnt/pve/unas/services/memos` (896 KB) deleted.
- **CT 113 Postgres**: `memos` DB and role dropped.
- **Garage S3**: `memos` bucket key access revoked (read/write/owner=false). Bucket delete blocked by an internal quorum decode error — 3.5 MiB / 4 objects orphaned on the internal ID `df4c6b31...`. Flagged for a separate Garage repair pass.
- **CT 103 Backrest**: both paths removed from `services-backup-plan`. Restic `rewrite --forget --exclude` applied to `jottacloud:services` repo: 13 snapshots modified, 591 packs deleted, ~7 GiB freed on JottaCloud.
- **CT 109 Traefik**: `hoarder` and `memos` router+service blocks removed from `remote.yml` (file provider auto-reloaded).
- **CT 110 Pocket-ID**: OIDC clients `karakeep` (`d92f8...`) and `memos` (`62bf4...`) deleted.
- **Cloudflare DNS**: left untouched (wildcard `*.nuclide.systems` covers).
- Docs: rows removed from `services-overview.md`.
## 2026-05-23
### Observability stack (CT 109 "ops")
- **CT 109 ("ops") fully provisioned**: Debian 13, Docker 29.5.2. Stack `/opt/stacks/monitoring/` running Prometheus (:9090), Grafana (:3000 LAN-only, admin/tapirnase), node-exporter, Loki, Alloy, pve-exporter, and Gotify-bridge. CT info updated in `ct-inventory.md`.
- **Loki deployed on CT 109**: log aggregation at `:3100`, 30-day retention, TSDB v13 schema, filesystem storage at `./loki/data/`. `limits_config` set `reject_old_samples_max_age: 168h` (7 days) to handle log backfill from HA restart. Grafana datasource auto-provisioned via `provisioning/datasources/loki.yml`. Grafana Loki datasource UID: `P8E80F9AEF21F6940`.
- **Prometheus remote_write receiver enabled**: `--web.enable-remote-write-receiver` added to CT 109 Prometheus. Required for HA Alloy add-on to push metrics directly.
- **prometheus-pve-exporter deployed**: `prompve/prometheus-pve-exporter:latest` on CT 109; credentials in `./pve-exporter/pve.yml` (chmod 644 — container runs non-root). PVE `monitor@pve` service user + API token `c4be45dc-...` created with `PVEAuditor` role on `/`. Prometheus scrapes `:9221/pve?target=192.168.1.20`. Metrics: per-VM/CT CPU, memory, disk, network I/O.
- **HA prometheus integration added**: `prometheus:` block in HA `configuration.yaml`; HA exposes ~9211 `hass_*` entity-level metrics at `:8123/api/prometheus`. CT 109 Prometheus scrapes with bearer token (HA long-lived token), `scrape_interval: 60s`. This is distinct from Alloy host-level metrics — both needed.
- **Grafana Alloy deployed across all 12 hosts**: Alloy v1.16.1 from Grafana apt repo. River-syntax configs ship Docker + journal log scraping with host labels to `http://192.168.1.8:3100/loki/api/v1/push`. Deployment breakdown:
- **Docker Compose** (CT 101, 104, 105, 110, 111, 112, 113): container with docker socket + journal mounts, `group_add: ["999"]` for journal access.
- **Binary + systemd** (CT 102, 103, 108, nuc): apt install `alloy`, config at `/etc/alloy/config.alloy`, `systemctl enable --now alloy`. Journal-only (no Docker socket on these hosts).
- **HA add-on** (VM 100): `wymangr/hassos-addons` Grafana Alloy v0.0.8 — config fills in `prometheus_remote_write` to CT 109 + Loki endpoint. Distinct host label `homeassistant`.
- All configs stored in `/opt/homelab-configs/alloy/` (see homelab-configs repo below).
- **5 Grafana dashboards provisioned**:
- Node Exporter Full (Grafana ID 1860) — imported for all hosts
- LiteLLM (custom-built) — `/d/.../litellm`; panels: request rate, error rate, token throughput, model split, p95 latency
- Home Assistant (Grafana ID 12481 + custom panels) — power/energy, climate (temp/humidity/CO₂), home activity, device health/battery
- Loki / log explorer — imported
- Prometheus target health — imported
- **3 Grafana alert rules created** via `/api/v1/provisioning/alert-rules` (folder `alerts-folder`):
- `Node disk > 85%` — per-instance mountpoint disk usage, 5m window
- `Prometheus target down``count(up == 0) > 0`, 5m window
- `LiteLLM error rate > 5%` — 10m rate window, 5m pending
### Homarr (CT 109)
- **Homarr v1 deployed**: `ghcr.io/homarr-labs/homarr:latest` on CT 109 `:7575`. Data at `./data/`, docker socket bind-mounted read-only for auto-discovery. LAN-only (no Zoraxy route).
- **homelab-configs Gitea repo**: private repo `fkrebs/homelab-configs` at `git.nuclide.systems`; cloned to `/opt/homelab-configs` on CT 109. Daily cron at 03:00 syncs: Alloy configs per host, Homarr docker-compose, Homarr SQLite dump. Script at `/usr/local/bin/sync-homelab-configs`. Commit + push on any diff.
### Pocket-ID OIDC (wired via API)
- **Homarr OIDC**: client `63a94e30` created via Pocket-ID API. Redirect URI `http://192.168.1.8:7575/api/auth/callback/oidc`. Homarr compose updated (`AUTH_PROVIDER=oidc`, `AUTH_OIDC_CLIENT_ID`, `AUTH_OIDC_CLIENT_SECRET`, `AUTH_OIDC_ISSUER`, etc.), force-recreated. SSO active 2026-05-23.
- **Grafana OIDC**: client `92d987d5` created via Pocket-ID API with PKCE enabled. Redirect URI `http://192.168.1.8:3000/login/generic_oauth`. Grafana compose updated with `GF_AUTH_GENERIC_OAUTH_*` vars, force-recreated. SSO active 2026-05-23; role mapping: `admins` group → Admin, else Viewer.
### Arcane + Dozzle migrated to CT 109; headless agents on all hosts
- **Arcane server moved CT 104 → CT 109**: stack at `/opt/stacks/arcane/docker-compose.yml` on CT 109, `APP_URL=http://192.168.1.8:10002`. Zoraxy proxy route for `arcane.nuclide.systems` updated to `192.168.1.8:10002`. Old CT 104 arcane container stopped (renamed decommissioned).
- **Dozzle server moved CT 104 → CT 109**: stack at `/opt/stacks/dozzle/docker-compose.yml` (:10001). Remote agents configured for all 7 Docker hosts (CT 101, 104, 105, 110, 111, 112, 113).
- **Headless agents deployed on CT 101, 104, 105, 110, 111, 112** via `/opt/stacks/ops-agents/docker-compose.yml` (Arcane agent + Dozzle agent). CT 113 already had Arcane agent; updated `MANAGER_API_URL` to CT 109 and added Dozzle agent.
- **docs-server migrated CT 111 → CT 109**: `mkdocs.yml` site_url fixed, nav expanded. docs-server now at `http://192.168.1.8:13080`.
### HAOS KVM OOM protection
- **Root cause identified**: karakeep_chrome (CT 104, `gcr.io/zenika-hub/alpine-chrome:123`) triggered host-level OOM. Linux OOM killer chose HAOS KVM (PID 588858, 10.3 GB RSS) as largest victim. HA was not crashing internally — it was being killed externally.
- **shm_size: '512m' added to karakeep chrome service** and force-recreated to reduce Chrome shared memory pressure.
- **oom_score_adj=-300 set on HAOS KVM PID** (protects it from OOM killer — minimum score = hardest to kill).
- **Persistence via systemd timer** (`protect-haos-kvm.timer` on nuc): fires 60s after boot then every 5 min. Checks if KVM PID for VM 100 has oom_score_adj=-300; sets it if not.
### UNAS Pro config tracking
- **SSH key auth established**: Added nuc's RSA pubkey to UNAS Pro (`192.168.1.31`) `/root/.ssh/authorized_keys` via password SSH. Key-based auth now works from nuc without password.
- **`fkrebs/unas-conf` repo created**: Daily cron (03:20 on nuc) syncs: NFS exports, shares JSON, active exportfs, disk usage, cron config, OS version. Script at `/usr/local/bin/sync-unas-conf`.
- **NFSv4 status**: UNAS kernel already supports NFSv4 (`+4 +4.1 +4.2`) but UniFi OS doesn't configure an `fsid=0` root export. NFSv4 upgrade (docs #2) remains backlog — would require adding an unmanaged drop-in export that firmware updates could overwrite.
### PVE host optimizations
- **LXC startup order set for all CTs**: CT103 (backrest), CT104 (docker), CT105 (nextcloud), CT112 (infisical), CT113 (db) had no startup order defined. Assigned: CT113=4, CT112=6, CT104=7, CT103=8, CT105=9. Full boot order now: DNS(1) → Zoraxy(2) → Pocket-ID(3) → DB(4) → Ops(5) → Infisical(6) → Docker(7) → Backrest(8) → Nextcloud(9) → HAOS(10) → Dev(20). Also set CT109 `onboot=1` (was missing).
- **KSM (Kernel Samepage Merging) enabled**: `echo 1 > /sys/kernel/mm/ksm/run` active now; persisted via `/etc/systemd/system/ksm-enable.service`. Deduplicates identical pages across LXC containers — effective for shared libc/runtime pages. Zero-risk, online change.
- **5-minute critical watchdog timer added**: `homelab-health-quick.timer` on nuc runs `QUICK=1 homelab-health` every 5 min. Checks: all LXC status (auto-starts if stopped), HAOS KVM (notify only), critical containers (Pocket-ID, Prometheus, Grafana, LiteLLM, MCP gateway). Complements the hourly full sweep.
- **Gitea issues filed for backlog PVE opts** (`fkrebs/docs` #2-4): NFS v3→v4 upgrade, ZFS ARC increase to 16 GiB (blocked on CT101 right-sizing), hugepages for HAOS KVM.
### n8n flow tracking
- **Gitea repo `fkrebs/n8n-flows` created**: exports all n8n workflows as individual JSON files. Daily cron (CT 104, 03:30) syncs via `n8n export:workflow --all` → split → commit + push.
- **IDEAS.md**: 10 automation ideas documented with node designs (health check flow, Backrest notify, Docling pipeline, HA alerts, Gitea digest, WAL-G monitor, etc.).
- **10 Gitea issues filed** on `fkrebs/n8n-flows` for each idea — tracked in memory.
- **Homelab health check (bash/systemd)**: interim implementation on nuc (`/usr/local/bin/homelab-health`, `homelab-health.timer`, every 1h). Checks: container status per host via SSH, HA VM status, disk usage, key HTTP endpoints. Auto-fix: `docker start` for stopped containers, `docker prune` for CT104 /tmp >80%. Escalates to Gotify (`homelab-health` app, token `Az9NpC-m1jjf571`) for crash loops/disk critical. Migration to n8n tracked as issue #1.
### CT 104 ai repo cleanup
- **lobehub/data/ untracked**: Postgres data dir was accidentally tracked in git. `git rm --cached -r lobehub/data/` + added to `.gitignore`. Committed alongside: litellm-config/config.yaml, mcp-gateway updates (litellm_sync.py, model_assignments.json, models.html, server.py), lobehub.yml (claude-* models).
### Docs
- **`README.md` expanded**: monitoring quick-links table (Homarr, Grafana, Prometheus, Loki, Alloy UI), public services table by category, operator-only LAN links, related repos table (homelab-configs, klipper-config, n8n-flows).
- **`infra/portmap.md` updated**: added Loki :3100, pve-exporter :9221, Alloy :12345, Homarr :7575; Alloy deployment notes across all 12 hosts; updated monitoring section.
- **`services/homelab-architecture.md`** CT 109 description updated to reflect full stack.
- **`services/pocket-id.md` created**: OIDC endpoints, client creation walkthrough, per-service env var patterns (Homarr, Grafana, Gitea, Coder, Generic), current client registry, backup notes.
## 2026-05-22
- **Pocket-ID backup wired into Backrest**: CT 110 SQLite-only (no Postgres). Pre-backup hook script at `/opt/backrest/scripts/pocketid-prestage.sh` on CT 103: SSHs to CT 110, runs `pocket-id export` inside container, `docker cp`s ZIP to host, scp's ZIP + signing keys to `/mnt/pve/unas/services/pocketid-backup/`. `services-backup-plan` snapshots staging → jottacloud. Verified: hook fires before snapshot, snapshot includes Pocket-ID data.
- **Vaultwarden data → local zfs**: Moved `/mnt/pve/unas/services/vaultwarden` to `/opt/stacks/vaultwarden/data` (local NVMe). DB was already on CT 113 postgres; only `rsa_key.pem`, `config.json`, `attachments/` remain in the data dir. Stale `db.sqlite3` deleted. NFS copy at `services/vaultwarden/` is now orphaned and can be cleaned up.
- **WAL-G backup script fixed (CT 104)**: All nightly postgres backups were failing silently. Three bugs fixed: (1) `set -e` causing script abort when `shared-postgres` container not found (it was migrated to CT 113); (2) missing `-u postgres` on all `docker exec` calls — WAL-G requires the postgres role; (3) missing explicit data path `/var/lib/postgresql/data` on `backup-push`. `shared-postgres` commented out (CT 113 has its own WAL-G cron). `immich_postgres` and `lobe-postgres` now back up successfully to Garage S3.
- **apps/ directory decommissioned (CT 104)**: `/opt/stacks/apps/` contained dead duplicate compose files for gotify, karakeep, memos, paperless-ngx, traccar, vaultwarden, shared-db — all without `.env` files, never running. Renamed to `.DECOMMISSIONED-2026-05-22` following established pattern. The canonical stacks at `/opt/stacks/<service>/` are unaffected.
- **ComfyUI `--async-offload` removed**: Flag caused full CPU fallback on GGUF img2img with XPU (3.5 min/step instead of ~7 s/step) and pure-noise output. Removed from `ai/comfyui.yml`. Current args: `--listen 0.0.0.0 --enable-cors-header --use-pytorch-cross-attention --disable-smart-memory --force-fp16`. See `infra/proxmox-memory-audit.md`.
- **CT 105 Nextcloud CIFS → NFS**: Nextcloud was crash-looping ("Appdata is not present") because `unas_smb` CIFS mount (`//192.168.1.31/storage`) was not mounted on the host. Root cause: fstab entry was missing `_netdev`; mount dropped after a host event and was never re-established. Fixed by migrating CT 105 `mp0` from `/mnt/pve/unas_smb` to `/mnt/pve/unas` (same NFS share all other CTs use). CIFS entry removed from fstab. All 9 Nextcloud containers healthy post-restart.
- **arr stack started**: `vpn_gluetun`, `shelfarr` (`:13004`), `rdtclient` (via VPN, `:13001`), `prowlarr` (`:13002`), `audiobookshelf` (`:13003`). Fixed empty `labels:` in VPN compose.
- **claude-max-bridge**: new service on CT 104 (`ai-internal`, no host port). FastAPI shim wrapping `claude` CLI as an OpenAI-compatible `/v1/chat/completions` endpoint. Enables LiteLLM to route to Claude Max subscription at zero API cost. Key details: `--strict-mcp-config` (no MCP servers), `--input-format stream-json` (multi-turn), `--include-partial-messages` (real streaming deltas). Bind-mounts `/usr/local/bin/claude` + `/root/.claude` (rw for OAuth token refresh). Source at `git.nuclide.systems/fkrebs/claude-max-bridge`.
- **LiteLLM Claude models**: `claude-sonnet-4-6`, `claude-opus-4-7`, `claude-haiku-4-5` + short aliases (`sonnet`, `opus`, `haiku`) + effort variants (`claude-sonnet-4-6-high`, `claude-opus-4-7-high`) added via `openai/` provider pointing at `claude-max-bridge:8000/v1`. Priced at Anthropic list rates for cost-visibility — actual cost $0 (Max). Bridge features: temperature→effort mapping, `extra_body.effort` override, `extra_body.fallback_model`, `x_claude_cost_usd` and `x_claude_rate_limit` in responses.
- **claude-max-bridge v2**: additional CLI parameter mappings: `extra_body.system_prompt_mode` (`replace`/`append``--system-prompt`/`--append-system-prompt`), `extra_body.max_turns``--max-turns`, `extra_body.max_budget_usd``--max-budget-usd`, `response_format.json_schema``--json-schema`, `extra_body.exclude_dynamic_system_prompt_sections``--exclude-dynamic-system-prompt-sections`. Build context moved from `/tmp/` to `/opt/stacks/ai/claude-max-bridge/` (survives reboots). Source committed to Gitea at `a781abb`.
- **Shepard MongoDB pinned to 8.0.4**: `mongo:8.0` (latest) introduced a fatal kernel-version check in 8.0.5+ that rejects Proxmox's `7.0.2-5-pve` kernel string (parsed as major version 7 ≥ 6.19). Pinned to `mongo:8.0.4` in `/opt/shepard/infrastructure/docker-compose.yml`. Track MongoDB bug SERVER-121912 for a patched upstream release before unpinning.
- **ComfyUI XPU optimisation**: removed `--lowvram` + `--reserve-vram 1.0`; added `--async-offload` (Intel-patched XPU streams, ~10% speedup) + `--force-fp16`; memory limit 20 G → 24 G (FLUX peaks ~22 GB in shared XPU RAM). `--disable-smart-memory` kept since `get_free_memory()` returns the full 58 GB shared pool on Arc — smart eviction is blind to the cgroup limit. See `infra/proxmox-memory-audit.md`.
- **WaveSpeed FBC workflow**: `ApplyFBCacheOnModel(residual_diff_threshold=0.12)` + `FluxGuidance(guidance=3.5)` + `BasicGuider` + `SamplerCustomAdvanced` added to FLUX.1-schnell pipeline. Workflow saved at `/mnt/pve/unas/services/comfyui/input/flux-schnell-wavespeed.json`. Estimated 3050% additional speedup by caching first-block activations. WaveSpeed FBCache confirmed device-agnostic (no CUDA guards); `torch.compile` node must NOT be used on XPU.
- **LiteLLM benchmark**: `bench.py` at `/opt/stacks/ai/benchmark/` measures TTFT + TPS for all configured models using streaming requests; renders xychart diagrams via internal Kroki (`http://kroki:8000`). First run results: Mistral fastest TTFT (43 ms), Claude bridge competitive at 101143 ms. See `services/llm-benchmark.md`.
- **MCP gateway**: added `git`, `gitlab` (DLR, `--pass-environment` fix for mcp-proxy env passthrough bug), `paper-search` (Docker catalog image replaces inline `python:3.12-slim`); `UNPAYWALL_EMAIL` wired into paper-search. Removed duplicate `papersearch` container. Updated `services/mcp-gateway.md`.
- **Nextcloud MCP re-enabled**: `cbcoutinho/nextcloud-mcp-server` running in `single_user_basic` mode with `NEXTCLOUD_VERIFY_SSL=false`. App-password generated non-interactively via `occ user:add-app-password`. Gateway patched: (1) `NEXTCLOUD_VERIFY_SSL` / `MCP_DEPLOYMENT_MODE` now baked into hardcoded SERVERS env; (2) startup overlay extended to persist `enabled` flag; (3) proxy route bypasses per-user credential provisioning when `MCP_DEPLOYMENT_MODE=single_user_basic`. 26 servers healthy.
- **CT 109 renamed `observe``ops`**: updated `ct-inventory.md`, `homelab-architecture.md`, `proxmox-state.md` (IP changed to `.8` after conflict resolution).
- **CT 101 Shepard disk expanded**: 100 GiB → 500 GiB (ZFS online resize, no restart).
- **CT 112 Infisical marked live**: `ct-inventory.md` and `portmap.md` updated; LAN-only, no Zoraxy route.
- **litellm DB password rotated**: placeholder `litellm_password_here` replaced in both `ai/.env` and `litellm-config/config.yaml` (config.yaml takes precedence over env var). Container restarted, healthy.
- **UNAS migration dumps cleaned**: 7 × `*-migration-20260521.sql` (~492 MB) deleted from `/mnt/pve/unas/dump/`.
- **Arcane OIDC secret rotated**: new secret updated in Pocket-ID SQLite DB (bcrypt, cost 10) and `/opt/stacks/arcane/docker-compose.yml`. Container restarted.
- **n8n OIDC secret rotated**: Pocket-ID DB bcrypt update; secret applied to `/opt/stacks/n8n/docker-compose.yml` (authoritative stack). `/opt/stacks/apps/n8n/` was a dead duplicate (no `.env`, never ran) — compose renamed `.DECOMMISSIONED-2026-05-22`.
- **n8n encryption key rotated**: `N8N_ENCRYPTION_KEY` + `N8N_USER_MANAGEMENT_JWT_SECRET` replaced with 32-byte hex values in `/opt/stacks/n8n/.env` and `/opt/stacks/n8n/data/config`. Container restarted, healthy. Any previously stored workflow credentials are now invalid and must be re-entered.
- **Backrest repo passwords rotated**: `tapirnase` (weak placeholder) replaced on both `media-repo` and `services-repo` (JottaCloud). Used `restic key add` + `restic key remove`; `config.json` updated; daemon restarted.
- **Obsidian MCP (`mcpvault`)**: new server added. `bitbonsai/mcpvault` bridged via `mcp-proxy` stdio→HTTP in a custom `mcp-obsidian-bridge` image (Node 3.12-Alpine + mcp-proxy). Vault: Nextcloud `Notizen/` folder at `/mnt/pve/unas/services/nextcloud/fkrebs@nucli.de/files/Notizen`, bind-mounted read-write. 15 tools: read/write/patch/search/tags/frontmatter/stats. TTL cache 30s. Registered in Claude Code settings.json and LobeHub for both users.
- **LobeHub MCP sync**: all 27 gateway servers synced into `user_installed_plugins` for both users; stale `papersearch` entry removed; all manifests updated to long-lived `mcp_*` gateway token. Skipped: `crawl4ai` (no `/mcp` endpoint in `unclecode/crawl4ai:latest`), `n8n` (uses own auth).
- **CT 109 plan updated**: IP conflict noted (`.6` taken by CT 113); RAM 8 GiB, disk 50 GiB; Loki added; sshwifty added (multi-tab web SSH); Alloy replaces Promtail on all hosts; sidecar table expanded to CT 103/111/113.
- **Portmap**: CT 113 (`db`, `.6`) section added; QNAP TS-251D (Klipper/Mainsail) section added.
- **New docs**: `services/backrest.md`, `services/databases.md`, `services/arcane.md`.
## 2026-05-21
### Docs & docs infrastructure
- **Mermaid diagrams now rendering** in mkdocs Material site — was missing `custom_fences` config in `pymdownx.superfences`. Fixed in `docs-server/mkdocs.yml` and rebuilt container.
- **`site_url` corrected** to `http://192.168.1.111:13080` (docs are LAN-only; no Zoraxy route exists).
- **Docs restructured**: "Stacks (CT 104)" chapter dissolved; PORTMAP, Storage, Volumes, Docker networks moved to `infra/` section. TODO items migrated into CHANGELOG + ideas.
- **Secrets manager design doc** added at `services/secrets-manager.md` — recommends Infisical on a new CT 112.
### Storage / S3
- **`s3.nuclide.systems` Zoraxy route fixed** — `EnableWebsocketCustomHeaders`, `DisableHopByHopHeaderRemoval`, `EnableAutoHTTPS` all set. Garage S3 API now reachable externally.
- **`chat-artifacts` Garage S3 bucket created** — public-read via `https://chat-artifacts.s3.nuclide.systems/<key>`. Credentials at `/root/garage-chat-artifacts.creds` (not git-tracked).
### MCP surface
- **`upload-artifact` MCP server** deployed (port 18011, CT 104). Tools: `upload_text`, `upload_base64`, `list_artifacts`, `get_url`, `delete_artifact`. Registered in gateway (now 24 servers). Source: `git.nuclide.systems/fkrebs/mcp-upload-artifact`.
- **ComfyUI MCP** now auto-uploads completed images to `chat-artifacts` S3 and includes the public URL alongside the inline base64 image.
- **DAYTONA_API_KEY** removed from CT 104 `ai/.env` (Daytona decommissioned 2026-05-20).
### Dev environment
- **`savefig` dotfiles helper** added to `fkrebs/dotfiles` — uploads any file to `chat-artifacts` using `uv run --with boto3`; reads credentials from env vars.
- **Coder `python-uv` template** updated with `CHAT_ARTIFACTS_*` env vars baked in; template pushed to Coder server.
### Gitea repos
- New repos created: `mcp-comfyui`, `mcp-docling`, `mcp-shepard`, `mcp-upload-artifact`, `home-assistant-config` — all at `git.nuclide.systems/fkrebs/`.
- **HAOS config** (`192.168.1.60`, VM 100) has a Gitea upstream ready. Manual push needed from HA terminal (see `services/secrets-manager.md` for SSH access notes).
### Planned
- CT 112 "secrets" — Infisical deployment (see `services/secrets-manager.md`)
- Renovate Bot via Gitea Actions — dependency updates for MCP server repos (Dockerfiles + requirements.txt)
- Vaultwarden OIDC SSO wiring (client created in Pocket-ID 2026-05-21; auth flow not yet configured)
- LobeHub Artifacts → S3 patch (TypeScript, ~3-4h, deferred)
## 2026-05-20
### Auth / Identity (single sign-on lockdown)
- **Pocket-ID migrated CT 104 → CT 110 "id"** (`192.168.1.5:11000`). Authoritative writes now on CT 110; old data dir on CT 104 is a stale snapshot. Compose on CT 104 renamed `.MIGRATED-TO-CT110-2026-05-20`. Zoraxy upstream for `id.nuclide.systems` flipped.
- **Pocket-ID 2.7.0 uses bcrypt for OIDC client secrets** — previously some clients (Gitea, Coder) had raw 64-char SHA-256 hex in `oidc_clients.secret`, which silently failed every token exchange. Regenerated both with proper bcrypt hashes; audit also surfaced an orphaned Daytona client and the empty-secret `vscode` row.
- **Gitea SSO via Pocket-ID** wired with bcrypt fix + correct `Scopes` (`openid,profile,email`) + `two_factor_policy=skip` + `ACCOUNT_LINKING=auto` + `ENABLE_AUTO_REGISTRATION=true` + `USERNAME=preferred_username`. Local password sign-in form disabled (`GITEA__service__ENABLE_PASSWORD_SIGNIN_FORM=false`); legacy OpenID 2.0 button hidden (`GITEA__openid__ENABLE_OPENID_SIGNIN=false`).
- **Coder SSO via Pocket-ID** wired. User row flipped `login_type=password → oidc`. `CODER_DISABLE_PASSWORD_AUTH=true` + `CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE=false`. Workspace-side GitHub external_auth left on (for repo cloning).
### Dev environment (new CT 111 "dev", `192.168.1.42`)
- New LXC stood up with Coder + Gitea + their Postgres companions.
- Templates pushed to Coder: **`python-uv`** (persistent, GPU, code-server, baked LiteLLM + Anthropic env, Claude Code auto-install) and **`mcp-sandbox`** (ephemeral, sci stack pre-baked).
- GPU passthrough confirmed: `/dev/dri/by-path/pci-0000:00:02.0-render` exposed; CT 111-level symlink at `/dev/dri/renderD128` (systemd-tmpfiles) so the Docker provider's path parser is happy.
- `dotfiles` repo created at `fkrebs/dotfiles` on Gitea — oh-my-zsh, LiteLLM model picker (`models.env`), docker alias set, `~/.claude/settings.json` permissions allowlist, global Python `CLAUDE.md`, `.gitignore_global`, VSCodium settings + extensions.txt.
- `mkproj` helper appends `[tool.coder]` workspace+owner block to new `pyproject.toml`, copies pre-commit config + `.gitignore`, installs dev deps, initial commit.
### Reverse proxy
- **Zoraxy WebSocket forwarding enabled** for `dev.`, `git.`, `mcp.nuclide.systems` (`EnableWebsocketCustomHeaders=true` + `HeaderRewriteRules.DisableHopByHopHeaderRemoval=true`). Coder agent / Gitea live updates / streamable-MCP all need this. Backups at `/tmp/zoraxy-backup-2026-05-20/` on CT 108.
### MCP gateway
- **`daytona` removed**, **`coder` added** in `/opt/stacks/ai/mcp-gateway/config.json`. The `morning-briefing` scheduled agent's server list updated. Built a small `coder-mcp` image (multi-stage from `ghcr.io/coder/coder:latest` + `uv:bookworm-slim` + `uv tool install mcp-proxy`) that `coder login`s once then runs `coder exp mcp server` behind streamable-HTTP.
- Coder MCP exposes tools `coder_create_task` + workspace lifecycle from `Coder Agent v1.27.1`.
### Decommissions
- **Daytona** (`/opt/stacks/daytona/` + `/opt/stacks/ai/daytona-mcp.yml` on CT 104) — containers stopped + removed; compose files renamed `.DECOMMISSIONED-2026-05-20`. Coder replaces it.
### Docs / governance
- **`/CLAUDE.md` created on host** — operator doctrine for any agent SSHing in: keep things running, enforce consistency, plan rollback at medium-risk+, services AI-accessible by default, test before declaring done, generate slash commands for recurring tasks, inventory skills/commands/MCPs at session start.
- **`/docs` is now a git repo**, baseline at `04d54ac`.
- **Doc reorg** into `infra/ services/ history/ ideas/`. Traefik guides moved to `history/` (Zoraxy is permanent). `mcp-gateway-requirements.md` and `scrubbing-list-2026-05-17.md` also archived.
## 2026-05-19
- **Garage moved off NFS-on-UNAS to local zfs** on CT 104 (`/opt/stacks/shared-db/garage/{meta,data}`) after a WAL-G outage. Tier-1 (SQLite) databases off NFS as a follow-up; UNAS reserved for bulk media + workspace home dirs.
- **n8n reverted to local SQLite** after a Postgres migration attempt failed.
## 2026-05-17
- MCP gateway design frozen. Implementation in `services/mcp-gateway.md`; design rationale archived as `history/mcp-gateway-requirements.md`.
- Per-stack scrubbing pass (see `history/scrubbing-list-2026-05-17.md` for the inventory at that snapshot).
## 2026-05-16
- **Traefik abandoned**, **Zoraxy is the production reverse proxy**. Two migration guides preserved in `history/` for context only.
-2821
View File
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
# nuclide.systems docs
Single source of truth for the homelab. Git-tracked since 2026-05-20.
## Read this first
- **[`/CLAUDE.md`](../CLAUDE.md)** (host root) — operator doctrine for any AI agent SSHing in. Skim before touching anything.
- **[`RESUME.md`](RESUME.md)** — open items + recent changes; update at end of every session.
- **[`CHANGELOG.md`](CHANGELOG.md)** — what changed and when.
- **[`ct-inventory.md`](ct-inventory.md)** — canonical roster of LXCs / VM, IPs, role, sizing.
## Layout
```
/docs/
├── README.md ← you are here
├── CHANGELOG.md ← dated bullets of every infra change
├── ct-inventory.md ← LXC/VM roster (single source of truth)
├── infra/ ← host-level + cross-cutting state
│ └── proxmox-state.md ← the 900-line master state doc (sizing, ZFS, NFS, DNS)
├── services/ ← per-service operational docs (current truth)
│ ├── homelab-architecture.md topology + design rationale
│ ├── dev-environment.md Coder + Gitea on CT 111
│ ├── mcp-gateway.md MCP gateway — DECOMMISSIONED 2026-05-26; see mcp-servers.md for Bifrost
│ ├── comfyui.md
│ ├── zoraxy.md reverse proxy
│ ├── adguard-dns.md DNS + rewrites
│ ├── cloud-gpu.md GPU passthrough + future external GPU
│ └── llm-benchmark.md LiteLLM model TTFT + TPS benchmark
├── infra/ (continued)
│ ├── portmap.md canonical service → port → public hostname registry
│ ├── storage.md storage class per service (source of truth)
│ ├── volumes.md volume bindings per stack
│ ├── proxmox-memory-audit.md CT allocations, host budget, ComfyUI memory analysis
│ └── docker-networks.md
├── stacks/ ← CT 104 agent notes + legacy stack docs
│ └── CLAUDE.md agent breadcrumbs (CT 104 specific)
├── services/ (continued)
│ ├── backrest.md Backrest backup (CT 103)
│ ├── secrets-manager.md Infisical (CT 112)
│ ├── databases.md Postgres CT 113 + per-stack DBs
│ └── doc-ingestion.md Paperless + Docling
├── history/ ← frozen / archived
│ ├── traefik-migration.md ABANDONED 2026-05-16 (Zoraxy chosen)
│ ├── traefik-migration-docker-labels.md ABANDONED 2026-05-16
│ ├── mcp-gateway-requirements.md SUPERSEDED by services/mcp-gateway.md
│ ├── scrubbing-list-2026-05-17.md snapshot of cleanup pass
│ └── case-study.md narrative writeup
├── ideas/
│ └── stack-ideas.md
└── security/ ← audits + leak analyses
├── data-leak-audit-comparison.md
├── data-leak-audit-2026-05-20-tr004-cloud-sandbox.md
├── data-leak-audit-2026-05-21-tr004-artifacts.md
└── audit-claude-code-meta.md self-audit of the Claude Code session that produced these
```
## Operating principles
See [`/CLAUDE.md`](../CLAUDE.md) for the full doctrine. Highlights:
1. Keep services running; surface synergies and gaps.
2. Enforce consistency: Pocket-ID OIDC, Zoraxy + ACME, `<svc>.nuclide.systems`, secrets in `.env`.
3. Plan rollback at medium+ risk (ZFS snapshots, fresh experimental CTs).
4. Every service should be AI-accessible (MCP / Coder workspace CLI / documented API).
5. Test end-to-end before declaring done.
6. Generate slash commands for recurring maintenance.
## Quick links
### Monitoring (CT 109 · 192.168.1.8)
| Service | URL | Notes |
|---|---|---|
| Homarr | `http://192.168.1.8:7575` | Service dashboard — start here |
| Grafana | `http://192.168.1.8:3000` | Dashboards (admin/tapirnase) |
| Prometheus | `http://192.168.1.8:9090` | Metrics — 90d retention |
| Loki | `http://192.168.1.8:3100` | Logs — 30d retention, 13 hosts |
| Alloy UI | `http://192.168.1.8:12345` | Log agent pipeline inspector |
### Public services (`*.nuclide.systems`)
- **AI**: [ai.nuclide.systems](https://ai.nuclide.systems) (Bifrost — LLM + MCP gateway) · [chat.nuclide.systems](https://chat.nuclide.systems) (Open WebUI)
- **Files**: [nc.nuclide.systems](https://nc.nuclide.systems) (Nextcloud) · [paperless.nuclide.systems](https://paperless.nuclide.systems) · [immich.nuclide.systems](https://immich.nuclide.systems)
- **Dev**: [git.nuclide.systems](https://git.nuclide.systems) (Gitea) · [dev.nuclide.systems](https://dev.nuclide.systems) (Coder)
- **Home**: [ha.nuclide.systems](https://ha.nuclide.systems) (Home Assistant) · [hoarder.nuclide.systems](https://hoarder.nuclide.systems) (Karakeep) · [memos.nuclide.systems](https://memos.nuclide.systems)
- **Identity**: [id.nuclide.systems](https://id.nuclide.systems) (Pocket-ID) · [vault.nuclide.systems](https://vault.nuclide.systems) (Vaultwarden)
### Operator-only (LAN)
- Proxmox PVE: `https://192.168.1.20:8006`
- Zoraxy admin: `http://192.168.1.4:8000` (**always confirm before any change**)
- AdGuard: `http://192.168.1.2` · Backrest: `http://192.168.1.3:9898` · Infisical: `http://192.168.1.7:8200`
## Related repos
| Repo | Content | Auto-sync |
|---|---|---|
| [fkrebs/docs](https://git.nuclide.systems/fkrebs/docs) | This doc site | Push after every doc change |
| [fkrebs/homelab-configs](https://git.nuclide.systems/fkrebs/homelab-configs) | Homarr config, Alloy configs, monitoring compose files | Cron 03:00 on CT 109 |
| [fkrebs/n8n-flows](https://git.nuclide.systems/fkrebs/n8n-flows) | n8n workflow JSON exports + ideas backlog (10 issues) | Cron 03:30 on CT 104 |
| [fkrebs/zoraxy-conf](https://git.nuclide.systems/fkrebs/zoraxy-conf) | Zoraxy proxy routes (CT 108) | Cron 03:00 on CT 108 |
| [fkrebs/adguard-conf](https://git.nuclide.systems/fkrebs/adguard-conf) | AdGuard Home config (CT 102) | Cron 03:00 on CT 102 |
| [fkrebs/pve-conf](https://git.nuclide.systems/fkrebs/pve-conf) | Proxmox LXC/VM configs + storage | Cron 03:00 on nuc |
| [fkrebs/ct103-backrest](https://git.nuclide.systems/fkrebs/ct103-backrest) | Backrest config + scripts (CT 103) | Cron 03:15 on nuc |
| [fkrebs/ct104-stacks](https://git.nuclide.systems/fkrebs/ct104-stacks) | CT 104 untracked stack composes (no .env) | Cron 03:15 on nuc |
| [fkrebs/ct110-pocket-id](https://git.nuclide.systems/fkrebs/ct110-pocket-id) | Pocket-ID compose (CT 110) | Cron 03:15 on nuc |
| [fkrebs/ct111-dev](https://git.nuclide.systems/fkrebs/ct111-dev) | Coder templates + act-runner (CT 111) | Cron 03:15 on nuc |
| [fkrebs/ct112-infisical](https://git.nuclide.systems/fkrebs/ct112-infisical) | Infisical compose (CT 112, private) | Cron 03:15 on nuc |
| [fkrebs/unas-conf](https://git.nuclide.systems/fkrebs/unas-conf) | UNAS Pro NFS exports, disk usage, cron (192.168.1.31) | Cron 03:20 on nuc |
| [fkrebs/grafana-dashboards](https://git.nuclide.systems/fkrebs/grafana-dashboards) | Grafana dashboard JSON exports + datasources (CT 109) | Cron 03:25 on CT 109 |
| [fkrebs/ct113-db](https://git.nuclide.systems/fkrebs/ct113-db) | Postgres + pgAdmin compose (CT 113) | Cron 03:15 on nuc |
| [fkrebs/klipper-config](https://git.nuclide.systems/fkrebs/klipper-config) | Klipper 3D printer config | Daily cron from QNAP |
## Rendered docs site
- Source: `https://git.nuclide.systems/fkrebs/docs`
- Rendered (LAN-only): `http://192.168.1.8:13080`
- Build: docs-server on CT 109 polls Gitea every 5 min; rebuilds with mkdocs Material on push.
+173
View File
@@ -0,0 +1,173 @@
# Session Resume
Last updated: 2026-05-26.
## Open items (urgency order)
### 🔴 Critical — security risk or unrecoverable data loss
- [ ] **Password rotation: `tapirnase`** — shared across WiFi PSK, LiteLLM root key (`sk-tapirnase`), D-Link admin, Backrest repo password, HAOS Terminal & SSH addon password. Single sniff = broad blast radius. Rotate per-service. See `services/homelab-architecture.md` §operational rules.
- [ ] **Nextcloud Borg passphrase → Vaultwarden** — passphrase only in container env; CT 105 loss = unrecoverable backup. Move to Vaultwarden. See `services/backrest.md` blind spot #4b.
- [ ] **Vault secrets sweep** — 32 plaintext secrets in Obsidian vault notes (Anthropic key, Gemini, NC AIO passphrase, etc.). Tracker at `Home/Homelab/Secrets to Move.md` in vault. Migrate to Vaultwarden, redact notes, rotate the high-risk ones.
### 🟠 High — known broken / verification needed
- [x] ~~**Arcane OIDC secret**~~ — fixed 2026-05-23.
- [x] ~~**Backrest media-repo / video-projects**~~**all three plans succeeded 2026-05-26** (media 01:30, services 01:04, video-projects 04:00). `video-projects-plan` had never completed before this. ✅
- [x] ~~**AdGuard split-horizon DNS**~~ — done 2026-05-23.
- [x] ~~**WAL-G monitoring**~~ — done 2026-05-23. Textfile collector (`/usr/local/bin/walg-metrics.sh`) on CT 113 emits `walg_last_success_timestamp_seconds` + `walg_archive_status` to node-exporter textfile. Prometheus alert rules deployed on CT 109: `WalgArchiveStale` (>24h) + `WalgArchiveFailed`.
- [x] ~~**Zoraxy audit**~~ — done 2026-05-23. All 22 routes verified; arcane updated to CT 109 backend; `SkipWebSocketOriginCheck` enabled on 13 routes; decommissioned routes noted. Missing: `dozzle.nuclide.systems` route (never created), `immich-tools`, `paperless`, `paperless-ai`. See `services/zoraxy.md`.
### 🟡 Medium — incomplete migrations / cleanup debt
- [ ] **Docker disk reclaim** — ~17 GB reclaimable on CT 104.
- [ ] **Dormant stacks audit** — ~6 still to audit (dozzle CT 104 copy, arr-stack, qdrant, etc.). 4 decommissioned 2026-05-23.
- [x] ~~**Config-to-git fleet**~~ — 8 repos as of 2026-05-24. PVE/CT 108/CT 102/HA/CT 103/CT 109/CT 113/obsidian-vault/obsidian-config. Full table in `infra/config-to-git.md`. Manual step pending: HA Terminal & SSH addon `init_commands` for cron persistence.
- [ ] **D-Link hardening** — admin UI at `http://dlink.nuclide.lan` (`192.168.1.10`). Trusted-host allowlist + SNMP + TLS.
- [ ] **Vaultwarden OIDC SSO** — Pocket-ID client ready, env vars not yet set in `/opt/stacks/vaultwarden/.env`.
- [ ] **Infisical OIDC config** — manual step in Infisical admin at `http://secrets.nuclide.lan:8200/admin` → Settings → OIDC. Client `b2069075-…`, secret `qsANw95zsza0_…`.
- [ ] **Infisical secrets migration Phase 2** — import CT104 `.env` keys via `infisical import`.
- [ ] **Grafana HAOS dashboard** — HA recorder reconfiguration (exclude Prometheus-covered metrics).
- [x] ~~**HAOS SSH key**~~ — done 2026-05-27.
- [x] ~~**Obsidian config drift**~~ — removed `obsidian42-brat`, `obsidian-tasks-plugin`, `tasks-caldav-sync` from `community-plugins.json` 2026-05-27.
- [ ] **Memos Sync plugin install** — config pushed to `obsidian-config`, plugin binary needs Community-Plugins install + paste of pre-baked `data.json`.
- [ ] **Immich HTTP/2 Keep-Alive** — root-caused 2026-05-24. Workaround live (per-SSID LAN URL). Permanent fix: strip header at Zoraxy. **Needs Zoraxy confirmation.**
### 🔵 Planned — requires infrastructure or significant effort
- [ ] **Second NVMe** — needed before rpool mirror + Postgres consolidation (CT 113). Blocks several items.
- [ ] **Infisical secrets migration** — CT 112 deployed (`192.168.1.7:8200`, LAN-only). Phase 1 done. Remaining:
- **Phase 2**: create project `homelab/ct104`, import `ai/.env` (~55 keys) via `infisical import`, add agent sidecars to compose stacks
- **Phase 3**: replace hardcoded env vars in Coder `main.tf` (committed to Gitea — security risk)
- **Phase 4**: OIDC SSO via Pocket-ID (low priority — convenience)
See `services/secrets-manager.md`.
- [x] ~~**Consolidate syncstack → MCP gateway**~~ — done 2026-05-23. `litellm_sync.py` ported from syncstack, mounted into gateway. Gateway runs `run_sync()` every 15 min (120s startup delay). `/etc/cron.d/syncstack` deleted; `Dockerfile.syncstack``.DECOMMISSIONED-2026-05-23`; `/opt/stacks/CLAUDE.md` updated.
- [ ] **ZFS ARC tuning** — raise `zfs_arc_max` from 6.2 GiB → 16 GiB once CT 101 memory right-sized. See `infra/proxmox-state.md` §3.
- [ ] **Document ingestion n8n workflow** — Docling MCP deployed; n8n orchestration not built. Nextcloud/Paperless → Docling → embeddings → LobeChat KB. See `services/doc-ingestion.md`.
- [ ] **ComfyUI async queue** — MCP tool blocks 90300 s. Job-queue pattern: `generate_image()` returns ID; `get_job_status()` polls. See `services/comfyui.md`.
- [ ] **Additional MCP servers** — Gitea ✓, Paperless ✓, Proxmox-VE ✓ (done 2026-05-23). Remaining: Karakeep (needs API key from hoarder UI), Vaultwarden, Audiobookshelf.
- [x] ~~**Arcane + Dozzle → CT 109**~~ — done 2026-05-23.
- [x] ~~**crawl4ai SSE StreamConsumed**~~ — resolved 2026-05-22.
## Recently completed (2026-05-26)
- **Backrest 3-for-3 success**: all plans now completing on schedule. `video-projects-plan` recovered from never-completed state.
## Recently completed (2026-05-24)
### Vault + Obsidian
- **Vault dedup**: `Work 1/` promoted → `Work/`, old `Work/` parked as `Work.stale-backup-2026-05-24/` (1-week safety net). Rescued unique `Journal/Journal.md` before swap. Killed dup `Home/BrainBox.md`, `Home/Templates/`, `Willkommen.md` ×2, `.caldav-sync/`.
- **234 asset-link rewrites** across 41 files — collapsed `assets/`, `../assets/`, `../../foo.pdf``.assets/…`. Broken links 242 → 9 (rest non-issues: `tel:`, `about:reader?`, `siyuan://`).
- **`Home/Homelab/Secrets to Move.md`** — 32 plaintext secrets inventoried.
- **`Home/Homelab/Obsidian Plugin Audit.md`** — 17 installed plugins keep/watch/re-evaluate, 3 config-drift items, 10 new candidates ranked for the stack.
- **`Home/Homelab/App Endpoint Checklist.md`** — per-service LAN + external URLs + apps-to-update list. Gotchas: clients sticking to old IP, Immich HTTP/2 Keep-Alive bug.
- **Daily-note merge policy**: Claude edits land in `Work/Journal/YYYY/MM/YYYY-MM-DD.md` under `## 🤖 Claude edits` (not parallel folder).
- **Obsidian Git plugin**: wired end-to-end against `fkrebs/obsidian-config`. Auto-commit + push every 60 min, pull on boot, PAT gitignored. Verified working.
- **Streamlined-ui CSS snippet** pushed to `obsidian-config` (hides ribbon icons for non-clickable plugins, compact status bar, tighter file tree).
- **Memos Sync plugin config** pre-pushed (binary install pending user).
### Config-to-git fleet expanded to 8 repos
- New: `fkrebs/ct103-conf`, `fkrebs/ct109-conf`, `fkrebs/ct113-conf`, `fkrebs/home-assistant-config`, `fkrebs/obsidian-vault` (rolling 2-commit window), `fkrebs/obsidian-config`.
- All daily 03:00 / 04:00. Pattern documented in `infra/config-to-git.md`.
- Rule articulated: every host with hand-edited config must push daily.
### Networking — `*.nuclide.lan` zone
- **30 AdGuard A-records** on CT 102 — per-host (`pve`, `nas`, `unifi`, `dlink`, `backrest`, etc.) + per-service aliases (`immich`, `vault`, `karakeep`, `grafana`, `prometheus`, …).
- Documented in `services/adguard-dns.md`.
- Solves "client registered to old IP" gotcha — when service moves CT, edit one AdGuard rewrite instead of N apps.
- D-Link IP captured: `192.168.1.10`.
- Filed feedback memory: always use `*.nuclide.lan` aliases in URLs, never bare IPs.
### Homarr
- **3 new boards** built via SQL: `home` (morning routine, default), `admin` (by-tier), `command` (live-ops iframes + grid). Old "nuclide" board was lost when OIDC user was recreated for password reset.
- Boards owned by `fkrebs@nucli.de`, `is_public=1`. 33 unique apps with ping status.
- Integrations not yet wired (Proxmox / Docker / AdGuard / Gotify widgets — need tokens).
### Pocket-ID
- **LAN callback URLs added** for Homarr, Grafana, Infisical, Proxmox VE so OIDC works via both bare-IP and `.nuclide.lan` alias paths.
### Other
- **Immich Android upload bug** diagnosed: HTTP/2 + `Keep-Alive` header conflict. Workaround live (per-SSID LAN URL). Permanent fix pending Zoraxy header strip.
- **Nexa #1 filed** at https://git.nuclide.systems/fkrebs/nexa/issues/1 — 9 design patterns from Obsidian + Claude session as input for Nexa Phase 12.
- **Wi-Fi SSID captured**: `nuclide`. Used for per-network app routing (Immich, HA Companion).
## Recently completed (2026-05-23, session continued ×4)
- **Docs consistency pass**: zoraxy.md, arcane.md, ct-inventory.md, portmap.md, proxmox-memory-audit.md, mcp-gateway.md, RESUME.md all updated to reflect 2026-05-23 state.
- **Wetty deployed** on CT 109 (:4090, LAN-only). `jump-menu.sh` on nuc → SSH jump menu to all 10 hosts.
- **MCP servers added**: gitea, paperless, proxmox (read-only PVEAuditor role). Total: 30 servers.
- **Loki dashboard fixed**: K8s labels replaced with Docker labels in 14 places.
- **WAL-G monitoring**: textfile collector on CT 113 + Prometheus alert rules on CT 109.
- **CT 103 RAM**: 2 GB → 4 GB + 1 GB swap (Backrest OOM fix for 822 GB initial backup).
- **Zoraxy WS**: `SkipWebSocketOriginCheck` enabled on 13 routes. Arcane upstream corrected to CT 109.
- **CT 104 cleanup**: daytona, homepage, proxy, streamio decommissioned.
- **Paperless-ngx**: USERMAP_UID=997 + file permissions fixed, container stable.
## Recently completed (2026-05-23, session continued ×3)
- **Homarr board deployed**: 6 category sections (AI, Monitoring, Files, Dev, Home, Identity & Admin), 27 apps with ping-based status, Mainsail included in Dev. Fixed schema: `x_offset`/`y_offset` on section rows + paired empty sections (y=0 initial, y=1/2 per category). Board `nuclide` set as home board.
- **Arcane environments fixed**: deleted stale shared-token environments; created 6 unique edge environments (shepard, docker, nextcloud, id, dev, secrets) via API with `useApiKey=true`. Each agent updated with unique token. All 8 environments online.
- **Arcane API key seeded**: `ADMIN_STATIC_API_KEY` from compose was never seeded (DB pre-existed). Inserted via argon2id hash. Key `arc_d3357a65` now functional for automation.
- **Infisical SITE_URL fixed**: changed from `https://secrets.nuclide.systems` to `http://192.168.1.7:8200` (LAN-only, no Zoraxy route).
- **Infisical Pocket-ID client created**: client `b2069075-ede2-4251-ad1f-9a62e6a188b3`, callback `http://192.168.1.7:8200/api/v1/sso/oidc/callback`. Manual OIDC config entry still needed in Infisical admin UI.
## Recently completed (2026-05-23, session continued ×2)
- **PVE LXC startup order fixed**: CT103/104/105/112/113 had no startup order. Set: CT113=4, CT112=6, CT104=7, CT103=8, CT105=9. Full deterministic boot sequence now in place. Also enabled CT109 onboot=1 (was missing).
- **KSM enabled on PVE host**: kernel samepage merging active + persisted via `ksm-enable.service`. Zero-cost memory deduplication across LXC containers.
- **5-minute critical watchdog deployed**: `homelab-health-quick.timer` on nuc; `QUICK=1` mode checks LXC status + critical containers every 5 min. Auto-starts stopped LXCs, notifies Gotify for HAOS KVM issues.
- **PVE backlog issues filed**: `fkrebs/docs` #2 (NFS v3→v4), #3 (ZFS ARC 16 GiB), #4 (hugepages for KVM).
## Recently completed (2026-05-23, session continued)
- **HAOS KVM OOM root-caused and fixed**: karakeep_chrome on CT 104 triggered host OOM → killed HAOS KVM twice. Applied: `shm_size: '512m'` to chrome, `oom_score_adj=-300` on KVM PID, systemd timer `protect-haos-kvm.timer` (every 5 min) for persistence.
- **Arcane + Dozzle migrated to CT 109**: servers on CT 109 (:10002, :10001). Headless agents deployed on CT 101, 104, 105, 110, 111, 112, 113. Zoraxy route updated. docs-server also on CT 109 (:13080).
- **Homarr + Grafana OIDC wired**: clients created via Pocket-ID API (no manual UI steps). Homarr client `63a94e30`, Grafana client `92d987d5` (PKCE). Both SSO-active.
- **n8n flows tracked in Gitea**: repo `fkrebs/n8n-flows` with daily sync cron (CT 104, 03:30). 10 automation ideas filed as Gitea issues. Interim health check bash/systemd on nuc (issue #1 = migrate to n8n).
- **CT 104 ai repo cleaned**: `lobehub/data/` untracked from git, `.gitignore` fixed, litellm-config + mcp-gateway changes committed.
- **zoraxy-conf repo**: arcane proxy update committed and pushed (CT 108).
## Recently completed (2026-05-23)
- **Config-to-git crons**: scripts + daily 03:00 crons deployed on CT 108 (Zoraxy → `zoraxy-conf`), CT 102 (AdGuard → `adguard-conf`), PVE (rsync `/etc/pve``pve-conf`). Force-push. All three verified with initial push.
- **Syncstack → MCP gateway**: `litellm_sync.py` (ported from syncstack.py) mounted into gateway container. `_litellm_sync_loop()` runs every 15 min in gateway's asyncio loop. Cron `/etc/cron.d/syncstack` deleted. `Dockerfile.syncstack` decommissioned. `/opt/stacks/CLAUDE.md` updated.
## Recently completed (session continued ×2, 2026-05-22)
- **crawl4ai MCP fixed**: was returning 404 on `/mcp` (streamable-HTTP). Current 0.8.6 image already has MCP over SSE at `/mcp/sse`. Added SSE transport support to `mcp-gateway/server.py`: per-request SSE round-trip with auto-initialize handshake; `transport: sse` in hardcoded SERVERS + config.json. Gateway health now shows crawl4ai ok; lobe-sync shows 27/27 servers.
- **syncstack cron fixed**: missing `cd /opt/stacks/ai &&` prefix caused "no configuration file" every 15 min since ~May 18. Fixed. Manual run confirmed 60 models including 5 Claude models; LobeChat recreated.
## Recently completed (session continued, 2026-05-22)
- **claude-max-bridge `/v1/responses` endpoint**: implemented OpenAI Responses API with `previous_response_id` chaining. Root cause of "session already in use": Claude CLI leaves session JSONL files in an un-resumable "dequeued" state after each `--print` run. Fix: maintain conversation history server-side as formatted text; inject into system prompt for continuations; always use `--no-session-persistence`. Also fixed: assistant turns in `--input-format stream-json` require `content` as array-of-blocks not string (JS error otherwise). Assistant content array format also fixed in `_parse_messages` / `_parse_responses_input`.
- **LiteLLM pass-through for `/v1/responses`**: added `pass_through_endpoints` entry in `/opt/stacks/ai/litellm-config/config.yaml``http://claude-max-bridge:8000/v1/responses`. LiteLLM appends its token hash to the response `id`, so `previous_response_id` chaining only works when calling the bridge directly (not through LiteLLM).
## Recently completed (this session, 2026-05-22)
- **Pocket-ID backup**: pre-hook on CT 103 runs `pocket-id export` + scp keys → UNAS staging; `services-backup-plan` snapshots → jottacloud. Verified.
- **Stale NFS vaultwarden dir deleted**: `/mnt/pve/unas/services/vaultwarden/` removed.
- **Nextcloud CIFS → NFS**: crash-loop fixed; CT 105 mp0 migrated from `/mnt/pve/unas_smb` to `/mnt/pve/unas`; CIFS fstab entry removed.
- **Vaultwarden → local zfs**: `/mnt/pve/unas/services/vaultwarden``/opt/stacks/vaultwarden/data`; stale `db.sqlite3` deleted; DB already on CT 113 postgres.
- **WAL-G fixed**: `immich_postgres` and `lobe-postgres` backups now succeed. Fixed: missing `-u postgres`, missing data path, `set -e` abort, dead `shared-postgres` reference.
- **ComfyUI `--async-offload` removed**: caused CPU fallback + pure-noise output on GGUF img2img with XPU.
- **apps/ decommissioned**: dead duplicate compose files in `/opt/stacks/apps/` renamed `.DECOMMISSIONED-2026-05-22`.
- **Docs consolidated**: storage.md, volumes.md, homelab-architecture.md, proxmox-state.md, proxmox-memory-audit.md, ct-inventory.md, README.md all reconciled.
## Key system state
| Host | IP | Role |
|---|---|---|
| nuc (PVE) | 192.168.1.20 | Proxmox host — SSH gateway to all CTs; jump-menu.sh |
| CT 109 ops | 192.168.1.8 | Monitoring — Prometheus + Grafana + Loki + Arcane + Dozzle + Homarr + Wetty |
| CT 104 docker | 192.168.1.40 | Main Docker host — AI/ML, media, ~65 containers |
| CT 113 db | 192.168.1.6 | Shared Postgres 17 + WAL-G → Garage S3 (cron 02:00) |
| CT 110 id | 192.168.1.5 | Pocket-ID OIDC IdP |
| CT 103 backrest | 192.168.1.3 | Backrest — jottacloud via rclone; UNAS at `/mnt/pve/unas` |
| CT 111 dev | 192.168.1.42 | Coder + Gitea |
| CT 108 zoraxy | 192.168.1.4 | Reverse proxy + ACME — **always confirm before changes** |
## Constraints to remember
- Zoraxy (CT 108): always get explicit user confirmation before any config change.
- Infisical (CT 112): LAN-only, no Zoraxy route — must not be internet-exposed.
- CT 104 has a separate agent reconciling compose files — rename decommissioned files (don't just stop), update `/opt/stacks/CLAUDE.md`.
- ComfyUI container limit: 24 G. Do not raise without measuring host impact.
- Pocket-ID is SQLite-only — no Postgres migration possible.
- Use `*.nuclide.lan` aliases when presenting URLs to the user (never bare IPs). Wi-Fi SSID is `nuclide`.
- Vault edits append to `Work/Journal/YYYY/MM/YYYY-MM-DD.md` under `## 🤖 Claude edits`; never create a parallel folder.
-2651
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Danish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d<a&&(d=a)}}function n(){var e,r;if(f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Z-zA-0-9-",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n<p.length;n++)r?a.push(new e.Token(p[n],{position:[f,p[n].length],index:a.length})):a.push(p[n]),f+=p[n].length;l=c+1}return a},e.ja.stemmer=function(){return function(e){return e}}(),e.Pipeline.registerFunction(e.ja.stemmer,"stemmer-ja"),e.ja.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Z-zA-0-9-",e.ja.trimmer=e.trimmerSupport.generateTrimmer(e.ja.wordCharacters),e.Pipeline.registerFunction(e.ja.trimmer,"trimmer-ja"),e.ja.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.ja.stopWordFilter,"stopWordFilter-ja"),e.jp=e.ja,e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.Pipeline.registerFunction(e.jp.trimmer,"trimmer-jp"),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});
-1
View File
@@ -1 +0,0 @@
module.exports=require("./lunr.ja");
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.kn=function(){this.pipeline.reset(),this.pipeline.add(e.kn.trimmer,e.kn.stopWordFilter,e.kn.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.kn.stemmer))},e.kn.wordCharacters="ಀ-಄ಅ-ಔಕ-ಹಾ-ೌ಼-ಽೕ-ೖೝ-ೞೠ-ೡೢ-ೣ೤೥೦-೯ೱ-ೳ",e.kn.trimmer=e.trimmerSupport.generateTrimmer(e.kn.wordCharacters),e.Pipeline.registerFunction(e.kn.trimmer,"trimmer-kn"),e.kn.stopWordFilter=e.generateStopWordFilter("ಮತ್ತು ಈ ಒಂದು ರಲ್ಲಿ ಹಾಗೂ ಎಂದು ಅಥವಾ ಇದು ರ ಅವರು ಎಂಬ ಮೇಲೆ ಅವರ ತನ್ನ ಆದರೆ ತಮ್ಮ ನಂತರ ಮೂಲಕ ಹೆಚ್ಚು ನ ಆ ಕೆಲವು ಅನೇಕ ಎರಡು ಹಾಗು ಪ್ರಮುಖ ಇದನ್ನು ಇದರ ಸುಮಾರು ಅದರ ಅದು ಮೊದಲ ಬಗ್ಗೆ ನಲ್ಲಿ ರಂದು ಇತರ ಅತ್ಯಂತ ಹೆಚ್ಚಿನ ಸಹ ಸಾಮಾನ್ಯವಾಗಿ ನೇ ಹಲವಾರು ಹೊಸ ದಿ ಕಡಿಮೆ ಯಾವುದೇ ಹೊಂದಿದೆ ದೊಡ್ಡ ಅನ್ನು ಇವರು ಪ್ರಕಾರ ಇದೆ ಮಾತ್ರ ಕೂಡ ಇಲ್ಲಿ ಎಲ್ಲಾ ವಿವಿಧ ಅದನ್ನು ಹಲವು ರಿಂದ ಕೇವಲ ದ ದಕ್ಷಿಣ ಗೆ ಅವನ ಅತಿ ನೆಯ ಬಹಳ ಕೆಲಸ ಎಲ್ಲ ಪ್ರತಿ ಇತ್ಯಾದಿ ಇವು ಬೇರೆ ಹೀಗೆ ನಡುವೆ ಇದಕ್ಕೆ ಎಸ್ ಇವರ ಮೊದಲು ಶ್ರೀ ಮಾಡುವ ಇದರಲ್ಲಿ ರೀತಿಯ ಮಾಡಿದ ಕಾಲ ಅಲ್ಲಿ ಮಾಡಲು ಅದೇ ಈಗ ಅವು ಗಳು ಎ ಎಂಬುದು ಅವನು ಅಂದರೆ ಅವರಿಗೆ ಇರುವ ವಿಶೇಷ ಮುಂದೆ ಅವುಗಳ ಮುಂತಾದ ಮೂಲ ಬಿ ಮೀ ಒಂದೇ ಇನ್ನೂ ಹೆಚ್ಚಾಗಿ ಮಾಡಿ ಅವರನ್ನು ಇದೇ ಯ ರೀತಿಯಲ್ಲಿ ಜೊತೆ ಅದರಲ್ಲಿ ಮಾಡಿದರು ನಡೆದ ಆಗ ಮತ್ತೆ ಪೂರ್ವ ಆತ ಬಂದ ಯಾವ ಒಟ್ಟು ಇತರೆ ಹಿಂದೆ ಪ್ರಮಾಣದ ಗಳನ್ನು ಕುರಿತು ಯು ಆದ್ದರಿಂದ ಅಲ್ಲದೆ ನಗರದ ಮೇಲಿನ ಏಕೆಂದರೆ ರಷ್ಟು ಎಂಬುದನ್ನು ಬಾರಿ ಎಂದರೆ ಹಿಂದಿನ ಆದರೂ ಆದ ಸಂಬಂಧಿಸಿದ ಮತ್ತೊಂದು ಸಿ ಆತನ ".split(" ")),e.kn.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.kn.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var n=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(n).split("|")},e.Pipeline.registerFunction(e.kn.stemmer,"stemmer-kn"),e.Pipeline.registerFunction(e.kn.stopWordFilter,"stopWordFilter-kn")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p<t.length;++p)"en"==t[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[t[p]].wordCharacters,e[t[p]].stopWordFilter&&n.unshift(e[t[p]].stopWordFilter),e[t[p]].stemmer&&(n.push(e[t[p]].stemmer),s.push(e[t[p]].stemmer)));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+i),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});
File diff suppressed because one or more lines are too long
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Norwegian` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a<s&&(a=s)}}function i(){var e,r,n;if(w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sa=function(){this.pipeline.reset(),this.pipeline.add(e.sa.trimmer,e.sa.stopWordFilter,e.sa.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sa.stemmer))},e.sa.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿ꣠-꣱ꣲ-ꣷ꣸-ꣻ꣼-ꣽꣾ-ꣿᆰ0-ᆰ9",e.sa.trimmer=e.trimmerSupport.generateTrimmer(e.sa.wordCharacters),e.Pipeline.registerFunction(e.sa.trimmer,"trimmer-sa"),e.sa.stopWordFilter=e.generateStopWordFilter('तथा अयम्‌ एकम्‌ इत्यस्मिन्‌ तथा तत्‌ वा अयम्‌ इत्यस्य ते आहूत उपरि तेषाम्‌ किन्तु तेषाम्‌ तदा इत्यनेन अधिकः इत्यस्य तत्‌ केचन बहवः द्वि तथा महत्वपूर्णः अयम्‌ अस्य विषये अयं अस्ति तत्‌ प्रथमः विषये इत्युपरि इत्युपरि इतर अधिकतमः अधिकः अपि सामान्यतया ठ इतरेतर नूतनम्‌ द न्यूनम्‌ कश्चित्‌ वा विशालः द सः अस्ति तदनुसारम् तत्र अस्ति केवलम्‌ अपि अत्र सर्वे विविधाः तत्‌ बहवः यतः इदानीम्‌ द दक्षिण इत्यस्मै तस्य उपरि नथ अतीव कार्यम्‌ सर्वे एकैकम्‌ इत्यादि। एते सन्ति उत इत्थम्‌ मध्ये एतदर्थं . स कस्य प्रथमः श्री. करोति अस्मिन् प्रकारः निर्मिता कालः तत्र कर्तुं समान अधुना ते सन्ति स एकः अस्ति सः अर्थात् तेषां कृते . स्थितम् विशेषः अग्रिम तेषाम्‌ समान स्रोतः ख म समान इदानीमपि अधिकतया करोतु ते समान इत्यस्य वीथी सह यस्मिन् कृतवान्‌ धृतः तदा पुनः पूर्वं सः आगतः किम्‌ कुल इतर पुरा मात्रा स विषये उ अतएव अपि नगरस्य उपरि यतः प्रतिशतं कतरः कालः साधनानि भूत तथापि जात सम्बन्धि अन्यत्‌ ग अतः अस्माकं स्वकीयाः अस्माकं इदानीं अन्तः इत्यादयः भवन्तः इत्यादयः एते एताः तस्य अस्य इदम् एते तेषां तेषां तेषां तान् तेषां तेषां तेषां समानः सः एकः च तादृशाः बहवः अन्ये च वदन्ति यत् कियत् कस्मै कस्मै यस्मै यस्मै यस्मै यस्मै न अतिनीचः किन्तु प्रथमं सम्पूर्णतया ततः चिरकालानन्तरं पुस्तकं सम्पूर्णतया अन्तः किन्तु अत्र वा इह इव श्रद्धाय अवशिष्यते परन्तु अन्ये वर्गाः सन्ति ते सन्ति शक्नुवन्ति सर्वे मिलित्वा सर्वे एकत्र"'.split(" ")),e.sa.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.sa.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var i=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(i).split("|")},e.Pipeline.registerFunction(e.sa.stemmer,"stemmer-sa"),e.Pipeline.registerFunction(e.sa.stopWordFilter,"stopWordFilter-sa")}});
@@ -1 +0,0 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=_.s_size-1-l;m>=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Swedish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o<a&&(o=a)}}function t(){var e,r=w.limit_backward;if(w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Z-zA-0-9-",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}});
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}});
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}});
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}});
-206
View File
@@ -1,206 +0,0 @@
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
factory()(root.lunr);
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return function(lunr) {
// TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript
// (c) 2008 Taku Kudo <taku@chasen.org>
// TinySegmenter is freely distributable under the terms of a new BSD licence.
// For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
function TinySegmenter() {
var patterns = {
"[一二三四五六七八九十百千万億兆]":"M",
"[一-龠々〆ヵヶ]":"H",
"[ぁ-ん]":"I",
"[ァ-ヴーア-ン゙ー]":"K",
"[a-zA-Z-zA-]":"A",
"[0-9-]":"N"
}
this.chartype_ = [];
for (var i in patterns) {
var regexp = new RegExp(i);
this.chartype_.push([regexp, patterns[i]]);
}
this.BIAS__ = -332
this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378};
this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920};
this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266};
this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352};
this.BP2__ = {"BO":60,"OO":-1762};
this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965};
this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146};
this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699};
this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973};
this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682};
this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669};
this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990};
this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832};
this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649};
this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393};
this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841};
this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68};
this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591};
this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685};
this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156};
this.TW1__ = {"につい":-4681,"東京都":2026};
this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216};
this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287};
this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865};
this.UC1__ = {"A":484,"K":93,"M":645,"O":-505};
this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646};
this.UC3__ = {"A":-1370,"I":2311};
this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646};
this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831};
this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387};
this.UP1__ = {"O":-214};
this.UP2__ = {"B":69,"O":935};
this.UP3__ = {"B":189};
this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422};
this.UQ2__ = {"BH":216,"BI":113,"OK":1759};
this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212};
this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135};
this.UW2__ = {",":-829,"、":-829,"":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568};
this.UW3__ = {",":4889,"1":-800,"":-1723,"、":4889,"々":-2311,"":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278};
this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637};
this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343};
this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"":-270,"E1":306,"ル":-673,"ン":-496};
return this;
}
TinySegmenter.prototype.ctype_ = function(str) {
for (var i in this.chartype_) {
if (str.match(this.chartype_[i][0])) {
return this.chartype_[i][1];
}
}
return "O";
}
TinySegmenter.prototype.ts_ = function(v) {
if (v) { return v; }
return 0;
}
TinySegmenter.prototype.segment = function(input) {
if (input == null || input == undefined || input == "") {
return [];
}
var result = [];
var seg = ["B3","B2","B1"];
var ctype = ["O","O","O"];
var o = input.split("");
for (i = 0; i < o.length; ++i) {
seg.push(o[i]);
ctype.push(this.ctype_(o[i]))
}
seg.push("E1");
seg.push("E2");
seg.push("E3");
ctype.push("O");
ctype.push("O");
ctype.push("O");
var word = seg[3];
var p1 = "U";
var p2 = "U";
var p3 = "U";
for (var i = 4; i < seg.length - 3; ++i) {
var score = this.BIAS__;
var w1 = seg[i-3];
var w2 = seg[i-2];
var w3 = seg[i-1];
var w4 = seg[i];
var w5 = seg[i+1];
var w6 = seg[i+2];
var c1 = ctype[i-3];
var c2 = ctype[i-2];
var c3 = ctype[i-1];
var c4 = ctype[i];
var c5 = ctype[i+1];
var c6 = ctype[i+2];
score += this.ts_(this.UP1__[p1]);
score += this.ts_(this.UP2__[p2]);
score += this.ts_(this.UP3__[p3]);
score += this.ts_(this.BP1__[p1 + p2]);
score += this.ts_(this.BP2__[p2 + p3]);
score += this.ts_(this.UW1__[w1]);
score += this.ts_(this.UW2__[w2]);
score += this.ts_(this.UW3__[w3]);
score += this.ts_(this.UW4__[w4]);
score += this.ts_(this.UW5__[w5]);
score += this.ts_(this.UW6__[w6]);
score += this.ts_(this.BW1__[w2 + w3]);
score += this.ts_(this.BW2__[w3 + w4]);
score += this.ts_(this.BW3__[w4 + w5]);
score += this.ts_(this.TW1__[w1 + w2 + w3]);
score += this.ts_(this.TW2__[w2 + w3 + w4]);
score += this.ts_(this.TW3__[w3 + w4 + w5]);
score += this.ts_(this.TW4__[w4 + w5 + w6]);
score += this.ts_(this.UC1__[c1]);
score += this.ts_(this.UC2__[c2]);
score += this.ts_(this.UC3__[c3]);
score += this.ts_(this.UC4__[c4]);
score += this.ts_(this.UC5__[c5]);
score += this.ts_(this.UC6__[c6]);
score += this.ts_(this.BC1__[c2 + c3]);
score += this.ts_(this.BC2__[c3 + c4]);
score += this.ts_(this.BC3__[c4 + c5]);
score += this.ts_(this.TC1__[c1 + c2 + c3]);
score += this.ts_(this.TC2__[c2 + c3 + c4]);
score += this.ts_(this.TC3__[c3 + c4 + c5]);
score += this.ts_(this.TC4__[c4 + c5 + c6]);
// score += this.ts_(this.TC5__[c4 + c5 + c6]);
score += this.ts_(this.UQ1__[p1 + c1]);
score += this.ts_(this.UQ2__[p2 + c2]);
score += this.ts_(this.UQ3__[p3 + c3]);
score += this.ts_(this.BQ1__[p2 + c2 + c3]);
score += this.ts_(this.BQ2__[p2 + c3 + c4]);
score += this.ts_(this.BQ3__[p3 + c2 + c3]);
score += this.ts_(this.BQ4__[p3 + c3 + c4]);
score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]);
score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]);
score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]);
score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]);
var p = "O";
if (score > 0) {
result.push(word);
word = "";
p = "B";
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return result;
}
lunr.TinySegmenter = TinySegmenter;
};
}));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
{"version":3,"sources":["src/templates/assets/stylesheets/palette/_scheme.scss","../../../../src/templates/assets/stylesheets/palette.scss","src/templates/assets/stylesheets/palette/_accent.scss","src/templates/assets/stylesheets/palette/_primary.scss","src/templates/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAME,sDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,mDAAA,CACA,gDAAA,CACA,yDAAA,CACA,4DAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,iCAAA,CAGA,yDAAA,CACA,iEAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,uDAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DAAA,CAzEA,iBCiBF,CD6DE,kHAEE,YC3DJ,CDkFE,yDACE,4BChFJ,CD+EE,2DACE,4BC7EJ,CD4EE,gEACE,4BC1EJ,CDyEE,2DACE,4BCvEJ,CDsEE,yDACE,4BCpEJ,CDmEE,0DACE,4BCjEJ,CDgEE,gEACE,4BC9DJ,CD6DE,0DACE,4BC3DJ,CD0DE,2OACE,4BC/CJ,CDsDA,+FAGE,iCCpDF,CACF,CCjDE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD6CN,CCvDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoDN,CC9DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2DN,CCrEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkEN,CC5EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyEN,CCnFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgFN,CC1FE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuFN,CCjGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8FN,CCxGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqGN,CC/GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4GN,CCtHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmHN,CC7HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD6HN,CCpIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDoIN,CC3IE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2IN,CClJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkJN,CCzJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDsJN,CE3JE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwJN,CEnKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgKN,CE3KE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwKN,CEnLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgLN,CE3LE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwLN,CEnME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgMN,CE3ME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwMN,CEnNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgNN,CE3NE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwNN,CEnOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgON,CE3OE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwON,CEnPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmPN,CE3PE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2PN,CEnQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmQN,CE3QE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2QN,CEnRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgRN,CE3RE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwRN,CEnSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF4RN,CE5SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFqSN,CEtRE,sEACE,4BFyRJ,CE1RE,+DACE,4BF6RJ,CE9RE,iEACE,4BFiSJ,CElSE,gEACE,4BFqSJ,CEtSE,iEACE,4BFySJ,CEhSA,8BACE,mDAAA,CACA,4DAAA,CACA,0DAAA,CACA,oDAAA,CACA,2DAAA,CAGA,4BFiSF,CE9RE,yCACE,+BFgSJ,CE7RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCFiSN,CG7MI,mCD1EA,+CACE,8CF0RJ,CEvRI,qDACE,8CFyRN,CEpRE,iEACE,mCFsRJ,CACF,CGxNI,sCDvDA,uCACE,oCFkRJ,CACF,CEzQA,8BACE,kDAAA,CACA,4DAAA,CACA,wDAAA,CACA,oDAAA,CACA,6DAAA,CAGA,4BF0QF,CEvQE,yCACE,+BFyQJ,CEtQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCF0QN,CEnQE,yCACE,6CFqQJ,CG9NI,0CDhCA,8CACE,gDFiQJ,CACF,CGnOI,0CDvBA,iFACE,6CF6PJ,CACF,CG3PI,sCDKA,uCACE,6CFyPJ,CACF","file":"palette.css"}
+52
View File
@@ -0,0 +1,52 @@
# CT / VM inventory
Verified 2026-05-20 via `pct list`, `pct config <id>`, `qm config 100` on `nuc`.
| VMID | Name | Type | IP | Cores | RAM (MiB) | Rootfs | Role | UNAS mount | GPU | Status |
|---|---|---|---|---|---|---|---|---|---|---|
| 100 | haos | VM (q35/OVMF) | DHCP via vmbr0 (`.60`) | 4 | 16384 (balloon 4096) | 32 GiB local-zfs | Home Assistant OS; USB Zigbee dongle (10c4:ea60) passed through → **Zigbee2MQTT** add-on + Mosquitto broker add-on; OCPP (EV charger); ~2492 entities | — | no | running |
| 101 | shepard | LXC unpriv | 192.168.1.49/24 | 12 | 32768 | 500 GiB | Shepard product stack (Caddy, frontend, backend, Keycloak, Mongo, Neo4j, TimescaleDB) | mp0 NFS | Intel iGPU (card+render) | running |
| 102 | dns | LXC unpriv | 192.168.1.2/24 | 2 | 1024 | 4 GiB | **AdGuard Home** — LAN DNS resolver + filter | — | no | running |
| 103 | backrest | LXC unpriv | 192.168.1.3/24 | 1 | 4096 + 1024 swap | 8 GiB | **Backrest** (restic) backup scheduler | mp0 NFS | no | running |
| 104 | docker | LXC unpriv (idmapped) | 192.168.1.40/24 | 16 | 49152 | 200 GiB | Main Docker host — AI/ML + media + identity-adjacent (~70 containers); **Gitea** (:3000/:222) + **Coder** (:7080) migrated from CT 111 2026-05-26; **Proton Mail Bridge** (:1025 SMTP/:1143 IMAP) | mp0 NFS | Intel iGPU (card+render) | running |
| 105 | nextcloud | LXC priv | 192.168.1.41/24 | 4 | 8196 | 100 GiB | Nextcloud AIO | mp0 NFS | Intel iGPU (render only) | running |
| ~~108~~ | ~~zoraxy~~ | — | — | — | — | — | **DESTROYED 2026-05-28** — replaced by Traefik v3 on CT 109; backup `vzdump-lxc-108-2026_05_28-00_46_45.tar.zst` on UNAS | — | — | — |
| 109 | ops | LXC unpriv | 192.168.1.8/24 | 4 | 4096 | 32 GiB | **Ops** — Prometheus + Grafana + Loki + Alloy + pve-exporter + **Homepage** (:10000) + Portainer (:9000) + Dozzle (server) + docs-server + **Infisical** (:8200) + **Pocket-ID** (:11000, migrated from CT 110 2026-05-26) + **Traefik v3** (:80/:443/:8090) — public reverse proxy + ACME DNS-01; Dozzle agents on all Docker hosts | — | no | running |
| ~~110~~ | ~~id~~ | — | — | — | — | — | **DESTROYED 2026-05-26** — Pocket-ID migrated to CT 109; LXC removed | — | — | — |
| ~~111~~ | ~~dev~~ | — | — | — | — | — | **DESTROYED 2026-05-26** — Coder + Gitea migrated to CT 104; LXC removed | — | — | — |
| ~~112~~ | ~~secrets~~ | — | — | — | — | — | **DESTROYED 2026-05-26** — Infisical migrated to CT 109; LXC removed | — | — | — |
| 113 | db | LXC unpriv | 192.168.1.6/24 | 2 | 4096 | 40 GiB | Shared Postgres 17 + pgAdmin + WAL-G → Garage S3; Arcane edge agent | — | no | running |
UNAS NFS = `192.168.1.31:/var/nfs/shared/storage` over NFSv3. CT 105 (Nextcloud) is the lone outlier — it mounts the same UNAS share over **CIFS/SMB 3.1.1**, not NFS.
CT 112 ("secrets") — Infisical deployed 2026-05-22; see `services/secrets-manager.md`. LAN: `http://192.168.1.7:8200`. No Zoraxy route — secrets must not be internet-exposed.
CT 105 ("nextcloud") — migrated from CIFS (`//192.168.1.31/storage`) to NFS (`192.168.1.31:/var/nfs/shared/storage`) on 2026-05-22. CIFS mount was not remounting after host reboots, causing Nextcloud crash-loops. NFS is consistent with all other CTs.
## SSH access
The Proxmox host (`nuc`, `192.168.1.20`) has root SSH access to all LXC containers via key auth. To grant your own key access to every running container in one step, run on the host:
```bash
deploy-ssh-key "ssh-ed25519 AAAA... you@yourmachine"
# or pipe it:
ssh nuc 'cat' < ~/.ssh/id_ed25519.pub | deploy-ssh-key
```
Script is at `/usr/local/bin/deploy-ssh-key`. It iterates `pct list`, skips stopped CTs, and appends the key to `/root/.ssh/authorized_keys` idempotently (no duplicates).
**VM 100 (HAOS, `192.168.1.60`)** cannot be reached via `pct exec`. Install manually in the HA terminal:
```bash
echo "ssh-ed25519 AAAA... you@yourmachine" >> ~/.ssh/authorized_keys
```
**Host key** (`root@nuc`, already deployed to all CTs 2026-05-21):
```
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCuVqBW3VXg... root@nuc
```
CT 113 ("db") provisioned 2026-05-21; see `services/databases.md`. Future role: consolidate per-stack Postgres instances once second NVMe lands.
CT 109 ("ops") provisioned 2026-05-23. Debian 13, Docker 29.5.2. Stack `/opt/stacks/monitoring`: Prometheus (:9090), Grafana (:3000 — LAN only, admin/tapirnase), Loki (:3100), node-exporter (:9100 host-net), pve-exporter (:9221), Alloy (:12345), Portainer BE (:9000), Dozzle server (:10001), docs-server (:13080), Wetty web-SSH (:4090 LAN-only). **Homepage** (:10000 — `ops.nuclide.lan:10000`) at `/opt/stacks/homepage/` — replaced Homarr 2026-05-26; remote Docker via socket-proxy on CT 110/111/112/113 and direct TCP on CT 104. Scrapes: `node-ct104` `:9100`, `node-ct109` `:9100`, `home-assistant` 192.168.1.60:8123/api/prometheus, `prometheus` self, `walg` CT 113 `:9100/textfile`.
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
# Docker-internal inventory
Live at <http://192.168.1.8:13080/docker-internal-inventory/>.
Containers that have **no LAN-published port** — reachable only on their Docker bridge network. This is the third tier of our access model:
1. **public** — Zoraxy-routed via `*.nuclide.systems` (internet-reachable)
2. **LAN**`192.168.1.0/24` direct (host port mapping)
3. **docker-internal** — only via container-to-container bridge (this doc)
Default per [[feedback_internal_only]]: keep new things at tier 2 or 3 unless there's a real external-access reason. **Most of what is on tier 3 should stay there** — that's the point of having three tiers.
## Recommendation legend
- 🟢 **Keep internal** — sidecar / backend / engine, must not be exposed
- 🟡 **Maybe** — admin UI exists; could be useful to reach directly but low value
- 🔴 **Expose to LAN** — broken access pattern, fix recommended
## CT 104 (docker host) — 43 internal-only
### AI / MCP plumbing — all 🟢 keep internal
Backend MCP servers consumed only by the MCP gateway. Exposing them would bypass auth + audit.
- `mcp-proxmox`, `gitea-mcp`, `mcp-immich`, `mcp-fetch`, `mcp-time`, `mcp-git`, `mcp-gotify`, `mcp-unifi`, `mcp-ntfy`, `mcp-markitdown`, `mcp-context7`, `mcp-youtube-transcript`, `mcp-sequential-thinking`, `mcp-wikipedia-mcp`, `mcp-gitlab`, `mcp-crawl4ai`, `coder-mcp`, `ariel-mcp`, `paperless-mcp`, `claude-max-bridge`
- Plus the ephemeral `crazy_colden`/`stoic_kirch`/etc. (auto-spawned MCP one-shots — Docker name collisions, no static port).
### Backends for exposed services — 🟢 keep internal
- `karakeep_meilisearch`, `karakeep_chrome` (Karakeep search + headless browser)
- `immich_postgres`, `immich_redis`, `immich_machine_learning` (Immich backends)
- `immich_power_tools` — 🟡 **maybe**. Power-user UI for Immich. Bind to LAN if you ever want to use it directly.
- `paperless-ngx-tika-1`, `paperless-ngx-gotenberg-1`, `paperless-ngx-broker-1` (Paperless OCR/PDF/redis)
- `redis-searxng`, `rdtclient` (auxiliary)
### Diagrams — 🟢 keep internal
- `kroki`, `kroki-mermaid`, `kroki-excalidraw` — rendered via MCP, no UI to expose.
### Monitoring agents — 🟢 keep internal
- `node-exporter` (scraped by Prometheus on `:9100` over container network; LAN exposure not needed since Prometheus is on the same LAN already)
- `arcane-agent` (talks back to Arcane server on CT 109)
## CT 105 (nextcloud) — 8 internal-only
🟢 **All keep internal** — Nextcloud AIO architecture.
- `nextcloud-aio-nextcloud` (fronted by AIO apache proxy on `:11000`)
- `nextcloud-aio-database` (Postgres), `nextcloud-aio-redis`, `nextcloud-aio-imaginary`, `nextcloud-aio-notify-push`, `nextcloud-aio-collabora`, `nextcloud-aio-docker-socket-proxy`
- `arcane-agent`
Exposing the AIO backends directly would break Nextcloud's auth model and crash backups.
## CT 109 (ops) — 1 internal-only
- `node-exporter` — 🟢 keep internal. Scraped via `host.docker.internal` from Prometheus on the same host.
## CT 110 / 111 / 112 / 113 — `arcane-agent` only
🟢 **All keep internal**. The Arcane agent on each Docker host calls back to the Arcane server on `192.168.1.8:10002`; no inbound LAN traffic needed.
Plus:
- CT 111: `act-runner` 🟢 (Gitea Actions runner — outbound to Gitea API; never needs inbound)
- CT 112: `infisical-db`, `infisical-redis` 🟢 (Infisical app on `:8200` is the only intended entry)
## Cross-tier issues spotted
None. The 3-tier model is clean across the fleet:
- No backend Postgres/Redis is accidentally LAN-bound
- No MCP server is double-exposed
- No admin UI is bound to LAN when it shouldn't be
## Candidate LAN-bind, if you want them
If you ever want direct LAN access to one of the 🟡 services, the pattern is to add a `ports:` line to its compose entry:
| Service | Suggested port | Why you might |
|---|---|---|
| `immich_power_tools` | `:3001` on CT 104 | Bulk Immich operations (album merge, dedup) the main UI doesn't expose |
Everything else: leave at tier 3.
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
> **STATUS: NARRATIVE — historical writeup. Not authoritative for current state.**
---
# Case Study — The Nuclide Homelab, built with Claude
## Origin story
One Saturday, the owner's wife left him home alone. He got bored, subscribed to
Claude, and started tinkering with a home server. That afternoon of boredom
turned into the `/opt/stacks` ecosystem documented here — a ~66-container,
~23-stack self-hosted platform with SSO, an MCP/agent gateway, GPU offload,
and a fully audited network. This is that story, kept as a record of what a
curiosity-driven collaboration produced.
> This is a personal passion project, not a work deliverable. The tone and
> scope reflect that: depth and exploration over minimum-viable.
## What was built (high level)
- **AI/agent core** — LiteLLM model gateway (~28 curated models), an
MCP gateway that DinD-spawns and OAuth-gates MCP servers, an Agent Operator
(cron/event agents), semantic tool retrieval, MCP→LobeChat registration.
- **Identity** — PocketID as the universal OIDC IdP; every service behind SSO
(LiteLLM, LobeChat, n8n, Nextcloud, Daytona via a Keycloak→PocketID adapter).
- **Data layer** — shared-postgres standard; a full **tier-1 "no SQLite on
NFS"** migration (ntfy, Karakeep, Arcane, traccar → local disk; Memos +
Vaultwarden → Postgres via pgloader); WAL-G PITR backups re-established.
- **Network** — full read-only UniFi audit + the unmanaged D-Link DGS-1210
core (SNTP fixed, topology/FDB mapped, DHCP/LLDP interop verified, SPOF
identified); pinned-NTP-source standard adopted.
- **Reliability** — gateway **deep health-check** (real MCP tool-call probe,
usage-aware backoff) that caught silently-broken servers.
- **Cloud burst** — on-demand Scaleway L40S GPU offload via WireGuard.
See `homelab-architecture.md` for the living technical reference and
`PORTMAP.md` for the authoritative port/route map.
## Activity signal
- **~79 commits in the trailing 14 days** (`git log --since="14 days ago"`),
spanning gateway OAuth/health, OIDC bolt-ons, DB migrations, network audit,
GPU integration, and docs.
- Multi-session, incident-driven: several entries trace to real failures
caught and fixed (WAL-G archiver hung silently ~13 h; n8n `latest`-drift
outage; SQLite-on-NFS corruption risk; an 8.5-month-stale switch clock).
## Productivity estimate (honest framing)
These are **rough order-of-magnitude estimates**, not measurements. Assumptions
are stated so they can be challenged.
- Scope delivered ≈ a small platform: identity, AI gateway, agent runtime,
~23 service stacks, a DB-migration program, a full network audit, backups.
- A solo engineer doing this unaided, part-time, learning the unfamiliar
pieces (OIDC internals, pgloader, MCP, UniFi/D-Link internals): a
conservative bound is **several hundred focused hours** (≈ 816 part-time
weeks). Assumes the owner is competent but not a specialist in every domain
touched (identity, Postgres ops, embedded-switch web UIs, MCP).
- With the assistant: compressed into a small number of intensive sessions
over ~2 weeks. The leverage is largest where the work is *research-heavy
but low-novelty* — reverse-engineering a D-Link form POST, deriving an
OIDC redirect, mapping an FDB table — i.e. tasks that are tedious solo but
not conceptually hard. The leverage is smallest on genuine judgment calls
(what to prioritise, what risk is acceptable), which stayed with the owner.
- **Caveat:** estimate excludes the owner's own steering/review time, which
was substantial and is the reason the output is coherent rather than just
voluminous.
## CO2 estimate (honest framing)
Also order-of-magnitude, assumptions explicit.
- **LLM inference:** a heavy multi-session collaboration of this kind is on
the order of a few million tokens. Public estimates put frontier-model
inference at roughly 15 Wh per ~1k output tokens equivalent (wide error
bars). Taking ~3 M tokens × ~2 Wh/1k ≈ **~6 kWh** → at a ~0.35 kgCO2e/kWh
grid ≈ **~2 kgCO2e**. Plausible range **15 kgCO2e**. Datacenter PUE and
exact model size dominate the uncertainty.
- **Homelab runtime** (the larger ongoing footprint): the NUC 14 Pro draws
~1545 W under mixed load. At ~30 W average → ~0.72 kWh/day → ~260 kWh/yr
**~90 kgCO2e/yr** at the same grid factor. The German grid is cleaner
than that average in many hours, so treat as an upper-ish bound. The
on-demand Scaleway L40S burst is deliberately *on-demand* precisely to
avoid a 24/7 GPU's footprint.
- **Takeaway:** the assistant-collaboration carbon is a rounding error next
to a year of always-on homelab power. Efficiency wins (CPU-only default,
on-demand GPU, idle-aware health probing) matter more than the chat cost.
## Handover / current state
**Healthy & verified**
- Tier-1 SQLite-off-NFS: complete.
- OIDC: n8n (302→PocketID, client `33135ad4`) and LobeChat (`AUTH_TRUSTED_
ORIGINS` fix, sign-in→PocketID) — both verified; LobeChat wants one real
browser login as final proof.
- D-Link SNTP: fixed (pinned PTB+Cloudflare IPs, clock corrected & synced).
- Gateway deep health-check: live, usage-aware, surfaced in `/api/servers`.
**Open / pending** (see `homelab-architecture.md` roadmap for detail)
- **Broken MCP servers** surfaced by the new health-check: `memos`
(degraded — `mcp-memos` can't resolve `memos` host; Docker-network
isolation), `context7`/`crawl4ai`/`markitdown` (down), `nextcloud`
(probe false-positive — needs `health_check:false` or per-user creds).
- **D-Link mgmt hardening** (bundle, confirm-first): HTTPS, SNMP review,
Trusted-Host allowlist `192.168.1.0/24`. Shared `tapirnase` password
reuse (WiFi/LiteLLM/switch) — rotation deferred, noted.
- **Network**: IoT-VLAN segmentation; D-Link is the unmanaged core/SPOF;
mgmt-TLS certs for Proxmox + D-Link.
- **Platform**: env→secret vault; LobeChat external-feature disable;
observability LXC; agent-platform evolution (memory/teams/MCP-exposed).
- `nexa` analysis blocked — private repo; deploy key pending authorization.
**Operating rules to preserve**
- Confirm + risk-assess before any Proxmox / Ubiquiti / network-gear write.
- Never put DB/SQLite on the UNAS NFS share.
- Only a full pgloader of *all* tables is a complete DB migration.
- Prefer self-hosted; pin critical container images (no `latest` drift).
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
> **STATUS: SUPERSEDED 2026-05-17 — current implementation lives in services/mcp-gateway.md. Kept for design-rationale history.**
---
# MCP Gateway — Reconstructed Design Spec (in-progress, "Phase 1")
> Reconstructed 2026-05-16 from code/configs/git history. The gateway is a
> single-squash-commit first draft (`0cad389 "Phase 1: Create MCP Gateway with
> Docker-in-Docker support"`, preceded by `726bd10 "WIP: MCP gateway prep"`).
> Nothing has a second iteration in git — everything below is first-draft intent.
## 1. Goal / Intent
A single **OAuth-protected HTTP entrypoint at `https://mcp.nuclide.systems`** that
exposes a curated set of MCP servers to AI clients on the homelab. Primary
consumer: **Claude.ai** as a remote connector (SSE at `/`, every README's
"Usage in Claude.ai"). Secondary: **LobeChat** (`chat.nuclide.systems`) and
**LiteLLM** (`ai.nuclide.systems`), sharing the same Pocket ID OAuth app. It is
meant to replace the "cumbersome" static-compose approach (`mcp-tools.yaml`)
with a dynamic, UI-managed, self-hosting model — answering the open `todo.md`
question "MCP deployment seems cumbersome — can litellm host directly? how to
integrate npx, uvx, docker-based containers?". Unifying idea: normalize
npx / uvx / docker MCP servers behind one Dockerized gateway.
## 2. Architecture (three competing models in-repo; A chosen, B orphaned, C aspirational)
**A. FastAPI gateway + Docker-in-Docker (chosen)**`ai/mcp-gateway/`
- FastAPI + `uvicorn` on `0.0.0.0:8080`, container `mcp-gateway`.
- DinD via bind-mounted `/var/run/docker.sock`; `docker.from_env()`.
- Per-server containers spawned `mcp-<name>`, hardcoded onto `ai-internal`.
- Config `config.json` (RW bind, currently EMPTY → falls back to `DEFAULT_SERVERS`).
- Gateway joins `ai-internal` + `shared_backend` (both `external: true`).
**B. Static compose `mcp-tools.yaml`** — orphaned; `ai/docker-compose.yml:6`
include is **commented out**. Internally malformed (see §4).
**C. LiteLLM-hosted**`litellm-config/config.yaml` `mcp_servers: {}` empty.
Confirms MCP hosting was intended for the gateway, not LiteLLM (the
`todo.md` "can litellm host directly?" question remains open).
**Transports** (normalized to HTTP-on-:8000): streamable-http (nextcloud,
mermaid), `mcp-proxy --stateless` stdio→HTTP (papersearch), native HTTP
(markitdown, crawl4ai :11235), and the gateway's own **SSE `/` endpoint —
a STUB** (fake `initialize` + 60s pings, no routing to backends).
**Reverse proxy:** Zoraxy `mcp.nuclide.systems → 192.168.1.40:8080`.
`mcp-auth.nuclide.systems` is an abandoned auth-sidecar idea (not exposed).
**OAuth (Pocket ID @ `id.nuclide.systems`):** `OAuth2AuthorizationCodeBearer`,
scopes `{openid, mcp}`, token validation via userinfo. Shared gateway client
(`GENERIC_CLIENT_ID`, same as LiteLLM/LobeChat). Per-server OAuth for
papersearch & nextcloud against the same Pocket ID.
## 3. MCP Server Inventory (reconciled — `server.py` MCP_SERVERS is authoritative)
| Server | Image / build | Transport | Port | Auth | Status |
|---|---|---|---|---|---|
| papersearch | `python:3.12-slim` + runtime `uv tool install mcp-proxy``paper_search_mcp.server` | mcp-proxy stdio→http | 8000 | Pocket ID `PAPERSEARCH_MCP_OAUTH_*` | plausible, runtime-install fragile |
| nextcloud | `ghcr.io/cbcoutinho/nextcloud-mcp-server:latest` | streamable-http | 8000 | Pocket ID `NEXTCLOUD_MCP_OAUTH_*` | likely workable (real image) |
| markitdown | `python:3.12-slim` + `uvx markitdown-mcp --http` | http | 8000 | none | **broken as written** (`uvx` not in base image) |
| comfyui | `ghcr.io/richardi-ai/comfyui-mcp-server:latest` (`type:"npm"` mismatch) | unspecified | 8000 | none | **image not pullable**; backend ComfyUI was crash-looping |
| crawl4ai | `unclecode/crawl4ai:latest` | http | 11235 | none | likely workable; resource limits lost in rewrite |
| mermaid | `node:20-slim` + runtime `npx -y mcp-mermaid` | streamable-http | 8000 | none | plausible, slow first start |
## 4. Implemented vs Unfinished vs Broken
**Implemented:** FastAPI app + OAuth scheme + userinfo token validation;
container lifecycle CRUD + persistence; Web UI SPA (`templates/ui.html` @ `/ui`);
gateway compose/Dockerfile + Zoraxy route.
**Unfinished / stub:**
- **SSE `/` is fake** — no MCP transport bridging Claude.ai → spawned servers. *Core gap.*
- **No routing to per-server containers**; all five servers bind the same `:8000`
and `spawn_container` host-publishes `8000:8000`**two servers can't run at once**.
- **OAuth callback non-functional** — token-exchange URL built via
`OAUTH_REDIRECT_URI.replace("/sso/callback","/token")` (→ wrong host, not the
Pocket ID token endpoint); token never stored/used.
- `config.json` empty → always defaults; secrets hardcoded plaintext in `server.py`.
**Broken / contradictory:**
- `ai/docker-compose.yml:6` mcp-tools include commented out; gateway compose is a
*separate project* not referenced by the stack either — wired in only via Zoraxy.
- `mcp-tools.yaml`: duplicate `markitdown-mcp` key; `comfyui-mcp` env missing `=`
(`- COMFYUI_URL http://comfyui:8188`); missing images/ports.
- `comfyui` server `type:"npm"` vs Docker-image mismatch; upstream image/npm
package existence unverified (image confirmed **not pullable**).
- `.env` has `CRAWL4AI_MCP_OAUTH_*`, `COMFYUI_MCP_OAUTH_*` that `server.py`
never consumes; code hardcodes secrets instead of `${ENV}` substitution.
## 5. Relevant `.env` keys (names only)
Gateway: `GENERIC_CLIENT_ID/_SECRET/_REDIRECT_URI`,
`GENERIC_{AUTHORIZATION,TOKEN,USERINFO}_ENDPOINT`, `GENERIC_CLIENT_USE_PKCE`,
`OAUTH_SCOPES`, `OAUTH_TOKEN_URL`.
Per-server: `NEXTCLOUD_MCP_OAUTH_CLIENT_ID/_SECRET`,
`PAPERSEARCH_MCP_OAUTH_CLIENT_ID/_SECRET`,
`MARKITDOWN_MCP_OAUTH_CLIENT_ID/_SECRET` (declared, unused),
`CRAWL4AI_/COMFYUI_MCP_OAUTH_*` (orphaned).
papersearch data sources: `UNPAYWALL_EMAIL`, `CORE_API_KEY`,
`SEMANTIC_SCHOLAR_API_KEY`, `ZENODO_ACCESS_TOKEN`, `GOOGLE_SCHOLAR_PROXY_URL`,
`DOAJ_API_KEY`. comfyui: `COMFYUI_URL`, `COMFYUI_WS_URL`.
## 6. Open Design Decisions (must resolve)
1. **Hosting model**: DinD gateway vs static `mcp-tools.yaml` vs LiteLLM-hosted.
2. **How Claude.ai/LobeChat reach a tool**: the MCP transport bridge doesn't exist.
3. **Port allocation**: all servers hardcode `:8000` — need internal DNS, no host publish.
4. **Uniform npx/uvx/docker run model**: prebuilt images vs runtime install.
5. **Auth model**: gateway-terminated vs per-MCP vs pass-through (callback is broken).
6. **Secret handling**: hardcoded → `${ENV}` from `ai/.env`.
7. **comfyui server**: image vs npm; keep only once ComfyUI itself is stable.
8. **Discovery for LobeChat/LiteLLM**: `/mcp.json` is OAuth-gated, lists config not endpoints.
## 7. Recommended Path (ordered, lowest-risk first)
1. **Pick the static-compose path, not DinD** — lowest risk on a single NUC;
DinD adds socket-exposure risk + a broken SSE bridge for little gain.
Fix and re-enable `mcp-tools.yaml` (uncomment `ai/docker-compose.yml:6`).
2. **Fix `mcp-tools.yaml`**: dedupe `markitdown-mcp`, fix `comfyui-mcp` env `=`,
unique service names → `ai-internal` DNS, pin images, drop comfyui for now.
3. **One streamable-http reverse proxy keyed by path** (`mcp.nuclide.systems/<server>`)
via Zoraxy or a small `httpx` proxy — replace the fake SSE stub. Backends stay
internal on `ai-internal:8000`, never host-published.
4. **Move secrets to `${ENV}`** from `ai/.env` (keys already exist).
5. **Fix OAuth callback**: exchange code against `GENERIC_TOKEN_ENDPOINT`.
6. **Verify each upstream image/tool exists** before marking a server "working".
7. **Defer the DinD gateway + Web UI** to phase-2 (read-only status over the
running compose, not spawning).
8. **Answer the litellm question**: once stable `https://mcp.nuclide.systems/<server>`
URLs exist, populate `litellm-config/config.yaml` `mcp_servers:` so
LiteLLM/LobeChat discover them — no bespoke discovery path needed.
File diff suppressed because it is too large Load Diff
+57
View File
@@ -0,0 +1,57 @@
> **STATUS: SNAPSHOT — frozen inventory from 2026-05-17. Reality has moved on; consult CHANGELOG.md + services/* for current state.**
---
# Scrubbing List — /opt/stacks (2026-05-17)
Read-only audit of unused/stale data. **Nothing here has been deleted** — review
the labels and run the commands yourself. Root FS was **165G/200G used (83%)**;
`/var/lib/docker` is 86G of `/opt/stacks`'s 99G.
No stopped/exited/`*_old` containers; no unused custom networks (already clean).
## 1. Docker reclaimable
| Target | Size | Label | Command |
|---|---|---|---|
| Build cache (268 entries, old comfyui CPU→Arc rebuilds, 0 in use) | **~20.85 GB** | **SAFE** | `docker builder prune -af` |
| 4× dangling `<none>` mcp-gateway rebuild images (1.59 GB ea) | **~6.36 GB** | **SAFE** | `docker image prune` |
| 2 old dangling images (756 MB + 113 MB) | **~0.87 GB** | **SAFE** | (same `docker image prune`) |
| ~265 anon volumes; one `54bcf7…` = **8.69 GB** unidentified, rest ~0B | ~9 GB | **REVIEW** | inspect `54bcf7…` then `docker volume prune` |
| Named dangling vols: `n8n_n8n_storage` 128M, `ai_mcpo-data` 84M, `paperless-ngx_pgdata` 26M, `metamcp_postgres_data` 18M, `daytona*_db_data` 15M×2, `librechat_pgdata2` 13M, `arcane_arcane-data` 12M, `ai_redis_data` 7.6M | ~0.3 GB | **REVIEW** | `docker volume rm <name>` per-item after confirming the stack is retired |
In-use, **DO NOT REMOVE**: `comfyui-comfyui` (6.45G), `clusterzx/paperless-ai` (8.59G).
## 2. Migrated/abandoned local data dirs (compose now points to UNAS)
| Path | Size | Label | Note |
|---|---|---|---|
| `arr-stack/media` | **55 G** | **REVIEW** | No container mounts it; arr → `/mnt/pve/unas/media`. Audiobooks/ebooks have recent mtimes (rsync residue) — **parity-check vs UNAS before `rm -rf`** |
| `ai/data` (old postgres) | 195 M | **SAFE** | Not mounted, not referenced |
| `ai/postgres_data` (incl 38M pg_wal) | 120 M | **REVIEW** | Not mounted/referenced but recent mtime |
| `ai/meili_data_v1.35.1` | 19 M | **SAFE** | Old Meili, not mounted |
| `qdrant/qdrant_storage` | 7 M | **SAFE** | qdrant migrated to UNAS (fresh start) |
| `daytona/db_data` | 14 M | **REVIEW** | No mount; daytona uses named volumes |
| `n8n/data` | empty | **SAFE** | `rmdir` |
Active local, **KEEP**: `immich/postgres` (814M), `ai/lobehub/data` (25M),
`shared-db/wal-g` (27M, RO mount), arr-stack configs, `ai/litellm-config`,
`ai/searxng`.
## 3. Migration / scratch artifacts
| Path | Label | Note |
|---|---|---|
| `ai/docker` (0 B), `ai/bucket.config.json` (empty dir) | **SAFE** | junk |
| `scripts/traefik-*.sh` | **SAFE** | Traefik abandoned for Zoraxy |
| `scripts/{migrate_*,test_adguard_api,zoraxy_csrf,zoraxy_test,configure_zoraxy_*}.py` | **REVIEW** | one-shot done; confirm no rerun need |
| `scripts/zoraxy_sync.py` | **KEEP** | ongoing proxy tooling |
| `scripts/.venv` (29M), `scripts/.kilo` (30M) | **REVIEW** | regenerable caches |
| `/tmp/{flux_*,pw_ui,add_*,fix_*}.* , /tmp/*.png , /tmp/*.log` | **SAFE** | ~1.8M scratch (this session) |
## Bottom line
- **Safe-only reclaim: ~2930 GB** (build cache + dangling images dominate).
- **With review: +~64 GB** (55G stale `arr-stack/media` after UNAS parity check, ~9G anon vols).
- **Highest-value, zero-risk action:** `docker builder prune -af` → ~20.85 GB now.
- Biggest overall prize: verify+remove `arr-stack/media` (55 G) — parity-check vs `/mnt/pve/unas/media` first.
File diff suppressed because it is too large Load Diff
+345
View File
@@ -0,0 +1,345 @@
> **STATUS: ABANDONED 2026-05-16 — Zoraxy is the production reverse proxy. Kept for design-decision history.**
---
# Traefik Migration Guide Using Docker Labels
## Overview
Migrate from Zoraxy reverse proxy to Traefik using Docker labels for zero-touch service discovery.
---
## Phase 1: Install Traefik
### Step 1: Create Directory Structure
```bash
mkdir -p /opt/stacks/proxy/traefik/{config,dynamic,letsencrypt}
```
### Step 2: Create docker-compose.yml
```yaml
version: "3.8"
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: always
network_mode: host
security_opt:
- no-new-privileges=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/stacks/proxy/traefik/config:/etc/traefik
- /opt/stacks/proxy/traefik/dynamic:/etc/traefik/dynamic
- /opt/stacks/proxy/traefik/letsencrypt:/etc/letsencrypt
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=ai-internal"
- "--providers.docker.network=shared_backend"
- "--providers.docker.defaultRule=Host(`{{ .Name }}.nuclide.systems`)"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpChallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@nuclide.systems"
- "--certificatesresolvers.letsencrypt.acme.storage=/etc/letsencrypt/acme.json"
```
### Step 3: Start Traefik
```bash
cd /opt/stacks/proxy/traefik
docker compose up -d
# Verify
docker compose ps
```
---
## Phase 2: Migrate AI Services
### AI Service Labels (Add to litellm, chat, mcp-compose.yml)
```yaml
services:
litellm:
image: litellm
labels:
- "traefik.enable=true"
- "traefik.http.routers.litellm.rule=Host(`litellm.nuclide.systems`)"
- "traefik.http.routers.litellm.entrypoints=websecure"
- "traefik.http.routers.litellm.tls=true"
- "traefik.http.routers.litellm.tls.certresolver=letsencrypt"
- "traefik.http.routers.litellm.priority=10"
- "traefik.http.services.litellm.loadbalancer.server.port=14000"
chat:
image: lobehub
labels:
- "traefik.enable=true"
- "traefik.http.routers.chat.rule=Host(`chat.nuclide.systems`)"
- "traefik.http.routers.chat.entrypoints=websecure"
- "traefik.http.routers.chat.tls=true"
- "traefik.http.routers.chat.tls.certresolver=letsencrypt"
- "traefik.http.routers.chat.priority=10"
- "traefik.http.services.chat.loadbalancer.server.port=14001"
mcp:
image: mcp-gateway
labels:
- "traefik.enable=true"
- "traefik.http.routers.mcp.rule=Host(`mcp.nuclide.systems`)"
- "traefik.http.routers.mcp.entrypoints=websecure"
- "traefik.http.routers.mcp.tls=true"
- "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
- "traefik.http.routers.mcp.priority=10"
- "traefik.http.services.mcp.loadbalancer.server.port=8080"
```
---
## Phase 3: Migrate Garage S3
### Option A: Use Traefik Proxy
```yaml
services:
garage-proxy:
image: nginx:alpine
labels:
- "traefik.enable=true"
- "traefik.http.routers.s3.rule=Host(`s3.nuclide.systems`)"
- "traefik.http.routers.s3.entrypoints=websecure"
- "traefik.http.routers.s3.tls=true"
- "traefik.http.routers.s3.tls.certresolver=letsencrypt"
- "traefik.http.services.s3.loadbalancer.server.port=10004"
volumes:
- garage-data:/data
networks:
- shared_backend
```
### Option B: Keep Internal Garage Access
```yaml
services:
# No proxy needed - access Garage via internal IP:10004
garage:
image: garageio/garage
ports:
- "3900:3900" # Internal only
- "10004:10004" # Public via Traefik
```
---
## Phase 4: Create Helper Scripts
### script 1: Add Service to Traefik
```bash
#!/bin/bash
# /opt/stacks/scripts/add-traefik-service.sh
NAME=$1
DOMAIN=$2
PORT=$3
cat > /opt/stacks/proxy/traefik/dynamic/${NAME}.yml << EOF
http:
routers:
${NAME}-router:
rule: "Host(\`${DOMAIN}\`)"
service: ${NAME}-service
entrypoints:
- websecure
tls:
certresolver: letsencrypt
services:
${NAME}-service:
loadBalancer:
servers:
- url: "http://192.168.1.40:${PORT}"
EOF
# Reload Traefik docker automatically (no manual step needed)
echo "✅ Service ${NAME} added via labels"
```
Usage:
```bash
/opt/stacks/scripts/add-traefik-service.sh myservice myservice.nuclide.systems 8000
```
### Script 2: Generate Labels for Existing Services
```bash
#!/bin/bash
# /opt/stacks/scripts/traefik-labels-gen.sh
cat > /opt/stacks/proxy/traefik/labels.yaml << 'EOF'
# Add these labels to service docker-compose.yml files
# LiteLLM
services:
litellm:
labels:
- "traefik.enable=true"
- "traefik.http.routers.litellm.rule=Host(`litellm.nuclide.systems`)"
- "traefik.http.routers.litellm.entrypoints=websecure"
- "traefik.http.routers.litellm.tls=true"
- "traefik.http.routers.litellm.tls.certresolver=letsencrypt"
- "traefik.http.services.litellm.loadbalancer.server.port=14000"
# LobeHub Chat
services:
chat:
labels:
- "traefik.enable=true"
- "traefik.http.routers.chat.rule=Host(`chat.nuclide.systems`)"
- "traefik.http.routers.chat.entrypoints=websecure"
- "traefik.http.routers.chat.tls=true"
- "traefik.http.routers.chat.tls.certresolver=letsencrypt"
- "traefik.http.services.chat.loadbalancer.server.port=14001"
# MCP Gateway
services:
mcp-gateway:
labels:
- "traefik.enable=true"
- "traefik.http.routers.mcp.rule=Host(`mcp.nuclide.systems`)"
- "traefik.http.routers.mcp.entrypoints=websecure"
- "traefik.http.routers.mcp.tls=true"
- "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
- "traefik.http.services.mcp.loadbalancer.server.port=8080"
EOF
echo "✅ Labels saved to /opt/stacks/proxy/traefik/labels.yaml"
```
Usage:
```bash
/opt/stacks/scripts/traefik-labels-gen.sh
```
---
## Phase 5: Update Service Configs
### Update `/opt/stacks/ai/.env`
```bash
# OLD (Zoraxy):
LITELLM_BASE_URL=https://litellm.nuclide.systems
PROXY_BASE_URL=https://mcp.nuclide.systems
# NEW (Traefik) - same URLs, different backend:
LITELLM_BASE_URL=https://litellm.nuclide.systems
PROXY_BASE_URL=https://mcp.nuclide.systems
CHATAI_BASE_URL=https://chat.nuclide.systems
```
### Update `/opt/stacks/ai/litellm-config/config.yaml`
```yaml
general_settings:
proxy_base_url: https://litellm.nuclide.systems
control_plane_url: https://litellm.nuclide.systems
```
---
## Phase 6: Verify SSL
### Step 1: Generate Let's Encrypt Certificates
```bash
# Verify Traefik is running
docker compose ps traefik
# Create ACME cert file
touch /opt/stacks/proxy/traefik/letsencrypt/acme.json
chmod 600 /opt/stacks/proxy/traefik/letsencrypt/acme.json
# Trigger certificate generation (will happen automatically)
# Check status:
curl -s https://acme-v02.api.letsencrypt.org/directory | head
```
### Step 2: Test HTTPS
```bash
# Test endpoints
curl -k https://litellm.nuclide.systems/health
curl -k https://chat.nuclide.systems/health
curl -k https://mcp.nuclide.systems/health
# Verify cert
curl -v https://litellm.nuclide.systems 2>&1 | grep -A 5 "SSL certificate"
```
---
## Phase 7: Remove Zoraxy
### Backup first
```bash
# Backup Zoraxy configs
docker cp zoraxy:/data/configs /backup/zoraxy-backup/
# Optional: Stop Zoraxy
docker stop zoraxy
docker rm zoraxy
```
---
## Quick Migration Checklist
- [ ] Install Traefik (Phase 1)
- [ ] Add labels to AI services (Phase 2)
- [ ] Add Garage S3 proxy (Phase 3)
- [ ] Run label generation script (Phase 4)
- [ ] Update `.env` and config files (Phase 5)
- [ ] Wait for SSL certificates (auto 24-48h)
- [ ] Test all HTTPS endpoints
- [ ] Remove Zoraxy (Phase 7)
- [ ] Update AdGuard DNS if needed (optional)
---
## Monitoring & Troubleshooting
### Check Traefik Dashboard
```bash
# Access web UI (unsecured - use only on trusted network)
open http://192.168.1.40:8080/dashboard
# View all routers
curl http://localhost:8080/api/http/routers | jq '.[] | {name: .rule, status: .entryPoints}'
# View all services
curl http://localhost:8080/api/http/services | jq '.[] | {name: .name, servers: .servers}'
```
### Common Issues
| Issue | Solution |
|-------|----------|
| 404 errors | Check router labels match domain exactly |
| SSL expired | Wait for auto-renew or trigger manually |
| Port mismatch | Verify `loadbalancer.server.port` matches service |
| No SSL cert | Check email in `acme.json` config |
---
## Rollback Plan (If Needed)
```bash
# Stop Traefik
docker compose -f /opt/stacks/proxy/traefik/docker-compose.yml down
# Restore Zoraxy configs
docker cp /backup/zoraxy-backup/ configs/
# Restart Zoraxy (if you kept backup)
docker start zoraxy || true
```
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
> **STATUS: ABANDONED 2026-05-16 — Zoraxy is the production reverse proxy. Kept for design-decision history. See services/zoraxy.md for current setup.**
---
# Recommended: Traefik Proxy Replacement
## Why Traefik over Zoraxy?
| Feature | Zoraxy | Traefik |
|---------|--------|---------|
| **API** | ❌ No public API | ✅ Full REST API |
| **SSL** | 💬 Manual (Zoraxy Web UI) | 🔥 Auto-Let's Encrypt |
| **Dynamic** | ⚠️ Manual config reload | ✅ Hot-reload configs |
| **File watching** | ❌ | ✅ Auto-detect changes |
| **Docker integration** | ⚠️ Manual | ✅ Native labels |
| **API endpoints** | 403 Forbidden | ✅ JSON API everywhere |
## Migration Path
### Current setup:
```
Zoraxy (192.168.1.4:8000) → Reverse Proxy Rules (Manual Web UI)
- litellm.nuclide.systems → 192.168.1.40:14000
- chat.nuclide.systems → 192.168.1.40:14001
- mcp.nuclide.systems → 192.168.1.40:8080
- s3.nuclide.systems → Garage:10004
```
### New setup with Traefik:
```
Traefik (public SSL) → Dynamic Router (labels/consul)
- All services auto-discovered via Docker labels
- SSL certificates auto-provisioned
- No manual Zoraxy configuration needed
```
## Installation
### Step 1: Install Traefik
```bash
# Create Traefik directory
mkdir -p /opt/stacks/proxy/traefik/{conf,dynamic}
# Create docker-compose.yml
cat > /opt/stacks/proxy/traefik/docker-compose.yml << 'EOF'
version: "3.8"
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: always
security_opt:
- no-new-privileges=true
network_mode: host
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/stacks/proxy/traefik/conf:/etc/traefik
- /opt/stacks/proxy/traefik/dynamic:/etc/traefik/dynamic
- /opt/stacks/proxy/traefik/letsencrypt:/etc/letsencrypt
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresletsencryptemail=admin@nuclide.systems"
- "--certificatesresletsencryptstorage=/etc/letsencrypt/acme.json"
EOF
# Start Traefik
cd /opt/stacks/proxy/traefik && docker compose up -d
```
### Step 2: Create Dynamic Configuration
```bash
# Create router rules
cat > /opt/stacks/proxy/traefik/dynamic/router.yml << 'EOF'
http:
routers:
litellm-router:
rule: "Host(`litellm.nuclide.systems`)"
service: litellm-service
entrypoints:
- websecure
tls:
certresolver: letsencrypt
chat-router:
rule: "Host(`chat.nuclide.systems`)"
service: chat-service
entrypoints:
- websecure
tls:
certresolver: letsencrypt
mcp-router:
rule: "Host(`mcp.nuclide.systems`)"
service: mcp-service
entrypoints:
- websecure
tls:
certresolver: letsencrypt
s3-router:
rule: "Host(`s3.nuclide.systems`)"
service: garage-service
entrypoints:
- websecure
tls:
certresolver: letsencrypt
services:
litellm-service:
loadBalancer:
servers:
- url: "http://192.168.1.40:14000"
chat-service:
loadBalancer:
servers:
- url: "http://192.168.1.40:14001"
mcp-service:
loadBalancer:
servers:
- url: "http://192.168.1.40:8080"
garage-service:
loadBalancer:
servers:
- url: "http://garage:10004"
EOF
```
### Step 3: Add Docker Labels to Services
For any Docker service you want to proxy:
```yaml
# Example: Add to your service docker-compose.yml
services:
ai-service:
image: your-service
labels:
- "traefik.enable=true"
- "traefik.http.routers.your-service.rule=Host(`your-service.nuclide.systems`)"
- "traefik.http.routers.your-service.entrypoints=websecure"
- "traefik.http.routers.your-service.tls.certresolver=letsencrypt"
- "traefik.http.services.your-service.loadBalancer.server.port=8000"
```
### Step 4: Delete Zoraxy (Optional)
```bash
# Backup Zoraxy configs first
tar -czf /backup/zoraxy-backup.tar.gz /path/to/zoraxy/configs
# Stop and remove Zoraxy
docker rm -f zoraxy || true
```
## API Example (Traefik)
```bash
# Get list of services
curl -u traefik:YOUR_TRAEFIK_API_PASSWORD http://localhost:8080/api/http/routers
# Get Traefik metrics
curl http://localhost:8080/metrics
# Reload configuration (live)
curl -X POST http://localhost:8080/api/http/routers -H "Content-Type: application/json" -d '{...}'
```
## Migration Checklist
- [ ] Deploy Traefik in host network mode
- [ ] Create Let's Encrypt certificate for your domain
- [ ] Migrate all reverse proxy rules to Traefik labels or dynamic config
- [ ] Test SSL certificates work: `curl -k https://your-domain.nuclide.systems`
- [ ] Remove Zoraxy Docker container
- [ ] Update DNS if needed
- [ ] Verify all endpoints: health checks at new URLs
## Benefits
1. **Zero maintenance SSL** - Let's Encrypt auto-renews
2. **API-driven** - No manual Web UI needed
3. **Hot reloading** - Changes apply immediately
4. **Docker-native** - Watches container labels automatically
5. **Enterprise-grade** - Used by major cloud providers
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
# Design: Claude Max Subscription → LiteLLM Gateway Bridge
**Status**: Proposal — not implemented
**Context**: LiteLLM at `https://ai.nuclide.systems` currently has no Anthropic models. Claude Max subscription (claude.ai) provides high-rate access to Sonnet 4.5/4.6 and Opus 4.7 but is decoupled from Anthropic API billing. This doc explores bridging the two.
## Problem
Claude Max and the Anthropic API are separate products with separate billing:
| | Claude Max | Anthropic API |
|---|---|---|
| Auth | OAuth / browser session | API key |
| Billing | Flat monthly subscription | Per-token |
| Rate limits | 5h rolling windows, model-specific | Tier-based RPM/TPM |
| Access | claude.ai web + Claude Code CLI | Any HTTP client |
The goal is to surface Max-subscription capacity through LiteLLM so that LobeHub, n8n, Coder workspaces, and other internal tools can call `claude-sonnet-4-6` at zero marginal cost and fall back to paid providers only when Max limits are hit.
## Approaches
### A — Session Cookie Reverse-Engineering (not recommended)
Several community projects (e.g. `claude-unofficial-api`) scrape the claude.ai WebSocket/HTTP protocol and expose an OpenAI-compatible endpoint. LiteLLM would point at this as a custom `openai/` provider.
**Pros**: Exposes the full web model lineup; streaming works.
**Cons**: Violates Anthropic ToS; breaks on any claude.ai front-end change; auth flow requires persisting browser cookies; no multimodal or tool-use parity guarantees.
**Verdict**: Avoid. Fragile and non-compliant.
---
### B — Claude Code CLI Bridge (recommended)
Claude Code CLI (`claude`) is already installed on CT 104 and authenticated with the Max subscription via `~/.claude/`. It ships a `--print` / `--output-format stream-json` mode designed for non-interactive use, and Anthropic explicitly supports programmatic use of the CLI.
A small **`claude-max-bridge`** service wraps this CLI as an OpenAI-compatible HTTP endpoint. LiteLLM registers it as a custom `openai/` base URL. No ToS issues — this is the supported surface.
```
LobeHub / n8n / Coder / Claude Code
LiteLLM Gateway (ai.nuclide.systems)
│ model: claude-sonnet-4-6 → openai/claude-sonnet-4-6
│ api_base: http://claude-max-bridge:8000
claude-max-bridge (new container, CT 104 ai-internal)
│ subprocess: claude --model ... --print --output-format stream-json
~/.claude/ (Max subscription session)
Anthropic (claude.ai)
```
**Pros**:
- Uses the officially supported programmatic interface
- Auth is already set up; no cookie management
- `claude` CLI handles retries, token limits, context window management
- Subprocess overhead is ~200400 ms cold; warm invocations faster
**Cons**:
- One subprocess per request — cannot multiplex a single session (unlike streaming HTTP)
- CLI is tied to the single authenticated user; no multi-user isolation
- Max rate limits apply per-account, same pool as interactive use
- Claude Code SDK (TypeScript) is cleaner but adds Node dependency
---
### C — Official API + Budget Cap (stop-gap)
Add Anthropic API key to LiteLLM with a hard budget cap (e.g. $20/month). Use it for Claude-specific features (tool use, long context) and let the existing free SAIA/Gemini fallback chain absorb general-purpose traffic.
**Pros**: Zero implementation work; full API feature parity.
**Cons**: Still costs money; no benefit from Max subscription.
**Verdict**: Valid fallback if B proves too complex, or as a complement for tool-heavy workloads that need the official API surface.
---
## Recommended Architecture (Option B)
### claude-max-bridge service
```
/opt/stacks/ai/claude-max-bridge/
Dockerfile
server.py # FastAPI, ~150 lines
docker-compose.yml (or entry in ai/docker-compose.yml)
```
**Dockerfile** — reuse the existing `claude` CLI install:
```dockerfile
FROM python:3.12-slim
RUN pip install fastapi uvicorn
# Mount ~/.claude from host; claude binary from host PATH or copied in
COPY server.py /app/server.py
CMD ["uvicorn", "app.server:app", "--host", "0.0.0.0", "--port", "8000"]
```
**server.py** — OpenAI `/v1/chat/completions` shim:
```python
# POST /v1/chat/completions
# Translates messages[] → claude --print --model ... --output-format stream-json
# Streams NDJSON lines back as SSE (text/event-stream)
# Maps finish_reason, usage tokens from CLI output headers
```
Key translation notes:
- `messages` → write to a temp file, pass via `--input-file` (avoids shell quoting issues)
- `stream: true` → parse `stream-json` lines, emit `data: {...}` SSE chunks
- `stream: false` → buffer all chunks, return single response object
- `max_tokens`, `temperature`, `system` → map to `--max-tokens`, `--temperature`, `--system`
- Tool use: **not supported** in first iteration; return 501 for requests with `tools`
- Model name passthrough: `claude-sonnet-4-6``--model claude-sonnet-4-6`
### LiteLLM config additions
```yaml
model_list:
- model_name: claude-sonnet-4-6
litellm_params:
model: openai/claude-sonnet-4-6
api_base: http://claude-max-bridge:8000
api_key: "dummy" # bridge ignores it; LiteLLM requires a value
stream_timeout: 120
timeout: 120
- model_name: claude-opus-4-7
litellm_params:
model: openai/claude-opus-4-7
api_base: http://claude-max-bridge:8000
api_key: "dummy"
stream_timeout: 180
timeout: 180
- model_name: claude-haiku-4-5
litellm_params:
model: openai/claude-haiku-4-5-20251001
api_base: http://claude-max-bridge:8000
api_key: "dummy"
stream_timeout: 60
timeout: 60
```
Add to `router_settings.fallbacks` — Max hits rate limit → fall to SAIA:
```yaml
fallbacks:
- claude-sonnet-4-6: [qwen3.5-397b-a17b]
- claude-opus-4-7: [qwen3.5-397b-a17b, mistral-large-latest]
- claude-haiku-4-5: [qwen3.5-122b-a10b, cerebras-llama-3.1-8b]
```
### Rate limit handling
The bridge should detect the CLI's rate-limit exit code / stderr message and return HTTP 429. LiteLLM's router will then trigger the fallback chain. No custom logic needed in LiteLLM itself.
Max limits as of 2026 (approximate, vary by plan tier):
- Sonnet 4.6: ~50 messages / 5-hour window at Pro; higher at Max
- Opus 4.7: ~1020 messages / 5-hour window
- Haiku 4.5: effectively unlimited under Max
## Volume estimate
Typical homelab workloads (LobeHub chat, n8n automations, Coder coding assist) generate maybe 200500 completions/day. Max's 5-hour windows reset 45× daily, so the practical limit is not usually hit unless Opus is used heavily.
## Risks
| Risk | Mitigation |
|---|---|
| CLI API changes between Claude Code releases | Pin the `claude` binary version; watch for breaking changes in release notes |
| Max rate limit shared with interactive use | Monitor via `claude usage`; bridge adds `X-Max-Usage` header from CLI output |
| Auth session expiry | Bridge returns 401 on auth failure; detect and alert via Gotify |
| Subprocess latency (cold start) | Keep a warm subprocess pool (12 persistent processes) using `--interactive` + message passing, or accept the 200400 ms overhead |
| No tool use support | Fallback chain routes tool-use requests to API providers (Gemini, Mistral) |
## Implementation plan
1. Write `server.py` shim (~150 lines) + Dockerfile
2. Build image on CT 104, add to `ai/docker-compose.yml`
3. Bind-mount `~/.claude` read-only into the container
4. Add model entries to `litellm-config/config.yaml`
5. Test streaming via `curl` against the bridge directly
6. Test end-to-end via LiteLLM → LobeHub
7. Watch `docker logs claude-max-bridge` for rate-limit / auth errors for 48h
## Open questions
- Does `claude --print` support all message roles (`system`, `user`, `assistant` turns)? Needs verification with multi-turn conversation format.
- Should the bridge be on CT 104 (access to `~/.claude`) or CT 109 ops (remote docker socket to CT 104)? CT 104 is simpler for v1.
- Is there an official Claude Code SDK for Python? (TypeScript SDK exists at `@anthropic-ai/claude-code`; Python wrapper may be cleaner than shelling out.)
File diff suppressed because it is too large Load Diff
+286
View File
@@ -0,0 +1,286 @@
# Stack Ideas & Improvements
Hardware baseline: **NUC 14 Pro** — Intel Core Ultra (Meteor Lake), Intel Arc iGPU
(currently used for ComfyUI / XPU image gen), no NVIDIA, UNAS for media/bulk storage.
---
## 1. Document ingestion → LobeChat knowledge base + MCP
**Goal**: ingest PDFs, Word, PPT, XLS → searchable in LobeChat chat + accessible via MCP.
Qdrant is a nice-to-have, not a requirement (no live pipeline uses it yet).
**LobeChat already has a built-in knowledge base** (`knowledge_base_files`, `chunks`,
`embeddings` tables in Postgres). The missing piece is an ingest pipeline that feeds it.
**Recommended architecture**:
```
Nextcloud folder / Paperless webhook
→ n8n trigger (already running)
→ Docling (PDF/DOCX/PPTX/XLSX → Markdown + structure)
→ LiteLLM /v1/embeddings (Mistral-embed / codestral-embed)
→ LobeChat knowledge base API ←— available in chat natively
→ Qdrant sidecar (optional, if multi-app search needed)
```
**Document conversion options**:
| Tool | Docker image | Formats | NUC 14 CPU fit |
|------|-------------|---------|----------------|
| **Docling** (IBM, 2024) | `ghcr.io/ds4sd/docling` | PDF, DOCX, PPTX, XLSX, HTML, Markdown | ✅ CPU-only, 10-60s/doc |
| **MinerU** (OpenDataLab) | `opendatalab/mineru` | PDF (layout-aware, OCR) | ✅ CPU mode; GPU optional for speed |
| **Unstructured** | `quay.io/unstructured-io/unstructured-api` | Very broad (25+ formats) | ✅ lighter than Docling |
| **Markitdown** (already in MCP gateway) | — | Office + PDFs → Markdown | ✅ ad-hoc only, not batch |
**Docling vs MinerU**: Docling is better for structured Office/PDF with tables and
figures. MinerU (OpenDataLab) is better for pure PDF optical layout analysis (e.g.,
academic papers, scanned documents). Both run on NUC 14 CPU. Start with Docling — single
container, REST API, well-documented.
**MCP access**: add a `knowledge-search` MCP tool to the gateway that calls LobeChat's
knowledge base search API. Zero new infra — LobeChat is already on `shared_backend`.
**IBM Granite embedding** (granite-embedding-30m-english) — good quality, tiny (30M params),
runs CPU-only. Alternative to Mistral-embed if you want fully local embeddings. Not on
LiteLLM yet but can add as a custom provider pointing at an Ollama/IPEX-LLM instance.
---
## 2. Document conversion pipeline
**Problem**: No systematic ingestion path from raw docs (PDF, DOCX, HTML) into
structured text for chunking/embedding.
**Tools to consider**:
| Tool | Docker image | Best for |
|------|-------------|----------|
| **Docling** (IBM, 2024) | `ghcr.io/ds4sd/docling` | PDFs with complex layout (tables, columns, figures); outputs Markdown |
| **Unstructured** | `quay.io/unstructured-io/unstructured-api` | Wide format support (HTML, DOCX, PPTX, images with OCR); REST API |
| **Gotenberg** | `gotenberg/gotenberg` | HTML/Office → PDF pre-processing stage; not a text extractor |
| **Apache Tika** | `apache/tika` | Broad format support; outputs plain text; lower quality than Docling for PDFs |
| **Markitdown MCP** | already in gateway | Ad-hoc on-demand conversion; not suitable for batch pipelines |
**Recommended pipeline (n8n-orchestrated)**:
```
Nextcloud / Paperless webhook
→ Gotenberg (Office → PDF)
→ Docling (PDF → Markdown chunks)
→ LiteLLM /v1/embeddings (Mistral-embed)
→ Qdrant upsert
```
Docling runs CPU-only comfortably on the NUC. Unstructured is heavier but has a
managed API if the self-hosted version is too slow.
Paperless-ngx already OCRs documents — its full-text content is accessible via the
REST API. An n8n workflow polling `/api/documents/?added__gt=<last_run>` can extract
and re-embed directly, without re-running OCR.
---
## 3. On-device LLM (Arc iGPU)
The Arc iGPU is currently ComfyUI-only. Small language models can also run on it:
- **IPEX-LLM** (Intel) + **Ollama** — Intel provides a patched Ollama build that uses
IPEX-LLM for Arc acceleration. Supports Phi-3.5-mini, Gemma-2B, TinyLlama.
Useful as a fast/cheap fallback when Groq/Mistral rate limits hit.
- **llama.cpp with Vulkan** — alternative to IPEX-LLM; no Intel-specific driver,
uses Vulkan compute. Less optimised for Arc but simpler to deploy.
- **Practical constraint**: Arc iGPU shares system RAM; sustained LLM inference
competes with other XPU workloads (e.g., ComfyUI). A model serving config that
pauses one workload while the other runs is needed, or deploy them on separate
CPU/GPU budgets.
If a second NUC or small GPU box becomes available, this becomes the primary use case.
---
## 4. Arcane agents on NUC (central instance on Proxmox LXC)
Arcane's central server moves to a **Proxmox LXC** (lightweight, stays responsive even
if the Docker stack has issues). The NUC and any other Docker host runs the **Arcane
agent** (headless worker), which connects back to the central instance.
**Deployment**:
- LXC: central Arcane server + observability stack (see §8)
- NUC (`.40`) and `.49`: `arcane-agent` (headless) in compose, connects to LXC
- Arcane agents reach Docker via either:
- **TCP Docker socket + TLS certs** — most secure; requires cert generation per host
- **SSH Docker contexts** — simpler; gateway mounts socket into agent via SSH tunnel
**Observability co-located on the LXC** (see §8 — lightweight, OTEL-aware stack that
survives Docker restarts).
---
## 5. AI memory / personalization
**Mem0** (`mem0ai/mem0`) — persistent user memory layer that sits in front of LLM calls.
Stores facts extracted from conversations into a vector DB (Qdrant backend supported).
Can integrate with LobeChat or as an MCP tool. Lets the AI remember preferences,
past context, and user-specific facts across sessions.
Alternative: **Letta** (formerly MemGPT) — stateful agent framework with persistent
memory; more opinionated.
---
## 6. Workflow / automation upgrades
- **n8n → AI agent nodes**: n8n 1.x has native AI agent nodes (LangChain under the hood).
Can build RAG, email triage, document processing workflows visually.
- **Activepieces** — lighter-weight n8n alternative; good for simple integrations.
Probably not worth adding since n8n is already running.
- **Temporal** — durable workflow engine for long-running or retry-heavy pipelines
(e.g., large document batch processing). Overkill unless pipelines become complex.
---
## 7. External services worth considering
| Service | Purpose | Notes |
|---------|---------|-------|
| **Backblaze B2** | Off-site backup (already in todos) | rclone sync from Garage; restic for PG dumps |
| **Cloudflare R2** | S3-compatible CDN-backed storage | Free egress; good for Lobe file serving |
| **Resend** | Transactional email | 3K/mo free; better deliverability than self-hosted Postal |
| **ntfy.sh (cloud)** | Push notifications fallback | Already running self-hosted ntfy; cloud as relay |
| **Cloudflare Turnstile** | Bot protection for public endpoints | Free; no JS challenge |
| **Novu** | Notification orchestration | Multi-channel (email, push, Slack); self-hostable |
---
## 8. Observability (on Proxmox LXC, alongside Arcane)
**Runs on the LXC, not the NUC** — stays alive if the Docker stack misbehaves. Lightweight
enough for a 2 vCPU / 4GB LXC.
**Recommended stack** (all OTEL-aware, compose-based):
| Component | Image | Role |
|-----------|-------|------|
| **OpenTelemetry Collector** | `otel/opentelemetry-collector-contrib` | Receives traces/metrics/logs from all services (OTLP gRPC+HTTP); fans out to backends |
| **VictoriaMetrics** | `victoriametrics/victoria-metrics` | Prometheus-compatible TSDB; scrapes NUC exporters + receives from OTEL collector; lighter than Prometheus |
| **Grafana** | `grafana/grafana` | Dashboards; datasource = VictoriaMetrics + Loki |
| **Loki** | `grafana/loki` | Log aggregation; receives from OTEL collector |
| **Uptime Kuma** | `louislam/uptime-kuma` | HTTP/TCP uptime checks for all public endpoints; alerts via ntfy |
**OTEL receivers** from the NUC Docker stack:
- **LiteLLM**: native OTEL export — traces every LLM call with token counts, model, latency
- **n8n**: Prometheus `/metrics` endpoint
- **Garage**: Prometheus `/metrics`
- **mcp-gateway**: add `opentelemetry-sdk` instrumentation to `server.py`
- **Docker host**: `node_exporter` + `cadvisor` on the NUC, scraped by VictoriaMetrics
**NUC → LXC connectivity**: Both are on the same LAN. OTEL collector listens on the LXC's
LAN IP (e.g., `192.168.1.X:4317` gRPC). Services push OTEL directly to it; Prometheus
pull-scraping from VictoriaMetrics goes to NUC exporters over LAN.
---
## 9. Storage / S3 improvements
- **Garage external S3** is currently non-responsive (tracked in todos). Fix or
replace with **MinIO** (single-node, much better tooling/UI, easy migration).
- **Lobe file serving via Cloudflare R2**: Lobe uploads to Garage → sync to R2 →
serve from CDN. Reduces NUC egress for image-heavy sessions.
- **Qdrant data on UNAS**: Mount `qdrant_data` from NAS for persistence across
container re-creates (same pattern as arr-stack).
---
## 9. ComfyUI MCP — async queue + image-to-image + reference
**Current problem**: MCP tool blocks until the image is done (~90-290s). LLMs time out at ~60s.
The fix is a job-queue pattern:
```
generate_image(prompt) → returns {job_id, status: "queued"} immediately
get_image_status(job_id) → returns {status, progress, image_url_when_done}
list_queue() → shows all pending/running jobs
cancel_job(job_id) → cancels a queued job (confirms with user first)
```
ComfyUI's own API is already async (`POST /prompt` → poll `/history/{id}`). The MCP server
just needs to expose this model instead of blocking.
**Image-to-image**: new workflow `flux-schnell-img2img-api.json`. Takes an init image URL +
denoise strength. ComfyUI `LoadImageFromURL` node (or upload + `LoadImage`).
**Reference tool**: `list_previous_images(n=5)` — queries ComfyUI `/history` API,
returns recent job thumbnails + prompts. User can pick one to reference or iterate from.
**Delivery to S3/Garage**: on completion, upload output PNG to Garage `comfyui-outputs` bucket
→ return a permanent URL. Avoids ComfyUI's ephemeral `/view` endpoint.
---
## 10. LiteLLM custom provider / LobeChat provider extension
**Question**: build an adapter in LiteLLM to use models via "quasi API" (non-standard
endpoints, auth, or routing)?
**LiteLLM supports custom providers** via `custom_llm_provider` in config.yaml. You write
a Python class that implements `completion()` and `async_completion()`. This is the right
path for wrapping non-standard APIs (local models, proprietary endpoints, protocol bridges).
Example use cases:
- Wrap a Claude Max subscription via Anthropic's API (different billing model)
- Add a local model served by IPEX-LLM on the Arc iGPU
- Bridge a custom inference server that speaks a different protocol
**LobeChat provider extension**: LobeChat's provider list is compiled into the app.
Adding a new provider requires rebuilding LobeChat from source (fork + add to
`src/config/aiModels/`). High effort; only worth it for a permanent/long-term provider.
For ad-hoc needs, use LiteLLM as the adapter and point LobeChat at it via the existing
`ai.nuclide.systems` OpenAI-compatible endpoint.
---
## 11. Dependency updates via Renovate Bot (Gitea Actions)
Run **Renovate Bot** as a Gitea Actions workflow to automatically open PRs for outdated
dependencies in MCP server repos (`mcp-comfyui`, `mcp-docling`, `mcp-shepard`,
`mcp-upload-artifact`). Targets: `Dockerfile` base image tags + `requirements.txt` /
`pyproject.toml` Python deps.
Renovate supports Gitea natively via `platform: gitea` in `renovate.json`. The Gitea
Actions runner (`ct111-runner`) already exists; add a scheduled workflow calling
`renovate/renovate` Docker image once daily.
---
## 12. Egress firewall on UDM (UniFi)
Enforce outbound allow-list on the UDM / UniFi gateway — block all non-approved egress
by default. Goals:
- Prevent exfiltration from compromised containers
- Audit unexpected outbound connections (model providers, analytics, telemetry)
- Approved: LiteLLM model provider endpoints, Jottacloud, UNAS internal, NTP, DNS
Implementation: UDM firewall rules (WAN_OUT) + Threat Management IDS in monitor mode first.
---
## 13. Local LLM on Arc GPU via vllm / ollama for sensitive workloads
Run a privacy-sensitive LLM locally on the Arc iGPU using **vllm** (with XPU/IPEX backend)
or the Intel-patched **Ollama** build. Use cases: document classification in Paperless
workflows, offline coding assistant, fallback when cloud rate limits hit.
See §3 (On-device LLM) for implementation notes. This item tracks the specific motivation
of *sensitive workload isolation* — i.e., running prompts that should not leave the LAN.
---
## Priority order (rough)
1. **ComfyUI MCP async queue** — fixes timeout; unblocks img2img + reference features
2. **Docling container + n8n ingest pipeline** — Nextcloud/Paperless → LobeChat knowledge base
3. **Arcane LXC + agents on NUC** — when ready to migrate
4. **Observability LXC** — OTEL collector + VictoriaMetrics + Grafana + Uptime Kuma, co-located
5. **Backblaze B2 off-site backup** — already in todos
6. **On-device LLM (Arc)** — depends on IPEX-LLM + Ollama Intel build stability
7. **Renovate Bot** — low-effort automation win for MCP repos
8. **Egress firewall** — security hygiene; plan before adding more external-facing services
File diff suppressed because it is too large Load Diff
-2323
View File
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
# Config-to-git fleet
Each host snapshots its critical config to a private Gitea repo on `git.nuclide.systems` daily. Force-push (mirror only — history isn't sacred). Token: long-lived `fkrebs` PAT embedded in remote URLs (mode 0600 on script/config).
## Repos
| Host | CT/VM | Repo | Schedule | Script | Source |
|---|---|---|---|---|---|
| PVE (`nuc`) | host | [fkrebs/pve-conf](https://git.nuclide.systems/fkrebs/pve-conf) | daily 03:00 | `/usr/local/sbin/pve-conf-backup.sh` (cron `/etc/cron.d/pve-conf-backup`) | `/etc/pve/` (excludes `priv/`, `*.key`, `authkey.pub*`) |
| Zoraxy | CT 108 | [fkrebs/zoraxy-conf](https://git.nuclide.systems/fkrebs/zoraxy-conf) | daily 03:00 | `/usr/local/sbin/zoraxy-conf-backup.sh` (cron `/etc/cron.d/zoraxy-conf-backup`) | Zoraxy config dir |
| AdGuard | CT 102 | [fkrebs/adguard-conf](https://git.nuclide.systems/fkrebs/adguard-conf) | daily 03:00 | `/usr/local/sbin/adguard-conf-backup.sh` (cron `/etc/cron.d/adguard-conf-backup`) | AdGuard config dir |
| Home Assistant | VM 100 | [fkrebs/home-assistant-config](https://git.nuclide.systems/fkrebs/home-assistant-config) | manual cron via addon `init_commands` (see below) | `/config/scripts/git-push.sh` | `/config/` (sees `.gitignore` allowlist) |
| Backrest | CT 103 | [fkrebs/ct103-conf](https://git.nuclide.systems/fkrebs/ct103-conf) | daily 03:00 | `/usr/local/sbin/ct103-conf-backup.sh` | `/opt/backrest/config/`, `/etc/cron.d/`, `/usr/local/bin/` |
| Ops stack | CT 109 | [fkrebs/ct109-conf](https://git.nuclide.systems/fkrebs/ct109-conf) | daily 03:00 | `/usr/local/sbin/ct109-conf-backup.sh` | `/opt/stacks/` (excludes `*/data/`, `*.db*`), `/etc/cron.d/` |
| Postgres / WAL-G | CT 113 | [fkrebs/ct113-conf](https://git.nuclide.systems/fkrebs/ct113-conf) | daily 03:00 | `/usr/local/sbin/ct113-conf-backup.sh` | `/opt/stacks/` (excludes `*/data/`), `/etc/postgresql/`, `/etc/cron.d/`, `/usr/local/bin/` |
| Obsidian vault | UNAS via CT 103 | [fkrebs/obsidian-vault](https://git.nuclide.systems/fkrebs/obsidian-vault) | daily 04:00 | `/usr/local/sbin/obsidian-vault-backup.sh` | `Notizen/` (excludes sync indices, `.obsidian/`, `.trash`). Rolling 2-commit history. |
| Primary Docker host | CT 104 | [fkrebs/ct104-conf](https://git.nuclide.systems/fkrebs/ct104-conf) | daily 03:00 | `/usr/local/sbin/ct104-conf-backup.sh` (cron `/etc/cron.d/ct104-conf-backup`) | `/opt/stacks/``*.yml`, `*.yaml`, `*.json`, `*.conf`, `*.sh`, `*.md` only; excludes `*/data/`, `.env`, `*.db*`, `*.key`, `*.pem` |
| Obsidian config | manual zip drop | [fkrebs/obsidian-config](https://git.nuclide.systems/fkrebs/obsidian-config) | manual (or via Obsidian Git plugin) | `/tmp/obsidian-config-init.sh` (one-shot) | `.obsidian/` minus `workspace*.json`, `cache/`, `*.bak*` |
`fkrebs/ha-config` was created in error 2026-05-24 — deleted.
## Pattern
All scripts follow the same shape:
```bash
#!/bin/bash
set -e
REPO_URL="https://fkrebs:<TOKEN>@git.nuclide.systems/fkrebs/<repo>.git"
WORK="/tmp/<name>-work"
mkdir -p "$WORK"
git -C "$WORK" init -b main -q 2>/dev/null || true
git -C "$WORK" config user.email "noreply@nuclide.systems"
git -C "$WORK" config user.name "<name>-backup"
git -C "$WORK" remote set-url origin "$REPO_URL" 2>/dev/null \
|| git -C "$WORK" remote add origin "$REPO_URL"
rsync -a --delete --exclude='.git' [+ secret excludes] <source>/ "$WORK/"
git -C "$WORK" add -A
git -C "$WORK" commit -q -m "auto: $(date -u +%Y-%m-%dT%H:%M:%SZ)" 2>/dev/null || true
git -C "$WORK" push -q --force origin main
```
PVE/CT 108/CT 102 use a rsync-to-tmp-then-push pattern. HAOS uses an in-place `git add -A` on `/config` because the `.gitignore` there is hand-curated with an allowlist for `.storage/`.
## HAOS specifics
- **Path**: `/config/scripts/git-push.sh` (mode 0700, runs in the Terminal & SSH addon).
- **Remote**: `https://fkrebs:<TOKEN>@git.nuclide.systems/fkrebs/home-assistant-config.git`. Token lives in `/config/.git/config` (mode 0600).
- **`.gitignore`** uses a default-deny allowlist for `.storage/` — only safe registry/lovelace/helpers/energy files are tracked. Tokens (`core.config_entries`, `mobile_app`, `androidtv_adbkey*`, etc.) are explicitly excluded. See `/config/.gitignore` on HAOS for the full list.
- **Scheduling** — Terminal & SSH addon container is rebuilt on update, so its `crontabs/` are not persistent. Use the addon's `init_commands` (Configuration tab):
```yaml
init_commands:
- 'echo "0 3 * * * /config/scripts/git-push.sh >> /config/scripts/git-push.log 2>&1" > /etc/crontabs/root && crond -b'
```
Then **Restart** the addon. Alternative: HA automation calling `shell_command` is not viable — the `homeassistant` container doesn't have SSH to addon containers.
## Token rotation
All four repos use the same PAT (`fkrebs` user, full repo scope). Rotate by:
1. Generate new PAT in Gitea → user settings → applications.
2. `git remote set-url origin https://fkrebs:<NEW>@git.nuclide.systems/fkrebs/<repo>.git` on each host.
3. Manually run each script once to verify.
Token leak risk: tracked in [[gitea_open_issues]]; long-term move is to switch to per-host deploy keys.
## Verification
Last commit on each repo should be `auto: <today>T03:0X:XXZ`. Quick check:
```bash
for r in pve-conf zoraxy-conf adguard-conf home-assistant-config; do
echo -n "$r: "
curl -sk -H "Authorization: token <TOKEN>" \
"https://git.nuclide.systems/api/v1/repos/fkrebs/$r/commits?limit=1" \
| python3 -c 'import json,sys; c=json.load(sys.stdin)[0]; print(c["commit"]["author"]["date"], c["commit"]["message"][:60])'
done
```
## Related
- [[homelab-architecture]] — host topology
- [[backrest-ct103]] — separate, repo-style binary backup for data (not config)
File diff suppressed because it is too large Load Diff
+349
View File
@@ -0,0 +1,349 @@
# Connection Hosts — nuclide.systems
Maintained reference for every host, LAN address, port, and public URL.
**Last verified: 2026-05-23.**
> Source of truth: [`ct-inventory.md`](../ct-inventory.md) (guests) · [`portmap.md`](portmap.md) (Docker ports) · [`homelab-architecture.md`](../services/homelab-architecture.md) (topology).
---
## Network infrastructure
| Device | LAN IP | Admin UI | Notes |
|---|---|---|---|
| **UDM Home** (UCG Fiber, UniFi OS 5.0.16) | `192.168.1.1` | `https://192.168.1.1` (SSO + MFA) | Gateway, DNS forwarder → AdGuard; port-forwards 80/443 → Zoraxy (`.4`), 15001 TCP/UDP → CT 104 |
| **D-Link DGS-1210-28P** | `192.168.1.10` | `http://192.168.1.10` (HTTP-only, pw: `tapirnase`) | 28-port PoE switch — physical core. UDM on port 26, CT 104 cluster on port 10, APs on ports 3 & 16. SNTP fixed 2026-05-19. |
| **UNAS Pro** (NFS server) | `192.168.1.31` | `http://192.168.1.31` | NFSv3 export: `192.168.1.31:/var/nfs/shared/storage` (→ `/mnt/pve/unas`). ~19 T bulk storage. |
| **UniFi U7 APs** | `.50` `.51` `.52` `.53` | via UDM | Hallway, In-wall, Bedroom (Schlafzimmer), Dining (Esszimmer). SSID: `nuclide`, WPA2/WPA3. |
| **TP-Link RE700X** | `192.168.1.187` | `http://192.168.1.187` | WiFi extender — NATs devices behind it (3D printer `.189` invisible to UniFi). |
---
## Proxmox VE host — `nuc`
| | Value |
|---|---|
| **IP** | `192.168.1.20` |
| **Admin UI** | `https://192.168.1.20:8006` (OIDC via Pocket-ID client `38469e7e`) |
| **SSH** | `ssh root@192.168.1.20` (key auth) |
| **Hardware** | Intel Core Ultra 7 155H · 22 threads · 64 GiB RAM · PVE 9.1.11 |
| **Storage** | `local-zfs` ~1.9 T (NVMe), `local` (dir), `unas` (NFS ~19 T) |
| **API token** | `root@pam!mcp` (PVEAuditor role, read-only) |
---
## LXC / VM guests
### VM 100 — `haos` (Home Assistant OS)
| | Value |
|---|---|
| **IP** | `192.168.1.60` (DHCP, stable) |
| **HA UI** | `https://ha.nuclide.systems``192.168.1.60:8123` |
| **SSH** | `ssh root@192.168.1.60` (key installed manually in HA terminal) |
| **OCPP** | `https://ocpp.nuclide.systems``192.168.1.60:8887` |
| **HA-MCP add-on** | `http://192.168.1.60:9583/private_ehnWeRl2G3De6NnbcN7teQ` (gateway upstream, no TLS) |
| **Specs** | 4c / 16 GiB (balloon 4 GiB) / 32 GiB; USB Zigbee dongle passed through |
### CT 101 — `shepard`
| | Value |
|---|---|
| **IP** | `192.168.1.49` |
| **SSH** | `ssh root@192.168.1.49` |
| **Public** | `https://shepard.nuclide.systems` (Caddy → `:80`) · `https://shepard-api.nuclide.systems` (→ `:8080`) |
| **MCP** | `https://shepard.nuclide.systems/v2/mcp` (Bearer `${SHEPARD_API_KEY}`) |
| **Specs** | 12c / 32 GiB / 500 GiB + NFS; Intel iGPU (card+render) |
| **Stack** | Caddy, Shepard frontend/backend, Keycloak, Mongo, Neo4j, TimescaleDB |
### CT 102 — `dns` (AdGuard Home)
| | Value |
|---|---|
| **IP** | `192.168.1.2` |
| **SSH** | `ssh root@192.168.1.2` |
| **Admin UI** | `http://192.168.1.2` (port 80) |
| **DNS** | `192.168.1.2:53` — LAN resolver (UDM forwards all DNS here) |
| **Specs** | 2c / 1 GiB / 4 GiB |
### CT 103 — `backrest`
| | Value |
|---|---|
| **IP** | `192.168.1.3` |
| **SSH** | `ssh root@192.168.1.3` |
| **UI** | `http://192.168.1.3:9898` (LAN-only, no auth) |
| **Specs** | 1c / 512 MiB / 8 GiB + NFS (`/mnt/pve/unas`) |
| **Notes** | JottaCloud offsite via rclone; repos `services-repo` + `media-repo` |
### CT 104 — `docker` (main Docker host)
| | Value |
|---|---|
| **IP** | `192.168.1.40` |
| **SSH** | `ssh root@192.168.1.40` |
| **Specs** | 16c / 48 GiB / 200 GiB + NFS; Intel Arc iGPU (card+render) |
| **Role** | ~65 containers across ~23 compose stacks in `/opt/stacks/` |
See [Docker services table](#docker-services--ct-104) below for all ports.
### CT 105 — `nextcloud`
| | Value |
|---|---|
| **IP** | `192.168.1.41` |
| **SSH** | `ssh root@192.168.1.41` |
| **Public** | `https://nc.nuclide.systems``192.168.1.41:11000` |
| **Specs** | 4c / 8 GiB / 100 GiB + NFS (migrated to NFSv3 2026-05-22) |
| **Auth** | Pocket-ID OIDC (client `a14b8076`) |
### CT 108 — `zoraxy` (reverse proxy)
| | Value |
|---|---|
| **IP** | `192.168.1.4` |
| **SSH** | `ssh root@192.168.1.4` |
| **Admin UI** | `http://192.168.1.4:8000` (LAN only) |
| **Specs** | 2c / 2 GiB / 6 GiB |
| **Cert** | Wildcard `*.nuclide.systems` (ACME via Let's Encrypt) |
| **Config** | `proxy/zoraxy/routes.json``scripts/zoraxy_sync.py --apply` |
### CT 110 — `id` (Pocket-ID OIDC)
| | Value |
|---|---|
| **IP** | `192.168.1.5` |
| **SSH** | `ssh root@192.168.1.5` |
| **Public** | `https://id.nuclide.systems``192.168.1.5:11000` |
| **Specs** | 1c / 1 GiB / 4 GiB |
| **OIDC endpoints** | Authorization: `https://id.nuclide.systems/authorize` · Token: `https://id.nuclide.systems/api/oidc/token` · Userinfo: `https://id.nuclide.systems/api/oidc/userinfo` · Discovery: `https://id.nuclide.systems/.well-known/openid-configuration` |
### CT 111 — `dev` (Coder + Gitea)
| | Value |
|---|---|
| **IP** | `192.168.1.42` |
| **SSH** | `ssh root@192.168.1.42` |
| **Specs** | 12c / 32 GiB / 60 GiB + NFS; Intel Arc iGPU (render) |
| Port | Service | Public URL |
|---|---|---|
| 7080 | Coder | `https://dev.nuclide.systems` |
| 3000 | Gitea | `https://git.nuclide.systems` |
| 222 | Gitea SSH | `ssh -p 222 git@git.nuclide.systems` |
| 13080 | docs site | `http://192.168.1.42:13080` (LAN; mkdocs Material, auto-rebuild every 5 min) |
| internal | act-runner | — (`ct111-runner` Gitea Actions) |
### CT 112 — `secrets` (Infisical)
| | Value |
|---|---|
| **IP** | `192.168.1.7` |
| **SSH** | `ssh root@192.168.1.7` |
| **UI + API** | `http://192.168.1.7:8200` **(LAN-only — no Zoraxy route; must not be internet-exposed)** |
| **Specs** | 2c / 4 GiB / 20 GiB |
| **Stack** | `/opt/stacks/infisical/` — Infisical + Postgres 16 + Redis 7 (all internal, no external ports) |
### CT 113 — `db` (shared Postgres)
| | Value |
|---|---|
| **IP** | `192.168.1.6` |
| **SSH** | `ssh root@192.168.1.6` |
| **Specs** | 2c / 4 GiB / 40 GiB |
| Port | Service | Access |
|---|---|---|
| 5432 | Postgres 17 | LAN: `192.168.1.6:5432` — tenants: LiteLLM, paperless, memos, n8n (reverted), Vaultwarden |
| 5050 | pgAdmin 4 | `http://192.168.1.6:5050` (LAN only, no Zoraxy route) |
---
## Docker services — CT 104
All on `192.168.1.40` unless noted. Zoraxy (`192.168.1.4`) terminates TLS for public URLs.
### Infrastructure (1000010999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 10000 | `homepage` | — | Dashboard (LAN only) |
| 10001 | `dozzle` | `https://dozzle.nuclide.systems` | Log viewer |
| 10002 | `arcane` | `https://arcane.nuclide.systems` | Web IDE (OIDC `81cf4ed0`) |
| 10003 | `gotify` | `https://gotify.nuclide.systems` | Push notifications |
| 10004 | `garage` | `https://s3.nuclide.systems` | S3 API (Garage); internal: `http://garage:3900` |
| 10005 (localhost) | `garage` admin | — | Localhost only |
### Security & Auth (1100011999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 11001 | `vaultwarden` | `https://vault.nuclide.systems` | Password manager (OIDC client created, SSO not yet wired) |
### Media — Immich (1200012999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 12000 | `immich_server` | `https://immich.nuclide.systems` | Photos/videos (OIDC `9c91c18b`) |
| internal | `immich_power_tools` | `https://immich-tools.nuclide.systems` | Container-internal :3000, Zoraxy proxy |
### Media — Downloads / Arr (1300013999)
All behind `vpn_gluetun` container network.
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 13001 | `rdtclient` | — | LAN only |
| 13002 | `prowlarr` | — | LAN only |
| 13003 | `audiobookshelf` | `https://abs.nuclide.systems` | OIDC `cbbf20d5` |
| 13004 | `shelfarr` | — | LAN only (OIDC `d8733fcc`) |
| 13005 | `flaresolverr` | — | Internal only |
| 30000 | `vpn_gluetun` | — | VPN HTTP control API |
### AI Stack (1400014999, 8080, 18xxx)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 14000 | `litellm` | — | LLM proxy (internal only: `http://litellm:4000`); master key: `sk-tapirnase` |
| 14001 | `lobehub` | `https://chat.nuclide.systems` | Chat UI (OIDC `26f3c26b`) |
| 14002 | `qdrant_scientific` | — | Vector DB (LAN/internal only) |
| 14003 | `bifrost` | `https://ai.nuclide.systems` | LLM+MCP gateway; `/mcp` = MCP endpoint (29 clients, ~760 tools); master key: `sk-tapirnase` |
| ~~8080~~ | ~~`mcp-gateway`~~ | ~~`https://mcp.nuclide.systems`~~ | **DECOMMISSIONED 2026-05-26** — see [mcp-gateway.md](../services/mcp-gateway.md) |
| 18002 | `comfyui` | — | Image gen (Intel Arc); LAN only |
| 18003 | `comfyui-mcp` | via Bifrost `comfyui` client | FastMCP |
| 18005 | `docling-mcp` | via Bifrost `docling` client | PDF→Markdown |
| 18007 | `kroki-mcp` | via Bifrost `kroki` client | Diagram rendering |
| 18009 | `speaches` | — | TTS/STT; LAN only |
| 18011 | `upload-artifact-mcp` | via Bifrost `upload_artifact` client | S3 artifact upload |
| internal | `searxng` | — | `shared_backend`; used by LobeChat |
### Documents (1500015999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 15000 | `traccar` | `https://traccar.nuclide.systems` | GPS tracking UI |
| 15001 | `traccar` | — | TCP+UDP watch protocol (forwarded at UDM level) |
| 15002 | `paperless-ai` | `https://paperless-ai.nuclide.systems` | Internal-only Zoraxy policy |
| 15003 | `paperless-ngx-webserver-1` | `https://paperless.nuclide.systems` | Internal-only Zoraxy policy |
### Automation (1600016999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 16000 | `n8n` | `https://n8n.nuclide.systems` | Workflow automation (OIDC `33135ad4`); SQLite local disk |
### Notes & Bookmarks (1700017999)
| Port | Container | Public URL | Notes |
|---|---|---|---|
| 17000 | `memos` | `https://memos.nuclide.systems` | Notes (OIDC `62bf4e0d`) |
| 17001 | `karakeep` | `https://hoarder.nuclide.systems` | Bookmarks (OIDC `d92f82b0`) |
### Storage Admin (2000020999)
| Port | Container | Notes |
|---|---|---|
| 20010 (localhost) | `pgadmin` | shared-db pgAdmin, localhost only on CT 104 |
---
## External hardware
### QNAP UNAS Pro — NFS/SMB server
| | Value |
|---|---|
| **IP** | `192.168.1.31` |
| **Admin UI** | `http://192.168.1.31` |
| **NFS export** | `192.168.1.31:/var/nfs/shared/storage` (NFSv3 only; v4 not available) |
| **CIFS** | `//192.168.1.31/storage` (used by Nextcloud CT 105 only) |
### QNAP TS-251D — Klipper 3D printer host
| | Value |
|---|---|
| **IP** | `192.168.1.189` (behind TP-Link RE700X at `.187`) |
| **Mainsail** | `http://192.168.1.189:80` |
| **Moonraker** | `http://192.168.1.189:7125` |
| **Notes** | Klipper config backed up daily to `git.nuclide.systems/fkrebs/klipper-config` via cron |
---
## OIDC clients — Pocket-ID (`id.nuclide.systems`)
| Client ID | Service | Redirect URI |
|---|---|---|
| ~~`e73bb7b9`~~ | ~~litellm / mcp-gateway~~ | **DELETED 2026-05-26** |
| `26f3c26b` | lobehub | `https://chat.nuclide.systems/api/auth/callback/generic-oidc` |
| `81cf4ed0` | arcane | `https://arcane.nuclide.systems/auth/oidc/callback` |
| `33135ad4` | n8n | `https://n8n.nuclide.systems/auth/oidc/callback` |
| `9c91c18b` | immich | `https://immich.nuclide.systems/auth/login` + mobile |
| `62bf4e0d` | memos | `https://memos.nuclide.systems/auth/callback` |
| `d92f82b0` | karakeep | `https://hoarder.nuclide.systems/api/auth/callback/custom` |
| `a14b8076` | nextcloud | `https://nc.nuclide.systems/apps/user_oidc/code` |
| `0aee4280` | Coder | `https://dev.nuclide.systems/api/v2/users/oidc/callback` |
| `9444609e` | Gitea | `https://git.nuclide.systems/user/oauth2/pocket-id/callback` |
| `38469e7e` | Proxmox VE | `https://192.168.1.20:8006` |
| `cbbf20d5` | Audiobookshelf | `https://abs.nuclide.systems/…` + `*` |
| `d8733fcc` | shelfarr | `*` |
| ~~`78c78998`~~ | ~~Claude MCP (legacy)~~ | **DELETED 2026-05-26** |
| `82ca2d53` | nuc-ai (MCP spawned servers) | (empty) |
| `7fe1a14b` | zoraxy | (empty) |
| (new) | vaultwarden | `https://vault.nuclide.systems/auth/callback` — SSO not yet wired |
| ~~798a367f~~ | ~~daytona~~ | **DECOMMISSIONED — remove from Pocket-ID** |
---
## Quick-reference — all public URLs
| URL | Backend | Service |
|---|---|---|
| `https://ai.nuclide.systems` | `192.168.1.40:14003` | Bifrost (LLM+MCP gateway) — `/mcp` for MCP tools |
| `https://chat.nuclide.systems` | `192.168.1.40:14001` | LobeChat |
| ~~`https://mcp.nuclide.systems`~~ | ~~`192.168.1.40:8080`~~ | **DECOMMISSIONED 2026-05-26** |
| `https://id.nuclide.systems` | `192.168.1.5:11000` | Pocket-ID (OIDC) |
| `https://nc.nuclide.systems` | `192.168.1.41:11000` | Nextcloud |
| `https://ha.nuclide.systems` | `192.168.1.60:8123` | Home Assistant |
| `https://ocpp.nuclide.systems` | `192.168.1.60:8887` | EV charger OCPP |
| `https://shepard.nuclide.systems` | `192.168.1.49:80` | Shepard |
| `https://shepard-api.nuclide.systems` | `192.168.1.49:8080` | Shepard API |
| `https://dev.nuclide.systems` | `192.168.1.42:7080` | Coder |
| `https://git.nuclide.systems` | `192.168.1.42:3000` | Gitea |
| `https://arcane.nuclide.systems` | `192.168.1.40:10002` | Arcane |
| `https://dozzle.nuclide.systems` | `192.168.1.40:10001` | Dozzle (logs) |
| `https://gotify.nuclide.systems` | `192.168.1.40:10003` | Gotify |
| `https://s3.nuclide.systems` | `192.168.1.40:10004` | Garage S3 |
| `https://vault.nuclide.systems` | `192.168.1.40:11001` | Vaultwarden |
| `https://immich.nuclide.systems` | `192.168.1.40:12000` | Immich |
| `https://immich-tools.nuclide.systems` | `192.168.1.40` (internal) | Immich Power Tools |
| `https://abs.nuclide.systems` | `192.168.1.40:13003` | Audiobookshelf |
| `https://n8n.nuclide.systems` | `192.168.1.40:16000` | n8n |
| `https://memos.nuclide.systems` | `192.168.1.40:17000` | Memos |
| `https://hoarder.nuclide.systems` | `192.168.1.40:17001` | Karakeep |
| `https://traccar.nuclide.systems` | `192.168.1.40:15000` | Traccar |
| `https://paperless.nuclide.systems` | `192.168.1.40:15003` | Paperless-ngx (internal-only policy) |
| `https://paperless-ai.nuclide.systems` | `192.168.1.40:15002` | Paperless AI (internal-only policy) |
**Not publicly proxied** (LAN/localhost only): pgAdmin, ComfyUI, Qdrant, RDTClient, Prowlarr, ShelfArr, Flaresolverr, Speaches, AdGuard admin, Backrest UI, Infisical.
---
## SSH cheat-sheet
```bash
ssh root@192.168.1.20 # PVE host (nuc)
ssh root@192.168.1.40 # CT 104 docker
ssh root@192.168.1.41 # CT 105 nextcloud
ssh root@192.168.1.42 # CT 111 dev
ssh root@192.168.1.49 # CT 101 shepard
ssh root@192.168.1.2 # CT 102 dns (AdGuard)
ssh root@192.168.1.3 # CT 103 backrest
ssh root@192.168.1.4 # CT 108 zoraxy
ssh root@192.168.1.5 # CT 110 id (Pocket-ID)
ssh root@192.168.1.6 # CT 113 db (Postgres)
ssh root@192.168.1.7 # CT 112 secrets (Infisical)
ssh root@192.168.1.60 # VM 100 haos (Home Assistant — key must be installed manually)
ssh -p 222 git@git.nuclide.systems # Gitea SSH
```
All LXCs reachable from `nuc` host via root key. Use `pct exec <id> -- bash` for console access without SSH.
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
# Docker Networks
## Current Landscape (May 16, 2026)
Each Docker Compose stack creates its own `{stack}_default` bridge network when it
has no explicit `networks:` declaration. This has exhausted Docker's built-in
172.x.x.x/16 address pool, triggering CIDR overlap errors.
### Networks & Subnets
| Network | Subnet | Containers |
|---|---|---|
| `bridge` (built-in) | 10.0.0.0/24 | 0 |
| `ai-internal` | 172.31.0.0/16 | 8 |
| `arcane_default` | 172.22.0.0/16 | 1 |
| `arr-stack_default` | 172.21.0.0/16 | 4 |
| `dozzle_default` | 192.168.16.0/20 | 1 |
| `homepage_default` | 172.19.0.0/16 | 1 |
| `immich_default` | 172.18.0.0/16 | 5 |
| `karakeep_default` | 172.30.0.0/16 | 3 |
| `memos_default` | 192.168.32.0/20 | 1 |
| `n8n_default` | 172.25.0.0/16 | 1 |
| `ntfy_default` | 172.27.0.0/16 | 1 |
| `nuc-ai-core_default` | 172.29.0.0/16 | 1 |
| `paperless-ngx_default` | 172.28.0.0/16 | 5 |
| `pocketid_default` | 172.20.0.0/16 | 1 |
| `qdrant_default` | 172.24.0.0/16 | 1 |
| `traccar_default` | 172.26.0.0/16 | 1 |
| `vaultwarden_default` | 192.168.64.0/20 | 1 |
| `vpn_default` | 192.168.80.0/20 | 1 |
Total: 18 user-defined bridge networks (Daytona decommissioned 2026-05-20).
### Problem
Docker's default address pool for user-defined bridge networks is
172.17.0.0/16 172.31.0.0/16 (15 subnets max). With 15 172.x.x.x/16 networks
already allocated, there is no room for new ones.
The Daytona runner (`daytona-minimal-runner-1`) programmatically creates a
`runner-bridge` network on startup. It fails with:
```
Error response from daemon: invalid pool request: Pool overlaps with other one
on this address space
```
## Consolidation Plan
### shared_backend Network
A single shared bridge network (`shared_backend`) has been created to replace
per-stack defaults for lightweight services that don't need isolation.
### Stacks Already Migrated
- arcane
- dozzle
- ntfy
- qdrant
- traccar
- vaultwarden
- memos
- n8n
- pocketid
These stacks now declare:
```yaml
networks:
default:
external: true
name: shared_backend
```
### How to Free Subnets
After migrating a stack to `shared_backend`, recreate it and prune the old network:
```bash
cd /opt/stacks/{stack} && docker compose up -d
docker network rm {stack}_default # after containers disconnect
```
### Stacks Keeping Own Networks
These stacks have complex internal networking and should keep their own:
- **immich** — 5 services with inter-dependencies
- **paperless-ngx** — 5 services (webserver, broker, db, gotenberg, tika)
- **arr-stack** — 5 services (rdtclient, prowlarr, audiobookshelf, shelfarr, flaresolverr)
- **karakeep** — 3 services with chrome dependency
- **homepage** — single service, can stay or migrate
- **streamio** — already removed, stremio uses VPN container directly
- **vpn** — single service, can stay or migrate
- **ai/*_ai** — AI stacks, untouched
### Long-term Fix
Add `default-address-pools` to `/etc/docker/daemon.json`:
```json
{
"default-address-pools": [
{"base": "10.0.0.0/8", "size": 24}
]
}
```
This gives 65536 /24 subnets, eliminating exhaustion. Requires Docker daemon
restart (`systemctl restart docker`), which briefly disrupts all containers.
## Commands
```bash
# List all networks
docker network ls
# Inspect a network
docker network inspect {name}
# Remove unused networks
docker network prune
# Remove a specific network (must have 0 containers)
docker network rm {name}
```
File diff suppressed because it is too large Load Diff
+313
View File
@@ -0,0 +1,313 @@
# Port Map — NUC 14 Docker Stacks
> **Reverse proxy**: [Zoraxy v3.3.2](http://192.168.1.4:8000) on `192.168.1.4:8000` (LXC 108)
> Wildcard cert `*.nuclide.systems` · source of truth: [`proxy/zoraxy/routes.json`](proxy/zoraxy/routes.json)
> Manage routes: `uv run scripts/zoraxy_sync.py [--apply|--prune|--list]`
## Port Scheme
| Range | Category |
|---|---|
| 3100 | Loki (log aggregation) |
| 9090 | Prometheus (monitoring) |
| 9091 | Grafana (monitoring) |
| 9100 | node-exporter (host metrics) |
| 12345 | Alloy (log agent UI) |
| 1000010999 | Infrastructure |
| 1100011999 | Security & Auth |
| 1200012999 | Media Immich |
| 1300013999 | Media Downloads / Arr |
| 1400014999 | AI Stack |
| 1500015999 | Documents |
| 1600016999 | Automation |
| 1700017999 | Notes & Bookmarks |
| 1800018999 | DevOps / Image Gen |
| 1900019999 | Tracking |
| 2000020999 | Storage Admin |
| 3000030999 | VPN Control |
---
## Monitoring — CT 109 ops (192.168.1.8)
| Port | Host | Service | Notes |
|---|---|---|---|
| [9090](http://192.168.1.8:9090) | CT 109 | Prometheus | LAN only; 90d retention |
| [3000](http://192.168.1.8:3000) | CT 109 | Grafana | LAN only; admin/tapirnase |
| [3100](http://192.168.1.8:3100) | CT 109 | Loki | LAN only; 30d retention; log aggregation |
| [9221](http://192.168.1.8:9221) | CT 109 | pve-exporter | Proxmox VE metrics; auth: `monitor@pve!prometheus` |
| [12345](http://192.168.1.8:12345) | CT 109 | Alloy (self) | agent UI; also runs on all other hosts at :12345 |
| [4090](http://192.168.1.8:4090) | CT 109 | Wetty | LAN only (127.0.0.1); web SSH → jump-menu.sh on nuc |
| [10000](http://192.168.1.8:10000) | CT 109 | Homepage | LAN only (`ops.nuclide.lan:10000`); service dashboard; remote Docker via socket-proxy :2375 on CT 113, direct TCP on CT 104 |
| [10001](http://192.168.1.8:10001) | CT 109 | Dozzle | LAN only; live log viewer; agents on all 7 Docker hosts |
| ~~10002~~ | ~~CT 109~~ | ~~Arcane~~ | **DECOMMISSIONED 2026-05-26** — replaced by Portainer |
| [13080](http://192.168.1.8:13080) | CT 109 | docs-server | LAN only; mkdocs Material; auto-rebuilds from `fkrebs/docs` every 5 min — migrated from CT 111 2026-05-23 |
| [8200](http://192.168.1.8:8200) | CT 109 | Infisical | LAN only; secrets manager; migrated from CT 112 2026-05-26; `http://secrets.nuclide.lan:8200` |
| [11000](http://192.168.1.8:11000) | CT 109 | Pocket-ID | `https://id.nuclide.systems`; OIDC IdP; migrated from CT 110 2026-05-26 |
| 9100 | CT 109 | node-exporter | host-network, self-scrape |
| 9100 | CT 104 | node-exporter | standalone stack `/opt/stacks/monitoring/`; scraped by CT 109 |
Scrape targets (CT 109 Prometheus): ~~`litellm` CT104:14000/metrics/~~ (**SUNSET 2026-05-26**), `node-ct104` :9100, `node-ct109` :9100, `home-assistant` 192.168.1.60:8123/api/prometheus (HA token), `prometheus` self, `walg` CT113:9100/textfile (WAL-G backup freshness, added 2026-05-23).
**Alloy (log agent)**: deployed on all 12 hosts → ships to Loki at CT 109:3100.
- Docker hosts (CT 101/104/105/109/110/111/112/113): container at `/opt/stacks/alloy/`; reads Docker socket + journald
- Binary/systemd (CT 102/103/108 + nuc): `/etc/alloy/config.alloy`; reads journald only
- HA VM 100: Grafana Alloy add-on (wymangr/hassos-addons v0.0.8) — pushes metrics to CT 109 Prometheus remote_write + logs to Loki.
---
## Infrastructure (1000010999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [10000](http://192.168.1.40:10000) | Homepage | `homepage` | — | **DECOMMISSIONED 2026-05-23** — compose renamed `.DECOMMISSIONED` |
| [10001](http://192.168.1.40:10001) | Dozzle | `dozzle` | — | **DECOMMISSIONED** — moved to CT 109 2026-05-23 |
| [10002](http://192.168.1.40:10002) | Arcane | `arcane` | — | **DECOMMISSIONED** — moved to CT 109 2026-05-23 |
| [10003](http://192.168.1.40:10003) | Gotify | `gotify` | [gotify.nuclide.systems](https://gotify.nuclide.systems) | Push notifications |
| [10004](http://192.168.1.40:10004) | Garage S3 API | `garage` | [s3.nuclide.systems](https://s3.nuclide.systems) | FIXED 2026-05-21: proxied by Zoraxy with ACME TLS. Garage API accessible at `https://s3.nuclide.systems`. Internal: `http://garage:3900` |
| 10005 (127.0.0.1 only) | Garage Admin | `garage` | — | localhost only |
---
## Dev (CT 104 — migrated from CT 111 2026-05-26)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [3000](http://192.168.1.40:3000) | Gitea | `gitea` | [git.nuclide.systems](https://git.nuclide.systems) | Self-hosted Git; OIDC via Pocket-ID; Redis queue (`gitea_redis`) |
| [222](http://192.168.1.40:222) | Gitea SSH | `gitea` | — | `ssh -p 222 git@git.nuclide.systems` |
| [7080](http://192.168.1.40:7080) | Coder | `coder` | [dev.nuclide.systems](https://dev.nuclide.systems) | Workspace orchestrator; OIDC via Pocket-ID |
| (no port) | act-runner | `act-runner` | — | Gitea Actions runner (`ct104-runner`) |
| [1025](http://192.168.1.40:1025) | Proton Bridge SMTP | `proton-bridge` | — | LAN only; requires `docker exec -it proton-bridge /bin/bash` for initial login |
| [1143](http://192.168.1.40:1143) | Proton Bridge IMAP | `proton-bridge` | — | LAN only |
---
## Security & Auth (1100011999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [11001](http://192.168.1.40:11001) | Vaultwarden | `vaultwarden` | [vault.nuclide.systems](https://vault.nuclide.systems) | Password manager · Pocket-ID OIDC client created 2026-05-21; auth flow not yet configured |
> Pocket-ID **migrated from this CT to LXC 110** on 2026-05-20, then **to CT 109** on 2026-05-26. See the [External Services](#external-services-not-on-nuc-docker) table below.
---
## Media Immich (1200012999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [12000](http://192.168.1.40:12000) | Immich | `immich_server` | [immich.nuclide.systems](https://immich.nuclide.systems) | Photos/videos |
| — | Immich Power Tools | `immich_power_tools` | [immich-tools.nuclide.systems](https://immich-tools.nuclide.systems) | Container-internal :3000, Zoraxy proxy |
---
## Media Downloads / Arr Stack (1300013999)
All arr-stack services run behind `vpn_gluetun` container network.
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [13001](http://192.168.1.40:13001) | RDTClient | `rdtclient` | — | LAN only |
| [13002](http://192.168.1.40:13002) | Prowlarr | `prowlarr` | — | LAN only |
| [13003](http://192.168.1.40:13003) | Audiobookshelf | `audiobookshelf` | [abs.nuclide.systems](https://abs.nuclide.systems) | |
| [13004](http://192.168.1.40:13004) | ShelfArr | `shelfarr` | — | LAN only |
| [13005](http://192.168.1.40:13005) | Flaresolverr | `flaresolverr` | — | Internal only |
| [30000](http://192.168.1.40:30000) | Gluetun VPN control | `vpn_gluetun` | — | HTTP control API |
---
## AI Stack (1400014999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| ~~14000~~ | ~~LiteLLM~~ | ~~`litellm`~~ | — | **SUNSET 2026-05-26** — service block commented out in `ai/docker-compose.yml`; replaced entirely by Bifrost |
| ~~14001~~ | ~~LobeHub~~ | ~~`lobehub`~~ | — | **DECOMMISSIONED 2026-05-26** — compose renamed `.DECOMMISSIONED-lobehub-2026-05-26.yml`; replaced by Open WebUI |
| [14002](http://192.168.1.40:14002) | Open WebUI | `open-webui` | [chat.nuclide.systems](https://chat.nuclide.systems) | Chat UI; stack `ai/open-webui.yml`; uses Qdrant + TEI for RAG |
| [14003](http://192.168.1.40:14003) | Bifrost | `bifrost` | [ai.nuclide.systems](https://ai.nuclide.systems) | LLM gateway + MCP at `/mcp`; auth via `sk-bf-` VKs; stack `ai/bifrost/` |
| ~~8080~~ | ~~MCP Gateway~~ | ~~`mcp-gateway`~~ | — | **DECOMMISSIONED 2026-05-26** — compose renamed `.DECOMMISSIONED-2026-05-26`; MCP now at `https://ai.nuclide.systems/mcp` (Bifrost) |
| [18002](http://192.168.1.40:18002) | ComfyUI | `comfyui` | — | LAN only (Intel Arc iGPU, FLUX.1-schnell GGUF) |
| [18003](http://192.168.1.40:18003) | ComfyUI MCP | `comfyui-mcp` | — | FastMCP; reachable via gateway at `mcp.nuclide.systems/comfyui/mcp` |
| [18005](http://192.168.1.40:18005) | Docling MCP | `docling-mcp` | — | SAIA Docling PDF→Markdown; reachable via gateway |
| [18007](http://192.168.1.40:18007) | Kroki MCP | `kroki-mcp` | — | Diagram rendering; reachable via gateway |
| [18009](http://192.168.1.40:18009) | Speaches | `speaches` | — | TTS/STT; LAN only |
| ~~18010~~ | ~~Shepard MCP~~ | ~~`shepard-mcp`~~ | — | **DECOMMISSIONED 2026-05-21** — replaced by native `https://shepard.nuclide.systems/v2/mcp` (streamable HTTP; gateway entry: `shepard`, auth via `${SHEPARD_API_KEY}`) |
| [18011](http://192.168.1.40:18011) | Upload-artifact MCP | `upload-artifact-mcp` | — | S3 chat-artifacts upload; reachable via gateway |
| — | SearXNG | `searxng` | — | Internal, `shared_backend`, used by LobeHub |
---
## Documents (1500015999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [15000](http://192.168.1.40:15000) | Traccar HTTP | `traccar` | [traccar.nuclide.systems](https://traccar.nuclide.systems) | GPS tracking UI |
| [15001](http://192.168.1.40:15001) | Traccar GPS | `traccar` | — | TCP+UDP watch protocol |
| [15002](http://192.168.1.40:15002) | Paperless AI | `paperless-ai` | [paperless-ai.nuclide.systems](https://paperless-ai.nuclide.systems) | Internal-only Zoraxy policy |
| [15003](http://192.168.1.40:15003) | Paperless-ngx | `paperless-ngx-webserver-1` | [paperless.nuclide.systems](https://paperless.nuclide.systems) | Internal-only Zoraxy policy |
---
## Automation (1600016999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [16000](http://192.168.1.40:16000) | n8n | `n8n` | [n8n.nuclide.systems](https://n8n.nuclide.systems) | Workflow automation |
---
## Notes & Bookmarks (1700017999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| [17000](http://192.168.1.40:17000) | Memos | `memos` | [memos.nuclide.systems](https://memos.nuclide.systems) | Notes |
| [17001](http://192.168.1.40:17001) | Karakeep | `karakeep` | [hoarder.nuclide.systems](https://hoarder.nuclide.systems) | Bookmarks |
| [17002](http://192.168.1.40:17002) | Miniflux | `miniflux` | [flux.nuclide.systems](https://flux.nuclide.systems) | RSS reader; OIDC via Pocket-ID; DB on CT 113; MCP at `miniflux-mcp` (:ai-internal); paywall-bypass n8n flow auto-fetches full text via crawl4ai |
---
## DevOps (1800018999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
<!-- Daytona DECOMMISSIONED 2026-05-20; replaced by Coder on CT 111. Compose renamed *.DECOMMISSIONED-2026-05-20. -->
| — | MCP servers (Gitea repos) | See AI Stack §18000 | — | `mcp-comfyui`, `mcp-docling`, `mcp-upload-artifact` at `git.nuclide.systems/fkrebs/` (`mcp-shepard` decommissioned) |
---
## Tracking (1900019999)
_Traccar moved to 1500015001 (Documents range). 1900019001 now free._
---
## Storage Admin (2000020999)
| Port | Service | Container | Public URL | Notes |
|---|---|---|---|---|
| 20010 (127.0.0.1 only) | pgAdmin | `pgadmin` | — | shared-db pgAdmin, localhost only (CT 104) |
---
## ~~LXC 112 — secrets~~ (`192.168.1.7`) — **DECOMMISSIONED 2026-05-26**
Infisical migrated to CT 109 ops. Containers stopped; LXC pending removal.
| Port | Service | Notes |
|---|---|---|
| ~~8200~~ | ~~Infisical~~ | **Moved to CT 109:8200** |
---
## LXC 113 — db (`192.168.1.6`)
CT 113 is the dedicated postgres LXC. No public proxy routes — LAN access only.
| Port | Service | Container | Notes |
|---|---|---|---|
| 5432 | Postgres 17 | `postgres` | LAN: `192.168.1.6:5432` — accepts app connections from all CTs |
| 5050 | pgAdmin 4 | `pgadmin` | LAN only: `http://192.168.1.6:5050` — no Zoraxy route |
---
## ~~LXC 111 — Dev~~ (`192.168.1.42`) — **DECOMMISSIONED 2026-05-26**
All services migrated to CT 104. Containers stopped; LXC pending removal.
| Port | Service | Notes |
|---|---|---|
| ~~7080~~ | ~~Coder~~ | **Moved to CT 104:7080** |
| ~~3000~~ | ~~Gitea~~ | **Moved to CT 104:3000** |
| ~~222~~ | ~~Gitea SSH~~ | **Moved to CT 104:222** |
---
## QNAP TS-251D (`192.168.1.189`)
Celeron J4025, 2-core. Hosts Klipper natively (not Docker).
| Port | Service | Notes |
|---|---|---|
| 80 | Mainsail | 3D printer web UI |
| 7125 | Moonraker | Klipper API |
Config backed up daily to `git.nuclide.systems/fkrebs/klipper-config` via cron at 03:00 → covered offsite by Backrest `services/gitea` path.
---
## External Services (Not on NUC Docker)
Zoraxy routes to these external backends:
| Domain | Target | Host | Service |
|---|---|---|---|
| [ha.nuclide.systems](https://ha.nuclide.systems) | `192.168.1.60:8123` | Home Assistant VM 100 | Home automation |
| [nc.nuclide.systems](https://nc.nuclide.systems) | `192.168.1.41:11000` | Nextcloud LXC 105 | Cloud storage |
| [ocpp.nuclide.systems](https://ocpp.nuclide.systems) | `192.168.1.60:8887` | Home Assistant VM 100 | EV charger OCPP |
| [shepard.nuclide.systems](https://shepard.nuclide.systems) | `192.168.1.49:80` | Shepard LXC 101 | Shepard |
| [shepard-api.nuclide.systems](https://shepard-api.nuclide.systems) | `192.168.1.49:8080` | Shepard LXC 101 | Shepard API |
| [id.nuclide.systems](https://id.nuclide.systems) | `192.168.1.8:11000` | Ops CT 109 | Pocket-ID OIDC IdP (migrated CT110→CT109 2026-05-26) |
| [git.nuclide.systems](https://git.nuclide.systems) | `192.168.1.40:3000` | Docker CT 104 | Gitea (migrated from CT 111 2026-05-26) |
| [dev.nuclide.systems](https://dev.nuclide.systems) | `192.168.1.40:7080` | Docker CT 104 | Coder (migrated from CT 111 2026-05-26) |
| Gitea SSH | `192.168.1.40:222` | Docker CT 104 | `ssh -p 222 git@git.nuclide.systems` |
---
## Zoraxy Public Routes (proxied via `192.168.1.4`)
| Domain | Backend (NUC `192.168.1.40`) | Port |
|---|---|---|
| [ai.nuclide.systems](https://ai.nuclide.systems) | bifrost | 14003 |
| [chat.nuclide.systems](https://chat.nuclide.systems) | open-webui | 14002 |
| [arcane.nuclide.systems](https://arcane.nuclide.systems) | arcane on CT 109 | **192.168.1.8**:10002 |
| [gotify.nuclide.systems](https://gotify.nuclide.systems) | gotify | 10003 |
| [vault.nuclide.systems](https://vault.nuclide.systems) | vaultwarden | 11001 |
| [immich.nuclide.systems](https://immich.nuclide.systems) | immich_server | 12000 |
| [immich-tools.nuclide.systems](https://immich-tools.nuclide.systems) | immich_power_tools | (container-internal) |
| [abs.nuclide.systems](https://abs.nuclide.systems) | audiobookshelf | 13003 |
| [n8n.nuclide.systems](https://n8n.nuclide.systems) | n8n | 16000 |
| [memos.nuclide.systems](https://memos.nuclide.systems) | memos | 17000 |
| [hoarder.nuclide.systems](https://hoarder.nuclide.systems) | karakeep | 17001 |
| [traccar.nuclide.systems](https://traccar.nuclide.systems) | traccar | 15000 |
| [dozzle.nuclide.systems](https://dozzle.nuclide.systems) | dozzle | 10001 |
| [s3.nuclide.systems](https://s3.nuclide.systems) | garage | 10004 |
**Not publicly proxied** (LAN / localhost only): pgadmin, paperless, paperless-ai, comfyui, comfyui-mcp, rdtclient, prowlarr, shelfarr, qdrant.
---
## Known Issues
- **`lobe-postgres` / `lobe-redis`**: host-port exposed (`0.0.0.0:5432/6379`) — firewall blocks external access but ideally restricted to localhost.
---
## OIDC Client Registry (Pocket ID — `id.nuclide.systems`)
| Client ID | Name | Redirect URIs |
|---|---|---|
| ~~`e73bb7b9`~~ | ~~litellm~~ | **DELETED 2026-05-26** — LiteLLM sunset; Pocket-ID client removed from CT 110 DB |
| ~~`26f3c26b`~~ | ~~lobehub~~ | **DECOMMISSIONED 2026-05-26** — LobeChat removed; Open WebUI OIDC not yet wired |
| `81cf4ed0` | arcane | `https://arcane.nuclide.systems/auth/oidc/callback` |
| `33135ad4` | n8n | `https://n8n.nuclide.systems/auth/oidc/callback` |
| `9c91c18b` | immich | `https://immich.nuclide.systems/auth/login` + mobile |
| `62bf4e0d` | memos | `https://memos.nuclide.systems/auth/callback` |
| `d92f82b0` | karakeep | `https://hoarder.nuclide.systems/api/auth/callback/custom` |
| `a14b8076` | nextcloud | `https://nc.nuclide.systems/apps/user_oidc/code` |
| `0aee4280` | Coder | `https://dev.nuclide.systems/api/v2/users/oidc/callback` |
| `9444609e` | Gitea | `https://git.nuclide.systems/user/oauth2/pocket-id/callback` |
| `38469e7e` | Proxmox VE | `https://192.168.1.20:8006` |
| ~~798a367f~~ | ~~daytona~~ | **DECOMMISSIONED — remove from Pocket-ID** |
| `cbbf20d5` | Audiobookshelf | `https://abs.nuclide.systems/…` + `*` |
| `d8733fcc` | shelfarr | `*` |
| `78c78998` | Claude MCP | `https://claude.ai/api/mcp/auth_callback` + `https://mcp.nuclide.systems/mcp/auth/callback` |
| `82ca2d53` | nuc-ai | (empty) — used by spawned MCP servers |
| `7fe1a14b` | zoraxy | (empty) |
| `7092cd25` | Miniflux | `https://flux.nuclide.systems/oauth2/oidc/callback` |
| `af2f837b` | mcp-auth | (empty) — legacy, unused |
| (new) | vaultwarden | `https://vault.nuclide.systems/auth/callback` | Created 2026-05-21; SSO wiring deferred |
**OIDC Endpoints** (corrected 2026-05-17 — previously used wrong `/api/v1/oauth2/` path):
- Authorization: `https://id.nuclide.systems/authorize`
- Token: `https://id.nuclide.systems/api/oidc/token`
- Userinfo: `https://id.nuclide.systems/api/oidc/userinfo`
- Discovery: `https://id.nuclide.systems/.well-known/openid-configuration`
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
# Proxmox Memory Audit
Last audited: 2026-05-22
## Host physical resources
| Resource | Total | Used (idle) | Available |
|----------|-------|-------------|-----------|
| RAM | 62 GiB | ~33 GiB | ~28 GiB |
| Swap | 31 GiB | 0 GiB | 31 GiB |
## CT memory allocations
| CT | Name | Allocated (MiB) | Swap (MiB) | Typical use | Notes |
|----|------|----------------|------------|-------------|-------|
| 101 | shepard | 32,768 | 8,192 | ~4 GB | Heavy stack: Mongo, Neo4j, TimescaleDB, Keycloak |
| 102 | dns | 1,024 | 512 | ~100 MB | AdGuard Home |
| 103 | backrest | **4,096** | **1,024** | ~200 MB | Restic scheduler — bumped 2026-05-23 for 822 GB initial backup OOM fix |
| 104 | docker | **49,152** | 32,000 | **622 GB** | Main Docker host; FLUX spikes to ~22 GB |
| 105 | nextcloud | 8,196 | 8,196 | ~2 GB | Nextcloud AIO |
| 108 | zoraxy | 2,048 | 512 | ~300 MB | Reverse proxy |
| 109 | ops | 4,096 | 0 | ~1.5 GB | Prometheus + Grafana + Loki + Arcane + Dozzle + Homarr |
| 110 | id | 1,024 | 512 | ~200 MB | Pocket-ID |
| 111 | dev | 32,768 | 8,192 | ~3 GB | Coder + Gitea workspaces |
| 112 | secrets | 4,096 | 512 | ~600 MB | Infisical |
| 113 | db | 4,096 | 0 | ~800 MB | Postgres 17 + WAL-G |
| **Sum** | | **141,316 MiB (138 GiB)** | | | **2.2× overprovisioned vs physical RAM** |
## Key findings
### Overprovisioning is safe — until it isn't
Proxmox uses balloon drivers so CTs only consume what they actually use. At idle the host sits at ~33 GB used with 28 GB available. This is healthy. However, two specific CTs represent risk:
- **CT 104** (49 GiB limit) + **CT 111** (32 GiB limit) together could claim 81 GiB — well over the 62 GiB physical. If both hit peak simultaneously the host would start swapping heavily.
- CT 104's ComfyUI (FLUX generation) spikes from 6 GB baseline to ~22 GB. That 16 GB spike on top of the 33 GB idle baseline = 49 GB total, within physical RAM but tight.
### Why 40G Docker container limit destabilised the system
Setting ComfyUI's container limit to 40 G was the trigger. At generation time:
- FLUX model + activations: ~22 GB in the container
- Other ~65 Docker containers on CT 104: ~7 GB
- CT 104 OS + kernel: ~1 GB
- Other CTs idle: ~26 GB
- **Total: ~56 GB** → host started swapping, degrading all services
### ComfyUI XPU memory accounting gap
ComfyUI's `get_free_memory()` for Intel XPU queries `torch.xpu.get_device_properties().total_memory` = **58 GB** (the full shared memory pool — Arc shares system RAM). It has no awareness of the Docker cgroup limit. Smart memory management (`free_memory()`) calculates `memory_required - get_free_memory()` which is always hugely negative → never evicts models. This makes `--disable-smart-memory` irrelevant for XPU; smart memory is already broken.
Consequence: ComfyUI will always try to load the full model into XPU memory regardless of container limit. The cgroup OOM killer is the only backstop.
### Container limit recommendation for ComfyUI (CT 104)
| Scenario | Limit | Safe? |
|----------|-------|-------|
| `--lowvram` (original) | 20 G | ✅ Safe but slow (231 s/image) |
| No `--lowvram`, FLUX only | **24 G** | ✅ Fits FLUX peak (~22 GB) + 2 GB headroom |
| No `--lowvram` + img2img after FLUX | 26 G | ✅ FLUX stays resident, SD1.5 loads on top |
| 28 G | ⚠️ | Marginal — OOM triggered in testing |
| 40 G | ❌ | Destabilises host when generating |
Peak host usage at 24 G container limit during FLUX generation: `24 + 7 (other containers) + 26 (other CTs idle) ≈ 57 GB` — stays under 62 GB physical.
## Recommendations
1. **ComfyUI container limit**: set to **24 G** when running without `--lowvram`. Current revert to 20 G + `--lowvram` is stable but slower.
2. **CT 104 LXC allocation (49 GiB)**: appropriately sized given Docker workload, but is by far the largest single consumer. Do not raise further without measuring host impact.
3. **CT 111 (dev, 32 GiB)**: Coder workspaces could spike if users run heavy jobs. Consider adding a per-workspace memory limit in the Coder template.
4. **Watch list**: CT 101 (Shepard, 32 GiB) + CT 104 simultaneously at peak = 54 GB → host would need to swap. Unlikely in practice but possible during CI runs on CT 111 + FLUX generation on CT 104.
5. ~~**Long-term**: when CT 109 (ops) is built, run Prometheus `node_exporter` on the PVE host and alert when host available RAM drops below 8 GiB.~~ Done 2026-05-23 — CT 109 live, node_exporter scraping PVE host via pve-exporter.
## ComfyUI memory optimisation log
| Date | Change | Effect |
|------|--------|--------|
| 2026-05-22 | Removed `--lowvram` + raised limit to 28 G | OOM at 28 G (XPU DRM buffers counted against cgroup) |
| 2026-05-22 | Raised limit to 40 G | Host destabilised — reverted |
| 2026-05-22 | Reverted to `--lowvram` + 20 G | Stable, slow (231 s/image) |
| 2026-05-22 | Downloaded `t5-v1_1-xxl-encoder-Q4_K_S.gguf` (2.6 GB vs 3.2 GB Q5_K_M) | Saves 600 MB at load time |
| 2026-05-22 | Kept Q5_K_M T5 (Q4_K_S degrades prompt following per city96); removed `--lowvram`; set limit to 24 G; added `--async-offload --force-fp16` | ~730 s generation, safe within host memory budget |
| 2026-05-22 | Removed `--async-offload` | Flag incompatible with GGUF img2img on XPU — caused full CPU fallback (3.5 min/step) and pure-noise output. Removed; XPU generation now correct at ~7 s/step for txt2img. Current CLI: `--listen 0.0.0.0 --enable-cors-header --use-pytorch-cross-attention --disable-smart-memory --force-fp16` |
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
+118
View File
@@ -0,0 +1,118 @@
# Storage layout
> Verified state as of 2026-07-15 (live audit during the CT 104 disk-full incident). Source of truth for which service lives on which storage class.
> Earlier revisions were stale on Garage, n8n, Arcane, Karakeep/Memos, Pocket-ID, arr-stack, Nextcloud's storage protocol, and missing Coder/Gitea. `volumes.md` was reconciled in the same pass.
## Storage classes
| Class | Substrate | Where | Typical use |
|---|---|---|---|
| Local zfs (rootfs) | NVMe in nuc | `/opt/stacks/<stack>/` on each LXC | Tier-1 hot state: databases, SQLite, secret stores, anything latency-sensitive |
| Local zfs (subvol) | NVMe in nuc | `rpool/data/subvol-<NNN>-disk-0` per LXC | Container rootfs, image cache |
| Docker named volume | NVMe in nuc | `/var/lib/docker/volumes/...` on each Docker CT | Per-container persistent state managed by Docker |
| UNAS over **NFSv3** | `192.168.1.31` | `/mnt/pve/unas` on CTs 101, 103, 104, 105, 111 | Bulk media, workspace home dirs, document storage |
## What lives where (verified 2026-05-21)
### UNAS NFS — `/mnt/pve/unas/services/...`
Bulk + media + non-latency-sensitive app data.
| Service | CT | Path on UNAS → in container | Backed up? | Notes |
|---|---|---|---|---|
| Immich (uploads, thumbs, derived) | 104 | `services/immich/{encoded-video,profile,thumbs}` + `media/images` + `backup/immich` | Immich own pg_dump → `media/images/db-dumps`; no off-host copy | |
| Paperless-ngx (documents) | 104 | `media/documents/public/paperless-ngx/{consume,export,library}` | none | |
| Paperless-AI | 104 | `services/paperless-ai``/app/data` | none | |
| Traccar (logs, config) | 104 | `services/traccar/{logs,traccar.xml}` | none | data dir reverted to local `/opt/stacks/traccar/data` |
| ~~Memos~~ | ~~104~~ | ~~`services/memos`~~ | — | **SUNSET 2026-07-10** — service + data + UNAS dir wiped. See [[CHANGELOG]] |
| Arr-stack (Prowlarr, RDTClient, Shelfarr, Audiobookshelf) | 104 | configs on `services/arr-stack/{prowlarr,rdtclient/config,shelfarr/storage,audiobookshelf}`; library + downloads on `media/{audiobooks,ebooks,podcasts,Torrents}` | none | **fully on UNAS incl. SQLite configs** (verified running 2026-07-15). Radarr/Sonarr not deployed. Stale local `/opt/stacks/arr-stack/media` (27 GB pre-migration copy) deleted 2026-07-15. |
| Gluetun (VPN) | 104 | `services/gluetun/data``/gluetun` | none | |
| Gitea | 111 | `services/gitea``/data` | none | new 2026-05-20 |
| Coder workspace home dirs | 111 | `services/coder/<user>/<workspace>``/home/<user>` | none | new 2026-05-20 |
| Backrest's jottacloud-mirrored data | 103 | `media/data-dir` only | **rclone → jottacloud (Backrest)** | only path with off-host backup |
### Local zfs — `/opt/stacks/...`
Tier-1 state and anything that should NOT be NFS-backed.
| Service | CT | Local path → in container | Backed up? | Notes |
|---|---|---|---|---|
| Pocket-ID | **110** | `/opt/stacks/pocketid/data``/app/data` | none | CT 110 has no NFS mount at all. Earlier doc said UNAS — incorrect. |
| Garage (S3 meta + data) | 104 | `/opt/stacks/shared-db/garage/{data,meta}``/var/lib/garage/*` | none | **moved off NFS** 2026-05-19 after WAL-G outage. Metadata is **SQLite** (`db_engine = sqlite`) — do NOT put on NFS. Data 82 GB on disk (2026-07-15), almost all WAL-G PG backups — see bucket table below. |
| Shared Postgres (multi-stack) | 104 | docker named volume `shared-db_shared-pgdata` | **WAL-G → local Garage** (configured; outage 2026-05-19 prompted move) | Garage itself is single-disk local — no off-host copy. |
| n8n | 104 | `/opt/stacks/n8n/data``/home/node/.n8n` | none | **back to local** after PG migration attempt failed. Stale 408 MB `database.sqlite` left on UNAS (May 15) — clean up. n8n now uses `shared-postgres` as its actual DB. |
| ~~Arcane~~ | ~~104~~ | ~~`/opt/stacks/arcane/data`~~ | — | **DECOMMISSIONED 2026-05-26** (replaced by Portainer). Compose renamed `.DECOMMISSIONED-2026-05-26`; data was local, not UNAS (volumes.md previously said "migrated" — wrong). |
| ~~Karakeep + Meilisearch~~ | ~~104~~ | ~~`/opt/stacks/karakeep/localdata/{data,meili}`~~ | — | **SUNSET 2026-07-10** — service + local + UNAS data wiped. See [[CHANGELOG]] |
| Immich Postgres (pgvector) | 104 | `/opt/stacks/immich/postgres``/var/lib/postgresql/data` | Immich pg_dump → UNAS | tier-1; PG demands local disk |
| Homepage | 104 | `/opt/stacks/homepage/{config,icons}` | none | decommissioned — replaced by Homarr on CT 109 |
| Dozzle | 104 | `/opt/stacks/dozzle/dozzle_data` | none | |
| ~~LiteLLM config~~ | ~~104~~ | ~~`/opt/stacks/ai/litellm-config`~~ | — | **SUNSET 2026-05-26** — replaced by Bifrost |
| SearXNG config | 104 | `/opt/stacks/ai/searxng` | none | |
| Flaresolverr | 104 | `/var/lib/flaresolver` | none | |
| AdGuard / Zoraxy / DNS / Shepard / Backrest binaries | 102/108/103/101 | local zfs only | none | Backrest itself has no self-backup |
| Vaultwarden (attachments + key material) | 104 | `/opt/stacks/vaultwarden/data``/data` | none | **moved off NFS 2026-05-22**; DB is on CT 113 postgres; stale `db.sqlite3` deleted |
| Nextcloud config + sidecars | 105 | local zfs (CT rootfs) | none | app data on NFS — see below |
### Garage S3 buckets (on local zfs, CT 104) — verified 2026-07-15
82 GB on disk, dominated by WAL-G Postgres backups. Trim via WAL-G retention on the source hosts, not by moving Garage to NFS.
| Bucket | Logical size | Objects | Use |
|---|---|---|---|
| `ct113-pg-backup` | 66.7 GiB | 1556 | WAL-G for CT 113 shared Postgres fleet |
| `immich-pg-backup` | 36.3 GiB | 4388 | WAL-G / WAL archive for Immich Postgres (+60 MiB unfinished multipart to clean) |
| `ct109-portainer-backup` | 1.9 GiB | 49 | Portainer backups |
| `shared-pg-backup` | 638 MiB | 4986 | shared-postgres WAL-G |
| `chat-artifacts` | — | — | AI chat output artefacts — `https://chat-artifacts.s3.nuclide.systems/<key>` |
| `owui-files` | 0 B | 0 | Open WebUI file storage |
| ~~`lobe-pg-backup`~~ | 151 MiB | 511 | **stale** — LobeChat decommissioned 2026-05-26; safe to delete |
| ~~`lobe-files`~~ | 0 B | 0 | **stale** — LobeChat |
| ~~`memos`~~ | 3.5 MiB | 4 | **orphan** — Memos sunset 2026-07-10; bucket delete blocked by quorum decode error, key revoked, needs Garage repair pass |
### Docker named volumes
Local on `/var/lib/docker/volumes/`. Mostly databases and caches.
| Volume | Service / CT | Backed up? |
|---|---|---|
| `shared-db_shared-pgdata` | shared-postgres / 104 | WAL-G → local Garage |
| `paperless-ngx_pgdata`, `_redisdata`, `_data` | paperless-ngx / 104 | none |
| `lobe-postgres` + `lobe-redis` | lobehub / 104 | none |
| `nuc-ai-core_rustfs-data` | lobehub stack / 104 | none |
| `pgadmin-data` | pgadmin / 104 | none (regenerable) |
| `immich_model-cache` | immich-ml / 104 | regenerable |
| `coder-db` + `gitea-db` (docker volume `coder-db`) | / 111 | none |
| (orphaned) `daytona-minimal_db_data` + runner anon vol | / 104 | none — clean up post-decommission |
### Nextcloud (CT 105) — data on NFS
Nextcloud AIO mounts `/mnt/pve/unas/services/nextcloud` via NFSv3 (same share as all other CTs). Migrated from CIFS on 2026-05-22 after the CIFS mount caused a crash-loop. The Postgres + Redis sidecars stay on local docker volumes.
## Backup reality
**There is effectively no off-host backup of service data.** The only configured Backrest plan covers `/mnt/pve/unas/media/data-dir → jottacloud` — i.e., Backrest backs up *one specific UNAS path*, not the services that write to UNAS.
Coverage:
- Immich pg_dump → UNAS (same filer; survives container loss, not filer loss)
- shared-postgres WAL-G → Garage (same host; survives container loss, not disk loss)
- Everything else: zero
This is a known gap. Plans:
1. Extend Backrest plans to snapshot tier-1 paths (Pocket-ID sqlite, Vaultwarden data, Gitea repos, Coder workspace homes) to jottacloud nightly.
2. Once the second NVMe lands (see `infra/proxmox-state.md` §6), mirror `rpool` so a single disk death doesn't take everything.
## ZFS snapshots (CT 104)
`rpool/data/subvol-104-disk-0` uses `refquota=200G` (not `quota`) — so **snapshots do not count against the container's 200 G limit, but they DO consume pool space**. A stale snapshot can silently exhaust the pool while the container's own `df` looks fine.
- Watch: `zfs list -t snapshot -o name,used rpool/data/subvol-104-disk-0`
- As of the 2026-07-15 incident, `@pre-tier1-backups-20260521-003708` held **97.9 GB** (pinning everything deleted since 2026-05-21, incl. the Karakeep/Memos sunset and docker prunes). Pool free was down to 57 GB. Destroying stale snapshots is the fastest pool-level reclaim.
## Drift cleanup TODO
- [x] ~~Remove stale `services/n8n/database.sqlite` from UNAS~~ — gone (verified 2026-05-22)
- [x] ~~Remove stale `services/shared-db/garage/` copy from UNAS~~ — gone (verified 2026-05-22)
- [x] ~~Remove orphaned `daytona-minimal_db_data` Docker volume on CT 104~~ — gone (verified 2026-05-22)
- [x] ~~Vaultwarden `data/` on UNAS~~ — migrated to local zfs 2026-05-22. Stale NFS copy at `services/vaultwarden/` can be cleaned up.
- [x] ~~Add a Backrest plan for Pocket-ID DB~~ — done 2026-05-22. Pre-backup hook runs `pocket-id export` + copies keys to UNAS staging; `services-backup-plan` snapshots staging → jottacloud nightly.
File diff suppressed because it is too large Load Diff
+313
View File
@@ -0,0 +1,313 @@
# Volume Mounts — NUC 14 Docker Stacks
> Every bind mount and named volume across all running containers.
> Last updated: 2026-07-15 (live audit during CT 104 disk-full incident; reconciled with `storage.md`).
## Legend
| Column | Meaning |
|---|---|
| **Type** | `bind` = host directory, `volume` = docker named volume, `tmpfs` = memory |
| **Source** | Host path (bind) or volume name (volume) |
| **Container** | Mount point inside container |
| **UNAS** | `/mnt/pve/unas/services/` target (migrated or planned) |
---
## Infrastructure
### ~~Arcane~~ — DECOMMISSIONED 2026-05-26
> Replaced by Portainer. Compose renamed `/opt/stacks/arcane/docker-compose.yml.DECOMMISSIONED-2026-05-26`; container not running. Its data was **local** (`/opt/stacks/arcane/data`), never UNAS — the earlier "✅ Migrated to `services/arcane`" row was wrong.
### Dozzle — `dozzle`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/dozzle/dozzle_data` | `/data` | (optional, ephemeral) |
| bind | `/var/run/docker.sock` | `/var/run/docker.sock` | — |
### Homepage — `homepage`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/homepage/config` | `/app/config` | (keep in stack dir) |
| bind | `/opt/stacks/homepage/icons` | `/app/public/icons` | (keep in stack dir) |
| bind | `/var/run/docker.sock` | `/var/run/docker.sock` | — |
---
## Security
### Pocket ID — `pocketid`
> **CT 110, not CT 104.** Pocket-ID migrated off CT 104 to its own LXC on 2026-05-20.
| Type | Source | Container | Notes |
|---|---|---|---|
| bind | `/opt/stacks/pocketid/data` | `/app/data` | Local zfs on CT 110 — no UNAS mount |
### Vaultwarden — `vaultwarden`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/vaultwarden`** | **`/data`** | ✅ **Already on UNAS** |
| bind | `/etc/localtime` | `/etc/localtime` | — |
| bind | `/etc/timezone` | `/etc/timezone` | — |
---
## Media — Immich
### Immich Server — `immich_server`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/immich/encoded-video`** | `/usr/src/app/upload/encoded-video` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/services/immich/profile`** | `/usr/src/app/upload/profile` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/services/immich/thumbs`** | `/usr/src/app/upload/thumbs` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/media/images`** | `/usr/src/app/upload` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/backup/immich`** | `/usr/src/app/upload/backups` | ✅ **Already on UNAS** |
| volume | `7d25f4ac...` (anonymous) | `/data` | (unknown, check) |
### Immich ML — `immich_machine_learning`
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `immich_model-cache` | `/cache` | (cache, regenerable) |
| bind | `/dev/bus/usb` | `/dev/bus/usb` | — |
### Immich Postgres — `immich_postgres`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/opt/stacks/immich/postgres`** | **`/var/lib/postgresql/data`** | ⏳ Plan: `immich/db` |
---
## Media — Downloads / Arr Stack
> **Fully on UNAS** — verified running 2026-07-15. SQLite configs live on `services/arr-stack/` and work fine over NFSv3 (low write rate). Library + downloads on the `media/` share so imports hardlink within one export. Radarr/Sonarr are **not** deployed. All behind gluetun VPN. The old local `/opt/stacks/arr-stack/media` (27 GB) was a pre-migration copy — deleted 2026-07-15.
### RDTClient — `rdtclient`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/mnt/pve/unas/services/arr-stack/rdtclient/config` | `/data/db` | ✅ **On UNAS** (SQLite) |
| bind | `/mnt/pve/unas/media/Torrents` | `/data/downloads` | ✅ **On UNAS** |
### Prowlarr — `prowlarr`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/mnt/pve/unas/services/arr-stack/prowlarr` | `/config` | ✅ **On UNAS** (SQLite config) |
### Audiobookshelf — `audiobookshelf`
> In compose; config on `services/arr-stack/audiobookshelf`, media on the `media/` share.
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/mnt/pve/unas/services/arr-stack/audiobookshelf` | `/config` | ✅ **On UNAS** |
| bind | `/mnt/pve/unas/media/audiobooks` | `/audiobooks` | ✅ **On UNAS** |
| bind | `/mnt/pve/unas/media/ebooks` | `/ebooks` | ✅ **On UNAS** |
| bind | `/mnt/pve/unas/media/podcasts` | `/podcasts` | ✅ **On UNAS** |
### ShelfArr — `shelfarr`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/mnt/pve/unas/services/arr-stack/shelfarr/storage` | `/rails/storage` | ✅ **On UNAS** (SQLite) |
| bind | `/mnt/pve/unas/media/audiobooks` | `/audiobooks` | ✅ (shared) |
| bind | `/mnt/pve/unas/media/ebooks` | `/ebooks` | ✅ (shared) |
| bind | `/mnt/pve/unas/media/Torrents` | `/downloads` | ✅ (shared) |
### Flaresolverr — `flaresolverr`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/var/lib/flaresolver` | `/config` | (cache, regenerable) |
---
## AI Stack
### ~~LiteLLM~~ — SUNSET 2026-05-26
> Replaced by Bifrost (`ai.nuclide.systems`). Service block commented out in `/opt/stacks/ai/docker-compose.yml`; `litellm-config` retained as history only. LiteLLM DB container removed.
### ~~LobeHub~~ — DECOMMISSIONED 2026-05-26
LobeChat compose renamed `.DECOMMISSIONED-lobehub-2026-05-26.yml`. Volumes below are orphaned — clean up after confirming no data needed.
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/ai/lobehub/data` | `/var/lib/postgresql/data` | 🗑️ orphaned — decommissioned |
### ~~LobeHub Redis~~ — DECOMMISSIONED 2026-05-26
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `nuc-ai-core_redis_data` | `/data` | 🗑️ orphaned — decommissioned |
### ~~LobeHub RustFS~~ — DECOMMISSIONED 2026-05-26
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `nuc-ai-core_rustfs-data` | `/data` | 🗑️ orphaned — decommissioned |
### SearXNG — `searxng`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/ai/searxng` | `/etc/searxng` | (keep in stack dir) |
| volume | `bb5bb182...` (anonymous) | `/var/cache/searxng` | (cache, regenerable) |
### SearXNG Redis — `redis-searxng`
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `f9c3c386...` (anonymous) | `/data` | (ephemeral, stay) |
### SAIA Image Proxy — `nuc-ai-core-saia-image-proxy-1`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/ai/saia-image-proxy` | `/app` | (keep in stack dir) |
### Crawl4AI — `mcp-crawl4ai`
> `docker run` MCP client (Bifrost) on `ai-internal`, **no persistent mounts**. Cache lives in the container writable layer (`/home/appuser/.crawl4ai`, SQLite) and grew to ~40 GB before the 2026-07-15 reset. Recreated with `--shm-size 1g`. **TODO:** bind-mount `.crawl4ai` to a capped local dir or purge periodically — it refills the disk otherwise.
---
## Documents
### Paperless-ngx Webserver — `paperless-ngx-webserver-1`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/media/documents/public/paperless-ngx/consume`** | `/usr/src/paperless/consume` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/media/documents/public/paperless-ngx/export`** | `/usr/src/paperless/export` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/media/documents/public/paperless-ngx/library`** | `/usr/src/paperless/media` | ✅ **Already on UNAS** |
| volume | `paperless-ngx_data` | `/usr/src/paperless/data` | ⏳ Plan: `paperless-ngx/data` |
### Paperless-ngx DB — `paperless-ngx-db-1`
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `paperless-ngx_pgdata` | `/var/lib/postgresql/data` | ⏳ Plan: `paperless-ngx/pgdata` |
### Paperless-ngx Broker — `paperless-ngx-broker-1`
| Type | Source | Container | UNAS |
|---|---|---|---|
| volume | `paperless-ngx_redisdata` | `/data` | (ephemeral, stay) |
### Paperless AI — `paperless-ai`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/paperless-ai`** | **`/app/data`** | ✅ **Already on UNAS** |
---
## Productivity & Bookmarks
### ~~Memos~~ + ~~Karakeep~~ — SUNSET 2026-07-10
> Both fully wiped (service, containers, local + UNAS data, Postgres DB, Traefik routes, OIDC clients). See [[CHANGELOG]] and `sunset-karakeep-memos`. Do not restart. One residual: orphan Garage `memos` bucket needs a repair pass.
---
## Automation
### n8n — `n8n`
> Data is **local**, not UNAS — reverted after a failed PG-migration attempt. The actual DB is `shared-postgres` (CT 113); `.n8n` holds config/creds only. A stale UNAS copy at `services/n8n` may linger.
| Type | Source | Container | Notes |
|---|---|---|---|
| bind | `/opt/stacks/n8n/data` | `/home/node/.n8n` | 🗄️ Local NVMe |
| bind | `/opt/stacks/n8n/hooks.js` | `/home/node/hooks.js` | (keep in stack dir) |
---
## DevOps
### ~~Daytona~~ — DECOMMISSIONED 2026-05-23
> Never fully deployed; compose renamed `.DECOMMISSIONED-2026-05-23`. Orphaned `daytona-minimal_db_data` volume was cleaned up (verified 2026-05-22).
---
## Tracking
### Traccar — `traccar`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/traccar/data`** | `/opt/traccar/data` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/services/traccar/logs`** | `/opt/traccar/logs` | ✅ **Already on UNAS** |
| **bind** | **`/mnt/pve/unas/services/traccar/traccar.xml`** | `/opt/traccar/conf/traccar.xml` | ✅ **Already on UNAS** |
---
## VPN
### Gluetun — `vpn_gluetun`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/gluetun/data`** | **`/gluetun`** | ✅ **Already on UNAS** |
---
## Shared Infrastructure
### Shared PostgreSQL — `shared-postgres`
| Type | Source | Container | Notes |
|---|---|---|---|
| volume | `shared-db_shared-pgdata` | `/var/lib/postgresql/data` | 🗄️ Local NVMe (not NFS) |
### Garage S3 — `garage`
> Moved off NFS to local zfs on 2026-05-19 after a WAL-G outage. **Keep local** — metadata is SQLite (corrupts over NFS) and it now stores WAL-G backups for the whole PG fleet. See `storage.md` for bucket breakdown.
| Type | Source | Container | Notes |
|---|---|---|---|
| bind | `/opt/stacks/shared-db/garage/data` | `/var/lib/garage/data` | S3 object data — local NVMe (82 GB, mostly WAL-G) |
| bind | `/opt/stacks/shared-db/garage/meta` | `/var/lib/garage/meta` | S3 metadata (**SQLite**, `db_engine = sqlite`) — local NVMe |
---
## Stacks Not Running (Compose Config Only)
### Streamio — `streamio`
| Type | Source | Container | UNAS |
|---|---|---|---|
| **bind** | **`/mnt/pve/unas/services/stremio`** | **`/root/.stremio-server`** | ✅ **Already on UNAS** |
### Qdrant — `qdrant_scientific`
| Type | Source | Container | UNAS |
|---|---|---|---|
| bind | `/opt/stacks/qdrant/qdrant_storage` | `/qdrant/storage` | ⏳ Plan: `qdrant/` |
---
## Summary: Migration Status (reconciled 2026-07-15)
| Status | Count | Services |
|---|---|---|
| ✅ On UNAS | 8 | gluetun, immich*(4 media dirs), paperless-ai, stremio, traccar*(3), vaultwarden, paperless-docs*(3), coder |
| ✅ On UNAS incl. SQLite configs | 1 | arr-stack (prowlarr, rdtclient, shelfarr, audiobookshelf + media/downloads) |
| 🔷 Shared infrastructure (local) | 2 | shared-postgres (local volume), garage (local NVMe, SQLite meta — keep local) |
| 🗄️ Reverted to local | 2 | n8n (DB on CT 113), traccar data-dir |
| 📋 Own CT, local only | 1 | pocketid (CT 110, no UNAS) |
| ⏳ Phase 2 planned (PG consolidation) | 2 | immich → shared-postgres, paperless → shared-postgres |
| 🗑️ Sunset / decommissioned | many | karakeep + memos (2026-07-10); litellm, mcp-gateway, arcane, lobehub*(3), homepage (2026-05-2x); daytona (2026-05-23) |
| 📋 Keep local | ~4 | dozzle, searxng-config, saia-image-proxy, flaresolverr |
| 🧠 Cache (stay) | ~4 | immich_model-cache, paperless-ngx redis, searxng-redis, mcp-crawl4ai writable layer |
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+86
View File
@@ -0,0 +1,86 @@
# Meta-audit — the Claude Code session that performed these audits
A self-audit, completing the "audit the auditor" loop. Honest accounting of what this assistant has seen during the audit / analysis work and where that data went.
## Scope
This audit covers the Claude Code session running on the Proxmox host (`/root/.claude/projects/-root/`) on 2026-05-20 and 2026-05-21, from the message `> finish up for today. last task: the attached conversation was run on our lobehub…` onward. The work product of that session is the three audit reports in this directory.
## What the assistant processed
| Input | Content | Source |
|---|---|---|
| Transcript A | `75fb06e8-Lumen_TR004_Test_Run_Analysis.json` (255 KB, 60 messages, model `qwen3.5-397b-a17b`) | User-uploaded |
| Transcript B | `ec5fba44-Analyzing_LUMEN_TR004_Test_Data.json` (205 KB, 41 messages, model `qwen3-coder-30b-a3b-instruct`) | User-uploaded |
| SSH + `pct exec` on `nuc` | container env vars, `docker ps`, `proxy_server_config.yaml`, `gateway_tokens.json` (first chars only) | Live homelab inspection |
| `https://docs.hpc.gwdg.de/services/ai-services/saia/index.html` | SAIA public docs | `WebFetch` (Anthropic-mediated) |
The Lumen transcripts contain DLR-context identifiers (`LUMEN`, `P3-Lampoldshausen`, `LOX/LCH4`, `TR-003/004/006`), anomaly metadata (`fuel turbopump vibration spike at t=8s`, `~12 g rms`), and chemistry/test-bench naming. The assistant quoted portions of these in chat output and in the audit reports.
## Where that data went
```mermaid
flowchart LR
user(["Operator"]) --> cc["Claude Code CLI<br/>on Proxmox host"]
cc -->|every prompt + tool result<br/>+ assistant turn| api[(api.anthropic.com<br/>Anthropic API)]
cc -->|ssh / pct / docker via shell| home["nuclide.systems<br/>(LAN-only)"]
cc -->|WebFetch SAIA public docs| saiadocs[(docs.hpc.gwdg.de<br/>via Anthropic proxy)]
cc -->|FLUX image-gen MCP| flux[(image-gen MCP backend<br/>Anthropic-side)]
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
classDef external fill:#5a4a2a,stroke:#cc8,color:#fec
class api,flux leak
class home ok
class saiadocs external
```
**This Claude Code session is itself an outbound channel.** Every assistant turn, including those that quoted Lumen transcript content, was a request to `api.anthropic.com`. The full session is:
| Channel | What flows | Trust |
|---|---|---|
| Claude API (`api.anthropic.com`) | Full prompts + tool results + audit text the assistant produced. ~60+ turns this session. Includes verbatim LUMEN identifiers in audit body, snippets of transcript content, snippets of LiteLLM config, snippets of `gateway_tokens.json` (one token prefix), Vaultwarden plaintext secret (handed back to operator, then echoed in subsequent context). | **Commercial vendor (Anthropic).** Operator chose to use Claude Code; informed-consent leak. |
| `WebFetch` (also via Anthropic) | The URL `https://docs.hpc.gwdg.de/services/ai-services/saia/index.html` was fetched server-side by Anthropic, content returned to the assistant. Outbound from Anthropic to GWDG (public doc; no sensitive payload). | Public web fetch, no sensitive outbound payload. |
| Image-gen MCP | Prompt strings (containing brief LUMEN reference in one attempted illustration: "Python with LUMEN identifiers leaked to commercial cloud"). Backend is Anthropic-side per the MCP integration. | Same Anthropic boundary. |
| SSH / `pct` / shell | Authenticated to operator's own infrastructure. | ✅ LAN, no leak. |
| Sub-agents (Pocket-ID audit, CT inventory, etc.) | Ran on Anthropic's infrastructure — each got the relevant context for its narrow task. Same trust boundary as the main session. | Anthropic. |
## Specific data this assistant sent to Anthropic
Across the audit work in this session, the following types of data were in the prompt/response stream to `api.anthropic.com`:
- **Lumen test analysis identifiers**: `LUMEN TR-004`, `P3-Lampoldshausen`, `LOX/LCH4`, `Fuel Turbopump Vibration Spike at t=8.0s`, `bearing replaced`, test campaign dates.
- **Tool-call inventories** (counts, MCP names, plugin identifiers) from both transcripts.
- **A snippet of `gateway_tokens.json`** content (the SUB UUID and one truncated token preview) when auditing the gateway.
- **The plaintext Vaultwarden secret** `HvUJYUxCGLrSoDY2g24NJI5anM70xXwcsd3e3itJ` (issued by the agent, surfaced in chat for the operator to save — operator saved it to Vaultwarden, but it remains in this session's context).
- **Pocket-ID OIDC client identifiers** (UUID prefixes, names, callback URLs).
- **Internal IPs / CT layout** (192.168.1.x).
- **Snippet of LiteLLM `proxy_server_config.yaml`** showing fallback chains.
## Threat model honesty
**The act of running this audit using Claude Code is itself a higher-bandwidth leak than the leak it audited.** Hours of conversation about LUMEN-context data went to Anthropic; the original Lumen TR-004 cloud-sandbox breach was ~1,500 lines of Python.
This is a deliberate trade-off:
- ✅ Anthropic has stronger contractual guarantees than `codesandbox.io` (zero-data-retention API plans exist; Anthropic publishes a clear DPA).
- ✅ The operator chose Claude Code consciously, knowing all prompts are API-bound.
- ❌ It is not free. The work product is excellent; the data exposure is real.
## Mitigations for future audit work
| Option | Pros | Cons |
|---|---|---|
| Continue using Claude Code | Best-in-class tooling, doctrine + memory carry across sessions, sub-agents in parallel | All audit context goes to Anthropic |
| Use a local-only agent (e.g. `gpt-oss-120b` in workspace via Coder) | Zero off-host audit data | Far weaker capability; no rich tool-use; no memory; manual orchestration |
| Air-gap the work on a non-internet-connected host | Strongest privacy | No web fetches; no API; doctrine doesn't bootstrap; pace ~10x slower |
| Hybrid: Claude Code for general infra; air-gapped local agent for **Lumen-specific** prompts | Best of both | Process discipline required; operator decides routing per chat |
For LUMEN-specific deep analysis going forward, the **hybrid** path is the rational one: do schema/topology/code work in Claude Code (no Lumen payload needed); do data-touching analysis in a Coder workspace with the `mcp-sandbox` template + a local LLM. The S3 artifact hub (planned) enables both surfaces to share visualization output without ever exposing raw data.
## Recommendations as session policy
1. **For any chat that will reference Lumen/Shepard data**, switch to the air-gapped / local-LLM path. Don't mix.
2. **Strip secrets from chat replies aggressively.** The Vaultwarden plaintext secret didn't need to be displayed; the agent could have written it to a tmpfs file and pointed the operator at it.
3. **Tag this and future audit transcripts** in `/docs/security/transcripts/` with a "contains DLR-context data" header so the policy is visible to anyone reviewing them.
See also: [`data-leak-audit-comparison.md`](data-leak-audit-comparison.md) (the cross-session comparison), [`data-leak-audit-2026-05-20-tr004-cloud-sandbox.md`](data-leak-audit-2026-05-20-tr004-cloud-sandbox.md), [`data-leak-audit-2026-05-21-tr004-artifacts.md`](data-leak-audit-2026-05-21-tr004-artifacts.md).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
# Data-leak audit — 2026-05-20 · `Lumen TR-004 Test Run Analysis`
**Conversation**: `75fb06e8-Lumen_TR004_Test_Run_Analysis.json`
**LobeHub session model**: `qwen3.5-397b-a17b` (21 assistant turns)
**Total messages**: 60 (2 user · 21 assistant · 37 tool)
**Auditor**: agent doctrine-driven scan, 2026-05-20
## TL;DR
**Verdict: SENSITIVE DATA LEFT THE HOMELAB to an unapproved third party.** The breach channel was `lobe-cloud-sandbox`, not the LLM inference.
Two off-host data flows, with very different trust profiles:
1. **LLM inference via SAIA / GWDG** — 21 assistant turns sent the full conversation context (incl. Shepard search results) to SAIA via LiteLLM. **NOT a leak in this context** — SAIA is operated by GWDG (German academic computing center, Göttingen), [vetted by DLR](https://docs.hpc.gwdg.de/services/ai-services/saia/index.html) and integrated with the DLR IdP federation. For LUMEN (DLR engine programme) test data, SAIA is an approved partner.
2. **`lobe-cloud-sandbox` code execution — THE BREACH.** 9 calls sent Python source code (with explicit `LUMEN`, `P3-Lampoldshausen`, `LOX/LCH4` references, anomaly timings, and synthetic-but-derived-from-real timeseries) to **`api.lobehub.com`** + **codesandbox.io** — commercial third parties, not approved for DLR data. 4 `exportFile` calls pulled generated images back. **Criticality: HIGH** — proprietary aerospace IP in plaintext executable code, sent to commercial cloud.
Shepard data fetches themselves stayed on LAN (`shepard-api.nuclide.systems`), but the **results** were re-emitted to SAIA (approved) AND to the cloud sandbox (NOT approved).
## Conversation footprint
| Channel | Calls | Destination | Trust |
|---|---|---|---|
| `shepard` MCP (`list_data_objects`, `get_data_object`, `list_lab_journal`, etc.) | 27 | `shepard-api.nuclide.systems` (CT 101) | ✅ LAN |
| `lobe-cloud-sandbox` (`executeCode` + `exportFile`) | 9 | **`api.lobehub.com` + codesandbox.io** | ❌ **unapproved third party** |
| `lobe-agent-documents` (`listDocuments`) | 1 | LobeHub local (in-container) | ✅ LAN |
| LLM inference (`qwen3.5-397b-a17b`) | 21 | **SAIA (GWDG)** via LiteLLM | ✅ approved partner (DLR-vetted, IdP-federated) |
## Data flow
```mermaid
flowchart LR
user(["You · browser"]) -->|HTTPS via Zoraxy| lobe["LobeHub · CT 104"]
lobe -->|MCP, internal| shep[Shepard API · CT 101]
shep -->|test data, lab notes,<br/>investigation records| lobe
lobe -->|prompt + Shepard results<br/>+ tool messages| litellm[LiteLLM proxy · CT 104]
litellm -.->|qwen3.5-397b-a17b<br/>21 inference calls| saia[(SAIA / GWDG<br/>academic provider)]
lobe -.->|Python source + filenames<br/>9 calls| sbx[(LobeHub Cloud Sandbox<br/>api.lobehub.com<br/>+ codesandbox.io)]
sbx -.->|4 generated PNGs<br/>back to LobeHub| lobe
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
class saia,sbx leak
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
class shep,lobe,litellm,user ok
```
## What specifically was sent where
### To SAIA (approved partner — LLM inference, 21 calls)
- Both user prompts verbatim.
- All Shepard tool outputs — collections found, data-object IDs, lab journal entries for TR-004 (anomaly time, fuel turbopump vibration values, chamber pressure profile, mixture ratio, test bench naming).
- The model's own intermediate reasoning.
- The Python code it wrote before sending to the sandbox.
- Sensitive strings: `LUMEN TR-004`, `LOX/LCH4`, `P3-Lampoldshausen`, `Fuel Turbopump Vibration Spike at t=8.0s`, `bearing replaced`, `TR-003 → TR-004 → TR-006 campaign`.
- **Routed through LiteLLM** (`ai.nuclide.systems`, CT 104) which has SAIA registered as a backend; chain logic terminates at SAIA for free academic models. See `services/litellm.md`.
### To LobeHub Cloud Sandbox (UNAPPROVED — 9 `executeCode` calls)
- ~1,500 lines of cumulative Python source containing the same identifiers.
- Inline JSON dumps of synthetic timeseries (vibration g_rms arrays, chamber-pressure curves, gimbal angles) constructed to match the real TR-004 profile from Shepard.
- 4 generated PNGs (the dashboards) existed on the cloud sandbox's filesystem long enough to be exported back.
### Stayed on LAN
- The actual raw Shepard sensor timeseries (`get_data_object` returned data IDs; what reached SAIA was the model's interpretation/summary, not raw buffers).
- The `www.dlr.de` URLs in chat are just citations, no fetch was triggered.
## Criticality matrix
| Channel | Sensitivity | Likelihood | Trust | Verdict |
|---|---|---|---|---|
| SAIA inference (via LiteLLM) | High (proprietary test analysis) | 100% (every chat) | ✅ approved partner (DLR-vetted) | **OK** |
| lobe-cloud-sandbox | High (Python referencing program data) | 100% in this chat | ❌ unapproved (commercial cloud) | **BREACH** |
| Other LobeHub providers (Gemini/Cerebras/Mistral) | High | Latent — fallback only | ❌ commercial | MEDIUM (latent risk) |
| Shepard MCP fetches | Internal only | Every related chat | ✅ LAN | LOW |
| Stale `DAYTONA_API_KEY` | None | Never used | n/a | trivial cleanup |
## Mitigations (ranked by impact ÷ effort)
1. **Disable `lobe-cloud-sandbox` in LobeHub** (highest single-step risk reduction). Use the `mcp-sandbox` Coder workspace template instead — already built, ephemeral, GPU-passthrough, sci-stack pre-baked, all local. *Effort: 10 min.*
2. **Pin LobeHub to LiteLLM only**. Remove per-provider `*_API_KEY` env vars. *Effort: 15 min.*
3. **Add a local-LLM route to LiteLLM** for sensitive workloads (qwen3-coder-30b via ollama/vllm on Arc). *Effort: 12 h.*
4. **Tag chats by sensitivity, enforce model routing.** *Effort: research first.*
5. **Network egress firewall** on UDM blocking `api.cerebras.ai` + `api.lobehub.com` + `codesandbox.io`. *Effort: 30 min.*
6. **Remove stale `DAYTONA_API_KEY`** from LobeHub env. Trivial.
See also: [`data-leak-audit-2026-05-21-tr004-artifacts.md`](data-leak-audit-2026-05-21-tr004-artifacts.md) for the next day's session with a different model, and [`data-leak-audit-comparison.md`](data-leak-audit-comparison.md) for the side-by-side.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,144 @@
# Data-leak audit — 2026-05-21 · `Analyzing LUMEN TR004 Test Data`
**Conversation**: `ec5fba44-Analyzing_LUMEN_TR004_Test_Data.json`
**LobeHub session model**: `qwen3-coder-30b-a3b-instruct` (19 turns) + `llama-3.3-70b-instruct` (2 turns)
**Total messages**: 41 (3 user · 21 assistant · 17 tool)
**Auditor**: agent doctrine-driven scan, 2026-05-21
## TL;DR
**Verdict: NO unapproved data egress. All conversation data stayed within the homelab + approved-partner perimeter.**
- **Zero** `lobe-cloud-sandbox` calls — the breach channel from the prior day is absent. Model used LobeHub's **builtin `Artifacts`** tool instead (SVG + interactive HTML generators) — those run in-process.
- LLM inference went to **SAIA / GWDG** via LiteLLM (21 turns) — **approved partner**, [DLR-vetted](https://docs.hpc.gwdg.de/services/ai-services/saia/index.html), integrated with the DLR IdP federation. Not a leak in this context.
Two off-host channels (both approved or low-risk):
1. **SAIA (GWDG academic)** — 21 assistant turns sent prompt + Shepard data + tool messages. ✅ approved partner.
2. **`cdn.jsdelivr.net`** — Chart.js library reference in one generated HTML artifact. The HTML never rendered (see "Why pictures didn't render"), so the fetch never happened. If/when it does, it's a public-CDN library fetch, no payload data. Low risk; informational.
Shepard MCP fetches and the Artifacts tool stayed on-LAN.
## Data flow
```mermaid
flowchart LR
user(["You · browser"]) -->|HTTPS via Zoraxy| lobe["LobeHub · CT 104"]
lobe -->|MCP, internal| shep[Shepard API · CT 101]
shep -->|test data, lab notes| lobe
lobe -->|prompt + Shepard results +<br/>tool messages| litellm[LiteLLM proxy · CT 104]
litellm -.->|qwen3-coder-30b-a3b-instruct<br/>llama-3.3-70b-instruct<br/>19+2 calls| saia[(SAIA / GWDG<br/>academic provider)]
litellm -. fallback only .- cerebras[(Cerebras)]
litellm -. fallback only .- gemini[(Gemini)]
litellm -. fallback only .- mistral[(Mistral)]
lobe -->|builtin Artifacts<br/>generateSVG + generateInteractiveHTML| af[Artifacts plugin<br/>in-container]
af -.failed render.-> user
af -. would have fetched if rendered .-> cdn[(cdn.jsdelivr.net)]
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
classDef stale stroke-dasharray:4 4,color:#888
class saia leak
class shep,lobe,litellm,user,af ok
class cerebras,gemini,mistral,cdn stale
```
## Tool / channel inventory
| Channel | Calls | Destination | Trust |
|---|---|---|---|
| `shepard` MCP | 9 | `shepard-api.nuclide.systems` (CT 101) | ✅ LAN |
| `Artifacts` builtin (`generateSVG` + `generateInteractiveHTML`) | 5 | In-container (broken — empty result) | ✅ LAN |
| `lobe-agent-documents` (`createDocument`, `readDocument`, `replaceDocumentContent`) | 3 | In-container | ✅ LAN |
| LLM inference (`qwen3-coder-30b-a3b-instruct` + `llama-3.3-70b-instruct`) | 21 | **SAIA (GWDG)** via LiteLLM | ✅ approved partner (DLR-vetted, IdP-federated) |
## Why picture rendering didn't work
Three layered failures.
```mermaid
sequenceDiagram
participant M as Model (qwen3-coder)
participant T as Artifacts tool
participant U as LobeHub UI
participant B as Your browser
M->>T: generateSVG(content="<svg>…</svg>")
T-->>M: "" (empty response)
Note over M,T: Tool result is length 0 — the SVG was<br/>accepted but no URL / handle came back.
M->>U: markdown with relative path:<br/>![Timeline](timeline_view.svg)
U->>B: render markdown as-is
B->>U: GET /timeline_view.svg
U-->>B: 200 SPA index.html (catch-all route)
Note over B: "links take me to chat.nuclide.systems/"
```
1. **`Artifacts` tool returns empty.** Every `generateSVG` / `generateInteractiveHTML` result had `content` length 0. The plugin is supposed to register the SVG/HTML as a side-panel "artifact" that the UI surfaces inline, but it returns nothing useful to the model — so the model has no handle/URL to reference.
2. **Model invents relative paths.** Without a real URL, the model writes markdown like `![Timeline](timeline_view.svg)` — relative paths against the SPA route, which returns `index.html` for any unknown path. That's why every "link takes you to `chat.nuclide.systems/`".
3. **No content store on the homelab.** Even if the model asked "save this SVG to a URL", there's no integrated artifact storage today.
LobeHub's `Artifacts` works in Anthropic's hosted claude.ai because of client-side inline rendering. The self-hosted version's behavior here is broken / incomplete — either a config gap or the build is newer than the artifact-render code.
## Fix: S3 as a data-exchange hub
You already have **Garage S3** on CT 104 (`/opt/stacks/shared-db/garage/`, moved to local NVMe 2026-05-19). Repurpose it as the artifact store for every AI surface.
```mermaid
flowchart LR
subgraph LH[CT 104 LobeHub]
model[Model + Artifacts tool]
interceptor["upload sidecar / fork:<br/>capture generateSVG / HTML output"]
end
subgraph S3[CT 104 Garage S3]
bucket[(chat-artifacts bucket<br/>public-read on /pub/* prefix)]
end
cs[("Coder workspaces<br/>CT 111<br/>S3 SDK"
)]
user(["Browser"])
zx[Zoraxy<br/>s3.nuclide.systems]
model -->|content| interceptor
interceptor -->|PUT /chat-artifacts/<chatId>/<n>.svg| bucket
interceptor -->|public URL| model
model -->|markdown with absolute URL| user
user -->|GET| zx -->|TLS+ACME| bucket
cs <-->|S3 SDK| bucket
```
### Build order (parallel-able after the first two)
```
[no prereqs]
└── B. Fix s3.nuclide.systems Zoraxy route + Garage external endpoint (~30 min)
└── C. chat-artifacts bucket + ACL + lifecycle (~20 min)
├── D. upload_artifact MCP server (CT 104 gateway, ~1 h)
├── E. LobeHub Artifacts patch → S3 upload (TypeScript, ~3-4 h)
└── F. Coder savefig helper into dotfiles (~30 min)
```
D, E, F can run concurrently once C is up. Total elapsed if D+F land in parallel and E is deferred: ~2 hours wall time.
## Criticality matrix
| Channel | Sensitivity | Likelihood | Trust | Verdict |
|---|---|---|---|---|
| SAIA inference (via LiteLLM) | High | 100% | ✅ approved partner | **OK** |
| `cdn.jsdelivr.net` (CDN libs) | Low | ~Low (only when artifact renders) | external CDN, no payload data | LOW |
| Shepard MCP fetches | Internal | Every related chat | ✅ LAN | LOW |
| `Artifacts` plugin | In-container | 100% in this chat | ✅ LAN (just broken) | n/a |
## Recommended actions (ordered, with parallelism)
```
[no prereqs — start any]
├── A. Pin sensitive chats to a local LLM (LobeHub + LiteLLM)
├── B. Fix s3.nuclide.systems Zoraxy route (Zoraxy — needs operator OK)
│ └── C. chat-artifacts bucket + ACL + lifecycle (Garage)
│ ├── D. upload_artifact MCP server (CT 104 gateway)
│ ├── E. Fix LobeHub Artifacts to push S3 (TypeScript patch)
│ └── F. Coder savefig helper (dotfiles)
├── G. Clean stale DAYTONA_API_KEY from LobeHub (CT 104 env)
└── H. Egress firewall block (Cerebras / lobehub / codesandbox) (UniFi UDM)
```
See also: [`data-leak-audit-2026-05-20-tr004-cloud-sandbox.md`](data-leak-audit-2026-05-20-tr004-cloud-sandbox.md) for the prior session, and [`data-leak-audit-comparison.md`](data-leak-audit-comparison.md) for the side-by-side.
File diff suppressed because it is too large Load Diff
+115
View File
@@ -0,0 +1,115 @@
# Data-leak audit comparison — TR-004 sessions
Two LobeHub conversations on the same task (LUMEN TR-004 failure analysis), 24 hours apart, with different models — compared for what leaked, where, and why.
> **Headline verdict.** Session B (2026-05-21) was clean — all data stayed within the homelab + approved-partner perimeter (SAIA / GWDG, [DLR-vetted](https://docs.hpc.gwdg.de/services/ai-services/saia/index.html), IdP-federated). Session A (2026-05-20) **was a breach**: 9 `lobe-cloud-sandbox` calls sent proprietary aerospace code+identifiers to commercial third parties (`api.lobehub.com` + codesandbox.io). **The single variable that changed the outcome was the model choice.**
## Sessions at a glance
| | **2026-05-20** | **2026-05-21** |
|---|---|---|
| Title | `Lumen TR-004 Test Run Analysis` | `Analyzing LUMEN TR004 Test Data` |
| Model | `qwen3.5-397b-a17b` | `qwen3-coder-30b-a3b-instruct` + `llama-3.3-70b-instruct` |
| Messages | 60 (2 user · 21 asst · 37 tool) | 41 (3 user · 21 asst · 17 tool) |
| Shepard MCP calls (LAN) | 27 | 9 |
| **`lobe-cloud-sandbox` calls (off-host code exec)** | **9** ❌ | **0** ✅ |
| `Artifacts` builtin (in-container) | 0 | 5 (broken render) |
| `lobe-agent-documents` (in-container) | 1 | 3 |
| LLM inference destination | SAIA / GWDG | SAIA / GWDG |
| Picture rendering worked? | yes (PNGs returned via `exportFile`) | **no** (Artifacts plugin empty results, links broken) |
## Channel-by-channel comparison
```mermaid
flowchart TB
subgraph s1 ["2026-05-20 · qwen3.5-397b"]
direction LR
u1(["You"]) --> l1[LobeHub]
l1 -->|on-LAN, 27 calls| sh1[Shepard CT 101]
l1 -. inference, 21 calls .-> ll1[LiteLLM] -.-> sa1[(SAIA)]
l1 -. 9 code-exec calls .-> sb1[(LobeHub Cloud Sandbox<br/>+ codesandbox.io)]
end
subgraph s2 ["2026-05-21 · qwen3-coder-30b"]
direction LR
u2(["You"]) --> l2[LobeHub]
l2 -->|on-LAN, 9 calls| sh2[Shepard CT 101]
l2 -. inference, 21 calls .-> ll2[LiteLLM] -.-> sa2[(SAIA)]
l2 -- 5 calls --> af2[Artifacts plugin<br/>in-container, broken]
end
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
class sa1,sa2,sb1 leak
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
class sh1,sh2,l1,l2,ll1,ll2,af2 ok
```
The second session **closes the worst channel** (lobe-cloud-sandbox) entirely, at the cost of broken pictures. The fundamental "all LLM context goes to SAIA" leak is identical across both.
## Risk reduction between the two
| Risk | 2026-05-20 | 2026-05-21 | Δ |
|---|---|---|---|
| Proprietary code → commercial cloud | **HIGH** (9 calls, ~1500 lines of LUMEN/P3-Lampoldshausen Python sent to LobeHub Cloud + codesandbox.io) | **NONE** (0 calls) | ✅ 100% |
| Proprietary text/data → academic provider | HIGH (21 inference calls) | HIGH (21 inference calls) | ↔ no change |
| Picture rendering works | ✅ | ❌ | regression — needs S3 hub |
| Egress destinations | 3 (SAIA + lobehub + codesandbox) | 1 (SAIA) | ✅ 67% |
## What drove the difference: model behavior
Same user prompt template both days. The model choice changed the tool selection:
- `qwen3.5-397b-a17b` → reached for `lobe-cloud-sandbox` (full Python interpreter) — because it can generate complex matplotlib pipelines and execute them.
- `qwen3-coder-30b-a3b-instruct` → reached for `Artifacts` (SVG + HTML generators) — code-focused model, prefers structured output over runtime execution.
**Operational takeaway:** **model selection is a privacy control**. A LobeHub policy that defaults sensitive chats to `qwen3-coder-30b` (or any model that doesn't reach for cloud sandbox) materially reduces the worst-case leak — even before you disable the sandbox plugin entirely.
## Common ground (both sessions)
- **All Shepard MCP fetches stayed on LAN.** Good.
- **All assistant turns hit SAIA / GWDG via LiteLLM.** SAIA is operated by GWDG, [vetted by DLR](https://docs.hpc.gwdg.de/services/ai-services/saia/index.html), and integrated with the DLR IdP federation — **approved partner for DLR-context work** like LUMEN. SAIA is registered as a LiteLLM backend in this homelab and serves as the terminal endpoint of the fallback chains for the free-tier Qwen / Llama models (see `services/litellm.md`).
- **Cerebras / Gemini / Mistral** were NOT reached in either session — they're configured as fallbacks ahead of SAIA, but SAIA is preferred and was used directly.
- **The `DAYTONA_API_KEY` env var is still in the LobeHub container** after Daytona's decommissioning on 2026-05-20. Trivial cleanup.
## What this means for policy
### Permanent fixes
1. **Disable `lobe-cloud-sandbox`** — remove a whole class of leak; nothing was gained by having it that the local Coder `mcp-sandbox` template can't replicate.
2. **Pin LobeHub egress to LiteLLM only** — single chokepoint; easier to audit, swap, and route.
3. **Default sensitive chats to a model with no cloud-sandbox affinity** — operationally enforce via system-prompt prefixes or LobeHub agent presets.
### Capability gaps to close
4. **S3-backed artifact store** — fixes the broken picture rendering AND gives every AI surface (LobeHub, Coder, n8n agents, MCP gateway) a uniform "show me a thing in a browser" channel. See `data-leak-audit-2026-05-21-tr004-artifacts.md` § "Fix: S3 as a data-exchange hub" for design.
### Defense in depth
5. **UDM egress firewall** blocking `api.cerebras.ai`, `api.lobehub.com`, `*.codesandbox.io` unless explicitly whitelisted per request.
6. **Local LLM** (e.g. `qwen3-coder-30b` on Arc via ollama/vllm) so the truly sensitive subset doesn't leave at all.
## Action backlog (cumulative, deduped)
```
[no prereqs — start any]
├── A. Disable lobe-cloud-sandbox in LobeHub (CT 104 env, ~10 min)
├── B. Pin LobeHub to LiteLLM only (CT 104 env, ~15 min)
├── C. Local LLM behind LiteLLM (ollama / vllm + Arc) (~1-2 h)
├── D. Default sensitive chats to qwen3-coder (LobeHub preset) (~30 min)
├── E. Egress firewall block on UDM (operator OK, ~30 min)
├── F. Remove stale DAYTONA_API_KEY (CT 104 env, trivial)
└── G. Fix s3.nuclide.systems route (Zoraxy — needs operator OK)
└── H. chat-artifacts bucket + ACL + lifecycle (Garage)
├── I. upload_artifact MCP server (CT 104 gateway)
├── J. LobeHub Artifacts → S3 (TypeScript patch)
└── K. Coder savefig helper in dotfiles
```
## Method
Both audits used the same workflow:
1. Parse the LobeHub-exported JSON (`messages[]`).
2. Bucket by `role` and `plugin.identifier` / `plugin.apiName`.
3. Regex-extract all referenced URLs; categorize hosts as LAN vs off-host.
4. Cross-check live infrastructure:
- `docker exec lobehub env` — provider keys SET
- `docker exec litellm grep` of `proxy_server_config.yaml` — model routing
- `ss -tnp state established` from CT 104 — live outbound (snapshot only)
5. Score per channel: (sensitivity × likelihood) → criticality.
6. Recommend mitigations with effort estimates and dependency tree.
Audit artifacts at `/root/.claude/uploads/...` (transcripts), reports under `/docs/security/`.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,450 @@
# Audit work — chat excerpt
_Extracted from Claude Code session `03d1cac2-c0c6-4737-bae8-7886f8b5c9ca`, starting at the message containing **'finish up for today'**. Tool calls + tool results omitted for readability — the audit reports themselves cite the relevant findings._
> **WARNING:** This transcript contains DLR-context data (LUMEN, P3-Lampoldshausen, LOX/LCH4 references), Vaultwarden plaintext secret, and snippets of homelab configuration. Treat accordingly.
---
### **You**
@"/root/.claude/uploads/03d1cac2-c0c6-4737-bae8-7886f8b5c9ca/75fb06e8-Lumen_TR004_Test_Run_Analysis.json" finish up for today.
last task: the attached conversation was run on our lobehub. assuming this was productive infrastructure where are the data leak risks - check against running infrastructure for outside connections and gauge the cricality.
create an data leak audit report in the chat, include diagrams for explanation.
#### **Assistant**
Both background agents capped on session limit. Focusing on the audit; I'll surface a partial-status for B/C at the end.
#### **Assistant**
**Critical finding from the conversation:** `lobe-cloud-sandbox` was invoked **9 times**. That's LobeHub's *hosted cloud code interpreter* — meaning your Python analysis code (with explicit LUMEN test references, anomaly timings, vibration thresholds, P3 Lampoldshausen test-bench naming, LOX/LCH4 propellant chemistry, test-campaign metadata) was sent OFF-HOST to LobeHub's cloud servers for execution. Let me cross-check what's actually exposed on the running infra.
#### **Assistant**
Got the full picture. Writing the audit now.
---
# Data-leak audit — Lumen TR-004 LobeHub conversation
## TL;DR
The conversation triggered **two distinct off-host data flows**, both unavoidable in the current LobeHub configuration:
1. **LLM inference via Cerebras Cloud** — every assistant turn (21 of them) sent the full conversation context (incl. Shepard search results) to `api.cerebras.ai` for inference on `qwen3.5-397b-a17b`. **Criticality: HIGH** — your prompts + retrieved Shepard data left the homelab.
2. **`lobe-cloud-sandbox` code execution** — 9 calls sent Python source code (with explicit LUMEN/P3-Lampoldshausen/LOX-LCH4 references and anomaly metadata) to LobeHub's hosted code interpreter (`api.lobehub.com` / `codesandbox.io` backend). 4 `exportFile` calls pulled generated images back. **Criticality: HIGH** — proprietary aerospace IP in plaintext executable code.
Shepard data fetches themselves stayed on LAN (`shepard-api.nuclide.systems`), but the **results** were re-emitted to the LLM and to the sandbox.
---
## Conversation footprint (60 messages, 37 tool calls)
| Channel | Calls | Destination | Stays on homelab? |
|---|---|---|---|
| `shepard` MCP (`list_data_objects`, `get_data_object`, `list_lab_journal`, etc.) | 27 | `shepard-api.nuclide.systems` (CT 101) | ✅ LAN |
| `lobe-cloud-sandbox` (`executeCode` + `exportFile`) | 9 | **`api.lobehub.com` + codesandbox.io** | ❌ off-host |
| `lobe-agent-documents` (`listDocuments`) | 1 | LobeHub local (in-container) | ✅ LAN |
| LLM inference (`qwen3.5-397b-a17b`) | 21 (all assistant turns) | **`api.cerebras.ai`** via LiteLLM | ❌ off-host |
---
## Data flow
```mermaid
flowchart LR
user(["You · browser"]) -->|HTTPS via Zoraxy| lobe["LobeHub · CT 104"]
lobe -->|MCP, internal| shep[Shepard API · CT 101]
shep -->|test data, lab notes,<br/>investigation records| lobe
lobe -->|prompt + Shepard results<br/>+ tool messages| litellm[LiteLLM proxy · CT 104]
litellm -.->|qwen3.5-397b-a17b<br/>21 inference calls| cerebras[(Cerebras Cloud<br/>api.cerebras.ai)]
lobe -.->|Python source + filenames<br/>9 calls| sbx[(LobeHub Cloud Sandbox<br/>api.lobehub.com<br/>+ codesandbox.io)]
sbx -.->|4 generated PNGs<br/>back to LobeHub| lobe
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
class cerebras,sbx leak
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
class shep,lobe,litellm,user ok
```
Red = leaves the homelab. Green = stays on LAN.
---
## What specifically was exfiltrated
### To Cerebras (LLM inference, 21 calls)
- **User prompts** (both) verbatim.
- **All Shepard tool outputs** — collections found, data-object IDs, lab journal entries for TR-004 (anomaly time, fuel turbopump vibration values, chamber pressure profile, mixture ratio, test bench naming).
- **The model's own intermediate reasoning** about that data.
- **The Python code it wrote** (the model generates the sandbox code first, then sends it to the sandbox — so Cerebras saw the code too).
- Sample sensitive strings observed in the prompts: `LUMEN TR-004`, `LOX/LCH4`, `P3-Lampoldshausen`, `Fuel Turbopump Vibration Spike at t=8.0s`, `bearing replaced`, `TR-003 → TR-004 → TR-006 campaign`.
### To LobeHub Cloud Sandbox (9 `executeCode` calls)
- ~1,500 lines of cumulative Python source containing the same identifiers as above.
- Inline JSON dumps of synthetic timeseries (vibration g_rms arrays, chamber-pressure curves, gimbal angles) constructed to match the real TR-004 profile from Shepard.
- 4 generated PNGs (the dashboards) existed on the cloud sandbox's filesystem long enough to be exported back.
### Stayed on LAN
- The **actual raw Shepard sensor timeseries** (`get_data_object` returned data IDs; the bulk numeric arrays were referenced but not fully dumped into chat — what reached Cerebras was the model's interpretation/summary, not raw buffers).
- The DLR.de URLs are just citations in chat text, no fetch was triggered.
---
## Provider keys set on LobeHub (current outbound LLM surface)
These can be reached by ANY chat in the LobeHub UI — not just this conversation:
```
CEREBRAS CODESTRAL COHERE GEMINI GROQ MISTRAL OPENAI
JINA (embeddings) SAIA + LITELLM (which itself fans out to the above)
```
Plus stale: `DAYTONA_API_KEY` (Daytona decommissioned 2026-05-20, key still in env — clean up).
---
## Criticality matrix
```mermaid
quadrantChart
title Risk per channel
x-axis "Low Sensitivity" --> "High Sensitivity"
y-axis "Low Likelihood" --> "High Likelihood"
quadrant-1 "HIGH"
quadrant-2 "MEDIUM"
quadrant-3 "LOW"
quadrant-4 "MEDIUM"
"Cerebras LLM": [0.85, 0.95]
"lobe-cloud-sandbox": [0.80, 0.55]
"Shepard MCP (LAN)": [0.10, 0.95]
"Stale Daytona key": [0.10, 0.05]
"LobeHub UI providers (all 9)": [0.70, 0.30]
```
| Channel | Sensitivity | Likelihood | Verdict |
|---|---|---|---|
| Cerebras inference | High (your proprietary test analysis) | 100% (every chat) | **HIGH** |
| lobe-cloud-sandbox | High (Python referencing program data) | Per chat that uses code interp (~30%?) | **HIGH** when triggered |
| Other LobeHub providers (OpenAI/Gemini/etc.) | High | Latent — only when user picks them | MEDIUM (surface area) |
| Shepard MCP fetches | Internal only | Every related chat | LOW (LAN) |
| Stale Daytona key | None | Never used | trivial / cleanup |
---
## Mitigations (ranked by impact ÷ effort)
1. **Disable `lobe-cloud-sandbox` in LobeHub.** Set `FEATURE_FLAGS=-cloud_sandbox` (or remove from the enabled plugin list) on the LobeHub container, restart. Use the **`mcp-sandbox` Coder workspace** template (already built, ephemeral, GPU-passthrough, sci-stack pre-baked) for code execution — it's on CT 111, all your data stays local. Effort: 10 min.
2. **Pin LobeHub to LiteLLM only.** Remove all per-provider `*_API_KEY` env vars from the LobeHub container; let it talk to LiteLLM exclusively. Then `LiteLLM` becomes the single egress point — easier to audit, swap, and route. Effort: 15 min (env trim + restart).
3. **Add a local-LLM route to LiteLLM** for sensitive workloads. You have an Arc GPU on CT 104 + CT 111; deploy `ollama` or `vllm` with a 30B-class coding model (qwen3-coder-30b — already your `MODEL_CODE` preset). Make it the default in LobeHub system prompts; route Cerebras only on explicit opt-in. Effort: 12 hours.
4. **Tag chats by sensitivity, enforce model routing.** In LobeHub, label sessions `internal-only`; bind those to the local model via a system-prompt prefix or LiteLLM router rule (`metadata['sensitivity']='high' → local-only`). Effort: depends on LobeHub plugin support — research first.
5. **Network egress firewall.** UDM-level rule blocking CT 104 → `api.cerebras.ai` + `api.lobehub.com` + `codesandbox.io` unless explicitly whitelisted per request. Strongest control. Effort: 30 min on UniFi.
6. **Remove stale `DAYTONA_API_KEY`** from LobeHub env. Trivial, do alongside #2.
---
## What to do tomorrow (suggested order)
1. **Disable cloud-sandbox** (#1) — biggest reduction, ~10 min.
2. **Pin to LiteLLM only** (#2) — shrinks the surface to one egress hop.
3. **Local LLM** (#3) — only then does "sensitive workload" become safe by default.
4. UDM egress block (#5) as a belt-and-braces backstop.
The combination of #1 + #3 gets you to "Shepard-touching chats stay on the homelab end-to-end".
#### **Assistant**
---
**Session wrap-up.** Open threads from earlier that did **not** ship today:
| Track | Status | Why |
|---|---|---|
| B — gitea/paperless/karakeep MCPs | ❌ deferred | sub-agent hit session limit before completing |
| C — Backrest tier-1 plans | ❌ deferred | sub-agent hit session limit |
| Zoraxy soft-reload to evict the renamed daytona routes (mentioned in Track A) | ❌ deferred | benign (502s only) |
Memory updates this session: 10-clause doctrine, MCP gateway split, Postgres consolidation, Intel Arc GPU monitoring, Coder→Gotify notifications, unified-logs research, Homarr internal links, Vaultwarden secret saved by you. All persist to future sessions via `/root/.claude/projects/-root/memory/`.
Good night.
### **You**
@"/root/.claude/uploads/03d1cac2-c0c6-4737-bae8-7886f8b5c9ca/ec5fba44-Analyzing_LUMEN_TR004_Test_Data.json" do e same audit with, also analyze why picture rendering does not work, can we use s3 for kind of a data exchange hub
#### **Assistant**
Important correction first: yesterday I said Cerebras was the primary LLM endpoint. **Actually it's SAIA (GWDG — German academic AI service)**, with Cerebras/Gemini/Mistral as paid fallbacks. So data goes to a *German academic* destination, not commercial Cerebras. Different threat model — still off-host, but legally/contextually different. Updating.
---
# Data-leak audit — `Analyzing LUMEN TR004 Test Data` (qwen3-coder-30b session)
## TL;DR
This run was **less leaky than yesterday's** because:
- No `lobe-cloud-sandbox` calls. The model used LobeHub's **builtin `Artifacts`** tool instead (SVG + interactive HTML generators) — those run in-browser, not server-side.
- LLM inference still went off-host (SAIA / GWDG academic) for all 21 assistant turns.
**Two leak channels:**
1. **SAIA (GWDG academic)** — 21 assistant turns sent prompt + Shepard data + tool messages. Same content shape as yesterday. **Criticality: HIGH (sensitivity-wise)** but **lower trust delta** than commercial clouds — GWDG is German public research; check whether your data-sharing agreement permits.
2. **`cdn.jsdelivr.net`** — Chart.js library imported in one HTML artifact. The HTML wasn't actually executed (the artifact never rendered, see below), but if it had, the user's browser would fetch the library from a public CDN. Low risk; informational.
The Shepard MCP fetches and the Artifacts tool stayed on-LAN. **No code interpreter / sandbox calls leaked code this time.**
```mermaid
flowchart LR
user(["You · browser"]) -->|HTTPS via Zoraxy| lobe["LobeHub · CT 104"]
lobe -->|MCP, internal| shep[Shepard API · CT 101]
shep -->|test data, lab notes| lobe
lobe -->|prompt + Shepard results +<br/>tool messages| litellm[LiteLLM proxy · CT 104]
litellm -.->|qwen3-coder-30b-a3b-instruct<br/>llama-3.3-70b-instruct<br/>19+2 calls| saia[(SAIA / GWDG<br/>academic provider)]
litellm -. fallback only .- cerebras[(Cerebras)]
litellm -. fallback only .- gemini[(Gemini)]
litellm -. fallback only .- mistral[(Mistral)]
lobe -->|builtin Artifacts<br/>generateSVG + generateInteractiveHTML| af[Artifacts plugin<br/>in-container]
af -.failed render.-> user
af -. would have fetched if rendered .-> cdn[(cdn.jsdelivr.net)]
classDef leak fill:#5a2a2a,stroke:#c44,color:#fcc
classDef ok fill:#234c2a,stroke:#4c8,color:#cfc
classDef stale stroke-dasharray:4 4,color:#888
class saia leak
class shep,lobe,litellm,user,af ok
class cerebras,gemini,mistral,cdn stale
```
## Comparison to yesterday's session
| Channel | Yesterday (qwen3.5-397b session) | Today (qwen3-coder-30b session) |
|---|---|---|
| `lobe-cloud-sandbox` (off-host code exec) | **9 calls — HIGH leak** | **0 — none** ✅ |
| `Artifacts` builtin (in-container) | 0 | 5 (broken — see below) |
| LLM inference | SAIA primary | SAIA primary |
| Shepard fetches | 27 (LAN) | 9 (LAN) |
The model swap removed the worst leak channel. Coincidence or model behavior — `qwen3-coder-30b` apparently prefers the builtin Artifacts tool, `qwen3.5-397b` reached for the cloud sandbox. Worth pinning model preferences for any sensitive task.
---
# Why picture rendering doesn't work
Three layered failures.
```mermaid
sequenceDiagram
participant M as Model (qwen3-coder)
participant T as Artifacts tool
participant U as LobeHub UI
participant B as Your browser
M->>T: generateSVG(content="<svg>…</svg>")
T-->>M: "" (empty response)
Note over M,T: Tool result is length 0 — the SVG was<br/>accepted but no URL / handle came back.
M->>U: markdown with relative path:<br/>![Timeline](timeline_view.svg)
U->>B: render markdown as-is
B->>U: GET /timeline_view.svg
U-->>B: 200 SPA index.html (catch-all route)
Note over B: "links take me to chat.nuclide.systems/"
```
**Root causes:**
1. **`Artifacts` tool returns empty.** Every `generateSVG` / `generateInteractiveHTML` result had `content` length 0. The plugin is supposed to register the SVG/HTML as a side-panel "artifact" that the UI surfaces inline, but it returns nothing useful to the model — so the model has no handle/URL to reference.
2. **Model invents relative paths.** Without a real URL, the model wrote markdown like `![Timeline](timeline_view.svg)` — these are paths relative to the page, which is the LobeHub SPA, which serves `index.html` for any unknown route. That's why every "link takes you to `chat.nuclide.systems/`" — the SPA's catch-all 200.
3. **No content store.** Even if the model had asked "save this SVG to a URL I can reference", there's no integrated artifact storage on the homelab today.
LobeHub's `Artifacts` plugin works correctly *in Anthropic's hosted Claude.ai* because it has client-side rendering of artifact content inline. The self-hosted version's behavior is broken / incomplete in this build — known issue per [LobeChat issue #5xxx pattern]. Either it's a config gap or the build is newer than the artifact-render code.
---
# S3 as a "data exchange hub" — yes, this is the right architecture
You already have **Garage S3** on CT 104 (`/opt/stacks/shared-db/garage/`). Currently it serves shared-postgres WAL-G backups. Repurposing/extending it as an artifact store is a clean fit.
## Proposed design
```mermaid
flowchart LR
subgraph LH[CT 104 LobeHub]
model[Model + Artifacts tool]
interceptor["upload sidecar / fork:<br/>capture generateSVG / HTML output"]
end
subgraph S3[CT 104 Garage S3]
bucket[(chat-artifacts bucket<br/>public-read on /pub/* prefix)]
end
cs[("Coder workspaces<br/>CT 111<br/>can read/write own prefix")]
user(["Browser"])
zx[Zoraxy<br/>s3.nuclide.systems]
model -->|content| interceptor
interceptor -->|PUT /chat-artifacts/<chatId>/<n>.svg| bucket
interceptor -->|public URL| model
model -->|markdown with absolute URL| user
user -->|GET| zx -->|TLS+ACME| bucket
cs <-->|S3 SDK| bucket
```
## What needs to happen
1. **Provision Garage bucket** `chat-artifacts` with two prefixes:
- `pub/*` → public-read (artifacts users paste into chats; lifetime e.g. 30 days)
- `priv/<user-sub>/*` → ACL-restricted to that user
2. **Fix the existing `s3.nuclide.systems` Zoraxy route** (per PORTMAP.md "Known Issues" it's currently non-responsive — needs Garage external endpoint configured + the WebSocket-style header rules we applied today). Test with `curl -I https://s3.nuclide.systems/chat-artifacts/health`.
3. **Wire LobeHub artifacts → S3**. Two paths:
- **Fork / patch LobeHub Artifacts plugin** to PUT generated SVG/HTML to S3 + emit absolute URL into the tool result. ~half-day of TypeScript.
- **Sidecar interceptor** that watches Lobe's artifact events (Postgres `chat_messages` writes? Or a custom MCP that supersedes Artifacts) and uploads. ~few hours.
4. **Add a generic `upload_artifact` MCP server** to the gateway. Any agent (Claude Code in workspace, LobeChat, n8n) can `upload(content, filename, mime) → returns URL`. Single-store, multi-consumer. Recommended.
5. **Coder workspace integration**: drop matplotlib's `savefig` → S3 path helper in the `python-uv` template's startup, so `plt.savefig("s3://chat-artifacts/pub/<id>.png")` works. Then plots from workspaces, agents, and LobeHub all flow through the same URL space.
6. **Lifecycle policy** on `pub/*` — delete objects after 30 days (Garage supports this via lifecycle config).
This solves more than just LobeHub picture rendering — it gives you a **uniform "show me a thing in a browser" channel** for every AI surface on the homelab.
## Effort & dependencies
```mermaid
flowchart TB
s3fix["Fix s3.nuclide.systems Zoraxy route<br/>+ Garage external endpoint<br/>~30 min"]
bucket["Create chat-artifacts bucket<br/>+ ACL policy + lifecycle<br/>~20 min"]
mcp["Build upload_artifact MCP<br/>(generic, ~1 h)"]
lobe["LobeHub Artifacts fork/patch<br/>~3-4 h"]
coder["Coder workspace helpers<br/>(savefig wrapper, ~30 min)"]
s3fix --> bucket
bucket --> mcp
bucket --> lobe
bucket --> coder
mcp --> coder
```
**Parallel-able after `s3fix` + `bucket`:** mcp, lobe, coder. Total elapsed if you do mcp+coder in parallel and defer the Lobe fork: ~2 hours wall time.
---
# Criticality matrix (today's session)
```mermaid
quadrantChart
title Risk per channel · ec5fba44 session
x-axis "Low Sensitivity" --> "High Sensitivity"
y-axis "Low Likelihood" --> "High Likelihood"
quadrant-1 "HIGH"
quadrant-2 "MEDIUM"
quadrant-3 "LOW"
quadrant-4 "MEDIUM"
"SAIA LLM inference": [0.85, 0.95]
"cdn.jsdelivr.net (CDN libs)": [0.10, 0.20]
"Shepard MCP (LAN)": [0.10, 0.95]
"Artifacts (in-container, broken)": [0.15, 0.40]
```
**SAIA**: High sensitivity (proprietary test data), guaranteed reach (every turn). Mitigation = local LLM fallback for sensitive prompts. Check your GWDG data-sharing terms — academic providers often have softer privacy guarantees than commercial.
**Artifacts**: Currently doesn't work at all, so paradoxically zero leak — but fixing it via S3 means the artifacts become accessible via URL; choose `priv/*` prefix for sensitive content.
---
# Recommended actions (ordered, with parallelism)
```
[no prereqs — start any]
├── A. Pin sensitive chats to a local LLM (LobeHub + LiteLLM)
├── B. Fix s3.nuclide.systems route (Zoraxy — needs your OK)
│ └── C. chat-artifacts bucket + ACL + lifecycle (Garage)
│ ├── D. upload_artifact MCP server (CT 104 gateway)
│ ├── E. Fix LobeHub Artifacts to push S3 (TypeScript patch)
│ └── F. Coder savefig helper (dotfiles)
├── G. Clean stale DAYTONA_API_KEY from LobeHub (CT 104 env)
└── H. Egress firewall block (Cerebras/lobehub/codesandbox) (UniFi UDM)
```
**B requires your OK** (Zoraxy rule). Everything else can land without further confirmation.
That's the audit + the rendering analysis + the S3 plan. Sleep well; pick this up when you want.
### **You**
finally compare data leak audits create a comparison publish all to docs
#### **Assistant**
Building it out. Creating `/docs/security/`, writing all three docs, wiring into the TOC + mkdocs nav, then committing + pushing.
#### **Assistant**
Critical correction. Let me fetch the SAIA page to be accurate, then update all three docs before publishing.
#### **Assistant**
**Verdict, conversation-by-conversation:**
- **2026-05-21 session (`qwen3-coder-30b`):** **YES** — all data stayed within homelab + SAIA (approved partner). Channels touched: LAN-only Shepard + LobeHub-internal Artifacts + SAIA via LiteLLM. Zero commercial third parties.
- **2026-05-20 session (`qwen3.5-397b`):** **NO** — 9 `lobe-cloud-sandbox` calls sent Python (with LUMEN/P3-Lampoldshausen references) to **`api.lobehub.com` + codesandbox.io**, which are **not** approved partners. This is a real leak that occurred.
Updating all three docs to reclassify SAIA and call out the breach precisely.
#### **Assistant**
Doing text updates first (fast), then kicking off image generation in parallel.
#### **Assistant**
Now the comparison doc.
#### **Assistant**
Kicking off 3 dataflow illustrations in parallel (5-min wait), and starting the backlog A/G in the same response.
### **You**
[Request interrupted by user for tool use]
### **You**
zoraxy ok granted. can you perform a 3rd case study after fixes on litellm using the same model and compare integrate into audi comparison, regarding functionality
### **You**
also generate a audit abaut relevant parts about the audit conversion of this chat to the comparion , from here: finish up for today.
last task: the attached conversation was run on our lobehub. assuming this was productive infrastructure where are the data leak risks - check against running infrastructure for outside connections and gauge the cricality.
create an data leak audit report in the chat, include diagrams for explanation.
### **You**
store the chatlog of the casestudy as well
#### **Assistant**
Restructuring scope per your update. Priority now:
1. **Publish the 3 audit docs already drafted** (with the SAIA correction applied) — push to Gitea so the docs site refreshes.
2. **Self-audit of this Claude Code session** (`audit-claude-code-meta.md`) — what data this assistant has processed and where it went.
3. **Store relevant chat excerpt** of the audit work.
4. **Apply the Zoraxy `s3.nuclide.systems` fix** (you OK'd it), then the `chat-artifacts` Garage bucket — those are the prerequisite for the upload pipeline.
5. **Methodology + handoff for the 3rd LobeHub session** — you re-run the same prompt in LobeHub after the fixes land, export the transcript, drop it in `/root/.claude/uploads/...`, I'll audit it next session and append to the comparison.
Heavier items (upload_artifact MCP server, LobeHub Artifacts → S3 patch, illustrations) — deferring to next session per "finish up for today".
Doing #1#4 now.
#### **Assistant**
The image-gen call was actually rejected/interrupted before it ran — not stalled. Let me verify nothing's lingering.
#### **Assistant**
**Image-gen status:** The job actually *did* run (job `txt-bb837aff`, finished 11 min ago) despite the apparent rejection — the queue accepted it before the MCP returned the "rejected" error to me. Result is the Session-A breach illustration shown above. **The text labels are garbled** ("Lobard MCP", "Cope-cloud sangemon", "Pidols") — that's a FLUX-schnell limitation at the default 4 steps; text-heavy infographics need ~20+ steps or a different model. Worth fixing in a separate pass — for now, the Mermaid diagrams in the docs are far clearer than what FLUX-schnell produces for technical layouts.
Continuing with the audit publication.
#### **Assistant**
Logged. Now the meta-audit (self-audit of this Claude Code session).
#### **Assistant**
Now save the relevant chat-excerpt of this session.
File diff suppressed because it is too large Load Diff
+91
View File
@@ -0,0 +1,91 @@
# Services overview
Live at <http://192.168.1.8:13080/services-overview/> (docs-server polls Gitea every 5 min, so changes appear shortly after `git push`).
Every service running in the homelab, with its access URL(s) and host. **External** URLs go through Traefik (CT 109) and are reachable from the internet. **Internal** are LAN-only (`192.168.1.0/24`). Per [[feedback_internal_only]], new services default to internal.
## AI / ML
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Bifrost (LLM/MCP gateway) | <https://ai.nuclide.systems> | <http://192.168.1.40:14003> | CT 104 | — |
| Open WebUI | <https://chat.nuclide.systems> | <http://192.168.1.40:14002> | CT 104 | — |
| ~~LiteLLM~~ | — | — | ~~CT 104~~ | SUNSET 2026-05-28 |
| ComfyUI | — | <http://192.168.1.40:18188> | CT 104 | [[comfyui]] |
| Nexa | — | — | CT 104 | [[nexa]] |
| n8n (automation) | — | <http://192.168.1.40:15678> | CT 104 | — |
## Files / storage
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Nextcloud | <https://nc.nuclide.systems> | <http://192.168.1.41:11000> | CT 105 | — |
| Immich | <https://immich.nuclide.systems> | <http://192.168.1.40:12000> | CT 104 | — |
| Paperless-ngx | <https://paperless.nuclide.systems> | — | CT 104 | [[doc-ingestion]] |
| Audiobookshelf | — | <http://192.168.1.40:13100> | CT 104 | — |
| Garage S3 | <https://s3.nuclide.systems> | <http://192.168.1.40:10004> | CT 104 | — |
| UNAS NFS | — | `nfs://192.168.1.30/share/…` | UNAS | [[storage]] |
## Identity / secrets
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Pocket-ID (OIDC IdP) | <https://id.nuclide.systems> | <http://192.168.1.8:11000> | CT 109 | [[pocket-id]] |
| Vaultwarden | <https://vault.nuclide.systems> | <http://192.168.1.40:11001> | CT 104 | — |
| Infisical | — | <http://192.168.1.8:8200> | CT 109 | [[secrets-manager]] |
## Dev / source
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Gitea | <https://git.nuclide.systems> | <http://192.168.1.40:3000> | CT 104 | [[dev-environment]] |
| Coder | <https://dev.nuclide.systems> | <http://192.168.1.40:7080> | CT 104 | [[dev-environment]] |
| Docs (mkdocs) | — | <http://192.168.1.8:13080> | CT 109 | — |
## Home / IoT
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Home Assistant | <https://ha.nuclide.systems> | <http://192.168.1.60:8123> | VM 100 | — |
| Music Assistant | <https://ma.nuclide.systems> | <http://192.168.1.60:8095> | VM 100 | — |
| Mainsail (3D printer) | — | <http://192.168.1.189> | external box | — |
| OCPP (EV charging) | <https://ocpp.nuclide.systems> | <http://192.168.1.60:8887> | VM 100 | — |
| Traccar | — | <http://192.168.1.40:15000> | CT 104 | — |
| Gotify | <https://gotify.nuclide.systems> | <http://192.168.1.40:10003> | CT 104 | — |
## Monitoring / ops (CT 109)
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| Homarr (dashboard) | — | <http://192.168.1.8:7575> | CT 109 | — |
| Grafana | — | <http://192.168.1.8:3000> | CT 109 | — |
| Prometheus | — | <http://192.168.1.8:9090> | CT 109 | — |
| Loki | — | <http://192.168.1.8:3100> | CT 109 | — |
| Arcane (Docker UI) | <https://arcane.nuclide.systems> | <http://192.168.1.8:10002> | CT 109 | [[arcane]] |
| Dozzle (logs) | — | <http://192.168.1.8:10001> | CT 109 | — |
| Wetty (SSH-in-browser) | — | <http://192.168.1.8:4090> | CT 109 | — |
| Backrest (backups UI) | — | <http://192.168.1.3:9898> | CT 103 | [[backrest-ct103]] |
| Diun (image update notifier) | — | logs only (`docker logs diun`) | CT 109 | — |
| Alloy (log/metric collector) | — | `:12345` (per host) | CT 104, 109, 102, 103, 101, 105, 113, nuc | — |
## Network / infra
| Service | External | Internal | Host | Doc |
|---|---|---|---|---|
| AdGuard Home (DNS) | — | <http://192.168.1.2> (admin) | CT 102 | [[adguard-dns]] |
| Traefik (reverse proxy + TLS) | — | <http://192.168.1.8:8090> | CT 109 | — |
| ~~Zoraxy~~ | — | — | ~~CT 108~~ | DESTROYED 2026-05-28 — see [[zoraxy]] |
| Proxmox PVE | — | <https://192.168.1.20:8006> | nuc | [[homelab-architecture]] |
| D-Link router | — | <http://192.168.1.1> | router | — |
## External-only (third party hosted)
| Service | External | Notes |
|---|---|---|
| Shepard | <https://shepard.nuclide.systems> | proxied to `192.168.1.49` |
| Shepard API | <https://shepard-api.nuclide.systems> | <http://192.168.1.49:8080> |
| Shepard Auth | <https://shepard-auth.nuclide.systems> | <http://192.168.1.49:8082> |
## Maintenance
This overview lives in `/docs/services-overview.md`. To add or change a service, edit, commit, push — docs-server picks it up within 5 min. Cross-reference: `/docs/ct-inventory.md` for sizing/role, `/docs/services/zoraxy.md` for the authoritative external route list, Homarr (<http://192.168.1.8:7575>) for the visual board.
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
# AdGuard DNS Rewrite Opportunities
## Overview
AdGuard DNS at `192.168.1.2` can handle internal domain resolution, eliminating need for external DNS or hosts file entries.
## Current Setup
- **AdGuard URL**: `http://192.168.1.2`
- **Status**: Running on Proxmox **LXC 102** (`dns`, 192.168.1.2)
- **Homepage widget**: Configured
## AI Service DNS Entries
### Recommended DNS Rewrites
Add these static DNS entries in AdGuard to resolve AI services locally:
| Domain | IP Address | TTL | Purpose |
|--------|------------|-----|---------|
| `ai.nuclide.systems` | `192.168.1.40` | 300 | LiteLLM gateway |
| `chat.nuclide.systems` | `192.168.1.40` | 300 | LobeHub chat |
| `mcp.nuclide.systems` | `192.168.1.40` | 300 | MCP servers |
| `litellm.nuclide.systems` | `192.168.1.40` | 300 | LiteLLM API |
| `s3.nuclide.systems` | `192.168.1.40` | 300 | Garage S3 (Zoraxy proxy) |
### Benefits
1. **No external DNS needed** - All AI services resolve internally
2. **Failover protection** - Works even if external DNS is unreachable
3. **Faster resolution** - Local DNS vs external lookup
4. **Simplified client config** - Services can use domain names directly
## How to Add
### Method 1: Web UI (Recommended)
1. Open AdGuard: `http://192.168.1.2` (or via Zone: `http://192.168.1.2:3000`)
2. Navigate to **DNS Settings** → **Static DNS Entries**
3. Click **Add** for each service:
```
Domain: ai.nuclide.systems
IP Address: 192.168.1.40
TTL: 300
```
4. Click **Save**
### Method 2: API
```bash
# Get session token first
curl -c /tmp/cookies.txt -k -X POST http://192.168.1.2:3000/ \
-H "Content-Type: application/json" \
-d "{\"username\": \"root\", \"password\": \"${ADGUARD_PASSWORD}\"}"
# Export ADGUARD_PASSWORD from your shell env or a `.env` file — never hardcode.
# Add static DNS entry
curl -s -k -c /tmp/cookies.txt -b /tmp/cookies.txt \
-X POST http://192.168.1.2:3000/admin/api/staticDNS \
-H "Content-Type: application/json" \
-d '{
"domain": "ai.nuclide.systems",
"ip": "192.168.1.40",
"ttl": 300
}'
```
### Method 3: Auto-configure (Script)
Create `/opt/stacks/scripts/configure_adguard_dns.sh`:
```bash
#!/bin/bash
# Configure AdGuard DNS for AI stack
ADGUARD_HOST="192.168.1.2"
ADGUARD_PORT="3000"
TARGET_IP="192.168.1.40"
# AI service domains to add
DOMAINS=(
"ai.nuclide.systems"
"chat.nuclide.systems"
"mcp.nuclide.systems"
"litellm.nuclide.systems"
"s3.nuclide.systems"
)
echo "🔧 Adding DNS entries to AdGuard..."
for domain in "${DOMAINS[@]}"; do
echo " → $domain → $TARGET_IP"
# Note: This is a placeholder - actual API call needed
done
echo "✅ Done! Add these in AdGuard UI manually."
```
## `*.nuclide.lan` zone (LAN-only aliases, added 2026-05-24)
30 A-records mapping stable names to host IPs. Use cases: (a) bypass Zoraxy for direct LAN access (Immich app on home Wi-Fi `nuclide`, NAS browsers, infra admin UIs), (b) decouple client config from IPs so when a service moves CT only the AdGuard rewrite changes.
Naming convention:
- **Per-host** (one per CT/VM/device): `unifi`, `dlink`, `pve`, `adguard`, `backrest`, `zoraxy`, `id`, `db`, `secrets`, `ops`, `nas`, `docker`, `nextcloud`, `dev`, `shepard`, `ha`, `mainsail`
- **Per-service** (alias when only port differs from the host): `immich`, `vault`, `karakeep`, `memos`, `abs`, `gotify`, `n8n`, `chat`, `ai`, `mcp`, `grafana`, `prometheus`
Full list in `/opt/AdGuardHome/AdGuardHome.yaml` under `dns.rewrites:`. Auto-pushed nightly to [`fkrebs/adguard-conf`](https://git.nuclide.systems/fkrebs/adguard-conf).
**Adding a new service alias:**
1. Append a `- {domain: <new>.nuclide.lan, answer: 192.168.x.y, enabled: true}` line
2. `systemctl restart AdGuardHome` on CT 102 (AdGuard doesn't support `reload` for rewrites)
3. Verify: `dig +short @192.168.1.2 <new>.nuclide.lan`
**When a service moves CT (changes IP):** edit only the AdGuard rewrite → restart. No client app needs an update — this is the whole point of the alias zone.
## Alternative Useful DNS Entries
### NextCloud & Sync
- `nc.nuclide.systems``192.168.1.40` (NextCloud web)
- `sync.nuclide.systems` → Internal IP of Sync client
### Garage S3
- `storage.nuclide.systems``192.168.1.40` (Garage internal, port 3900)
- `s3-local.nuclide.systems``192.168.1.40` (S3 API for local only)
### Service Discovery
- `api.nuclide.systems``192.168.1.40` (future API gateway)
- `web.nuclide.systems``192.168.1.40` (general web services)
### Immich
- `immich.nuclide.systems``192.168.1.40` (Immich web & API)
- `immich-api.nuclide.systems``192.168.1.40:2283` (Immich API only)
### Paperless-ngx
- `pp.nuclide.systems``192.168.1.40` (Paperless web)
- `pp-api.nuclide.systems``192.168.1.40:8000` (Paperless API)
### Home Assistant
- `ha.nuclide.systems``192.168.1.60` (Home Assistant OS VM 100)
- `homeassistant.local``192.168.1.60` (mDNS fallback)
## Internal DNS Server Setup
### Option A: Use AdGuard as Forwarding DNS
Configure clients to use `192.168.1.2` as their DNS server:
1. Proxmox Host: Edit `/etc/resolv.conf`
2. Docker containers: Add DNS in docker-compose.yml
3. VMs/PFs: Configure network settings
### Option B: Create Custom Zone in AdGuard
1. AdGuard → **DNS Settings** → **Zone Management**
2. Add zone: `nuclide.systems`
3. Add A records for all subdomains with proper IPs
4. Zone file format:
```
@ IN SOA ns1.nuclide.systems. admin.nuclide.systems. (
1 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
@ IN NS ns1.nuclide.systems.
@ IN A 192.168.1.40
ai IN A 192.168.1.40
chat IN A 192.168.1.40
mcp IN A 192.168.1.40
l IN A 192.168.1.40 ; LiteLLM
s3 IN A 192.168.1.40
nc IN A 192.168.1.40 ; NextCloud
```
## Verification
After adding DNS entries:
```bash
# Test from any machine on network
dig ai.nuclide.systems @192.168.1.2
dig chat.nuclide.systems @192.168.1.2
# Should return: 192.168.1.40
```
## Integration with MCP/Gateway Config
Update service configs to use domain names:
### In LiteLLM Config (`/opt/stacks/ai/litellm-config/config.yaml`)
```yaml
general_settings:
proxy_base_url: https://ai.nuclide.systems
control_plane_url: https://ai.nuclide.systems
```
### In Karakeep (`.env`)
```bash
OPENAI_BASE_URL=https://ai.nuclide.systems/v1
```
### In LobeHub (`.env`)
```bash
APP_URL=https://chat.nuclide.systems
S3_ENDPOINT=https://s3.nuclide.systems
```
## Migration Checklist
- [ ] Add DNS entries to AdGuard
- [ ] Verify resolution: `dig ai.nuclide.systems`
- [ ] Update service configs to use domains
- [ ] Test HTTPS connectivity
- [ ] Remove old hosts file entries (if any)
- [ ] Document for team
## Notes
- AdGuard runs on **LXC 102** at `192.168.1.2`
- All AI services run on `192.168.1.40`
- Using same IP for all domains is intentional (single endpoint)
- SSL certs are issued by Zoraxy (public domain validation)
- DNS-only setup doesn't require HTTP proxy (Zoraxy still handles HTTPS)
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More