feat: complete SyncServer implementation

Full-stack Go monolith with embedded React frontend for orchestrating
rsync-over-SSH file synchronization with Wake-on-LAN support.

Features:
- JWT auth (HS256) with bcrypt password hashing
- CRUD for machines (with WoL config) and sync_pairs
- Ed25519 SSH key generation and known_hosts management
- WoL magic packet sender + TCP-connect waiter with backoff
- Sync engine: rsync subprocess, per-pair job queue, progress parsing
- Homebrew cron parser for scheduled syncs
- SSE stream for live job status (queued/waking_up/running/success/failed)
- React+TS+Vite+Tailwind SPA embedded via embed.FS
- Debian packaging with systemd unit, postinst/prerm/postrm

Tech stack:
- Go 1.22+ (CGO_ENABLED=0, pure SQLite via modernc.org/sqlite)
- chi router for HTTP API
- TypeScript + React 18 + Tailwind CSS frontend
- Cross-compiled to Linux amd64 for Proxmox LXC deployment

Tests: wol (MAC parsing, magic packet), syncengine/queue, scheduler/cron
This commit is contained in:
2026-07-07 15:03:22 -04:00
parent 1a66ac58cd
commit 8e08c73f60
69 changed files with 7949 additions and 152 deletions
+35
View File
@@ -0,0 +1,35 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import Machines from './pages/Machines';
import SyncPairs from './pages/SyncPairs';
import JobHistory from './pages/JobHistory';
import Settings from './pages/Settings';
function ProtectedRoute({ children }: { children: JSX.Element }) {
const [authed, setAuthed] = useState<boolean | null>(null);
useEffect(() => {
fetch('/api/auth/me', { credentials: 'include' })
.then(r => setAuthed(r.ok))
.catch(() => setAuthed(false));
}, []);
if (authed === null) return <div className="p-4">Loading...</div>;
return authed ? children : <Navigate to="/login" />;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
<Route path="/machines" element={<ProtectedRoute><Machines /></ProtectedRoute>} />
<Route path="/sync-pairs" element={<ProtectedRoute><SyncPairs /></ProtectedRoute>} />
<Route path="/jobs" element={<ProtectedRoute><JobHistory /></ProtectedRoute>} />
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>
);
}