267 lines
7.4 KiB
Go
267 lines
7.4 KiB
Go
package camfish
|
|
|
|
import "os"
|
|
import "fmt"
|
|
import "log"
|
|
import "io/fs"
|
|
import "errors"
|
|
import "context"
|
|
import "strings"
|
|
import "path/filepath"
|
|
import "git.tebibyte.media/sashakoshka/go-cli"
|
|
import "git.tebibyte.media/sashakoshka/go-service/daemon"
|
|
import "git.tebibyte.media/sashakoshka/go-service/rotate"
|
|
|
|
func (this *environment) phase10FlagParsing() bool {
|
|
// create flag set and specify built-in flags
|
|
set := flagSet {
|
|
name: this.name,
|
|
description: this.description,
|
|
}
|
|
flagHelp := set.Flag('h', "help", "Display usage information and exit", nil)
|
|
flagPidFile := set.Flag('p', "pid-file", "Write the PID to the specified file", cli.ValString)
|
|
flagUser := set.Flag('u', "user", "The user:group to run as", cli.ValString)
|
|
flagLogDirectory := set.Flag('l', "log-directory", "Write logs to the specified directory", cli.ValString)
|
|
flagConfigFile := set.Flag('c', "config-file", "Use this configuration file", cli.ValString)
|
|
flagVerbose := set.Flag('v', "verbose", "Enable verbose output/logging", nil)
|
|
|
|
// ask actors to add flags
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
for _, actor := range sortActors(actors, actors.flagAdder.all()) {
|
|
actor.AddFlags(&set)
|
|
}
|
|
|
|
// parse flags
|
|
err := set.parse(os.Args)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
|
set.usage()
|
|
return false
|
|
}
|
|
|
|
// handle built-in flags
|
|
if _, ok := flagHelp.First(); ok {
|
|
set.usage()
|
|
return false
|
|
}
|
|
if pidFile, ok := flagPidFile.First(); ok {
|
|
this.flags.pidFile = pidFile
|
|
}
|
|
if user, ok := flagUser.First(); ok {
|
|
this.flags.user = user
|
|
}
|
|
if logDirectory, ok := flagLogDirectory.First(); ok {
|
|
this.flags.logDirectory = logDirectory
|
|
}
|
|
if configFile, ok := flagConfigFile.First(); ok {
|
|
this.flags.configFile = configFile
|
|
}
|
|
if _, ok := flagVerbose.First(); ok {
|
|
this.flags.verbose = true
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase13PidFileCreation() bool {
|
|
if this.flags.pidFile != "" {
|
|
err := daemon.PidFile(this.flags.pidFile).Start()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase17PrivilegeDropping() bool {
|
|
if this.flags.user != "" {
|
|
if this.Verb() {
|
|
fmt.Fprintf(os.Stderr,
|
|
"%s: dropping privilege to %s\n",
|
|
os.Args[0], this.flags.user)
|
|
}
|
|
user, group, _ := strings.Cut(this.flags.user, ":")
|
|
err := dropPrivelege(user, group)
|
|
fmt.Fprintf(os.Stderr, "%s: could not drop privilege %v\n", os.Args[0], err)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase20LogSwitching() bool {
|
|
if this.flags.logDirectory != "" {
|
|
directory := this.flags.logDirectory
|
|
if this.Verb() {
|
|
fmt.Fprintf(os.Stderr,
|
|
"%s: logging to %s\n",
|
|
os.Args[0], directory)
|
|
}
|
|
directory, err := filepath.Abs(directory)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: could not rotate logs: %v\n", os.Args[0], err)
|
|
return false
|
|
}
|
|
logger, err := rotate.New(directory)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: could not rotate logs: %v\n", os.Args[0], err)
|
|
return false
|
|
}
|
|
defer logger.Close()
|
|
log.SetOutput(logger)
|
|
}
|
|
log.Printf("====== [%s] START =======", this.name)
|
|
log.Printf("(i) (20) CAMFISH environment %s", version)
|
|
logActors(All())
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase30ConfigurationParsing() bool {
|
|
if this.Verb() { log.Println("... (30) parsing configuration") }
|
|
// blank config if nothing happens
|
|
this.conf = make(iniConfig)
|
|
// get list of config files
|
|
paths, err := configFiles(this.name)
|
|
if err != nil {
|
|
log.Println("!!! (30) could not determine location of file(s):", err)
|
|
return true
|
|
}
|
|
if this.flags.configFile != "" {
|
|
paths = append(paths, this.flags.configFile)
|
|
}
|
|
// parse every config and merge them all
|
|
configs := make([]iniConfig, 0, len(paths))
|
|
for _, path := range paths {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
log.Println("!!! (30) file present but inaccessible:", err)
|
|
} else if path == this.flags.configFile {
|
|
log.Println("!!! (30)", err)
|
|
}
|
|
continue
|
|
}
|
|
defer file.Close()
|
|
config, err := DecodeINI(path, file)
|
|
if err != nil {
|
|
log.Println("!!! (30) could not parse:", err)
|
|
continue
|
|
}
|
|
configs = append(configs, config.(iniConfig))
|
|
}
|
|
this.conf = mergeINI(configs...)
|
|
if this.Verb() { log.Println(".// (30) parsed configuration") }
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase40ConfigurationProcessing() bool {
|
|
if this.Verb() { log.Println("... (40) processing configuration") }
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
for _, actor := range sortActors(actors, actors.configProcessor.all()) {
|
|
err := actor.ProcessConfig(this.conf)
|
|
if err != nil {
|
|
log.Println("XXX (50) could not process configuration:", err)
|
|
return false
|
|
}
|
|
}
|
|
if this.Verb() { log.Println(".// (40) processed configuration") }
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase50ConfigurationApplication() bool {
|
|
if this.Verb() { log.Println("... (50) applying configuration") }
|
|
err := this.applyConfig()
|
|
if err != nil {
|
|
log.Println("XXX (50) could not apply configuration:", err)
|
|
return false
|
|
}
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
for _, actor := range sortActors(actors, actors.configurable.all()) {
|
|
err := actor.Configure(this.conf)
|
|
if err != nil {
|
|
log.Printf (
|
|
"XXX (50) could not apply configuration to %s: %v",
|
|
actor.(Actor).Type(), err)
|
|
return false
|
|
}
|
|
}
|
|
if this.Verb() { log.Println(".// (50) applied configuration") }
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase60Initialization() bool {
|
|
if this.Verb() { log.Println("... (60) initializing") }
|
|
var initializable []Initializable
|
|
func() {
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
initializable = actors.initializable.all()
|
|
}()
|
|
if err := this.initializeActors(this.ctx, initializable...); err != nil {
|
|
log.Println(".// (60) failed to initialize:", err)
|
|
return false
|
|
}
|
|
if this.Verb() { log.Println(".// (60) initialized") }
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase70Running() bool {
|
|
defer this.Done(nil)
|
|
if this.Verb() { log.Println("... (70) starting up") }
|
|
this.running.Store(true)
|
|
defer this.running.Store(false)
|
|
func() {
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
for _, actor := range actors.runnable.all() {
|
|
this.start(actor)
|
|
}
|
|
|
|
}()
|
|
log.Println(".// (70) startup sequence complete")
|
|
// await context cancellation or waitgroup completion
|
|
wgChannel := make(chan struct { }, 1)
|
|
go func() {
|
|
this.group.Wait()
|
|
wgChannel <- struct { } { }
|
|
}()
|
|
select {
|
|
case <- this.ctx.Done():
|
|
if this.Verb() { log.Println("(i) (70) canceled") }
|
|
case <- wgChannel:
|
|
if this.Verb() { log.Println("(i) (70) all actors have finished") }
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase70_5Trimming() bool {
|
|
if this.Verb() { log.Println("... (70.5) trimming") }
|
|
var trimmable []Trimmable
|
|
func() {
|
|
actors, done := this.actors.RBorrow()
|
|
defer done()
|
|
trimmable = actors.trimmable.all()
|
|
}()
|
|
if err := this.trimActors(this.ctx, trimmable...); err != nil {
|
|
log.Println(".// (70.5) failed to trim:", err)
|
|
return false
|
|
}
|
|
if this.Verb() { log.Println(".// (70.5) trimmed") }
|
|
return true
|
|
}
|
|
|
|
func (this *environment) phase80Shutdown() bool {
|
|
cause := context.Cause(this.ctx)
|
|
if cause != nil {
|
|
log.Println("XXX (80) shutting down because:", cause)
|
|
}
|
|
log.Println("... (80) waiting for actors to shut down")
|
|
defer func() {
|
|
log.Println(".// (80) shutdown succeeded, goodbye")
|
|
log.Printf("====== [%s] END =======", this.name)
|
|
}()
|
|
this.group.Wait()
|
|
return cause == nil
|
|
}
|