Add Audiobookshelf, Stirling PDF, Vaultwarden to sidebar; update PLAN.md
Deploy Dashboard / deploy (push) Successful in 1m32s

This commit is contained in:
Gan, Jimmy
2026-02-22 20:13:39 +08:00
parent dd54c41646
commit 4310ae5fee
2 changed files with 42 additions and 22 deletions
+33 -22
View File
@@ -21,8 +21,12 @@ Public Internet → nas.jimmygan.com / music.jimmygan.com
├── :4533 Navidrome (music) ├── :4533 Navidrome (music)
├── :2283 Immich (photos, Tailscale-only) ├── :2283 Immich (photos, Tailscale-only)
├── :3300 Gitea (Tailscale-only) ├── :3300 Gitea (Tailscale-only)
├── :8096 Emby (video, Tailscale-only) ├── :8096 Jellyfin (video, Tailscale-only)
── :3100 OpenClaw (AI assistant, 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 Existing VPS (158.101.140.85): xray VLESS+REALITY on :443 — untouched
``` ```
@@ -86,7 +90,7 @@ cloud-monitor/
## Implementation Phases ## Implementation Phases
### Phase 1 — Provision new AMD VPS ### Phase 1 — Provision new AMD VPS
1. Add `oci-amd-monitor.py` to cloud-monitor repo: 1. Add `oci-amd-monitor.py` to cloud-monitor repo:
- Shape: `VM.Standard.E2.1.Micro` (1 OCPU, 1GB RAM, AMD free tier) - Shape: `VM.Standard.E2.1.Micro` (1 OCPU, 1GB RAM, AMD free tier)
- Display name: `free-amd-caddy` - Display name: `free-amd-caddy`
@@ -99,7 +103,7 @@ cloud-monitor/
6. Update Cloudflare DNS: `nas.jimmygan.com` and `music.jimmygan.com` → new VPS IP (set to "DNS only", let Caddy handle TLS) 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 7. Clean up stale DNS: `moldcost` → dead server1 (152.53.226.207) — remove or update
### Phase 2 — Dashboard MVP: Docker management ### Phase 2 — Dashboard MVP: Docker management
8. Create directory structure, Dockerfile, docker-compose.yml 8. Create directory structure, Dockerfile, docker-compose.yml
9. Add docker-socket-proxy (Tecnativa) to limit Docker API access 9. Add docker-socket-proxy (Tecnativa) to limit Docker API access
10. Backend: main.py + config.py + docker router (list, start/stop, logs) 10. Backend: main.py + config.py + docker router (list, start/stop, logs)
@@ -111,37 +115,37 @@ cloud-monitor/
16. Deploy on NAS, port 4000 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) 17. Configure Caddy on new VPS: `nas.jimmygan.com` → NAS :4000 via Tailscale (behind basic auth from Phase 1)
### Phase 3 — Gitea + Files + Terminal ### Phase 3 — Gitea + Files + Terminal
18. Add gitea router (repos, commits, trigger workflows) — needs Gitea API token 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 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 20. Add terminal router (WebSocket pty bridge + xterm.js) — blocked for non-Tailscale IPs
21. Dashboard overview page with links to all services 21. Dashboard overview page with links to all services
### Phase 4 — Navidrome (Music) ### Phase 4 — Navidrome (Music)
22. Deploy Navidrome Docker stack on port 4533 (with log rotation) 22. Deploy Navidrome Docker stack on port 4533 (with log rotation)
23. Point at `/volume1/music` 23. Point at `/volume1/music`
24. Link from dashboard sidebar 24. Link from dashboard sidebar
25. Configure Caddy: `music.jimmygan.com` → NAS :4533 via Tailscale 25. Configure Caddy: `music.jimmygan.com` → NAS :4533 via Tailscale
### Phase 5 — Immich (Photos) ### Phase 5 — Immich (Photos)
26. Deploy Immich Docker stack on port 2283 (disable ML/face recognition, with log rotation) 26. Deploy Immich Docker stack on port 2283 (disable ML/face recognition, with log rotation)
27. Point at `/volume1/photo` + `/volume1/homes/zjgump/Photos` 27. Point at `/volume1/photo` + `/volume1/homes/zjgump/Photos`
28. Link from dashboard sidebar (Tailscale-only) 28. Link from dashboard sidebar (Tailscale-only)
### Phase 6 — OpenClaw (AI Assistant) ### Phase 6 — OpenClaw (AI Assistant)
29. Deploy OpenClaw Docker stack on port 3100 (with log rotation) 29. Deploy OpenClaw Docker stack on port 3100 (with log rotation)
30. Configure Claude API as LLM provider 30. Configure Claude API as LLM provider
31. Configure messaging channels (Telegram, etc.) 31. Configure messaging channels (Telegram, etc.)
32. Link from dashboard sidebar (Tailscale-only) 32. Link from dashboard sidebar (Tailscale-only)
### Phase 7 — Authentication & Security ### Phase 7 — Authentication & Security
33. Upgrade dashboard auth from Caddy basic auth → session-based login + TOTP 2FA 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 34. ~~Rate limiting for brute force protection on Caddy~~ — Done (fail2ban + failed login Telegram alerts)
### Phase 8 — CI/CD for Dashboard ### Phase 8 — CI/CD for Dashboard
35. Add Gitea workflow to nas-tools: on push to main, build Docker image, push to Gitea registry 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`; keep previous image for rollback 36. ~~Tag images with git SHA + `latest`~~ — Done
37. Auto-deploy on NAS: pull new image, restart container 37. ~~Auto-deploy on NAS~~ — Done
38. Rollback: `docker compose pull <previous-tag> && docker compose up -d` 38. Rollback: `docker compose pull <previous-tag> && docker compose up -d`
### Phase 9 — Backup & Recovery ### Phase 9 — Backup & Recovery
@@ -154,21 +158,28 @@ cloud-monitor/
34. Backups stored to `/volume1/backups` 34. Backups stored to `/volume1/backups`
35. Periodic restore test to verify backups work 35. Periodic restore test to verify backups work
### Phase 10 — Monitoring & Alerts ### Phase 10 — Monitoring & Alerts (partial)
36. Add system stats to dashboard overview: CPU, RAM, disk usage 36. ~~Add system stats to dashboard overview: CPU, RAM, disk usage~~ — Done
37. Container health checks in docker-compose files 37. ~~Container health checks in docker-compose files~~ — Done
38. Notification on failure (Telegram bot or email) 38. Notification on failure (Telegram bot or email)
### Phase 11 — Polish ### Phase 11 — Polish (partial)
39. Light/dark theme toggle, loading states, error handling 39. ~~Light/dark theme toggle~~ — Done
40. Add Emby link to dashboard sidebar (port 8096) 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 ## Media Paths
- Music: `/volume1/music` (70GB, FLAC/WAV/DSD/ALAC) - Music: `/volume1/music` (70GB, FLAC/WAV/DSD/ALAC)
- Photos: `/volume1/photo` (186GB) + `/volume1/homes/zjgump/Photos` - Photos: `/volume1/photo` (186GB) + `/volume1/homes/zjgump/Photos`
- Video: `/volume1/video` (759GB) - Video: `/volume1/video` (759GB)
- Emby: installed but unconfigured (port 8096, needs setup wizard) - Emby: replaced by Jellyfin (port 8096)
## VPS Strategy ## VPS Strategy
@@ -26,6 +26,9 @@
{ label: "Jellyfin", port: 8096, icon: "play" }, { label: "Jellyfin", port: 8096, icon: "play" },
{ label: "Gitea Web", port: 3300, icon: "git" }, { label: "Gitea Web", port: 3300, icon: "git" },
{ label: "Speedtest", port: 8080, icon: "speedtest" }, { label: "Speedtest", port: 8080, icon: "speedtest" },
{ label: "Audiobookshelf", port: 13378, icon: "headphones" },
{ label: "Stirling PDF", port: 8030, icon: "pdf" },
{ label: "Vaultwarden", port: 8222, icon: "lock" },
]; ];
function extHref(svc) { function extHref(svc) {
@@ -111,6 +114,12 @@
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg> <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
{:else if svc.icon === "speedtest"} {:else if svc.icon === "speedtest"}
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg> <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
{:else if svc.icon === "headphones"}
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M3 18v-6a9 9 0 0118 0v6M3 18a3 3 0 003 3h0a3 3 0 003-3v-2a3 3 0 00-3-3h0a3 3 0 00-3 3v2zm18 0a3 3 0 01-3 3h0a3 3 0 01-3-3v-2a3 3 0 013-3h0a3 3 0 013 3v2z" /></svg>
{:else if svc.icon === "pdf"}
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
{:else if svc.icon === "lock"}
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
{/if} {/if}
</span> </span>
{svc.label} {svc.label}