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 }