From 2521b8b17837d84df55daa9753a859dc6af71e72 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 2 May 2024 20:55:12 +0200 Subject: [PATCH 1/3] Guard against expiring tokens due to wrong database --- cmd/charger.go | 2 +- cmd/charger_ramp.go | 2 +- cmd/check_config.go | 2 +- cmd/device.go | 2 +- cmd/discuss.go | 2 +- cmd/dump.go | 2 +- cmd/flags.go | 3 +++ cmd/meter.go | 2 +- cmd/password_reset.go | 2 +- cmd/password_set.go | 2 +- cmd/root.go | 10 ++++++---- cmd/settings-get.go | 2 +- cmd/settings-set.go | 2 +- cmd/setup.go | 16 +++++++++++++++- cmd/tariff.go | 2 +- cmd/token.go | 2 +- cmd/vehicle.go | 2 +- 17 files changed, 38 insertions(+), 19 deletions(-) diff --git a/cmd/charger.go b/cmd/charger.go index e9f5e6cf77..6ba7cf8917 100644 --- a/cmd/charger.go +++ b/cmd/charger.go @@ -31,7 +31,7 @@ func init() { func runCharger(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/charger_ramp.go b/cmd/charger_ramp.go index abc759961f..70f52c9f26 100644 --- a/cmd/charger_ramp.go +++ b/cmd/charger_ramp.go @@ -66,7 +66,7 @@ func ramp(c api.Charger, digits int, delay time.Duration) { func runChargerRamp(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/check_config.go b/cmd/check_config.go index da265bb22b..6ba015ad14 100644 --- a/cmd/check_config.go +++ b/cmd/check_config.go @@ -19,7 +19,7 @@ func init() { } func runConfigCheck(cmd *cobra.Command, args []string) { - err := loadConfigFile(&conf) + err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed) if err != nil { log.FATAL.Println("config invalid:", err) diff --git a/cmd/device.go b/cmd/device.go index da5be89c24..1ece39b7d6 100644 --- a/cmd/device.go +++ b/cmd/device.go @@ -24,7 +24,7 @@ func init() { func runDevice(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/discuss.go b/cmd/discuss.go index 58132f203e..4cacb7a509 100644 --- a/cmd/discuss.go +++ b/cmd/discuss.go @@ -35,7 +35,7 @@ func errorString(err error) string { } func runDiscuss(cmd *cobra.Command, args []string) { - cfgErr := loadConfigFile(&conf) + cfgErr := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed) file, pathErr := filepath.Abs(cfgFile) if pathErr != nil { diff --git a/cmd/dump.go b/cmd/dump.go index 01ed3a60b8..d346e54835 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -45,7 +45,7 @@ func handle[T any](name string, h config.Handler[T]) config.Device[T] { func runDump(cmd *cobra.Command, args []string) { // load config - err := loadConfigFile(&conf) + err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed) // setup environment if err == nil { diff --git a/cmd/flags.go b/cmd/flags.go index 790d5d9854..bc3e470675 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -9,6 +9,9 @@ const ( flagHeaders = "log-headers" flagHeadersDescription = "Log headers" + flagIgnoreDatabase = "ignore-db" + flagIgnoreDatabaseDescription = "Run command ignoring service database" + flagBatteryMode = "battery-mode" flagBatteryModeDescription = "Set battery mode (normal, hold, charge)" flagBatteryModeWait = "battery-mode-wait" diff --git a/cmd/meter.go b/cmd/meter.go index d0ea6713c9..bc789815a3 100644 --- a/cmd/meter.go +++ b/cmd/meter.go @@ -25,7 +25,7 @@ func init() { func runMeter(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/password_reset.go b/cmd/password_reset.go index 78a6d205e4..9f9b352f4d 100644 --- a/cmd/password_reset.go +++ b/cmd/password_reset.go @@ -19,7 +19,7 @@ func init() { func runPasswordReset(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/password_set.go b/cmd/password_set.go index 1ee504a5ca..5775941e0c 100644 --- a/cmd/password_set.go +++ b/cmd/password_set.go @@ -19,7 +19,7 @@ func init() { func runPasswordSet(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/root.go b/cmd/root.go index 9654f2fd43..229a3d15d5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,7 +29,10 @@ import ( "github.com/spf13/viper" ) -const rebootDelay = 5 * time.Minute // delayed reboot on error +const ( + rebootDelay = 5 * time.Minute // delayed reboot on error + serviceDB = "/var/lib/evcc/evcc.db" +) var ( log = util.NewLogger("main") @@ -53,10 +56,9 @@ func init() { // global options rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file (default \"~/evcc.yaml\" or \"/etc/evcc.yaml\")") - rootCmd.PersistentFlags().BoolP("help", "h", false, "Help") - rootCmd.PersistentFlags().Bool(flagHeaders, false, flagHeadersDescription) + rootCmd.PersistentFlags().Bool(flagIgnoreDatabase, false, flagIgnoreDatabaseDescription) // config file options rootCmd.PersistentFlags().StringP("log", "l", "info", "Log level (fatal, error, warn, info, debug, trace)") @@ -107,7 +109,7 @@ func Execute() { func runRoot(cmd *cobra.Command, args []string) { // load config and re-configure logging after reading config file var err error - if cfgErr := loadConfigFile(&conf); errors.As(cfgErr, &viper.ConfigFileNotFoundError{}) { + if cfgErr := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); errors.As(cfgErr, &viper.ConfigFileNotFoundError{}) { log.INFO.Println("missing config file - switching into demo mode") if err := demoConfig(&conf); err != nil { log.FATAL.Fatal(err) diff --git a/cmd/settings-get.go b/cmd/settings-get.go index 12f4ecc881..59f0174733 100644 --- a/cmd/settings-get.go +++ b/cmd/settings-get.go @@ -24,7 +24,7 @@ func init() { func runSettingsGet(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/settings-set.go b/cmd/settings-set.go index cfdbabe2c0..23d5147ae7 100644 --- a/cmd/settings-set.go +++ b/cmd/settings-set.go @@ -23,7 +23,7 @@ func init() { func runSettingsSet(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/setup.go b/cmd/setup.go index 0fa77e7624..682fb8e3b4 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + "os" "regexp" "slices" "strconv" @@ -162,7 +163,7 @@ func nameValid(name string) error { return nil } -func loadConfigFile(conf *globalConfig) error { +func loadConfigFile(conf *globalConfig, checkDB bool) error { err := viper.ReadInConfig() if cfgFile = viper.ConfigFileUsed(); cfgFile == "" { @@ -177,6 +178,19 @@ func loadConfigFile(conf *globalConfig) error { } } + // check service database + if _, err := os.Stat(serviceDB); err == nil && conf.Database.Dsn != serviceDB && checkDB { + log.FATAL.Fatal(` + +Found systemd service database at "` + serviceDB + `", evcc has been invoked with database "` + conf.Database.Dsn + `". +Running evcc with vehicles configured in evcc.yaml may lead to expiring the yaml configuration's vehicle tokens. +This is due to the fact, that the token refresh will be saved to the local instead of the service's database. +If you have vehicles with touchy tokens like PSA or Tesla, make sure to remove vehicle configuration from the yaml file. + +If you now what you're doing, you can run evcc ignoring the service database with the --ignore-db flag. +`) + } + // parse log levels after reading config if err == nil { parseLogLevels() diff --git a/cmd/tariff.go b/cmd/tariff.go index b5cc3a3eb3..25ed72c16e 100644 --- a/cmd/tariff.go +++ b/cmd/tariff.go @@ -24,7 +24,7 @@ func init() { func runTariff(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { fatal(err) } diff --git a/cmd/token.go b/cmd/token.go index 05e785a0ab..752dc28d8f 100644 --- a/cmd/token.go +++ b/cmd/token.go @@ -24,7 +24,7 @@ func init() { func runToken(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { log.FATAL.Fatal(err) } diff --git a/cmd/vehicle.go b/cmd/vehicle.go index a052f66ee2..8ecbfde072 100644 --- a/cmd/vehicle.go +++ b/cmd/vehicle.go @@ -28,7 +28,7 @@ func init() { func runVehicle(cmd *cobra.Command, args []string) { // load config - if err := loadConfigFile(&conf); err != nil { + if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil { fatal(err) } From d8423a310f6089ecf23c893fed91d8165df983a0 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 2 May 2024 21:00:34 +0200 Subject: [PATCH 2/3] Check vehicle type --- cmd/setup.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/setup.go b/cmd/setup.go index 682fb8e3b4..b75bdbd3e6 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -163,6 +163,22 @@ func nameValid(name string) error { return nil } +func tokenDanger(conf []config.Named) bool { + problematic := []string{"tesla", "psa", "opel", "citroen", "ds", "peugeot"} + + for _, cc := range conf { + if slices.Contains(problematic, cc.Type) { + return true + } + template, ok := cc.Other["template"].(string) + if ok && cc.Type == "template" && slices.Contains(problematic, template) { + return true + } + } + + return false +} + func loadConfigFile(conf *globalConfig, checkDB bool) error { err := viper.ReadInConfig() @@ -179,7 +195,7 @@ func loadConfigFile(conf *globalConfig, checkDB bool) error { } // check service database - if _, err := os.Stat(serviceDB); err == nil && conf.Database.Dsn != serviceDB && checkDB { + if _, err := os.Stat(serviceDB); err == nil && checkDB && conf.Database.Dsn != serviceDB && tokenDanger(conf.Vehicles) { log.FATAL.Fatal(` Found systemd service database at "` + serviceDB + `", evcc has been invoked with database "` + conf.Database.Dsn + `". From 735897151963169622f88175baed7d3fa21d348b Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 3 May 2024 10:56:03 +0200 Subject: [PATCH 3/3] Update cmd/setup.go Co-authored-by: StefanSchoof <4662023+StefanSchoof@users.noreply.github.com> --- cmd/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/setup.go b/cmd/setup.go index b75bdbd3e6..66c472ba61 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -203,7 +203,7 @@ Running evcc with vehicles configured in evcc.yaml may lead to expiring the yaml This is due to the fact, that the token refresh will be saved to the local instead of the service's database. If you have vehicles with touchy tokens like PSA or Tesla, make sure to remove vehicle configuration from the yaml file. -If you now what you're doing, you can run evcc ignoring the service database with the --ignore-db flag. +If you know what you're doing, you can run evcc ignoring the service database with the --ignore-db flag. `) }