# 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 Emby (video, Tailscale-only) └── :3100 OpenClaw (AI assistant, 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 34. Rate limiting for brute force protection on Caddy ### Phase 8 — CI/CD for Dashboard 35. Add Gitea workflow to nas-tools: on push to main, build Docker image, push to Gitea registry 36. Tag images with git SHA + `latest`; keep previous image for rollback 37. Auto-deploy on NAS: pull new image, restart container 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 36. Add system stats to dashboard overview: CPU, RAM, disk usage 37. Container health checks in docker-compose files 38. Notification on failure (Telegram bot or email) ### Phase 11 — Polish 39. Light/dark theme toggle, loading states, error handling 40. Add Emby link to dashboard sidebar (port 8096) ## Media Paths - Music: `/volume1/music` (70GB, FLAC/WAV/DSD/ALAC) - Photos: `/volume1/photo` (186GB) + `/volume1/homes/zjgump/Photos` - Video: `/volume1/video` (759GB) - Emby: installed but unconfigured (port 8096, needs setup wizard) ## 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