@@ -46,28 +46,7 @@ var fullRestore bool // Flag for the restore command
4646
4747var password string // Flag for rotate-key password
4848// TODO should be moved to project root
49- // main is the entry point of the application.
50- func main () {
51- // Install signal handler for graceful shutdown of bootstrap sessions
52- bootstrap .InstallSignalHandler ()
53-
54- // Set up cleanup store for bootstrap operations
55- defer func () {
56- if err := bootstrap .CleanupAllActiveSessions (); err != nil {
57- log .Printf ("Error during final cleanup: %w" , err )
58- } else {
59- log .Println ("Bootstrap cleanup complete." )
60- }
61- }()
62-
63- rootCmd := NewRootCmd ()
64-
65- if err := rootCmd .Execute (); err != nil {
66- // TODO consider adding bootstrap logic
67- // The error is already printed by Cobra on failure.
68- os .Exit (1 )
69- }
70- }
49+ var appConfig config.Config
7150
7251func setupDefaultServices (cmd * cobra.Command , args []string ) error {
7352 // Load optional config file argument from cli
@@ -77,30 +56,47 @@ func setupDefaultServices(cmd *cobra.Command, args []string) error {
7756 }
7857
7958 // Load config
80- type Config struct {
81- Database struct {
82- Type string `mapstructure:"type"`
83- Dsn string `mapstructure:"dsn"`
84- } `mapstructure:"database"`
85- Language string `mapstructure:"language"`
86- }
87-
8859 defauls := map [string ]any {
8960 "database.type" : "sqlite" ,
9061 "database.dsn" : "./keymaster.db" ,
9162 "language" : "en" ,
9263 }
9364
94- config , err := config .LoadConfig [Config ](cmd , defauls , optional_config_path )
95- if err != nil {
65+ appConfig , err = config .LoadConfig [config.Config ](cmd , defauls , optional_config_path )
66+ // A "file not found" error is expected on first run, so we handle it specifically.
67+ // Other errors during loading are fatal.
68+ if errors .As (err , & viper.ConfigFileNotFoundError {}) {
69+ // This is the first run, or the config file was deleted. Create a default one.
70+ if writeErr := config .WriteConfigFile (& appConfig , false ); writeErr != nil {
71+ // Log a warning but don't fail, as the app can run on defaults.
72+ log .Printf ("Warning: could not write default config file: %v" , writeErr )
73+ }
74+ } else if err != nil {
75+ // Any other error during config loading is a problem.
9676 return fmt .Errorf ("error loading config: %w" , err )
9777 }
9878
79+ // Post-process config to ensure critical values are not empty, falling back to defaults.
80+ // This handles cases where the user's config file has empty values for these fields.
81+ // We also update viper's internal state to ensure subsequent saves are correct.
82+ if appConfig .Database .Type == "" {
83+ appConfig .Database .Type = defauls ["database.type" ].(string )
84+ viper .Set ("database.type" , appConfig .Database .Type )
85+ }
86+ if appConfig .Database .Dsn == "" {
87+ appConfig .Database .Dsn = defauls ["database.dsn" ].(string )
88+ viper .Set ("database.dsn" , appConfig .Database .Dsn )
89+ }
90+ if appConfig .Language == "" {
91+ appConfig .Language = defauls ["language" ].(string )
92+ viper .Set ("language" , appConfig .Language )
93+ }
94+
9995 // Initialize i18n
100- i18n .Init (config .Language )
96+ i18n .Init (appConfig .Language )
10197
10298 // Initialize the database
103- if err := db .InitDB (config .Database .Type , config .Database .Dsn ); err != nil {
99+ if err := db .InitDB (appConfig .Database .Type , appConfig .Database .Dsn ); err != nil {
104100 return errors .New (i18n .T ("config.error_init_db" , err ))
105101 }
106102
@@ -115,6 +111,27 @@ func setupDefaultServices(cmd *cobra.Command, args []string) error {
115111 return nil
116112}
117113
114+ // main is the entry point of the application.
115+ func main () {
116+ // Install signal handler for graceful shutdown of bootstrap sessions
117+ bootstrap .InstallSignalHandler ()
118+
119+ // Set up cleanup store for bootstrap operations
120+ defer func () {
121+ if err := bootstrap .CleanupAllActiveSessions (); err != nil {
122+ log .Printf ("Error during final cleanup: %w" , err )
123+ } else {
124+ log .Println ("Bootstrap cleanup complete." )
125+ }
126+ }()
127+
128+ rootCmd := NewRootCmd ()
129+
130+ if err := rootCmd .Execute (); err != nil {
131+ os .Exit (1 )
132+ }
133+ }
134+
118135func applyDefaultFlags (cmd * cobra.Command ) {
119136 cmd .Flags ().String ("database.type" , "sqlite" , "Database type (e.g., sqlite, postgres)" )
120137 cmd .Flags ().String ("database.dsn" , "./keymaster.db" , "Database connection string (DSN)" )
@@ -160,7 +177,7 @@ Running without a subcommand will launch the interactive TUI.`,
160177 PreRunE : setupDefaultServices ,
161178 Run : func (cmd * cobra.Command , args []string ) {
162179 // The database is already initialized by PersistentPreRunE.
163- i18n . Init ( viper . GetString ( "language" )) // Initialize i18n for the TUI
180+ // i18n is also initialized, so we can just run the TUI.
164181 tui .Run ()
165182 },
166183 Version : version ,
0 commit comments