From ad4dc740d7c31b7184d7ab310b913f47b3779657 Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:27:34 -0500 Subject: [PATCH 1/6] Add option to reboot device after a certain amount of time --- subsystems/provisioning/definitions.go | 10 ++++++++++ subsystems/provisioning/networkmanager.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/subsystems/provisioning/definitions.go b/subsystems/provisioning/definitions.go index c1134c60..69d58f3e 100644 --- a/subsystems/provisioning/definitions.go +++ b/subsystems/provisioning/definitions.go @@ -297,6 +297,12 @@ func ConfigFromJSON(defaultConf Config, jsonBytes []byte) (*Config, error) { return &conf, errw.Errorf("timeout values cannot be less than %s", time.Duration(minTimeout)) } + if conf.DeviceRebootAfterOfflineMinutes != 0 && + conf.DeviceRebootAfterOfflineMinutes < int(time.Duration(conf.OfflineTimeout).Minutes()) && + conf.DeviceRebootAfterOfflineMinutes < int(time.Duration(conf.UserTimeout).Minutes()) { + return &conf, errw.Errorf("device_reboot_after_offline_minutes cannot be less than offline_timeout or user_timeout") + } + return &conf, nil } @@ -370,6 +376,10 @@ type Config struct { // If set, will explicitly enable or disable power save for all wifi connections managed by NetworkManager. WifiPowerSave *bool `json:"wifi_power_save"` + + // If set, will reboot the device after it has been offline for this duration + // 0 will disable this feature. + DeviceRebootAfterOfflineMinutes int `json:"device_reboot_after_offline_minutes"` } // Timeout allows parsing golang-style durations (1h20m30s) OR seconds-as-float from/to json. diff --git a/subsystems/provisioning/networkmanager.go b/subsystems/provisioning/networkmanager.go index 9e0c2977..9ee70d93 100644 --- a/subsystems/provisioning/networkmanager.go +++ b/subsystems/provisioning/networkmanager.go @@ -6,6 +6,7 @@ import ( "os" "reflect" "sort" + "syscall" "time" gnm "github.com/Otterverse/gonetworkmanager/v2" @@ -691,6 +692,13 @@ func (w *Provisioning) mainLoop(ctx context.Context) { if pMode { // complex logic, so wasting some variables for readability + deviceRebootAfterOfflineDuration := time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes) * time.Minute + + if w.cfg.DeviceRebootAfterOfflineMinutes > 0 && now.After(lastOnline.Add(deviceRebootAfterOfflineDuration)) { + w.logger.Infof("device has been offline for more than %s minutes, rebooting", deviceRebootAfterOfflineDuration) + syscall.Sync() + syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + } // portal interaction time is updated when a user loads a page or makes a grpc request inactivePortal := w.connState.getLastInteraction().Before(now.Add(time.Duration(w.cfg.UserTimeout)*-1)) || userInputReceived From cf3efec53b38e36f258ce3e2b747052e0cc82f19 Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:01:22 -0500 Subject: [PATCH 2/6] check reboot error --- subsystems/provisioning/networkmanager.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/subsystems/provisioning/networkmanager.go b/subsystems/provisioning/networkmanager.go index 9ee70d93..36bc2199 100644 --- a/subsystems/provisioning/networkmanager.go +++ b/subsystems/provisioning/networkmanager.go @@ -697,7 +697,10 @@ func (w *Provisioning) mainLoop(ctx context.Context) { if w.cfg.DeviceRebootAfterOfflineMinutes > 0 && now.After(lastOnline.Add(deviceRebootAfterOfflineDuration)) { w.logger.Infof("device has been offline for more than %s minutes, rebooting", deviceRebootAfterOfflineDuration) syscall.Sync() - syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + if err != nil { + w.logger.Error(err) + } } // portal interaction time is updated when a user loads a page or makes a grpc request From a384c2fb397ff5a54176a6ac9f9d9b53b37eca1b Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:42:30 -0500 Subject: [PATCH 3/6] update with feedback --- subsystems/provisioning/definitions.go | 8 ++++---- subsystems/provisioning/networkmanager.go | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/subsystems/provisioning/definitions.go b/subsystems/provisioning/definitions.go index 69d58f3e..86fa3691 100644 --- a/subsystems/provisioning/definitions.go +++ b/subsystems/provisioning/definitions.go @@ -298,8 +298,8 @@ func ConfigFromJSON(defaultConf Config, jsonBytes []byte) (*Config, error) { } if conf.DeviceRebootAfterOfflineMinutes != 0 && - conf.DeviceRebootAfterOfflineMinutes < int(time.Duration(conf.OfflineTimeout).Minutes()) && - conf.DeviceRebootAfterOfflineMinutes < int(time.Duration(conf.UserTimeout).Minutes()) { + conf.DeviceRebootAfterOfflineMinutes < conf.OfflineTimeout && + conf.DeviceRebootAfterOfflineMinutes < conf.UserTimeout { return &conf, errw.Errorf("device_reboot_after_offline_minutes cannot be less than offline_timeout or user_timeout") } @@ -379,7 +379,7 @@ type Config struct { // If set, will reboot the device after it has been offline for this duration // 0 will disable this feature. - DeviceRebootAfterOfflineMinutes int `json:"device_reboot_after_offline_minutes"` + DeviceRebootAfterOfflineMinutes Timeout `json:"device_reboot_after_offline_minutes"` } // Timeout allows parsing golang-style durations (1h20m30s) OR seconds-as-float from/to json. @@ -396,7 +396,7 @@ func (t *Timeout) UnmarshalJSON(b []byte) error { } switch value := v.(type) { case float64: - *t = Timeout(value * float64(time.Second)) + *t = Timeout(value * float64(time.Minute)) return nil case string: tmp, err := time.ParseDuration(value) diff --git a/subsystems/provisioning/networkmanager.go b/subsystems/provisioning/networkmanager.go index 36bc2199..a337f117 100644 --- a/subsystems/provisioning/networkmanager.go +++ b/subsystems/provisioning/networkmanager.go @@ -4,9 +4,9 @@ import ( "context" "errors" "os" + "os/exec" "reflect" "sort" - "syscall" "time" gnm "github.com/Otterverse/gonetworkmanager/v2" @@ -692,14 +692,14 @@ func (w *Provisioning) mainLoop(ctx context.Context) { if pMode { // complex logic, so wasting some variables for readability - deviceRebootAfterOfflineDuration := time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes) * time.Minute - if w.cfg.DeviceRebootAfterOfflineMinutes > 0 && now.After(lastOnline.Add(deviceRebootAfterOfflineDuration)) { - w.logger.Infof("device has been offline for more than %s minutes, rebooting", deviceRebootAfterOfflineDuration) - syscall.Sync() - err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + if w.cfg.DeviceRebootAfterOfflineMinutes > 0 && lastOnline.Before(now.Add(time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes)*-1)) { + w.logger.Infof("device has been offline for more than %s minutes, rebooting", w.cfg.DeviceRebootAfterOfflineMinutes) + + cmd := exec.Command("systemctl", "reboot") + output, err := cmd.CombinedOutput() if err != nil { - w.logger.Error(err) + w.logger.Error(errw.Wrapf(err, "running 'systemctl reboot' %s", output)) } } From 9da45caada9b454ebadf1fe67ba64dc30e5b04b8 Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:53:23 -0500 Subject: [PATCH 4/6] use lastConnecity for reboot time --- subsystems/provisioning/definitions.go | 2 +- subsystems/provisioning/networkmanager.go | 25 ++++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/subsystems/provisioning/definitions.go b/subsystems/provisioning/definitions.go index 86fa3691..16a85d60 100644 --- a/subsystems/provisioning/definitions.go +++ b/subsystems/provisioning/definitions.go @@ -298,7 +298,7 @@ func ConfigFromJSON(defaultConf Config, jsonBytes []byte) (*Config, error) { } if conf.DeviceRebootAfterOfflineMinutes != 0 && - conf.DeviceRebootAfterOfflineMinutes < conf.OfflineTimeout && + conf.DeviceRebootAfterOfflineMinutes < conf.OfflineTimeout || conf.DeviceRebootAfterOfflineMinutes < conf.UserTimeout { return &conf, errw.Errorf("device_reboot_after_offline_minutes cannot be less than offline_timeout or user_timeout") } diff --git a/subsystems/provisioning/networkmanager.go b/subsystems/provisioning/networkmanager.go index a337f117..8ec5c2ad 100644 --- a/subsystems/provisioning/networkmanager.go +++ b/subsystems/provisioning/networkmanager.go @@ -7,6 +7,7 @@ import ( "os/exec" "reflect" "sort" + "syscall" "time" gnm "github.com/Otterverse/gonetworkmanager/v2" @@ -693,16 +694,6 @@ func (w *Provisioning) mainLoop(ctx context.Context) { if pMode { // complex logic, so wasting some variables for readability - if w.cfg.DeviceRebootAfterOfflineMinutes > 0 && lastOnline.Before(now.Add(time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes)*-1)) { - w.logger.Infof("device has been offline for more than %s minutes, rebooting", w.cfg.DeviceRebootAfterOfflineMinutes) - - cmd := exec.Command("systemctl", "reboot") - output, err := cmd.CombinedOutput() - if err != nil { - w.logger.Error(errw.Wrapf(err, "running 'systemctl reboot' %s", output)) - } - } - // portal interaction time is updated when a user loads a page or makes a grpc request inactivePortal := w.connState.getLastInteraction().Before(now.Add(time.Duration(w.cfg.UserTimeout)*-1)) || userInputReceived @@ -748,6 +739,20 @@ func (w *Provisioning) mainLoop(ctx context.Context) { } } + offlineRebootTimeout := w.cfg.DeviceRebootAfterOfflineMinutes > 0 && + lastConnectivity.Before(now.Add(time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes)*-1)) + if offlineRebootTimeout { + w.logger.Infof("device has been offline for more than %s minutes, rebooting", w.cfg.DeviceRebootAfterOfflineMinutes) + + syscall.Sync() // flush file system buffers + cmd := exec.Command("systemctl", "reboot") + output, err := cmd.CombinedOutput() + if err != nil { + w.logger.Error(errw.Wrapf(err, "running 'systemctl reboot' %s", output)) + } + time.Sleep(time.Second * 100) // systemd DefaultTimeoutStopSec defaults to 90 seconds + } + hitOfflineTimeout := lastConnectivity.Before(now.Add(time.Duration(w.cfg.OfflineTimeout)*-1)) && pModeChange.Before(now.Add(time.Duration(w.cfg.OfflineTimeout)*-1)) // not in provisioning mode, so start it if not configured (/etc/viam.json) From 0cfc15a947bdcf70db8261d34633e7760e73e719 Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:56:28 -0500 Subject: [PATCH 5/6] fix --- subsystems/provisioning/definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsystems/provisioning/definitions.go b/subsystems/provisioning/definitions.go index 16a85d60..941e73da 100644 --- a/subsystems/provisioning/definitions.go +++ b/subsystems/provisioning/definitions.go @@ -378,7 +378,7 @@ type Config struct { WifiPowerSave *bool `json:"wifi_power_save"` // If set, will reboot the device after it has been offline for this duration - // 0 will disable this feature. + // 0, default, will disable this feature. DeviceRebootAfterOfflineMinutes Timeout `json:"device_reboot_after_offline_minutes"` } From e8c6d200302b6fa8f1d7b31fb5999c29b3ecc575 Mon Sep 17 00:00:00 2001 From: Ale Paredes <1709578+ale7714@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:48:28 -0500 Subject: [PATCH 6/6] use mainloop sleep --- subsystems/provisioning/networkmanager.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subsystems/provisioning/networkmanager.go b/subsystems/provisioning/networkmanager.go index 8ec5c2ad..11d791f5 100644 --- a/subsystems/provisioning/networkmanager.go +++ b/subsystems/provisioning/networkmanager.go @@ -7,7 +7,6 @@ import ( "os/exec" "reflect" "sort" - "syscall" "time" gnm "github.com/Otterverse/gonetworkmanager/v2" @@ -743,14 +742,15 @@ func (w *Provisioning) mainLoop(ctx context.Context) { lastConnectivity.Before(now.Add(time.Duration(w.cfg.DeviceRebootAfterOfflineMinutes)*-1)) if offlineRebootTimeout { w.logger.Infof("device has been offline for more than %s minutes, rebooting", w.cfg.DeviceRebootAfterOfflineMinutes) - - syscall.Sync() // flush file system buffers cmd := exec.Command("systemctl", "reboot") output, err := cmd.CombinedOutput() if err != nil { w.logger.Error(errw.Wrapf(err, "running 'systemctl reboot' %s", output)) } - time.Sleep(time.Second * 100) // systemd DefaultTimeoutStopSec defaults to 90 seconds + if !w.mainLoopHealth.Sleep(ctx, time.Minute*5) { + return + } + w.logger.Errorf("failed to reboot after %s time", time.Minute*5) } hitOfflineTimeout := lastConnectivity.Before(now.Add(time.Duration(w.cfg.OfflineTimeout)*-1)) &&