diff --git a/cmd/password.go b/cmd/password.go index eb815eb12..f5a6a7d39 100644 --- a/cmd/password.go +++ b/cmd/password.go @@ -1,6 +1,10 @@ package cmd import ( + "fmt" + + "github.com/evcc-io/evcc/server/db" + "github.com/evcc-io/evcc/server/db/settings" "github.com/spf13/cobra" ) @@ -12,3 +16,19 @@ var passwordCmd = &cobra.Command{ func init() { rootCmd.AddCommand(passwordCmd) } + +// fatalDatabase logs a database error and exits, hinting at file permissions +// when the database is not writable by the current user. +func fatalDatabase(err error) { + if db.IsReadonly(err) { + log.FATAL.Println("database is not writable; run this command as the user owning the database file, e.g. sudo -u evcc") + } + log.FATAL.Fatal(err) +} + +// persistPasswordSettings writes pending settings to the database +func persistPasswordSettings() { + if err := settings.Persist(); err != nil { + fatalDatabase(fmt.Errorf("cannot save settings: %w", err)) + } +} diff --git a/cmd/password_reset.go b/cmd/password_reset.go index b82503f68..91f82d998 100644 --- a/cmd/password_reset.go +++ b/cmd/password_reset.go @@ -26,7 +26,7 @@ func runPasswordReset(cmd *cobra.Command, args []string) { // setup persistence if err := configureDatabase(conf.Database); err != nil { - log.FATAL.Fatal(err) + fatalDatabase(err) } confirm, _ := cmd.Flags().GetBool(flagForce) @@ -44,5 +44,6 @@ func runPasswordReset(cmd *cobra.Command, args []string) { if confirm { auth.New().RemoveAdminPassword() + persistPasswordSettings() } } diff --git a/cmd/password_set.go b/cmd/password_set.go index ca327b86d..ff8276474 100644 --- a/cmd/password_set.go +++ b/cmd/password_set.go @@ -25,7 +25,7 @@ func runPasswordSet(cmd *cobra.Command, args []string) { // setup persistence if err := configureDatabase(conf.Database); err != nil { - log.FATAL.Fatal(err) + fatalDatabase(err) } prompt := &survey.Password{ @@ -40,7 +40,9 @@ func runPasswordSet(cmd *cobra.Command, args []string) { if password == "" { log.FATAL.Fatal("password cannot be empty") + } else if err := auth.New().SetAdminPassword(password); err != nil { + log.FATAL.Fatal(err) } else { - auth.New().SetAdminPassword(password) + persistPasswordSettings() } } diff --git a/server/db/db.go b/server/db/db.go index af8a69420..b10cffebf 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -13,6 +13,7 @@ import ( "github.com/mitchellh/go-homedir" "gorm.io/gorm" sqlite3 "modernc.org/sqlite" + sqlite3lib "modernc.org/sqlite/lib" ) var ( @@ -112,6 +113,13 @@ func Close() error { return db.Close() } +// IsReadonly reports whether err indicates a database file that is not +// writable by the current user. The code mask covers extended result codes. +func IsReadonly(err error) bool { + var serr *sqlite3.Error + return errors.As(err, &serr) && serr.Code()&0xff == sqlite3lib.SQLITE_READONLY +} + type backuper interface { NewBackup(string) (*sqlite3.Backup, error) NewRestore(string) (*sqlite3.Backup, error)