diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..1155bee --- /dev/null +++ b/PLAN.md @@ -0,0 +1,205 @@ +# 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