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 path from "path";
import fs from "fs";
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;
@@ -46,7 +48,7 @@ export async function startWebDavServer(): Promise<void> {
const args = [
"serve", "webdav",
LOCAL_BACKUP_DIR,
APP_ROOT,
"--addr", `:${config.serverPort}`,
"--user", RCLONE_USER,
"--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
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");
// Sync a single folder from the source PC via WebDAV
function syncFolder(folder: string, webdavUrl: string, obscuredPass: string): Promise<void> {
const localDir = path.join(APP_ROOT, folder);
if (!fs.existsSync(localDir)) {
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 = [
"sync",
`:webdav:/`,
LOCAL_BACKUP_DIR,
`:webdav:/${folder}`,
localDir,
"--webdav-url", webdavUrl,
"--webdav-user", RCLONE_USER,
"--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("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();
});
});
}
// 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> {
const config = readRcloneConfig();
if (config.serverEnabled) {