fix: rclone PC-to-PC sync — serve backups, chat-history, uploads via filters; save source before Sync Now
The WebDAV server was narrowed to serve only backups/ (to stop exposing .env), but the receiver still pulls /backups, /chat-history and /uploads, so every sync failed with "directory not found". Serve the app root filtered with --include to just those three folders; everything else (.env, source, config/key files) returns 404. Sync Now now saves the typed source IP/port before pulling, since the pull reads the saved config. stop-app.sh also frees port 8080: kill -9 on the backend orphans the rclone child, which then keeps the port with stale settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
if (isServerRunning()) return;
|
||||
|
||||
@@ -46,16 +46,22 @@ export async function startWebDavServer(): Promise<void> {
|
||||
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
+3
-1
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user