5.8 KiB
SyncServer — Agent Guidance
Project Type
Go monolith (single binary) with embedded React SPA frontend. Targets Linux amd64 LXC containers on Proxmox.
Dev Commands
# Clean build and package (Linux amd64)
make clean && VERSION=X.Y.Z make package
# Build only (no package), Linux amd64
make build-all
# Run
./syncserver --data-dir ./data --addr :8080 \
SYNCSERVER_ADMIN_USER=admin SYNCSERVER_ADMIN_PASSWORD=secret
# Tests
go test ./...
# Deploy (full build + bump + push + LXC deploy + verify)
./scripts/deploy.sh
Build Artifacts
The following are not tracked in git — they are regenerated by make clean && make build-all:
| Path | Description |
|---|---|
bin/ |
Linux AMD64 + ARM64 built binaries |
server |
Local macOS dev binary (go build ./cmd/server) |
.data/ |
Runtime data dir created by make clean |
dist/ |
Root-level build output |
web/dist/ |
Frontend build output |
internal/webui/dist/ |
Embedded frontend in Go |
*.deb |
Debian packages built by make package |
The deploy pipeline always rebuilds from source + embedded frontend. Tracking prebuilt binaries bloats history with opaque diffs and produces unresolvable merge conflicts.
Version Bumping
Before each push that contains user-facing changes, run the version bump script:
./scripts/bump-version.sh
This reads the current version from cmd/server/main.go, increments the patch number (semver), and updates both cmd/server/main.go and Makefile. Then commit and push:
git add cmd/server/main.go Makefile
git commit -m 'Bump version to X.Y.Z'
git push
The DEBIAN/control uses @@VERSION@@ placeholder and is substituted at make package time — no manual edit needed.
To verify the LXC is running the expected version:
ssh root@10.5.1.30 '/usr/bin/syncserver --version'
ssh root@10.5.1.30 'journalctl -u syncserver -n 3 --no-pager'
Deploy Flow
The make deploy target (or ./scripts/deploy.sh) automates the full deployment pipeline:
- Pre-flight — checks git clean, on main branch, LXC reachable, disk space
- Version bump — runs
bump-version.sh, commits and pushes automatically - Build —
make clean && make package(Linux amd64 .deb) - Backup — copies current
/usr/bin/syncserverto/var/backups/syncserver.previous.<version> - Deploy — scp .deb to LXC, run
dpkg -i - Verify — service active, version matches, Cache-Control headers correct, HTTP 200
Deploy Options
./scripts/deploy.sh # Full deploy
./scripts/deploy.sh --dry-run # Simulate without making changes
SKIP_BUILD=1 ./scripts/deploy.sh # Use existing .deb
SKIP_VERIFY=1 ./scripts/deploy.sh # Skip post-deploy checks
LXC_HOST=root@10.5.1.30 ./scripts/deploy.sh # Custom target
Rollback
If verification fails after deploy, the script automatically rolls back to the backup binary. Manual rollback:
ssh root@10.5.1.30 "cp /var/backups/syncserver.previous.<version> /usr/bin/syncserver && systemctl restart syncserver"
LXC Host
- Host:
root@10.5.1.30 - Remote dir:
~/move-data-nas - Service:
syncserver(systemd)
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.