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:
@@ -15,6 +15,8 @@ import {
|
||||
writeSyncConfig,
|
||||
} from "../services/networkSyncConfigService";
|
||||
import { runNetworkSync, runNetworkFilesSync } from "../services/networkSyncService";
|
||||
import { readRcloneConfig, writeRcloneConfig } from "../services/rcloneConfigService";
|
||||
import { checkRcloneInstalled, isServerRunning, startWebDavServer, stopWebDavServer, runRclonePull } from "../services/rcloneService";
|
||||
|
||||
const UPLOADS_DIR = path.join(process.cwd(), "uploads");
|
||||
|
||||
@@ -605,4 +607,63 @@ router.post("/network-sync-now", async (req: Request, res: Response): Promise<an
|
||||
}
|
||||
});
|
||||
|
||||
// ==============================
|
||||
// Rclone Backup
|
||||
// ==============================
|
||||
|
||||
router.get("/rclone-config", (req, res) => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
res.json(readRcloneConfig());
|
||||
});
|
||||
|
||||
router.put("/rclone-config", (req: Request, res: Response): any => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
const { serverEnabled, serverPort, receiverEnabled, receiverSyncHour, sourceIp, sourcePort } = req.body;
|
||||
const patch: any = {};
|
||||
if (typeof serverEnabled === "boolean") patch.serverEnabled = serverEnabled;
|
||||
if (typeof serverPort === "number") patch.serverPort = serverPort;
|
||||
if (typeof receiverEnabled === "boolean") patch.receiverEnabled = receiverEnabled;
|
||||
if (typeof receiverSyncHour === "number") patch.receiverSyncHour = receiverSyncHour;
|
||||
if (typeof sourceIp === "string") patch.sourceIp = sourceIp.trim();
|
||||
if (typeof sourcePort === "number") patch.sourcePort = sourcePort;
|
||||
const updated = writeRcloneConfig(patch);
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
router.get("/rclone-status", async (req, res) => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
const installed = await checkRcloneInstalled();
|
||||
const serverRunning = isServerRunning();
|
||||
res.json({ installed, serverRunning });
|
||||
});
|
||||
|
||||
router.post("/rclone-server/start", async (req: Request, res: Response): Promise<any> => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
try {
|
||||
await startWebDavServer();
|
||||
res.json({ success: true, running: true });
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ error: "Failed to start rclone server", details: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/rclone-server/stop", (req: Request, res: Response): any => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
stopWebDavServer();
|
||||
res.json({ success: true, running: false });
|
||||
});
|
||||
|
||||
router.post("/rclone-pull-now", async (req: Request, res: Response): Promise<any> => {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
|
||||
try {
|
||||
await runRclonePull();
|
||||
writeRcloneConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "success", lastSyncError: null });
|
||||
res.json({ success: true, syncedAt: new Date() });
|
||||
} catch (err: any) {
|
||||
writeRcloneConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "failed", lastSyncError: err.message });
|
||||
res.status(500).json({ error: "Rclone pull failed", details: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user