Skip to content

Commit 2bb341e

Browse files
committed
feat(excalidash): fix slow loads — preview-strip patch + R2 storage + zstd
ExcaliDash was painfully slow over the home's asymmetric uplink (0.5–2 Mbps after r8169 SG/TSO/GSO fix). Three issues found: 1. `/api/drawings` list returned 12.6 MB of inline SVG previews (issue #59). 2. Drawing files column stored 13.9 MB of base64 images in SQLite — every open/save shipped 2–18 MB over the tunnel (issue #145). 3. No response compression in Caddy. Changes: - caddy/Caddyfile: add `encode zstd gzip` to (common) → 4× smaller JSON. - excalidash-backend: switch to custom image `excalidash-backend:s3` built from PR ZimengXiong/ExcaliDash#163 (S3 image upload). Wired to Cloudflare R2 bucket `excalidraw` via `excalidraw-bucket.phanthawas.dev` custom domain so images load from CF edge, bypassing the home uplink. - excalidash/patches/drawings.js: bind-mounted override that drops `preview` from `summarySelect` in both `/drawings` list endpoints. Frontend lazy-fetches preview per card. List response 12.6 MB → ~2 KB. - .env.example: document R2/S3 env var schema (values live in box .env). - .gitignore / glance / hermes / amp / sync / conflux: bundled pre-existing local edits made during the same investigation. Existing 26.6 MB of base64 in SQLite migrated to R2 via one-shot script (reused backend's processFilesForS3 + rewritePreviewForS3). VACUUM after: DB 39 MB → 848 KB. Also fixed on box (config not in repo): - /etc/systemd/system/nic-offload.service: persistent ethtool -K enp3s0 sg/tso/gso on (r8169 ships these off, killing TCP uplink → 4× boost).
1 parent 6889864 commit 2bb341e

7 files changed

Lines changed: 1225 additions & 65 deletions

File tree

homelab/.env.example

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,48 @@ CLOUDFLARED_TOKEN=
44

55
# Beszel agent — paste public key from Beszel hub UI after first login
66
BESZEL_KEY=
7+
8+
# ─── Hermes Agent ──────────────────────────────────────────────
9+
# Upstream LLM API key (Open WebUI / vLLM). Treat as exposed if shared.
10+
HERMES_LLM_KEY=
11+
12+
# Telegram bot token from @BotFather. Leave empty to disable Telegram.
13+
HERMES_TELEGRAM_TOKEN=
14+
# Comma-separated Telegram user IDs allowed to message the bot.
15+
HERMES_TELEGRAM_USERS=
16+
17+
# Caddy basic_auth bcrypt hashes. Generate with:
18+
# docker run --rm caddy:2-alpine caddy hash-password --plaintext 'YOUR_PASS'
19+
# Escape every $ as $$ when pasting into .env (compose interpolation).
20+
HERMES_DASHBOARD_BCRYPT=
21+
SYNCTHING_BCRYPT=
22+
23+
# ─── Conflux API ───────────────────────────────────────────────
24+
# External app from ghcr.io/conflux-888/conflux-api
25+
CONFLUX_MONGODB_URI=
26+
CONFLUX_JWT_SECRET=
27+
CONFLUX_GEMINI_API_KEY=
28+
CONFLUX_ADMIN_USER=
29+
CONFLUX_ADMIN_PASSWORD=
30+
31+
# ─── Cloudflare R2 — Excalidash image storage (PR #163) ────────
32+
# Create via: CF Dashboard → R2 → Manage R2 API Tokens → Create
33+
# Permissions: Object Read & Write, scoped to bucket `excalidraw`.
34+
# Bucket: excalidraw (APAC, Standard)
35+
# Custom public domain: https://excalidraw-bucket.phanthawas.dev
36+
# CORS: allow https://draw.phanthawas.dev (PUT/GET/POST/DELETE/HEAD, * headers)
37+
R2_S3_ENDPOINT=
38+
R2_ACCESS_KEY_ID=
39+
R2_SECRET_ACCESS_KEY=
40+
CF_R2_API_TOKEN=
41+
CF_ACCOUNT_ID=
42+
43+
# PR #163 backend S3 vars (consumed by excalidash-backend with S3 patch):
44+
S3_BUCKET=excalidraw
45+
S3_REGION=auto
46+
S3_ENDPOINT=
47+
S3_PUBLIC_URL=https://excalidraw-bucket.phanthawas.dev
48+
S3_FORCE_PATH_STYLE=true
49+
S3_KEY_PREFIX=excalidash
50+
AWS_ACCESS_KEY_ID=
51+
AWS_SECRET_ACCESS_KEY=

homelab/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ komodo/mongo/
1212
komodo/periphery/
1313
yourls/db/
1414
excalidash/data/
15+
hermes/data/
16+
hermes/vault/
17+
hermes/skills/*/
18+
syncthing/config/

homelab/README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ ssh root@100.106.13.67 "cd /opt/homelab && docker compose up -d"
2626
| Vaultwarden | https://vault.phanthawas.dev |
2727
| Dozzle (Cloudflare Access) | https://logs.phanthawas.dev |
2828
| Komodo (UI only) | https://komodo.phanthawas.dev |
29-
| YOURLS | https://yo.phanthawas.dev/admin/ |
29+
| YOURLS | https://link.phanthawas.dev/admin/ |
3030
| ExcaliDash | https://draw.phanthawas.dev |
31+
| Hermes Agent (dashboard + TUI chat) | https://hermes.phanthawas.dev |
32+
| Syncthing | https://sync.phanthawas.dev |
3133

3234
## Config Sync to Box
3335

@@ -36,6 +38,7 @@ When editing `homelab/` files locally, sync via:
3638
```bash
3739
rsync -av --inplace --exclude='.env' --exclude='cloudflared/credentials.json' \
3840
--exclude='*/data/' --exclude='*/work/' --exclude='*/conf/' --exclude='*/db/' \
41+
--exclude='hermes/data/' --exclude='hermes/vault/' --exclude='syncthing/config/' \
3942
--exclude='komodo/' \
4043
./homelab/ root@100.106.13.67:/opt/homelab/
4144

@@ -56,5 +59,22 @@ homelab/
5659
│ ├── glance.yml ← dashboard config
5760
│ └── custom.css ← (unused, kept for future)
5861
├── dozzle/data/users.yml ← bcrypt'd creds
62+
├── hermes/
63+
│ ├── config.yaml ← Hermes Agent config (committed)
64+
│ ├── skills/ ← agent skills (committed, RW)
65+
│ ├── data/ ← gitignored runtime: sessions, memories, .env
66+
│ ├── vault/ ← gitignored Obsidian vault, synced via Syncthing
67+
│ └── SETUP.md ← bootstrap order
68+
├── syncthing/config/ ← gitignored Syncthing state
5969
└── <service>/data ← per-service runtime data (gitignored)
6070
```
71+
72+
## Hermes Agent (virtual assistant)
73+
74+
Self-hosted AI assistant + Obsidian "second brain". See `hermes/SETUP.md` for bootstrap order. Key bits:
75+
76+
- Upstream LLM: Open WebUI proxy at `ai.devops.nipa.cloud` → Gemma 4 31B AWQ-8bit (64K ctx)
77+
- Vault on box at `/opt/homelab/hermes/vault`, synced to Mac via Syncthing
78+
- Telegram bot, morning brief 07:00 Asia/Bangkok
79+
- Skills: `ingest-url`, `wiki-compile`, `wiki-lint`, `process-inbox`, `daily-brief`
80+
- Karpathy LLM-wiki pattern: agent owns `wiki/`, `Daily/`, `sources/`, `inbox/`. User edits `Work/`, `Personal/`, `People/`.

homelab/caddy/Caddyfile

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
}
77

88
(common) {
9+
encode zstd gzip
910
header {
1011
Strict-Transport-Security "max-age=31536000; includeSubDomains"
1112
X-Content-Type-Options nosniff
@@ -19,10 +20,10 @@ http://glance.phanthawas.dev {
1920
reverse_proxy localhost:8888
2021
}
2122

22-
# AdGuard Home admin UI
23+
# AdGuard Home admin UI (port 3030 — 3000 reserved for Hermes WhatsApp bridge)
2324
http://adguard.phanthawas.dev {
2425
import common
25-
reverse_proxy localhost:3000
26+
reverse_proxy localhost:3030
2627
}
2728

2829
# Beszel monitoring
@@ -43,14 +44,8 @@ http://logs.phanthawas.dev {
4344
reverse_proxy localhost:8095
4445
}
4546

46-
# Komodo — GitOps docker stack manager
47-
http://komodo.phanthawas.dev {
48-
import common
49-
reverse_proxy localhost:9120
50-
}
51-
5247
# YOURLS — short URL service
53-
http://yo.phanthawas.dev {
48+
http://link.phanthawas.dev {
5449
import common
5550
reverse_proxy localhost:8081
5651
}
@@ -61,6 +56,36 @@ http://draw.phanthawas.dev {
6156
reverse_proxy localhost:6767
6257
}
6358

59+
# Hermes Agent — dashboard (memory inspector, sessions, skills, TUI chat)
60+
http://hermes.phanthawas.dev {
61+
import common
62+
basic_auth {
63+
raws {$HERMES_DASHBOARD_BCRYPT}
64+
}
65+
reverse_proxy localhost:9119
66+
}
67+
68+
# AMP — game server panel (CubeCoders)
69+
http://amp.phanthawas.dev {
70+
import common
71+
reverse_proxy localhost:8080
72+
}
73+
74+
# Syncthing — vault sync UI
75+
http://sync.phanthawas.dev {
76+
import common
77+
basic_auth {
78+
raws {$SYNCTHING_BCRYPT}
79+
}
80+
reverse_proxy localhost:8384
81+
}
82+
83+
# Conflux API — external app deployed from ghcr
84+
http://conflux-api.phanthawas.dev {
85+
import common
86+
reverse_proxy localhost:8083
87+
}
88+
6489
# Add new services here:
6590
# http://<name>.phanthawas.dev {
6691
# import common

homelab/compose.yaml

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ services:
1717
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
1818
- ./caddy/data:/data
1919
- ./caddy/config:/config
20+
environment:
21+
HERMES_DASHBOARD_BCRYPT: ${HERMES_DASHBOARD_BCRYPT}
22+
SYNCTHING_BCRYPT: ${SYNCTHING_BCRYPT}
2023
restart: unless-stopped
2124

2225
cloudflared:
@@ -126,51 +129,6 @@ services:
126129
DOZZLE_AUTH_PROVIDER: simple
127130
restart: unless-stopped
128131

129-
komodo-mongo:
130-
image: mongo:7.0
131-
container_name: komodo-mongo
132-
restart: unless-stopped
133-
environment:
134-
MONGO_INITDB_ROOT_USERNAME: komodo
135-
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASS}
136-
volumes:
137-
- ./komodo/mongo:/data/db
138-
command: ["--quiet", "--wiredTigerCacheSizeGB", "0.25"]
139-
140-
komodo-core:
141-
image: ghcr.io/mbecker20/komodo:latest
142-
container_name: komodo-core
143-
restart: unless-stopped
144-
depends_on:
145-
- komodo-mongo
146-
ports:
147-
- "9120:9120"
148-
environment:
149-
KOMODO_DATABASE_ADDRESS: komodo-mongo:27017
150-
KOMODO_DATABASE_USERNAME: komodo
151-
KOMODO_DATABASE_PASSWORD: ${MONGO_PASS}
152-
KOMODO_PASSKEY: ${KOMODO_PASSKEY}
153-
KOMODO_WEBHOOK_SECRET: ${KOMODO_WEBHOOK_SECRET}
154-
KOMODO_HOST: https://komodo.phanthawas.dev
155-
KOMODO_TITLE: raws-homelab
156-
KOMODO_FIRST_SERVER: http://komodo-periphery:8120
157-
KOMODO_DISABLE_CONFIRM_DIALOG: "false"
158-
KOMODO_LOCAL_AUTH: "true"
159-
KOMODO_DISABLE_USER_REGISTRATION: "true"
160-
KOMODO_ENABLE_NEW_USERS: "true"
161-
162-
komodo-periphery:
163-
image: ghcr.io/mbecker20/periphery:latest
164-
container_name: komodo-periphery
165-
restart: unless-stopped
166-
environment:
167-
PERIPHERY_PASSKEYS: ${KOMODO_PASSKEY}
168-
PERIPHERY_ROOT_DIRECTORY: /etc/komodo
169-
volumes:
170-
- /var/run/docker.sock:/var/run/docker.sock
171-
- ./komodo/periphery:/etc/komodo
172-
- /proc:/proc:ro
173-
174132
yourls-db:
175133
image: mariadb:10
176134
container_name: yourls-db
@@ -196,24 +154,42 @@ services:
196154
YOURLS_DB_USER: yourls
197155
YOURLS_DB_PASS: ${YOURLS_DB_PASS}
198156
YOURLS_DB_NAME: yourls
199-
YOURLS_SITE: https://yo.phanthawas.dev
157+
YOURLS_SITE: https://link.phanthawas.dev
200158
YOURLS_USER: raws
201159
YOURLS_PASS: ${YOURLS_ADMIN_PASS}
202160

203161
excalidash-backend:
204-
image: zimengxiong/excalidash-backend:latest
162+
# Custom image built from PR #163 (S3 image upload) on box:
163+
# git clone -b feat/s3-image-upload https://github.com/OhYee/ExcaliDash.git /opt/build/excalidash-s3
164+
# cd /opt/build/excalidash-s3/backend && docker build -t excalidash-backend:s3 .
165+
# Rebuild on upstream merge: switch back to zimengxiong/excalidash-backend:<tag>
166+
image: excalidash-backend:s3
205167
container_name: excalidash-backend
206168
restart: unless-stopped
207169
environment:
208170
DATABASE_URL: file:/app/prisma/dev.db
209171
PORT: 8000
210172
NODE_ENV: production
211173
AUTH_MODE: local
212-
TRUST_PROXY: "false"
174+
TRUST_PROXY: "true"
175+
FRONTEND_URL: https://draw.phanthawas.dev
213176
JWT_SECRET: ${EXCALIDASH_JWT_SECRET}
214177
CSRF_SECRET: ${EXCALIDASH_CSRF_SECRET}
178+
# ─── S3 image storage (Cloudflare R2 via PR #163) ─────────
179+
S3_BUCKET: ${S3_BUCKET}
180+
S3_REGION: ${S3_REGION}
181+
S3_ENDPOINT: ${S3_ENDPOINT}
182+
S3_PUBLIC_URL: ${S3_PUBLIC_URL}
183+
S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE}
184+
S3_KEY_PREFIX: ${S3_KEY_PREFIX}
185+
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
186+
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
215187
volumes:
216188
- ./excalidash/data:/app/prisma
189+
# Patch: drop `preview` field from /drawings list summary (issue #59).
190+
# Preview is ~2-8 MB base64 SVG per drawing → 12 MB list responses.
191+
# Frontend lazy-fetches preview via DrawingCard. Re-apply on image upgrade.
192+
- ./excalidash/patches/drawings.js:/app/dist/routes/dashboard/drawings.js:ro
217193

218194
excalidash:
219195
image: zimengxiong/excalidash-frontend:latest
@@ -225,3 +201,61 @@ services:
225201
BACKEND_URL: excalidash-backend:8000
226202
ports:
227203
- "6767:80"
204+
205+
# ─── Hermes Agent ───
206+
# Hermes runs NATIVELY on the box, installed via the official script:
207+
# curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
208+
# Managed by systemd: hermes-gateway.service + hermes-dashboard.service.
209+
# Caddy proxies hermes.phanthawas.dev → localhost:9119.
210+
# See hermes/SETUP.md for install / migration steps.
211+
#
212+
# systemd drop-ins mirror stdout/stderr to /var/log/hermes-*.log so the
213+
# tail container below makes the logs visible in Dozzle. The drop-ins live
214+
# at /etc/systemd/system/hermes-{gateway,dashboard}.service.d/log-to-file.conf
215+
hermes-logs:
216+
image: alpine:latest
217+
container_name: hermes-logs
218+
command: tail -F /var/log/hermes-gateway.log /var/log/hermes-dashboard.log
219+
volumes:
220+
- /var/log/hermes-gateway.log:/var/log/hermes-gateway.log:ro
221+
- /var/log/hermes-dashboard.log:/var/log/hermes-dashboard.log:ro
222+
restart: unless-stopped
223+
224+
# ─── Conflux API — external app, ghcr image ───
225+
conflux-api:
226+
image: ghcr.io/conflux-888/conflux-api:dev-1.0.0.35
227+
container_name: conflux-api
228+
restart: unless-stopped
229+
ports:
230+
- "8083:8080"
231+
environment:
232+
OG_LEVEL: debug
233+
PORT: 8080
234+
MONGODB_URI: ${CONFLUX_MONGODB_URI}
235+
MONGODB_DATABASE: conflux
236+
JWT_SECRET: ${CONFLUX_JWT_SECRET}
237+
SYNC_INTERVAL_MINUTES: 15
238+
SYNC_INITIAL_DAYS_BACK: 30
239+
SYNC_MIN_FATALITIES: 1
240+
GEMINI_API_KEY: ${CONFLUX_GEMINI_API_KEY}
241+
SUMMARY_CHECK_INTERVAL_MIN: 30
242+
SUMMARY_BACKFILL_DAYS: 7
243+
CORS_ALLOW_LOCALHOST: "false"
244+
ADMIN_UI_ENABLED: "true"
245+
ADMIN_USER: ${CONFLUX_ADMIN_USER}
246+
ADMIN_PASSWORD: ${CONFLUX_ADMIN_PASSWORD}
247+
248+
# ─── Syncthing — vault sync between Mac and homelab ───
249+
syncthing:
250+
image: syncthing/syncthing:latest
251+
container_name: syncthing
252+
hostname: homelab
253+
network_mode: host # 8384 GUI, 22000 sync, 21027 discovery
254+
environment:
255+
PUID: "1000"
256+
PGID: "1000"
257+
TZ: Asia/Bangkok
258+
volumes:
259+
- ./syncthing/config:/var/syncthing/config
260+
- ./hermes/vault:/var/syncthing/Sync/hermes-vault
261+
restart: unless-stopped

0 commit comments

Comments
 (0)