diff --git a/README.md b/README.md index 5176d536..2f36c55a 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ Two shortcuts will appear on your desktop: - **Dental App** — starts the app. Double-clicking it opens: - A terminal running `npm run dev` (Backend + Frontend + Python services) - A terminal running the Selenium service -- **Stop Dental App** — stops all services and frees all ports (5000, 5001, 5002, 5003, 3000/3001) +- **Stop Dental App** — stops all services and frees all ports (5000, 5001, 5002, 5003, 3000/3001, and the rclone backup server on 8080) > No username or path editing needed — the script automatically detects the current user's home folder. diff --git a/apps/Backend/src/services/rcloneService.ts b/apps/Backend/src/services/rcloneService.ts index 09c49681..1b456b6b 100644 --- a/apps/Backend/src/services/rcloneService.ts +++ b/apps/Backend/src/services/rcloneService.ts @@ -35,7 +35,7 @@ export function isServerRunning(): boolean { return serverProcess !== null && serverProcess.exitCode === null; } -// Source PC: rclone serve webdav ./backups --addr :8080 --user MyDentalApp --pass SuperSecret!@2026 +// Source PC: rclone serve webdav . --include "/backups/**" ... --addr :8080 --user MyDentalApp --pass SuperSecret!@2026 export async function startWebDavServer(): Promise { if (isServerRunning()) return; @@ -46,16 +46,22 @@ export async function startWebDavServer(): Promise { const config = readRcloneConfig(); - // Serve only the backups folder — never the app root, which contains - // .env (DB/Twilio/AI credentials) and the full source tree. - const backupsDir = path.join(APP_ROOT, "backups"); - if (!fs.existsSync(backupsDir)) { - fs.mkdirSync(backupsDir, { recursive: true }); + for (const folder of SYNC_FOLDERS) { + const dir = path.join(APP_ROOT, folder); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } } + // Serve the app root but filter it down to SYNC_FOLDERS only — the root + // also contains .env (DB/Twilio/AI credentials) and the full source tree, + // which must never be exposed. Any path outside these folders returns 404. + const includeArgs = SYNC_FOLDERS.flatMap((folder) => ["--include", `/${folder}/**`]); + const args = [ "serve", "webdav", - backupsDir, + APP_ROOT, + ...includeArgs, "--addr", `:${config.serverPort}`, "--user", RCLONE_USER, "--pass", RCLONE_PASS, diff --git a/apps/Frontend/src/components/database-management/network-backup-manager.tsx b/apps/Frontend/src/components/database-management/network-backup-manager.tsx index aab96d10..5bd2e2d3 100644 --- a/apps/Frontend/src/components/database-management/network-backup-manager.tsx +++ b/apps/Frontend/src/components/database-management/network-backup-manager.tsx @@ -142,6 +142,14 @@ function RcloneBackupSection() { const pullNowMutation = useMutation({ mutationFn: async () => { + // The pull reads the saved config, so persist the source typed in the + // form first — otherwise Sync Now fails until "Save" is clicked. + const saveRes = await apiRequest("PUT", "/api/database-management/rclone-config", { + sourceIp, + sourcePort, + }); + if (!saveRes.ok) throw new Error("Failed to save source PC settings"); + const res = await apiRequest("POST", "/api/database-management/rclone-pull-now"); if (!res.ok) { const body = await res.json(); diff --git a/stop-app.sh b/stop-app.sh index 8f30c5f7..79f8f3ac 100755 --- a/stop-app.sh +++ b/stop-app.sh @@ -1,7 +1,9 @@ #!/bin/bash # Kill processes by port (backend 5000, selenium 5002, patient extractor 5001, payment OCR 5003, frontend 3000/3001) -for PORT in 5000 5001 5002 5003 3000 3001; do +# Also kill the rclone WebDAV server the backend spawns (8080) — kill -9 on the backend orphans it, +# and a leftover one keeps the port so the next start serves with stale settings. +for PORT in 5000 5001 5002 5003 3000 3001 8080; do PIDS=$(lsof -ti :$PORT 2>/dev/null) if [ -n "$PIDS" ]; then echo "Killing port $PORT (PID $PIDS)"