mirror of https://github.com/hak5/overseer.git
175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
// Daemonizable self-upgrading binaries in Go (golang).
|
|
package overseer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/jpillora/overseer/fetcher"
|
|
)
|
|
|
|
const (
|
|
envSlaveID = "OVERSEER_SLAVE_ID"
|
|
envIsSlave = "OVERSEER_IS_SLAVE"
|
|
envNumFDs = "OVERSEER_NUM_FDS"
|
|
envBinID = "OVERSEER_BIN_ID"
|
|
envBinPath = "OVERSEER_BIN_PATH"
|
|
envBinCheck = "OVERSEER_BIN_CHECK"
|
|
envBinCheckLegacy = "GO_UPGRADE_BIN_CHECK"
|
|
)
|
|
|
|
type Config struct {
|
|
//Required will prevent overseer from fallback to running
|
|
//running the program in the main process on failure.
|
|
Required bool
|
|
//Program's main function
|
|
Program func(state State)
|
|
//Program's zero-downtime socket listening address (set this or Addresses)
|
|
Address string
|
|
//Program's zero-downtime socket listening addresses (set this or Address)
|
|
Addresses []string
|
|
//RestartSignal will manually trigger a graceful restart. Defaults to SIGUSR2.
|
|
RestartSignal os.Signal
|
|
//TerminateTimeout controls how long overseer should
|
|
//wait for the program to terminate itself. After this
|
|
//timeout, overseer will issue a SIGKILL.
|
|
TerminateTimeout time.Duration
|
|
//MinFetchInterval defines the smallest duration between Fetch()s.
|
|
//This helps to prevent unwieldy fetch.Interfaces from hogging
|
|
//too many resources. Defaults to 1 second.
|
|
MinFetchInterval time.Duration
|
|
//PreUpgrade runs after a binary has been retreived, user defined checks
|
|
//can be run here and returning an error will cancel the upgrade.
|
|
PreUpgrade func(tempBinaryPath string) error
|
|
//Debug enables all [overseer] logs.
|
|
Debug bool
|
|
//NoWarn disables warning [overseer] logs.
|
|
NoWarn bool
|
|
//NoRestart disables all restarts, this option essentially converts
|
|
//the RestartSignal into a "ShutdownSignal".
|
|
NoRestart bool
|
|
//NoRestartAfterFetch disables automatic restarts after each upgrade.
|
|
//Though manual restarts using the RestartSignal can still be performed.
|
|
NoRestartAfterFetch bool
|
|
//Fetcher will be used to fetch binaries.
|
|
Fetcher fetcher.Interface
|
|
}
|
|
|
|
func validate(c *Config) error {
|
|
//validate
|
|
if c.Program == nil {
|
|
return errors.New("overseer.Config.Program required")
|
|
}
|
|
if c.Address != "" {
|
|
if len(c.Addresses) > 0 {
|
|
return errors.New("overseer.Config.Address and Addresses cant both be set")
|
|
}
|
|
c.Addresses = []string{c.Address}
|
|
} else if len(c.Addresses) > 0 {
|
|
c.Address = c.Addresses[0]
|
|
}
|
|
if c.RestartSignal == nil {
|
|
c.RestartSignal = SIGUSR2
|
|
}
|
|
if c.TerminateTimeout <= 0 {
|
|
c.TerminateTimeout = 30 * time.Second
|
|
}
|
|
if c.MinFetchInterval <= 0 {
|
|
c.MinFetchInterval = 1 * time.Second
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//RunErr allows manual handling of any
|
|
//overseer errors.
|
|
func RunErr(c Config) error {
|
|
return runErr(&c)
|
|
}
|
|
|
|
//Run executes overseer, if an error is
|
|
//encounted, overseer fallsback to running
|
|
//the program directly (unless Required is set).
|
|
func Run(c Config) {
|
|
err := runErr(&c)
|
|
if err != nil {
|
|
if c.Required {
|
|
log.Fatalf("[overseer] %s", err)
|
|
} else if c.Debug || !c.NoWarn {
|
|
log.Printf("[overseer] disabled. run failed: %s", err)
|
|
}
|
|
c.Program(DisabledState)
|
|
return
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
//sanityCheck returns true if a check was performed
|
|
func sanityCheck() bool {
|
|
//sanity check
|
|
if token := os.Getenv(envBinCheck); token != "" {
|
|
fmt.Fprint(os.Stdout, token)
|
|
return true
|
|
}
|
|
//legacy sanity check using old env var
|
|
if token := os.Getenv(envBinCheckLegacy); token != "" {
|
|
fmt.Fprint(os.Stdout, token)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//SanityCheck manually runs the check to ensure this binary
|
|
//is compatible with overseer. This tries to ensure that a restart
|
|
//is never performed against a bad binary, as it would require
|
|
//manual intervention to rectify. This is automatically done
|
|
//on overseer.Run() though it can be manually run prior whenever
|
|
//necessary.
|
|
func SanityCheck() {
|
|
if sanityCheck() {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
//abstraction over master/slave
|
|
var currentProcess interface {
|
|
triggerRestart()
|
|
run() error
|
|
}
|
|
|
|
func runErr(c *Config) error {
|
|
//os not supported
|
|
if !supported {
|
|
return fmt.Errorf("os (%s) not supported", runtime.GOOS)
|
|
}
|
|
if err := validate(c); err != nil {
|
|
return err
|
|
}
|
|
if sanityCheck() {
|
|
return nil
|
|
}
|
|
//run either in master or slave mode
|
|
if os.Getenv(envIsSlave) == "1" {
|
|
currentProcess = &slave{Config: c}
|
|
} else {
|
|
currentProcess = &master{Config: c}
|
|
}
|
|
return currentProcess.run()
|
|
}
|
|
|
|
//Restart programmatically triggers a graceful restart. If NoRestart
|
|
//is enabled, then this will essentially be a graceful shutdown.
|
|
func Restart() {
|
|
if currentProcess != nil {
|
|
currentProcess.triggerRestart()
|
|
}
|
|
}
|
|
|
|
//IsSupported returns whether overseer is supported on the current OS.
|
|
func IsSupported() bool {
|
|
return supported
|
|
}
|