Files
docs/CHANGELOG.md
T
fkrebs ac55f25201 incident: CT 104 root full → Immich down + follow-ups
CT 104 root FS hit 200/200 GB. Postgres + Valkey couldn't write,
Immich down. Recovered with docker prune (89 GB freed) + daemon
restart + compose recreate. Added filesystem alerts on CT 109
(Prometheus rules + Grafana alert routing to existing Gotify contact
point). Top remaining disk consumers documented: mcp-crawl4ai
writable layer 40.7 GB, Garage 82 GB, arr-stack 27 GB.
2026-07-15 09:08:38 +02:00

29 KiB
Raw Blame History

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 downcount(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 cps ZIP to host, scp's ZIP + signing keys to /mnt/pve/unas/services/pocketid-backup/. services-backup-plan snapshots staging → jottacloud. Verified: hook fires before snapshot, snapshot includes Pocket-ID data.

  • Vaultwarden data → local zfs: Moved /mnt/pve/unas/services/vaultwarden to /opt/stacks/vaultwarden/data (local NVMe). DB was already on CT 113 postgres; only rsa_key.pem, config.json, attachments/ remain in the data dir. Stale db.sqlite3 deleted. NFS copy at services/vaultwarden/ is now orphaned and can be cleaned up.

  • WAL-G backup script fixed (CT 104): All nightly postgres backups were failing silently. Three bugs fixed: (1) set -e causing script abort when shared-postgres container not found (it was migrated to CT 113); (2) missing -u postgres on all docker exec calls — WAL-G requires the postgres role; (3) missing explicit data path /var/lib/postgresql/data on backup-push. shared-postgres commented out (CT 113 has its own WAL-G cron). immich_postgres and lobe-postgres now back up successfully to Garage S3.

  • apps/ directory decommissioned (CT 104): /opt/stacks/apps/ contained dead duplicate compose files for gotify, karakeep, memos, paperless-ngx, traccar, vaultwarden, shared-db — all without .env files, never running. Renamed to .DECOMMISSIONED-2026-05-22 following established pattern. The canonical stacks at /opt/stacks/<service>/ are unaffected.

  • ComfyUI --async-offload removed: Flag caused full CPU fallback on GGUF img2img with XPU (3.5 min/step instead of ~7 s/step) and pure-noise output. Removed from ai/comfyui.yml. Current args: --listen 0.0.0.0 --enable-cors-header --use-pytorch-cross-attention --disable-smart-memory --force-fp16. See infra/proxmox-memory-audit.md.

  • CT 105 Nextcloud CIFS → NFS: Nextcloud was crash-looping ("Appdata is not present") because unas_smb CIFS mount (//192.168.1.31/storage) was not mounted on the host. Root cause: fstab entry was missing _netdev; mount dropped after a host event and was never re-established. Fixed by migrating CT 105 mp0 from /mnt/pve/unas_smb to /mnt/pve/unas (same NFS share all other CTs use). CIFS entry removed from fstab. All 9 Nextcloud containers healthy post-restart.

  • arr stack started: vpn_gluetun, shelfarr (:13004), rdtclient (via VPN, :13001), prowlarr (:13002), audiobookshelf (:13003). Fixed empty labels: in VPN compose.

  • claude-max-bridge: new service on CT 104 (ai-internal, no host port). FastAPI shim wrapping claude CLI as an OpenAI-compatible /v1/chat/completions endpoint. Enables LiteLLM to route to Claude Max subscription at zero API cost. Key details: --strict-mcp-config (no MCP servers), --input-format stream-json (multi-turn), --include-partial-messages (real streaming deltas). Bind-mounts /usr/local/bin/claude + /root/.claude (rw for OAuth token refresh). Source at git.nuclide.systems/fkrebs/claude-max-bridge.

  • LiteLLM Claude models: claude-sonnet-4-6, claude-opus-4-7, claude-haiku-4-5 + short aliases (sonnet, opus, haiku) + effort variants (claude-sonnet-4-6-high, claude-opus-4-7-high) added via openai/ provider pointing at claude-max-bridge:8000/v1. Priced at Anthropic list rates for cost-visibility — actual cost $0 (Max). Bridge features: temperature→effort mapping, extra_body.effort override, extra_body.fallback_model, x_claude_cost_usd and x_claude_rate_limit in responses.

  • claude-max-bridge v2: additional CLI parameter mappings: extra_body.system_prompt_mode (replace/append--system-prompt/--append-system-prompt), extra_body.max_turns--max-turns, extra_body.max_budget_usd--max-budget-usd, response_format.json_schema--json-schema, extra_body.exclude_dynamic_system_prompt_sections--exclude-dynamic-system-prompt-sections. Build context moved from /tmp/ to /opt/stacks/ai/claude-max-bridge/ (survives reboots). Source committed to Gitea at a781abb.

  • Shepard MongoDB pinned to 8.0.4: mongo:8.0 (latest) introduced a fatal kernel-version check in 8.0.5+ that rejects Proxmox's 7.0.2-5-pve kernel string (parsed as major version 7 ≥ 6.19). Pinned to mongo:8.0.4 in /opt/shepard/infrastructure/docker-compose.yml. Track MongoDB bug SERVER-121912 for a patched upstream release before unpinning.

  • ComfyUI XPU optimisation: removed --lowvram + --reserve-vram 1.0; added --async-offload (Intel-patched XPU streams, ~10% speedup) + --force-fp16; memory limit 20 G → 24 G (FLUX peaks ~22 GB in shared XPU RAM). --disable-smart-memory kept since get_free_memory() returns the full 58 GB shared pool on Arc — smart eviction is blind to the cgroup limit. See infra/proxmox-memory-audit.md.

  • WaveSpeed FBC workflow: ApplyFBCacheOnModel(residual_diff_threshold=0.12) + FluxGuidance(guidance=3.5) + BasicGuider + SamplerCustomAdvanced added to FLUX.1-schnell pipeline. Workflow saved at /mnt/pve/unas/services/comfyui/input/flux-schnell-wavespeed.json. Estimated 3050% additional speedup by caching first-block activations. WaveSpeed FBCache confirmed device-agnostic (no CUDA guards); torch.compile node must NOT be used on XPU.

  • LiteLLM benchmark: bench.py at /opt/stacks/ai/benchmark/ measures TTFT + TPS for all configured models using streaming requests; renders xychart diagrams via internal Kroki (http://kroki:8000). First run results: Mistral fastest TTFT (43 ms), Claude bridge competitive at 101143 ms. See services/llm-benchmark.md.

  • MCP gateway: added git, gitlab (DLR, --pass-environment fix for mcp-proxy env passthrough bug), paper-search (Docker catalog image replaces inline python:3.12-slim); UNPAYWALL_EMAIL wired into paper-search. Removed duplicate papersearch container. Updated services/mcp-gateway.md.

  • Nextcloud MCP re-enabled: cbcoutinho/nextcloud-mcp-server running in single_user_basic mode with NEXTCLOUD_VERIFY_SSL=false. App-password generated non-interactively via occ user:add-app-password. Gateway patched: (1) NEXTCLOUD_VERIFY_SSL / MCP_DEPLOYMENT_MODE now baked into hardcoded SERVERS env; (2) startup overlay extended to persist enabled flag; (3) proxy route bypasses per-user credential provisioning when MCP_DEPLOYMENT_MODE=single_user_basic. 26 servers healthy.

  • CT 109 renamed observeops: 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 fixedEnableWebsocketCustomHeaders, 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 logins 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.