agent v0.26.0: slice 10 P2 activation — POST /guest/reboot (user-triggered)
Self-scoped guest reboot (pct reboot, detached, 202) so an enrolled-into-running- guest drive's persisted bind activates at next boot. Tests: accepted + cross-guest 403. Pairs with controller v0.49.0 pending-drive detection + "Újraindítás most". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
||||
@@ -62,9 +63,11 @@ type GuestLister interface {
|
||||
}
|
||||
|
||||
// GuestAttacher binds an enrolled user-data drive's felhom-data namespace into a guest as an RW bind
|
||||
// mount (slice 10 P2, Model A). Satisfied by *GuestBinder. The handler picks the slot + dedups.
|
||||
// mount (slice 10 P2, Model A) and reboots the guest to activate persisted-but-inactive binds (the
|
||||
// host-side live inject is blocked on unprivileged guests). Satisfied by *GuestBinder.
|
||||
type GuestAttacher interface {
|
||||
AttachBind(ctx context.Context, vmid int, mountKey, where string) error
|
||||
RebootGuest(ctx context.Context, vmid int) error
|
||||
}
|
||||
|
||||
// ---- handlers ---------------------------------------------------------------------------
|
||||
@@ -266,6 +269,45 @@ func (s *Server) handleDiskGuestAttach(w http.ResponseWriter, r *http.Request, v
|
||||
writeOK(w, map[string]any{"vmid": vmid, "attached": where, "slot": slot})
|
||||
}
|
||||
|
||||
type guestRebootRequest struct {
|
||||
VMID int `json:"vmid"`
|
||||
}
|
||||
|
||||
// handleGuestReboot reboots THIS guest (self-scoped) to activate persisted-but-inactive mountpoint
|
||||
// binds (slice 10 P2 activation). It runs the reboot DETACHED and returns 202 immediately, so the
|
||||
// calling controller gets a clean response before the reboot takes it (and the agent — host-side —
|
||||
// survives the guest reboot). User-triggered ("Újraindítás most"); the controller batches all pending
|
||||
// drives into one restart.
|
||||
func (s *Server) handleGuestReboot(w http.ResponseWriter, r *http.Request, vmid int) {
|
||||
if s.guestAttach == nil {
|
||||
writeErr(w, http.StatusServiceUnavailable, "guest passthrough not configured on this host")
|
||||
return
|
||||
}
|
||||
if r.ContentLength != 0 {
|
||||
var req guestRebootRequest
|
||||
if !decodeBody(w, r, &req) {
|
||||
return
|
||||
}
|
||||
if !s.scopedFromBody(w, req.VMID, vmid, r.URL.Path) {
|
||||
return
|
||||
}
|
||||
}
|
||||
base := s.baseCtx
|
||||
if base == nil {
|
||||
base = context.Background()
|
||||
}
|
||||
go func() {
|
||||
// Detached: pct reboot blocks ~30s until the guest is back; don't tie it to the request ctx.
|
||||
rebootCtx, cancel := context.WithTimeout(base, 5*time.Minute)
|
||||
defer cancel()
|
||||
if err := s.guestAttach.RebootGuest(rebootCtx, vmid); err != nil {
|
||||
s.logger.Error("local-api: guest-reboot failed", "vmid", vmid, "err", err)
|
||||
}
|
||||
}()
|
||||
s.logger.Warn("local-api: guest reboot requested (activating pending drive binds)", "vmid", vmid)
|
||||
writeStatus(w, http.StatusAccepted, true, map[string]any{"vmid": vmid, "rebooting": true}, "")
|
||||
}
|
||||
|
||||
// validGuestMountPath accepts an absolute /mnt/<name> path with no traversal (the enroll convention
|
||||
// root). Mirrors the controller's mount-name discipline so a hostile `where` can't escape /mnt.
|
||||
func validGuestMountPath(p string) bool {
|
||||
|
||||
Reference in New Issue
Block a user