Files
Gan, Jimmy dd64420de1
Run Tests / Secret Detection (pull_request) Failing after 6m45s
Run Tests / Backend Tests (pull_request) Failing after 3m2s
Run Tests / Frontend Tests (pull_request) Failing after 30s
feat: Phase 13 — off-site backup with rclone + crypt encryption
Adds cloud sync extension to the daily backup script using rclone
with client-side AES-256 encryption. Includes interactive setup
script for configuring OneDrive, Google Drive, or any rclone provider.

- scripts/setup-rclone.sh: interactive NAS rclone installer + config
- scripts/cloud-backup.sh: sourced by backup.sh when CLOUD_ENABLED=true
- backup.sh: cloud sync step runs after local backup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 01:21:41 +08:00

10 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  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/
  PLAN.md
  backup.sh                   # Daily backup cron script
  notify.sh                   # Telegram notification utility
  dashboard/
    docker-compose.yml
    Dockerfile
    .env.example
    backend/
      main.py
      config.py
      auth.py
      requirements.txt
      routers/
        auth.py
        docker.py
        gitea.py
        files.py
        terminal.py
        system.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

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 — Done (password + TOTP 2FA + passkey/WebAuthn login)
  2. Rate limiting for brute force protection on Caddy — Done (fail2ban + failed login Telegram alerts)

Phase 8 — CI/CD for Dashboard

  1. Add Gitea workflow to nas-tools: on push to main, auto-deploy — Done (Gitea Actions runs docker compose up -d on push)
  2. Tag images with git SHA + latest — Done
  3. Auto-deploy on NAS — Done
  4. Rollback: docker compose pull <previous-tag> && docker compose up -d

Phase 9 — Backup & Recovery

  1. Automated daily backup script — Done (Gitea DB, Immich DB, Gitea dump, docker configs + Vaultwarden, Paperless, n8n, Audiobookshelf, Calibre-web)
  2. Backups stored to /volume1/backups — Done (7-day retention)
  3. Telegram failure alerts — Done (sources creds from nas-dashboard .env)
  4. Periodic restore test to verify backups work

Phase 10 — Monitoring & Alerts

  1. Add system stats to dashboard overview: CPU, RAM, disk usage — Done
  2. Container health checks in docker-compose files — Done
  3. Notification on failure (Telegram bot) — Done (container monitor + failed login alerts)

Phase 11 — Polish

  1. Light/dark theme toggle — Done
  2. Add Jellyfin link to dashboard sidebar (port 8096) — Done (replaced Emby)

Phase 12 — Additional Services

  1. Deploy Audiobookshelf on port 13378 — Done, in dashboard sidebar
  2. Deploy Stirling PDF on port 8030 — Done, in dashboard sidebar
  3. Deploy Vaultwarden on port 8222 — Done, in dashboard sidebar
  4. Deploy Watchtower for auto-updates — Done (internal, no sidebar link needed)
  5. Deploy Speedtest on port 8080 — Done, in dashboard sidebar

Phase 14 — Hybrid AI Gateway (LiteLLM + cc-connect) 🚧

  1. Add litellm/ service scaffold (docker-compose.yml, config/litellm.yaml, .env.example) with localhost-only host binding and env-driven provider keys — Done
  2. Add dashboard backend LiteLLM operational probe (/api/litellm/health) and wire into protected routes — Done
  3. Add cc-connect/ templates (README.md, config.example.toml, optional compose) for mobile bridge rollout — Done
  4. Deploy LiteLLM image to NAS using server2 pull + stream load workflow to avoid slow GHCR pulls on NAS — Done
  5. Network fix: attach LiteLLM to nas-dashboard_internal and set dashboard-dev LITELLM_URL=http://litellm:4005 — Done
  6. Update backend health probe to support auth-required mode and configured health auth key — Done
  7. Add LiteLLM dashboard UI page with sidebar link and health status card — Done
  8. Add cc-connect dashboard UI page, sidebar link, and backend health/status endpoint — Done

Phase 13 — Off-site Backup

  1. Add cloud backup (OneDrive or Google Drive) for critical data — Done (rclone + crypt encryption)
  2. Encrypt backups before upload — Done (rclone crypt AES-256)
  3. Retention policy for cloud copies — Done (30-day configurable via env var)

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