feat: integrate rclone WebDAV backup for PC-to-PC file sync

Source PC serves backups/ folder via rclone WebDAV server (auto-starts with app).
Receiver PC pulls backups on schedule using rclone sync.
Network Backup UI now has two tabs: Rclone and API Key.
Rclone installed automatically via postinstall script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-24 23:29:36 -04:00
parent 24e66bfaf9
commit 5e881c9ff7
8 changed files with 723 additions and 148 deletions

View File

@@ -6,6 +6,8 @@ import { backupDatabaseToPath } from "../services/databaseBackupService";
import { cronJobLogStorage } from "../storage/cron-job-log-storage";
import { readSyncConfig, writeSyncConfig } from "../services/networkSyncConfigService";
import { runNetworkSync, runNetworkFilesSync } from "../services/networkSyncService";
import { readRcloneConfig, writeRcloneConfig } from "../services/rcloneConfigService";
import { runRclonePull } from "../services/rcloneService";
// Local backup folder in the app root (apps/Backend/backups)
const LOCAL_BACKUP_DIR = path.resolve(process.cwd(), "backups");
@@ -174,6 +176,42 @@ export const startBackupCron = () => {
console.log("✅ [9 PM] USB backup complete.");
});
// ============================================================
// Every hour — Rclone backup (runs only when hour matches config)
// ============================================================
cron.schedule("0 * * * *", async () => {
const rcloneConfig = readRcloneConfig();
if (!rcloneConfig.receiverEnabled || !rcloneConfig.sourceIp) return;
const currentHour = new Date().getHours();
if (currentHour !== rcloneConfig.receiverSyncHour) return;
console.log(`[${rcloneConfig.receiverSyncHour}:00] Running rclone pull from ${rcloneConfig.sourceIp}...`);
const admin = await getAdminUser();
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("rclone-backup", startedAt);
try {
await runRclonePull();
writeRcloneConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "success", lastSyncError: null });
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());
console.log(`Rclone backup complete.`);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error("Rclone backup failed:", err);
writeRcloneConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "failed", lastSyncError: errorMessage });
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
if (admin) {
await storage.createNotification(
admin.id,
"BACKUP",
`Rclone backup failed: ${errorMessage}`
);
}
}
});
// ============================================================
// Every hour — Network sync (runs only when hour matches config)
// ============================================================