Home Server Admin Dashboard
Section titled “Home Server Admin Dashboard”NutsNews includes a protected admin dashboard for monitoring the home server that runs local AI.
Dashboard route:
/admin/home-serverStats source:
GET https://ai.nutsnews.com/statsHeader: x-nutsnews-ai-key: <HOME_SERVER_STATS_API_KEY>The dashboard is designed for owner/admin use only. The browser never receives the API key. The Next.js admin page fetches stats server-side using server-only environment variables.
Related monitoring:
docs/GRAFANA_BACKUP_MONITORING.mdUse Grafana Cloud for backup time-series metrics such as last backup success, last successful backup time, backup age, available backup count, and next scheduled backup time. Use /admin/home-server for live instance/service health.
What the Dashboard Shows
Section titled “What the Dashboard Shows”The first version focuses on instance-level health and the local AI runtime:
Server identity:- hostname- OS platform- CPU architecture- Linux kernel- server uptime- local AI service process start time
CPU:- CPU model- CPU thread count- 1-minute load average- 5-minute load average- 15-minute load average- normalized load percentage
Memory:- total memory- used memory- available memory- memory usage percentage- swap total/free/used
Disk:- root filesystem- root mount point- total root disk size- used root disk size- available root disk size- disk usage percentage
Services:- ollama- nutsnews-local-ai- cloudflared
Local AI runtime:- service port- Ollama URL- default model- request timeout- max article character cap- model keep-alive- context size- output token cap- temperature
Ollama:- Ollama HTTP health- installed models- model sizes- model modified time
Node process:- Node.js version- process ID- process uptime- process memory usageArchitecture
Section titled “Architecture”NutsNews admin user ↓ browser session/admin/home-server ↓ server-side Next.js fetchHOME_SERVER_STATS_URL + HOME_SERVER_STATS_API_KEY ↓ HTTPS + x-nutsnews-ai-keyhttps://ai.nutsnews.com/stats ↓ Cloudflare Tunnelhome server: chingadera ↓ localhostlocal-ai-service /stats ↓ local system files + systemctl + Ollama tagsCPU / memory / disk / services / modelsThe public browser request only reaches the protected NutsNews admin page. The sensitive stats API key stays in server-side environment variables.
Files Added or Updated
Section titled “Files Added or Updated”local-ai-service/server.mjslocal-ai-service/.env.exampleweb/lib/adminHomeServer.tsweb/app/admin/(protected)/home-server/page.tsxweb/app/admin/(protected)/page.tsxlocal-ai-service/server.mjs
Section titled “local-ai-service/server.mjs”Adds:
GET /statsThe stats route:
- Requires
x-nutsnews-ai-key. - Uses the same secret as
/review. - Returns JSON only.
- Does not expose shell access.
- Reads basic Linux health data.
- Checks critical systemd services.
- Checks Ollama model availability.
web/lib/adminHomeServer.ts
Section titled “web/lib/adminHomeServer.ts”Adds the server-side helper used by the admin page.
It reads:
HOME_SERVER_STATS_URLHOME_SERVER_STATS_API_KEYFallback behavior:
HOME_SERVER_STATS_URL defaults to https://ai.nutsnews.com/statsHOME_SERVER_STATS_API_KEY can fall back to LOCAL_AI_API_KEY if presentThe helper normalizes the stats payload before the admin page renders it.
web/app/admin/(protected)/home-server/page.tsx
Section titled “web/app/admin/(protected)/home-server/page.tsx”Adds the protected dashboard page.
The page is dynamic and runs on Node.js:
export const dynamic = "force-dynamic";export const runtime = "nodejs";This keeps the stats fresh and allows secure server-side environment access.
web/app/admin/(protected)/page.tsx
Section titled “web/app/admin/(protected)/page.tsx”Adds a new live admin card:
Home Server → /admin/home-serverLocal AI Service /stats Endpoint
Section titled “Local AI Service /stats Endpoint”Request
Section titled “Request”LOCAL_AI_API_KEY="$(ssh rami@192.168.1.115 "grep '^LOCAL_AI_API_KEY=' /opt/nutsnews/local-ai-service/.env | cut -d= -f2-")"
curl -s https://ai.nutsnews.com/stats \ -H "x-nutsnews-ai-key: $LOCAL_AI_API_KEY" \ | python3 -m json.toolSuccessful Response Example
Section titled “Successful Response Example”{ "ok": true, "service": "nutsnews-local-ai-service", "server": { "hostname": "chingadera", "platform": "linux", "arch": "x64", "kernel": "7.0.0-22-generic", "uptimeSeconds": 9778 }, "cpu": { "model": "AMD Ryzen 7 5800H with Radeon Graphics", "threads": 16, "loadAverage": { "oneMinute": 0.47, "fiveMinute": 0.28, "fifteenMinute": 0.27, "normalizedOneMinutePercent": 3 } }, "memory": { "totalBytes": 63092441088, "usedBytes": 1207713792, "availableBytes": 61884727296, "usagePercent": 2 }, "disk": { "filesystem": "/dev/nvme0n1p2", "mount": "/", "totalBytes": 1966736678912, "usedBytes": 20122255360, "availableBytes": 1846634172416, "usagePercent": 2 }, "services": [ { "name": "ollama", "active": true, "status": "active" }, { "name": "nutsnews-local-ai", "active": true, "status": "active" }, { "name": "cloudflared", "active": true, "status": "active" } ], "localAi": { "defaultModel": "qwen2.5:3b", "maxArticleChars": 3000, "keepAlive": "30m", "numCtx": 2048, "numPredict": 180, "temperature": 0 }, "ollama": { "ok": true, "status": 200, "models": [ { "name": "qwen2.5:3b", "size": 1929912432 } ] }}Required Environment Variables
Section titled “Required Environment Variables”Local development
Section titled “Local development”File:
web/.env.localRequired values:
HOME_SERVER_STATS_URL=https://ai.nutsnews.com/statsHOME_SERVER_STATS_API_KEY=<same value as LOCAL_AI_API_KEY on the home server>Safe local update command:
cd /Users/ramideltoro/WebstormProjects/nutsnews2/web
export HOME_SERVER_STATS_API_KEY="$(ssh rami@192.168.1.115 "grep '^LOCAL_AI_API_KEY=' /opt/nutsnews/local-ai-service/.env | cut -d= -f2-")"
python3 <<'PY'from pathlib import Pathimport os
env_path = Path(".env.local")existing = env_path.read_text().splitlines() if env_path.exists() else []
updates = { "HOME_SERVER_STATS_URL": "https://ai.nutsnews.com/stats", "HOME_SERVER_STATS_API_KEY": os.environ["HOME_SERVER_STATS_API_KEY"],}
kept = []seen = set()
for line in existing: if "=" not in line or line.strip().startswith("#"): kept.append(line) continue
key = line.split("=", 1)[0] if key in updates: kept.append(f"{key}={updates[key]}") seen.add(key) else: kept.append(line)
for key, value in updates.items(): if key not in seen: kept.append(f"{key}={value}")
env_path.write_text("\n".join(kept).rstrip() + "\n")PY
grep -E '^HOME_SERVER_STATS_' .env.local | sed 's/^HOME_SERVER_STATS_API_KEY=.*/HOME_SERVER_STATS_API_KEY=<hidden>/'Do not commit .env.local.
Production on Vercel
Section titled “Production on Vercel”Add both as Production environment variables:
HOME_SERVER_STATS_URL=https://ai.nutsnews.com/statsHOME_SERVER_STATS_API_KEY=<same value as LOCAL_AI_API_KEY on the home server>Using Vercel CLI:
cd /Users/ramideltoro/WebstormProjects/nutsnews2/web
vercel env add HOME_SERVER_STATS_URL productionvercel env add HOME_SERVER_STATS_API_KEY productionWhen entering HOME_SERVER_STATS_API_KEY, paste the secret value only into Vercel. Do not paste it into Git, docs, chat, or logs.
Deploy the Updated Local AI Service
Section titled “Deploy the Updated Local AI Service”The dashboard requires the updated local AI service because /stats lives in local-ai-service/server.mjs.
From the Mac:
cd /Users/ramideltoro/WebstormProjects/nutsnews2
rsync -av local-ai-service/ rami@192.168.1.115:/opt/nutsnews/local-ai-service/
ssh -t rami@192.168.1.115 "cd /opt/nutsnews/local-ai-service && npm install && sudo systemctl restart nutsnews-local-ai && sleep 3 && sudo systemctl status nutsnews-local-ai --no-pager"Then verify:
LOCAL_AI_API_KEY="$(ssh rami@192.168.1.115 "grep '^LOCAL_AI_API_KEY=' /opt/nutsnews/local-ai-service/.env | cut -d= -f2-")"
curl -s https://ai.nutsnews.com/stats \ -H "x-nutsnews-ai-key: $LOCAL_AI_API_KEY" \ | python3 -m json.toolLocal Dashboard Test
Section titled “Local Dashboard Test”From the web app:
cd /Users/ramideltoro/WebstormProjects/nutsnews2/webnpm run devOpen:
http://localhost:3000/admin/home-serverExpected dashboard cards:
Hostname: chingaderaCPU Threads: 16Memory UsedDisk Usedollama: activenutsnews-local-ai: activecloudflared: activeDefault Model: qwen2.5:3bValidation Commands
Section titled “Validation Commands”Before committing:
cd /Users/ramideltoro/WebstormProjects/nutsnews2
node --check local-ai-service/server.mjs
cd webnpx tsc --noEmitnpm run buildA successful build should include:
/admin/home-serverCommit Checklist
Section titled “Commit Checklist”Do commit:
local-ai-service/.env.examplelocal-ai-service/server.mjsweb/lib/adminHomeServer.tsweb/app/admin/(protected)/home-server/page.tsxweb/app/admin/(protected)/page.tsxdocs/HOME_SERVER_DASHBOARD.mddocs/HOME_SERVER_LOCAL_AI.mddocs/README.mdREADME.mdDo not commit:
web/.env.localreal HOME_SERVER_STATS_API_KEY valuereal LOCAL_AI_API_KEY valueprivate SSH keysCloudflare API tokensTroubleshooting
Section titled “Troubleshooting”The dashboard says Missing HOME_SERVER_STATS_API_KEY
Section titled “The dashboard says Missing HOME_SERVER_STATS_API_KEY”Add the server-side environment variable to local .env.local or Vercel Production env vars.
The dashboard says Unauthorized
Section titled “The dashboard says Unauthorized”The value in HOME_SERVER_STATS_API_KEY does not match LOCAL_AI_API_KEY on the home server.
Fix by copying the current home-server key into the web environment variable.
https://ai.nutsnews.com/stats returns 404
Section titled “https://ai.nutsnews.com/stats returns 404”The updated local-ai-service/server.mjs has not been copied to the home server or the service has not been restarted.
Run:
rsync -av /Users/ramideltoro/WebstormProjects/nutsnews2/local-ai-service/ \ rami@192.168.1.115:/opt/nutsnews/local-ai-service/
ssh -t rami@192.168.1.115 "sudo systemctl restart nutsnews-local-ai"cloudflared is inactive
Section titled “cloudflared is inactive”Check the Cloudflare Tunnel service on the server:
ssh rami@192.168.1.115systemctl status cloudflared --no-pagerIf the tunnel is down, the Worker and admin dashboard cannot reach ai.nutsnews.com even if the local service is running.
Memory or disk usage is high
Section titled “Memory or disk usage is high”Use:
ssh rami@192.168.1.115htopdf -h /free -hThe dashboard is a fast warning signal. Use SSH for deeper investigation.
Security Notes
Section titled “Security Notes”The stats endpoint reveals operational details about the home server, so it must remain protected.
Current protections:
/stats requires x-nutsnews-ai-keylocal service listens only on 127.0.0.1public access goes through Cloudflare Tunneladmin dashboard requires Google-protected NutsNews admin authstats API key is read server-side onlyDo not make /stats public without authentication.
Future Improvements
Section titled “Future Improvements”Possible future dashboard additions:
- CPU temperature.
- GPU/ROCm utilization if available.
- Ollama request queue depth.
- Last local AI review latency from Supabase.
- Historical charts stored in Supabase.
- Alerts when
ollama,cloudflared, ornutsnews-local-aigoes inactive. - Grafana Cloud backup freshness alerts.
- Better Stack heartbeat from the home server.
