Skip to content

Commit

Permalink
feat: add safeguards to not get stuck in reboot loop
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenschneider committed Sep 14, 2023
1 parent ae38700 commit ee4ac7d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 4 deletions.
26 changes: 22 additions & 4 deletions internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"time"

"github.com/rs/zerolog/log"
"github.com/soerenschneider/conditional-reboot/internal/group"
"github.com/soerenschneider/conditional-reboot/internal/journal"
"github.com/soerenschneider/conditional-reboot/internal/uptime"

Check failure on line 14 in internal/app.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/soerenschneider/conditional-reboot/internal/uptime (-: # github.com/soerenschneider/conditional-reboot/internal/uptime
"github.com/soerenschneider/conditional-reboot/pkg/reboot"
"os"
"os/signal"
"time"
)

const DefaultSystemUptimeHours = 4

type ConditionalReboot struct {
groups []*group.Group
rebootImpl reboot.Reboot
Expand Down Expand Up @@ -45,6 +49,16 @@ func NewConditionalReboot(groups []*group.Group, rebootImpl reboot.Reboot, audit
}, nil
}

func IsSafeToReboot() bool {
systemUptime, err := uptime.Uptime()
if err != nil {
log.Error().Err(err).Msgf("could not determine system uptime, rebooting anyway: %w", err)
return true
}

return systemUptime.Hours() >= DefaultSystemUptimeHours
}

func (app *ConditionalReboot) Start() {
ctx, cancel := context.WithCancel(context.Background())
for _, group := range app.groups {
Expand All @@ -62,7 +76,11 @@ func (app *ConditionalReboot) Start() {
return

case group := <-app.rebootRequest:
log.Info().Msgf("Group '%s' requests reboot", group.GetName())
log.Info().Msgf("Reboot request from group '%s'", group.GetName())

if !IsSafeToReboot() {
log.Warn().Msg("Not safe to reboot (yet), ignoring reboot request")
}

if err := app.audit.Journal(actionToText(group)); err != nil {
log.Err(err).Msg("could not write journal")
Expand Down
24 changes: 24 additions & 0 deletions internal/uptime/uptime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package uptime

Check failure on line 1 in internal/uptime/uptime.go

View workflow job for this annotation

GitHub Actions / lint

: # github.com/soerenschneider/conditional-reboot/internal/uptime

import (
"strconv"
"strings"
"time"
)

// Clock interface makes it easier to test
type Clock interface {
Now() time.Time
}

type realClock struct{}

func (r *realClock) Now() time.Time {
return time.Now()
}

func parseLinuxUptime(uptime string) (float64, error) {
parts := strings.Split(string(uptime), " ")
secondsStr := strings.TrimSpace(parts[0])
return strconv.ParseFloat(secondsStr, 64)
}
11 changes: 11 additions & 0 deletions internal/uptime/uptime_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package uptime

import (
"time"
)

var start = time.Now()

func Uptime() (time.Duration, error) {
return time.Since(start), nil
}
32 changes: 32 additions & 0 deletions internal/uptime/uptime_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uptime

import "os"

var (
rawUptimeImpl UptimeSource = &LinuxUptime{}
clock Clock = &realClock{}
)

type UptimeSource interface {
RawUptime() (float64, error)
}

func Uptime() (time.Duration, error) {

Check failure on line 14 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

undefined: time

Check failure on line 14 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: time

Check failure on line 14 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: time
seconds, err := rawUptimeImpl.RawUptime()
if err != nil {
return time.Time{}, fmt.Errorf("could not read raw uptime: %w", err)

Check failure on line 17 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

undefined: time

Check failure on line 17 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

undefined: fmt

Check failure on line 17 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: time

Check failure on line 17 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: fmt

Check failure on line 17 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: time
}

return time.Second * time.Duration(seconds), nil

Check failure on line 20 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

undefined: time

Check failure on line 20 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: time
}

type LinuxUptime struct {
}

func (p *LinuxUptime) RawUptime() (float64, error) {
uptime, err := os.ReadFile("/proc/uptime")
if err != nil {
return math.MaxFloat64, err

Check failure on line 29 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

undefined: math

Check failure on line 29 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

undefined: math
}
return parseLinuxUptime(uptime)

Check failure on line 31 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / test

cannot use uptime (variable of type []byte) as string value in argument to parseLinuxUptime

Check failure on line 31 in internal/uptime/uptime_linux.go

View workflow job for this annotation

GitHub Actions / lint

cannot use uptime (variable of type []byte) as string value in argument to parseLinuxUptime (typecheck)
}

0 comments on commit ee4ac7d

Please sign in to comment.