8e08c73f60
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
3.1 KiB
3.1 KiB
SyncServer — Agent Guidance
Project Type
Go monolith (single binary) with embedded React SPA frontend. Targets Linux amd64 LXC containers on Proxmox.
Dev Commands
# Local dev build (macOS)
make build-local
# Production Linux amd64 build
make build # → packaging/debian/usr/bin/syncserver
make deb # → dist/syncserver_VERSION_amd64.deb
# Run
./syncserver --data-dir ./data --addr :8080 \
SYNCSERVER_ADMIN_USER=admin SYNCSERVER_ADMIN_PASSWORD=secret
# Tests
go test ./...
Critical Build Order
- Frontend must be built FIRST:
cd web && npm run build - Output copied to
internal/webui/dist/(must exist before Go build) - Then:
go build ./cmd/server
scripts/build.sh does this automatically. Running go build without the frontend dist will produce a binary without the web UI.
Architecture Notes
- Entry point:
cmd/server/main.go - DB:
modernc.org/sqlite— pure Go, CGO_ENABLED=0. Driver name is"sqlite"(not"sqlite3"). - Migrations: embedded via
//go:embed migrationsininternal/db/migrations.go. Must usemigrationsFSvariable, path"migrations"(not"internal/db/migrations"). - SSH keys: generated at startup if missing; stored in
$DATA_DIR/ssh/id_ed25519. Only the public key is stored in DB. - Sync engine: uses
os/exec.CommandContextto runrsync -e ssh .... SSH key is passed via-iflag, not via SSH config file. - Scheduler: homebrew cron parser (
m h dom mon dowformat, no seconds). Librarygithub.com/robfig/cronis NOT used. - SSE:
GET /api/jobs/streamstreamstext/event-stream. Clients must handleevent:field.
Frontend Stack
- TypeScript + React 18 + Vite + Tailwind CSS
- Source:
web/src/, built output:web/dist/ - API client:
web/src/api/client.ts - No React Router 6 data loaders; fetch hooks are manual in each page component
- Tests: no frontend test suite yet
Key Package Boundaries
| Package | Responsibility |
|---|---|
internal/api/ |
HTTP handlers, chi router, DTOs |
internal/auth/ |
JWT (HS256), bcrypt, middleware |
internal/db/ |
SQLite connection, migrations runner |
internal/models/ |
Data access (raw sql) |
internal/syncengine/ |
Job queue, rsync subprocess, event bus |
internal/scheduler/ |
Cron parsing, tick loop |
internal/sshmanager/ |
Ed25519 key generation, known_hosts |
internal/wol/ |
Magic packet, TCP/ping waiter |
internal/webui/ |
embed.FS for React dist |
DB Schema
Managed via ordered SQL migrations in internal/db/migrations/. Apply on startup via db.RunMigrations(). Table schema_migrations tracks applied versions.
Package Script Quirks
scripts/build.sh: cross-compiles to Linux amd64 viaGOOS=linux GOARCH=amd64. Version injected via-ldflags="-X main.version=$VERSION".scripts/package-deb.sh: callsdpkg-deb --build. Version must be substituted intoDEBIAN/controlbefore build (@@VERSION@@placeholder).
WoL Constraints
Magic packet uses SO_BROADCAST on a plain UDP socket. No raw sockets, no privileged container needed. Requires L2 reachability (same broadcast domain) from the server to the target machine's NIC.