Files
move-data-nas/AGENTS.md
T
darroyo 8e08c73f60 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
2026-07-07 15:03:22 -04:00

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

  1. Frontend must be built FIRST: cd web && npm run build
  2. Output copied to internal/webui/dist/ (must exist before Go build)
  3. 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 migrations in internal/db/migrations.go. Must use migrationsFS variable, 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.CommandContext to run rsync -e ssh .... SSH key is passed via -i flag, not via SSH config file.
  • Scheduler: homebrew cron parser (m h dom mon dow format, no seconds). Library github.com/robfig/cron is NOT used.
  • SSE: GET /api/jobs/stream streams text/event-stream. Clients must handle event: 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 via GOOS=linux GOARCH=amd64. Version injected via -ldflags="-X main.version=$VERSION".
  • scripts/package-deb.sh: calls dpkg-deb --build. Version must be substituted into DEBIAN/control before 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.