package cmd import ( "context" "fmt" "log" "log/slog" "os" "os/signal" "syscall" "git.bloy.org/mike/hasshelper/kiosk" "git.bloy.org/mike/hasshelper/web" _ "github.com/joho/godotenv/autoload" "github.com/lmittmann/tint" "github.com/spf13/cobra" "github.com/spf13/viper" ) var cfgFile string var rootCmd = &cobra.Command{ Use: "hasshelper", Short: "Helper for Home Assistant installations.", Run: rootCmdRun, } func rootCmdRun(cmd *cobra.Command, args []string) { var logLevel slog.Level logLevel.UnmarshalText([]byte(viper.GetString("loglevel"))) logger := slog.New( tint.NewHandler(os.Stdout, &tint.Options{ Level: logLevel, TimeFormat: "2006-01-02T15:04:05.999", NoColor: viper.GetString("deployment") == "prod", })) logger.Info("HASSHelper startup", "version", viper.GetString("version")) exitchan := make(chan bool) signalchan := make(chan os.Signal, 1) done := make(chan bool, 2) signal.Notify(signalchan, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { select { case <-signalchan: logger.Warn("received interrupt. Exiting") cancel() done <- true case <-exitchan: logger.Error("unexpected exit of component") cancel() done <- true } }() go web.Run(logger, exitchan, ctx) go kiosk.Run(logger, exitchan, ctx) logger.Debug("Waiting for exit") <-done } // Execute will kick off cobra's processing of the root command func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func init() { var userConfigDir, err = os.UserConfigDir() const dirName = "hasshelper" var defDir = fmt.Sprintf("%s%c%s", "/etc", os.PathSeparator, dirName) if err == nil { defDir = fmt.Sprintf("%s%c%s", userConfigDir, os.PathSeparator, dirName) viper.AddConfigPath(defDir) } else { log.Println("could not locate user config dir:", err) } rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", fmt.Sprintf("config file (default is %s%c%s)", defDir, os.PathSeparator, "hasshelper.toml")) cobra.OnInitialize(initConfig) viper.AddConfigPath(fmt.Sprintf("/etc%c%s", os.PathSeparator, dirName)) } func initConfig() { viper.Set("version", gitVersion) viper.SetDefault("deployment", "prod") viper.SetDefault("loglevel", "info") viper.SetEnvPrefix("HASS") viper.AutomaticEnv() if cfgFile != "" { viper.SetConfigFile(cfgFile) } if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { log.Printf("no config file found. Environment only") } else { log.Fatalf("error reading config file: %v\n", err) } } expected_config := []string{ "deployment", "image_dir", "version", "webserver_port", "mqtt_broker_url", "mqtt_broker_user", "mqtt_broker_password", "mqtt_presence_topic", "kiosk_cmd_screen_on", "kiosk_cmd_screen_off", } for _, key := range expected_config { if !viper.IsSet(key) { log.Fatalf("Missing configuration value: %s\n", key) os.Exit(1) } } }