Compare commits
112 Commits
gitea-pages
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c94d88da34 | |||
| 40a02f33ef | |||
| ac55f25201 | |||
| 449e549a39 | |||
| 7dd501abe6 | |||
| ae20382cbe | |||
| 9cbd02e3ff | |||
| b26796e30c | |||
| c05f38d008 | |||
| f1ed462c91 | |||
| 24c7d56a1b | |||
| 17b6fc6fcb | |||
| a0f1707e33 | |||
| 080fe21329 | |||
| 80fca56658 | |||
| 97cce4cd83 | |||
| 643ee2cd36 | |||
| 24d99d18fc | |||
| fc6507abd2 | |||
| cf2aad887b | |||
| a2f1d49170 | |||
| bcbe9e7a88 | |||
| 0b4727ed58 | |||
| 4f17d99f55 | |||
| ff7bacdaf9 | |||
| 4429b37c49 | |||
| 03fd23b37b | |||
| 8d73ae3e30 | |||
| f65ceef90a | |||
| 07b21a7e2e | |||
| dcd8f36331 | |||
| 6c401f2376 | |||
| 550b54085c | |||
| b065c2b849 | |||
| 0600032b75 | |||
| fbc6469fc4 | |||
| 14b21199ee | |||
| 3d71824295 | |||
| defc788ab5 | |||
| 7e0082379e | |||
| 067e42c25d | |||
| 3819794812 | |||
| 13bc1ecc12 | |||
| 3638e368b1 | |||
| 3fa4608266 | |||
| a422bbfb13 | |||
| aab7f3a6c6 | |||
| 688f3cac45 | |||
| d76d766c0a | |||
| 7af18a82f1 | |||
| 710c8afe3c | |||
| 829dd8f339 | |||
| 8aa807bb94 | |||
| e160e3dba8 | |||
| 895658a115 | |||
| 14137c32a7 | |||
| 10e138b677 | |||
| f8d8ee3ac0 | |||
| 1a68be98e4 | |||
| f441e8b762 | |||
| 3de4d5db63 | |||
| 7927390359 | |||
| c3fd649f68 | |||
| 4c97732720 | |||
| ff398a592f | |||
| 96df6e7bd0 | |||
| dc7ceab724 | |||
| 474af687af | |||
| 3d114c6873 | |||
| b2cb123414 | |||
| 80647c1a6b | |||
| 0979f71857 | |||
| a8ed4350f0 | |||
| 644ba72d77 | |||
| c7529bd867 | |||
| 6309286b96 | |||
| c3c40dc07b | |||
| 25da11b53a | |||
| 396aa0e48e | |||
| 9ab9033ab0 | |||
| 6aa55fdf2d | |||
| 8ccf76b37c | |||
| 377f20a817 | |||
| dd806f86b8 | |||
| 05ad2b7d26 | |||
| b090b4a4a8 | |||
| 3881f95e28 | |||
| f86135f683 | |||
| 811bc1af94 | |||
| 143343decf | |||
| e0199042c1 | |||
| 6e46b894cb | |||
| c2979f7c9d | |||
| b0160322b3 | |||
| 0e50996b43 | |||
| 0d83f83bc0 | |||
| 0def5c0be9 | |||
| 52db8ce9ed | |||
| 0baf283678 | |||
| ad0fdd2beb | |||
| 630c5e0ae5 | |||
| 333e3c99cd | |||
| 9f6d0dd195 | |||
| 7ef5aea9db | |||
| 061f09b2d2 | |||
| d63e0df647 | |||
| 4728fd096f | |||
| c163a7905e | |||
| e7e62d6bb3 | |||
| caf133f4a7 | |||
| 99f93b585f | |||
| 04d54ac9b0 |
@@ -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.*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+244
@@ -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 30–50% 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 101–143 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.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||||
@@ -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 90–300 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 1–2.
|
||||||
|
- **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
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
-16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-18
@@ -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")}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
@@ -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-Za-zA-Z0-90-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")}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-1
@@ -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")}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-1
@@ -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-Za-zA-Z0-90-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
@@ -1 +0,0 @@
|
|||||||
module.exports=require("./lunr.ja");
|
|
||||||
-1
@@ -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")}});
|
|
||||||
-1
File diff suppressed because one or more lines are too long
@@ -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))}}}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-18
@@ -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")}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
-1
@@ -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
@@ -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
@@ -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-Za-zA-Z0-90-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
@@ -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
@@ -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("|")}}});
|
|
||||||
-18
File diff suppressed because one or more lines are too long
-1
@@ -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
@@ -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")}});
|
|
||||||
@@ -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-Za-zA-Z]":"A",
|
|
||||||
"[0-90-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,"1":-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,"1":-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,"1":-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
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
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"}
|
|
||||||
@@ -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
@@ -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
@@ -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** (≈ 8–16 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 1–5 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 **1–5 kgCO2e**. Datacenter PUE and
|
||||||
|
exact model size dominate the uncertainty.
|
||||||
|
- **Homelab runtime** (the larger ongoing footprint): the NUC 14 Pro draws
|
||||||
|
~15–45 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
@@ -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
@@ -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: ~29–30 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
@@ -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
@@ -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
@@ -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 ~200–400 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: ~10–20 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 200–500 completions/day. Max's 5-hour windows reset 4–5× 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 (1–2 persistent processes) using `--interactive` + message passing, or accept the 200–400 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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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 (10000–10999)
|
||||||
|
|
||||||
|
| 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 (11000–11999)
|
||||||
|
|
||||||
|
| Port | Container | Public URL | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 11001 | `vaultwarden` | `https://vault.nuclide.systems` | Password manager (OIDC client created, SSO not yet wired) |
|
||||||
|
|
||||||
|
### Media — Immich (12000–12999)
|
||||||
|
|
||||||
|
| 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 (13000–13999)
|
||||||
|
|
||||||
|
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 (14000–14999, 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 (15000–15999)
|
||||||
|
|
||||||
|
| 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 (16000–16999)
|
||||||
|
|
||||||
|
| Port | Container | Public URL | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 16000 | `n8n` | `https://n8n.nuclide.systems` | Workflow automation (OIDC `33135ad4`); SQLite local disk |
|
||||||
|
|
||||||
|
### Notes & Bookmarks (17000–17999)
|
||||||
|
|
||||||
|
| 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 (20000–20999)
|
||||||
|
|
||||||
|
| 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
@@ -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
@@ -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) |
|
||||||
|
| 10000–10999 | Infrastructure |
|
||||||
|
| 11000–11999 | Security & Auth |
|
||||||
|
| 12000–12999 | Media – Immich |
|
||||||
|
| 13000–13999 | Media – Downloads / Arr |
|
||||||
|
| 14000–14999 | AI Stack |
|
||||||
|
| 15000–15999 | Documents |
|
||||||
|
| 16000–16999 | Automation |
|
||||||
|
| 17000–17999 | Notes & Bookmarks |
|
||||||
|
| 18000–18999 | DevOps / Image Gen |
|
||||||
|
| 19000–19999 | Tracking |
|
||||||
|
| 20000–20999 | Storage Admin |
|
||||||
|
| 30000–30999 | 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 (10000–10999)
|
||||||
|
|
||||||
|
| 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 (11000–11999)
|
||||||
|
|
||||||
|
| 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 (12000–12999)
|
||||||
|
|
||||||
|
| 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 (13000–13999)
|
||||||
|
|
||||||
|
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 (14000–14999)
|
||||||
|
|
||||||
|
| 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 (15000–15999)
|
||||||
|
|
||||||
|
| 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 (16000–16999)
|
||||||
|
|
||||||
|
| 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 (17000–17999)
|
||||||
|
|
||||||
|
| 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 (18000–18999)
|
||||||
|
|
||||||
|
| 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 (19000–19999)
|
||||||
|
|
||||||
|
_Traccar moved to 15000–15001 (Documents range). 19000–19001 now free._
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage Admin (20000–20999)
|
||||||
|
|
||||||
|
| 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
@@ -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 | **6–22 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` | ~7–30 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
@@ -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
@@ -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
@@ -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: 1–2 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/>
|
||||||
|
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 `` — 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
@@ -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: 1–2 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/>
|
||||||
|
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 `` — 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
@@ -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
@@ -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
Reference in New Issue
Block a user