# NAS Dashboard — Implementation Plan ## Context A modern, self-hosted web dashboard on your Synology DS224+ NAS (Tailscale-only) that serves as a single entry point to manage repos, Docker containers, files, terminal sessions, and media — plus an AI assistant. Replaces the need to SSH into the NAS for routine tasks. Public access via a dedicated Caddy reverse proxy VPS. ## Architecture ``` Public Internet → nas.jimmygan.com / music.jimmygan.com │ └── New AMD VPS (Caddy, HTTPS, Tailscale) │ └── Tailscale → NAS (100.78.131.124) ├── :4000 NAS Dashboard (FastAPI + Svelte) │ ├── /api/docker/* → Docker socket proxy (limited API) │ ├── /api/gitea/* → Gitea REST API │ ├── /api/files/* → /volume1 filesystem │ ├── /ws/terminal → WebSocket pty (Tailscale-only) │ └── /* → Svelte SPA ├── :4533 Navidrome (music) ├── :2283 Immich (photos, Tailscale-only) ├── :3300 Gitea (Tailscale-only) ├── :8096 Jellyfin (video, Tailscale-only) ├── :3100 OpenClaw (AI assistant, Tailscale-only) ├── :8080 Speedtest (Tailscale-only) ├── :13378 Audiobookshelf (audiobooks, Tailscale-only) ├── :8030 Stirling PDF (PDF tools, Tailscale-only) └── :8222 Vaultwarden (passwords, Tailscale-only) Existing VPS (158.101.140.85): xray VLESS+REALITY on :443 — untouched ``` ## Tech Stack (Dashboard) - Backend: Python FastAPI + uvicorn - Frontend: Svelte + Tailwind CSS (light theme default, with dark/light toggle) - Terminal: xterm.js + WebSocket pty bridge - Docker mgmt: via docker-socket-proxy (Tecnativa) for safety - Gitea: `httpx` async client - Build: Multi-stage Dockerfile (node → python-slim), built on Mac, pushed to Gitea container registry - LLM: Claude API (key from ~/.zshrc) ## File Structure ``` nas-tools/ dashboard/ docker-compose.yml # Dashboard + docker-socket-proxy Dockerfile .env.example backend/ main.py config.py requirements.txt routers/ docker.py gitea.py files.py terminal.py frontend/ package.json vite.config.js svelte.config.js src/ App.svelte lib/ api.js terminal.js routes/ Dashboard.svelte Docker.svelte Gitea.svelte Files.svelte Terminal.svelte components/ Sidebar.svelte ContainerCard.svelte LogViewer.svelte navidrome/ docker-compose.yml immich/ docker-compose.yml openclaw/ docker-compose.yml cloud-monitor/ oci-amd-monitor.py # New: grab AMD free-tier instance ``` ## Implementation Phases ### Phase 1 — Provision new AMD VPS ✅ 1. Add `oci-amd-monitor.py` to cloud-monitor repo: - Shape: `VM.Standard.E2.1.Micro` (1 OCPU, 1GB RAM, AMD free tier) - Display name: `free-amd-caddy` - AMD-compatible image (Ubuntu x86_64) - Same compartment, availability domain, subnet as existing VPS 2. Run script to grab AMD instance 3. Harden new VPS: SSH key-only, firewall (only 22/80/443), fail2ban, unattended-upgrades 4. Install Tailscale + Caddy on new VPS 5. Configure Caddy with HTTP basic auth immediately (placeholder until Phase 7 upgrades to 2FA) 6. Update Cloudflare DNS: `nas.jimmygan.com` and `music.jimmygan.com` → new VPS IP (set to "DNS only", let Caddy handle TLS) 7. Clean up stale DNS: `moldcost` → dead server1 (152.53.226.207) — remove or update ### Phase 2 — Dashboard MVP: Docker management ✅ 8. Create directory structure, Dockerfile, docker-compose.yml 9. Add docker-socket-proxy (Tecnativa) to limit Docker API access 10. Backend: main.py + config.py + docker router (list, start/stop, logs) 11. Frontend: Svelte app with sidebar + Docker container view 12. Terminal access control: backend middleware blocks `/ws/terminal` for non-Tailscale IPs (100.x.x.x) 13. File browser: read-write (mount `/volume1` as rw), with path traversal protection 14. Add Docker log rotation to all compose files (`max-size: 10m`, `max-file: 3`) 15. Build image on Mac, push to Gitea container registry 16. Deploy on NAS, port 4000 17. Configure Caddy on new VPS: `nas.jimmygan.com` → NAS :4000 via Tailscale (behind basic auth from Phase 1) ### Phase 3 — Gitea + Files + Terminal ✅ 18. Add gitea router (repos, commits, trigger workflows) — needs Gitea API token 19. Add files router (browse /volume1, download, upload, delete) with path traversal protection 20. Add terminal router (WebSocket pty bridge + xterm.js) — blocked for non-Tailscale IPs 21. Dashboard overview page with links to all services ### Phase 4 — Navidrome (Music) ✅ 22. Deploy Navidrome Docker stack on port 4533 (with log rotation) 23. Point at `/volume1/music` 24. Link from dashboard sidebar 25. Configure Caddy: `music.jimmygan.com` → NAS :4533 via Tailscale ### Phase 5 — Immich (Photos) ✅ 26. Deploy Immich Docker stack on port 2283 (disable ML/face recognition, with log rotation) 27. Point at `/volume1/photo` + `/volume1/homes/zjgump/Photos` 28. Link from dashboard sidebar (Tailscale-only) ### Phase 6 — OpenClaw (AI Assistant) ✅ 29. Deploy OpenClaw Docker stack on port 3100 (with log rotation) 30. Configure Claude API as LLM provider 31. Configure messaging channels (Telegram, etc.) 32. Link from dashboard sidebar (Tailscale-only) ### Phase 7 — Authentication & Security ✅ 33. ~~Upgrade dashboard auth from Caddy basic auth → session-based login + TOTP 2FA~~ — Done (password + TOTP 2FA + passkey/WebAuthn login) 34. ~~Rate limiting for brute force protection on Caddy~~ — Done (fail2ban + failed login Telegram alerts) ### Phase 8 — CI/CD for Dashboard ✅ 35. ~~Add Gitea workflow to nas-tools: on push to main, auto-deploy~~ — Done (Gitea Actions runs `docker compose up -d` on push) 36. ~~Tag images with git SHA + `latest`~~ — Done 37. ~~Auto-deploy on NAS~~ — Done 38. Rollback: `docker compose pull && docker compose up -d` ### Phase 9 — Backup & Recovery 33. Automated daily backup script for: - Gitea DB (pg_dump) + gitea-data - Immich DB + upload data - Navidrome config/DB - OpenClaw data - Dashboard .env and config 34. Backups stored to `/volume1/backups` 35. Periodic restore test to verify backups work ### Phase 10 — Monitoring & Alerts (partial) 36. ~~Add system stats to dashboard overview: CPU, RAM, disk usage~~ — Done 37. ~~Container health checks in docker-compose files~~ — Done 38. Notification on failure (Telegram bot or email) ### Phase 11 — Polish (partial) 39. ~~Light/dark theme toggle~~ — Done 40. ~~Add Jellyfin link to dashboard sidebar (port 8096)~~ — Done (replaced Emby) ### Phase 12 — Additional Services 41. Deploy Audiobookshelf on port 13378 — Done, add to dashboard sidebar 42. Deploy Stirling PDF on port 8030 — Done, add to dashboard sidebar 43. Deploy Vaultwarden on port 8222 — Done, add to dashboard sidebar 44. Deploy Watchtower for auto-updates — Done (internal, no sidebar link needed) 45. Deploy Speedtest on port 8080 — Done, already in dashboard sidebar ## Media Paths - Music: `/volume1/music` (70GB, FLAC/WAV/DSD/ALAC) - Photos: `/volume1/photo` (186GB) + `/volume1/homes/zjgump/Photos` - Video: `/volume1/video` (759GB) - Emby: replaced by Jellyfin (port 8096) ## VPS Strategy - Existing VPS (158.101.140.85): xray VLESS+REALITY on :443 — untouched - New AMD VPS: Caddy on :80/:443, Tailscale, reverse proxy to NAS - Both in Tokyo region, Oracle free tier ## DNS (Cloudflare) - `nas.jimmygan.com` → new AMD VPS IP (DNS only, Caddy handles TLS) - `music.jimmygan.com` → new AMD VPS IP (DNS only) - `moldcost.jimmygan.com` → remove (dead server1) - Existing `jimmygan.com` A record → keep as-is ## Build Strategy - Docker images built on Mac (faster than NAS Celeron) - Push to Gitea container registry (`100.78.131.124:3300`) - NAS pulls and runs images - CI/CD automates this via Gitea Actions ## Verification 1. Dashboard at `http://100.78.131.124:4000` — Docker page shows containers, start/stop works 2. Gitea page lists repos and commits 3. Files page browses /volume1 4. Terminal opens a working shell session 5. Navidrome at `:4533` — plays music from `/volume1/music` 6. Immich at `:2283` — browses photos 7. OpenClaw at `:3100` — responds via Claude API 8. `https://nas.jimmygan.com` — dashboard accessible from internet with login + 2FA 9. `https://music.jimmygan.com` — Navidrome accessible from internet 10. Backups run daily, restore tested 11. Dashboard shows system stats, alerts on disk/container issues