feat: rclone syncs all 3 folders (backups, chat-history, uploads)

WebDAV server now serves the Backend root directory. Receiver pulls
backups/, chat-history/, and uploads/ individually so all data is
synced between PCs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-24 23:50:40 -04:00
parent cbe7d13dd2
commit 99a54cee9a

View File

@@ -1,8 +1,10 @@
import { spawn, ChildProcess } from "child_process"; import { spawn, ChildProcess } from "child_process";
import path from "path"; import path from "path";
import fs from "fs";
import { readRcloneConfig, RCLONE_USER, RCLONE_PASS } from "./rcloneConfigService"; import { readRcloneConfig, RCLONE_USER, RCLONE_PASS } from "./rcloneConfigService";
const LOCAL_BACKUP_DIR = path.resolve(process.cwd(), "backups"); const APP_ROOT = process.cwd();
const SYNC_FOLDERS = ["backups", "chat-history", "uploads"];
let serverProcess: ChildProcess | null = null; let serverProcess: ChildProcess | null = null;
@@ -46,7 +48,7 @@ export async function startWebDavServer(): Promise<void> {
const args = [ const args = [
"serve", "webdav", "serve", "webdav",
LOCAL_BACKUP_DIR, APP_ROOT,
"--addr", `:${config.serverPort}`, "--addr", `:${config.serverPort}`,
"--user", RCLONE_USER, "--user", RCLONE_USER,
"--pass", RCLONE_PASS, "--pass", RCLONE_PASS,
@@ -85,25 +87,17 @@ export function stopWebDavServer(): void {
} }
} }
// Receiver PC: rclone sync :webdav:/ ./backups --webdav-url http://IP:PORT --webdav-user MyDentalApp --webdav-pass OBSCURED // Sync a single folder from the source PC via WebDAV
export async function runRclonePull(): Promise<void> { function syncFolder(folder: string, webdavUrl: string, obscuredPass: string): Promise<void> {
const config = readRcloneConfig(); const localDir = path.join(APP_ROOT, folder);
if (!config.sourceIp || !config.sourcePort) { if (!fs.existsSync(localDir)) {
throw new Error("Rclone receiver configuration is incomplete — source IP and port are required"); fs.mkdirSync(localDir, { recursive: true });
} }
const installed = await checkRcloneInstalled();
if (!installed) {
throw new Error("rclone is not installed. Run: curl https://rclone.org/install.sh | sudo bash");
}
const obscuredPass = await obscurePassword(RCLONE_PASS);
const webdavUrl = `http://${config.sourceIp}:${config.sourcePort}`;
const args = [ const args = [
"sync", "sync",
`:webdav:/`, `:webdav:/${folder}`,
LOCAL_BACKUP_DIR, localDir,
"--webdav-url", webdavUrl, "--webdav-url", webdavUrl,
"--webdav-user", RCLONE_USER, "--webdav-user", RCLONE_USER,
"--webdav-pass", obscuredPass, "--webdav-pass", obscuredPass,
@@ -122,12 +116,33 @@ export async function runRclonePull(): Promise<void> {
}); });
proc.on("error", (err) => reject(new Error(`Failed to start rclone: ${err.message}`))); proc.on("error", (err) => reject(new Error(`Failed to start rclone: ${err.message}`)));
proc.on("close", (code) => { proc.on("close", (code) => {
if (code !== 0) return reject(new Error(`rclone exited with code ${code}: ${stderr}`)); if (code !== 0) return reject(new Error(`rclone sync ${folder} failed (exit ${code}): ${stderr}`));
resolve(); resolve();
}); });
}); });
} }
// Receiver PC: pull backups, chat-history, and uploads from source
export async function runRclonePull(): Promise<void> {
const config = readRcloneConfig();
if (!config.sourceIp || !config.sourcePort) {
throw new Error("Rclone receiver configuration is incomplete — source IP and port are required");
}
const installed = await checkRcloneInstalled();
if (!installed) {
throw new Error("rclone is not installed. Run: curl https://rclone.org/install.sh | sudo bash");
}
const obscuredPass = await obscurePassword(RCLONE_PASS);
const webdavUrl = `http://${config.sourceIp}:${config.sourcePort}`;
for (const folder of SYNC_FOLDERS) {
console.log(`[rclone-pull] Syncing ${folder}...`);
await syncFolder(folder, webdavUrl, obscuredPass);
}
}
export async function autoStartServer(): Promise<void> { export async function autoStartServer(): Promise<void> {
const config = readRcloneConfig(); const config = readRcloneConfig();
if (config.serverEnabled) { if (config.serverEnabled) {