camfish/phases.go
2024-12-31 01:27:53 -05:00

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
}