Files
nas-tools/PLAN.md
T

7.9 KiB

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

  1. Create directory structure, Dockerfile, docker-compose.yml
  2. Add docker-socket-proxy (Tecnativa) to limit Docker API access
  3. Backend: main.py + config.py + docker router (list, start/stop, logs)
  4. Frontend: Svelte app with sidebar + Docker container view
  5. Terminal access control: backend middleware blocks /ws/terminal for non-Tailscale IPs (100.x.x.x)
  6. File browser: read-write (mount /volume1 as rw), with path traversal protection
  7. Add Docker log rotation to all compose files (max-size: 10m, max-file: 3)
  8. Build image on Mac, push to Gitea container registry
  9. Deploy on NAS, port 4000
  10. Configure Caddy on new VPS: nas.jimmygan.com → NAS :4000 via Tailscale (behind basic auth from Phase 1)

Phase 3 — Gitea + Files + Terminal

  1. Add gitea router (repos, commits, trigger workflows) — needs Gitea API token
  2. Add files router (browse /volume1, download, upload, delete) with path traversal protection
  3. Add terminal router (WebSocket pty bridge + xterm.js) — blocked for non-Tailscale IPs
  4. Dashboard overview page with links to all services

Phase 4 — Navidrome (Music)

  1. Deploy Navidrome Docker stack on port 4533 (with log rotation)
  2. Point at /volume1/music
  3. Link from dashboard sidebar
  4. Configure Caddy: music.jimmygan.com → NAS :4533 via Tailscale

Phase 5 — Immich (Photos)

  1. Deploy Immich Docker stack on port 2283 (disable ML/face recognition, with log rotation)
  2. Point at /volume1/photo + /volume1/homes/zjgump/Photos
  3. Link from dashboard sidebar (Tailscale-only)

Phase 6 — OpenClaw (AI Assistant)

  1. Deploy OpenClaw Docker stack on port 3100 (with log rotation)
  2. Configure Claude API as LLM provider
  3. Configure messaging channels (Telegram, etc.)
  4. Link from dashboard sidebar (Tailscale-only)

Phase 7 — Authentication & Security

  1. Upgrade dashboard auth from Caddy basic auth → session-based login + TOTP 2FA
  2. Rate limiting for brute force protection on Caddy

Phase 8 — CI/CD for Dashboard

  1. Add Gitea workflow to nas-tools: on push to main, build Docker image, push to Gitea registry
  2. Tag images with git SHA + latest; keep previous image for rollback
  3. Auto-deploy on NAS: pull new image, restart container
  4. Rollback: docker compose pull <previous-tag> && docker compose up -d

Phase 9 — Backup & Recovery

  1. Automated daily backup script for:
    • Gitea DB (pg_dump) + gitea-data
    • Immich DB + upload data
    • Navidrome config/DB
    • OpenClaw data
    • Dashboard .env and config
  2. Backups stored to /volume1/backups
  3. Periodic restore test to verify backups work

Phase 10 — Monitoring & Alerts

  1. Add system stats to dashboard overview: CPU, RAM, disk usage
  2. Container health checks in docker-compose files
  3. Notification on failure (Telegram bot or email)

Phase 11 — Polish

  1. Light/dark theme toggle, loading states, error handling
  2. 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