82 lines
2.7 KiB
Go
82 lines
2.7 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
|
)
|
|
|
|
// „Felhom support restarted your box at <time>" (D65 = A, operator ruling 2026-10-29 15:50; felhom.eu `04` §6.1).
|
|
//
|
|
// The operator can reboot the guest from the hub; the household must see afterwards that Felhom did it. The agent
|
|
// writes its record BEFORE the reboot, so this controller — which starts with the guest — asks for it once at start
|
|
// (GET /guest/operator-reboot) and the dashboard shows one neutral line until a day has passed since the reboot.
|
|
// Pinned by operator_reboot_notice_test.go.
|
|
|
|
// operatorRebootNoticeFor is how long the line shows (the agent also stops answering after a day).
|
|
const operatorRebootNoticeFor = 24 * time.Hour
|
|
|
|
// SetOperatorReboot records the last operator reboot (the zero time = none).
|
|
func (s *Server) SetOperatorReboot(at time.Time) {
|
|
s.opRebootMu.Lock()
|
|
defer s.opRebootMu.Unlock()
|
|
s.opRebootAt = at
|
|
}
|
|
|
|
// operatorRebootNotice is the dashboard's line for lang at now ("" = none).
|
|
func (s *Server) operatorRebootNotice(lang string, now time.Time) string {
|
|
s.opRebootMu.Lock()
|
|
at := s.opRebootAt
|
|
s.opRebootMu.Unlock()
|
|
if at.IsZero() || now.Sub(at) >= operatorRebootNoticeFor || now.Before(at) {
|
|
return ""
|
|
}
|
|
return s.i18n.Msgf(lang, "dashboard.operator_reboot", at.In(budapestLocation()).Format("15:04"))
|
|
}
|
|
|
|
// WatchOperatorReboot asks the agent once at start, retrying while the agent does not answer (it may start after us),
|
|
// at most for opRebootAskFor. A 404 (an older agent) ends it at once.
|
|
func (s *Server) WatchOperatorReboot(ctx context.Context) {
|
|
fetch := s.opRebootFetch
|
|
if fetch == nil {
|
|
fetch = func(ctx context.Context) (time.Time, error) {
|
|
c, err := s.agentClient()
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
return c.OperatorReboot(ctx)
|
|
}
|
|
}
|
|
every := s.opRebootRetry
|
|
if every <= 0 {
|
|
every = 30 * time.Second
|
|
}
|
|
for deadline := time.Now().Add(10 * time.Minute); ; {
|
|
cctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
at, err := fetch(cctx)
|
|
cancel()
|
|
var se *agentapi.StatusError
|
|
switch {
|
|
case err == nil:
|
|
if !at.IsZero() {
|
|
s.SetOperatorReboot(at)
|
|
s.logger.Printf("[INFO] [web] the operator rebooted this box from the hub at %s — the dashboard says so for a day", at.UTC().Format(time.RFC3339))
|
|
}
|
|
return
|
|
case errors.As(err, &se) && se.Code == 404:
|
|
return // an agent older than the route cannot reboot from the hub
|
|
}
|
|
if !time.Now().Before(deadline) {
|
|
s.logger.Printf("[WARN] [web] could not ask the agent about an operator reboot: %v — no notice on the dashboard", err)
|
|
return
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(every):
|
|
}
|
|
}
|
|
}
|