# SyncServer — Agent Guidance ## Project Type Go monolith (single binary) with embedded React SPA frontend. Targets Linux amd64 LXC containers on Proxmox. ## Dev Commands ```bash # 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.