Backend:
- New migration: add shutdown_command TEXT column to machines table
- Machine model updated with ShutdownCommand field (Create/GetAll/GetByID/Update)
- MachineRequest/MachineResponse DTOs updated with shutdown_command field
- New ShutdownResponse DTO
- New POST /api/machines/{id}/shutdown handler via SSH
- Refactor sshmanager/testconn.go: extract dialSSH() helper shared with shutdown.go
- New sshmanager/shutdown.go: RunRemoteCommand with 15s timeout
Frontend:
- New shutdownMachine() API helper and ShutdownResponse type in client.ts
- New shutdown_command field in MachineForm
- Power button (amber) in machines table actions
- Shutdown confirmation modal with WoL warning notice
- shutdown_command input field in machine edit/create form
- Machine interface updated with shutdown_command and last_seen_at fields
The status field was being decorated with "(last seen ...)" suffix
which broke frontend statusVariant() matching and prevented the
LastSeen column from showing the separate timestamp.
Changes:
- machineToResp() now returns clean status ("online"/"offline")
- MachineResponse includes last_seen_at and created_at as separate fields
- Fix whitespace typo in WakeCheckIntervalSeconds field
- New Last Seen column showing last_seen_at timestamp per machine
- Status badges: online=green (success), offline=red (error), waking/pending=amber
- Update Machine interface with last_seen_at and created_at fields
- Fix Bug A: RunRemote was re-prefixing destination with user@host: when
engine.go:135 already pre-prefixed it for remote-to-remote, causing
"admin@host:admin@host:/path" to be passed to rsync
- Fix Bug B: Run used args[1:] when inserting -e ssh, silently dropping
the first rsync flag (e.g. -aP became flag-less)
- Fix Bug C: RunRemote extracted source/dest in wrong order for pull
direction (buildArgs reverses them but RunRemote assumed push order)
- Add rsync_runner_test.go covering buildArgs, RunRemote prefixing,
and flag preservation
- Regenerate qnap.key and baby-nas.key to OpenSSH native format (387 bytes vs 119 PKCS8)
- Pre-install qnap.key on Baby NAS at /var/lib/syncserver/ssh/keys/
- Pre-populate Baby NAS known_hosts with Qnap host keys
- Simplify RunRemote: LXC SSH to Baby NAS, Baby NAS runs rsync with local qnap.key
- Remove wrapper script approach (rsync rejects remote-to-remote)
Machine status probe on page load:
- POST /api/machines/refresh probes all machines in parallel (max 20 concurrent, 1.5s timeout)
- Updates DB status and broadcasts via SSE to all connected browser tabs
- Server-side throttle: ignores refresh requests within 10s
- Machines.tsx and Dashboard.tsx fire probe on mount
- Visual "Checking machine status..." indicator in Machines table
- MachineHandler now accepts *Engine for ProbeAllMachines access
Smart WoL: TCP pre-check before sending magic packet (3s timeout).
Machine status updates via SSE: online/offline tracked in DB and
broadcast to all connected browser tabs in real-time.
- Pre-check: if machine already reachable, skip WoL and mark online
- WoL path: send 3 magic packets, wait for SSH, mark online/offline
- Backend: setMachineStatus() helper + machine_status SSE event
- Frontend: subscribeMachineStatus() SSE helper for Machines + Dashboard
- IsReachable() helper in wol package for TCP reachability checks
Fix Wake-on-LAN: set SO_BROADCAST on UDP socket, send 3 magic packets,
expose broadcast_addr and wake timeout fields in UI, add Test Wake endpoint,
surface send errors in job status, bump default wake timeout to 180s.
webui: use fs.Sub to fix embed FS root so static assets are found
Previously http.FileServer searched for assets/foo.css in the embed FS
rooted at dist/, but only paths starting with dist/ resolve. fs.Sub
strips the dist/ prefix so FileServer finds the actual files.
- Add scripts/bump-version.sh for semver patch increments
- Update AGENTS.md Dev Commands section and add Version Bumping docs
- Makefile default VERSION bumped to 1.0.2