Restructure device tree and remove soc server (#1060)
This commit is contained in:
parent
70da155c3d
commit
80177aa0ad
160 changed files with 60 additions and 1450 deletions
11
Makefile
11
Makefile
|
|
@ -102,13 +102,4 @@ image-update:
|
||||||
|
|
||||||
soc:
|
soc:
|
||||||
@echo Version: $(VERSION) $(BUILD_DATE)
|
@echo Version: $(VERSION) $(BUILD_DATE)
|
||||||
go build -o evcc-soc $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/client
|
go build -o evcc-soc $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/cmd/soc
|
||||||
|
|
||||||
server:
|
|
||||||
@echo Version: $(VERSION) $(BUILD_DATE)
|
|
||||||
go build -o soc-server $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/server
|
|
||||||
|
|
||||||
publish-server:
|
|
||||||
GOOS=linux GOARCH=amd64 go build -o soc-server $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/server
|
|
||||||
docker build -f soc/Dockerfile --platform linux/amd64 -t andig/evcc-cloud .
|
|
||||||
docker push andig/evcc-cloud
|
|
||||||
|
|
|
||||||
4
api/plugin.go
Normal file
4
api/plugin.go
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
// Custom meter/charger/vehicle type
|
||||||
|
const Custom = "custom"
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal"
|
|
||||||
"github.com/andig/evcc/provider"
|
"github.com/andig/evcc/provider"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +18,7 @@ type Charger struct {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registry.Add("default", NewConfigurableFromConfig)
|
registry.Add("default", NewConfigurableFromConfig)
|
||||||
registry.Add(internal.Custom, NewConfigurableFromConfig)
|
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigurableFromConfig creates a new configurable charger
|
// NewConfigurableFromConfig creates a new configurable charger
|
||||||
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
|
"github.com/andig/evcc/charger/easee"
|
||||||
"github.com/andig/evcc/core"
|
"github.com/andig/evcc/core"
|
||||||
"github.com/andig/evcc/internal/charger/easee"
|
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/andig/evcc/util/request"
|
"github.com/andig/evcc/util/request"
|
||||||
"github.com/andig/evcc/util/sponsor"
|
"github.com/andig/evcc/util/sponsor"
|
||||||
|
|
@ -58,7 +58,7 @@ func init() {
|
||||||
registry.Add("evsewifi", NewEVSEWifiFromConfig)
|
registry.Add("evsewifi", NewEVSEWifiFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||||
|
|
||||||
// NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config
|
// NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config
|
||||||
func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) {
|
func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/fritzdect"
|
"github.com/andig/evcc/charger/fritzdect"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/andig/evcc/util/request"
|
"github.com/andig/evcc/util/request"
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/keba"
|
"github.com/andig/evcc/charger/keba"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/nrgble"
|
"github.com/andig/evcc/charger/nrgble"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
"github.com/lunixbochs/struc"
|
"github.com/lunixbochs/struc"
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/openwb"
|
"github.com/andig/evcc/charger/openwb"
|
||||||
"github.com/andig/evcc/provider"
|
"github.com/andig/evcc/provider"
|
||||||
"github.com/andig/evcc/provider/mqtt"
|
"github.com/andig/evcc/provider/mqtt"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
|
|
@ -32,7 +32,7 @@ func init() {
|
||||||
registry.Add("phoenix-em-eth", NewPhoenixEMEthFromConfig)
|
registry.Add("phoenix-em-eth", NewPhoenixEMEthFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decoratePhoenixEMEth -b *PhoenixEMEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decoratePhoenixEMEth -b *PhoenixEMEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||||
|
|
||||||
// NewPhoenixEMEthFromConfig creates a Phoenix charger from generic config
|
// NewPhoenixEMEthFromConfig creates a Phoenix charger from generic config
|
||||||
func NewPhoenixEMEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
func NewPhoenixEMEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||||
|
|
@ -33,7 +33,7 @@ func init() {
|
||||||
registry.Add("phoenix-ev-eth", NewPhoenixEVEthFromConfig)
|
registry.Add("phoenix-ev-eth", NewPhoenixEVEthFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decoratePhoenixEVEth -b *PhoenixEVEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decoratePhoenixEVEth -b *PhoenixEVEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||||
|
|
||||||
// NewPhoenixEVEthFromConfig creates a Phoenix charger from generic config
|
// NewPhoenixEVEthFromConfig creates a Phoenix charger from generic config
|
||||||
func NewPhoenixEVEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
func NewPhoenixEVEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/tasmota"
|
"github.com/andig/evcc/charger/tasmota"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/andig/evcc/util/request"
|
"github.com/andig/evcc/util/request"
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/tplink"
|
"github.com/andig/evcc/charger/tplink"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ type Warp struct {
|
||||||
enabled bool // cache
|
enabled bool // cache
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||||
|
|
||||||
// NewWarp creates a new configurable charger
|
// NewWarp creates a new configurable charger
|
||||||
func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, error) {
|
func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, error) {
|
||||||
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger"
|
"github.com/andig/evcc/charger"
|
||||||
"github.com/andig/evcc/internal/meter"
|
"github.com/andig/evcc/meter"
|
||||||
"github.com/andig/evcc/internal/vehicle"
|
|
||||||
"github.com/andig/evcc/provider/mqtt"
|
"github.com/andig/evcc/provider/mqtt"
|
||||||
"github.com/andig/evcc/push"
|
"github.com/andig/evcc/push"
|
||||||
"github.com/andig/evcc/server"
|
"github.com/andig/evcc/server"
|
||||||
|
"github.com/andig/evcc/vehicle"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/andig/evcc/api/proto/pb"
|
||||||
"github.com/andig/evcc/core"
|
"github.com/andig/evcc/core"
|
||||||
"github.com/andig/evcc/hems"
|
"github.com/andig/evcc/hems"
|
||||||
"github.com/andig/evcc/provider/javascript"
|
"github.com/andig/evcc/provider/javascript"
|
||||||
"github.com/andig/evcc/provider/mqtt"
|
"github.com/andig/evcc/provider/mqtt"
|
||||||
"github.com/andig/evcc/push"
|
"github.com/andig/evcc/push"
|
||||||
"github.com/andig/evcc/server"
|
"github.com/andig/evcc/server"
|
||||||
"github.com/andig/evcc/soc/proto/pb"
|
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/andig/evcc/util/cloud"
|
"github.com/andig/evcc/util/cloud"
|
||||||
"github.com/andig/evcc/util/pipe"
|
"github.com/andig/evcc/util/pipe"
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/vehicle"
|
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
|
"github.com/andig/evcc/vehicle"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/internal/charger/keba"
|
"github.com/andig/evcc/charger/keba"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/internal/meter/sma"
|
"github.com/andig/evcc/meter/sma"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
5
go.mod
5
go.mod
|
|
@ -14,7 +14,6 @@ require (
|
||||||
github.com/containrrr/shoutrrr v0.4.4
|
github.com/containrrr/shoutrrr v0.4.4
|
||||||
github.com/deepmap/oapi-codegen v1.6.1 // indirect
|
github.com/deepmap/oapi-codegen v1.6.1 // indirect
|
||||||
github.com/denisbrodbeck/machineid v1.0.1
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
|
||||||
github.com/dylanmei/iso8601 v0.1.0
|
github.com/dylanmei/iso8601 v0.1.0
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.4
|
github.com/eclipse/paho.mqtt.golang v1.3.4
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
|
|
@ -61,8 +60,6 @@ require (
|
||||||
github.com/prometheus/common v0.23.0 // indirect
|
github.com/prometheus/common v0.23.0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
|
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
|
||||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa
|
|
||||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
|
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/spf13/afero v1.6.0 // indirect
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
|
|
@ -72,7 +69,7 @@ require (
|
||||||
github.com/thoas/go-funk v0.8.0
|
github.com/thoas/go-funk v0.8.0
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
||||||
github.com/volkszaehler/mbmd v0.0.0-20210117183837-59dcc46d62d4
|
github.com/volkszaehler/mbmd v0.0.0-20210117183837-59dcc46d62d4
|
||||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
golang.org/x/mod v0.4.2 // indirect
|
||||||
golang.org/x/net v0.0.0-20210508051633-16afe75a6701
|
golang.org/x/net v0.0.0-20210508051633-16afe75a6701
|
||||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c
|
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c
|
||||||
|
|
|
||||||
5
go.sum
5
go.sum
|
|
@ -135,7 +135,6 @@ github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpA
|
||||||
github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
|
github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
|
@ -630,10 +629,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA=
|
|
||||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
|
||||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
|
|
||||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
// Custom plugin type
|
|
||||||
const Custom = "custom"
|
|
||||||
|
|
@ -5,17 +5,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal"
|
|
||||||
"github.com/andig/evcc/provider"
|
"github.com/andig/evcc/provider"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registry.Add("default", NewConfigurableFromConfig)
|
registry.Add("default", NewConfigurableFromConfig)
|
||||||
registry.Add(internal.Custom, NewConfigurableFromConfig)
|
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||||
|
|
||||||
// NewConfigurableFromConfig creates api.Meter from config
|
// NewConfigurableFromConfig creates api.Meter from config
|
||||||
func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) {
|
func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||||
|
|
@ -27,7 +27,7 @@ func init() {
|
||||||
registry.Add("modbus", NewModbusFromConfig)
|
registry.Add("modbus", NewModbusFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateModbus -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateModbus -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||||
|
|
||||||
// NewModbusFromConfig creates api.Meter from config
|
// NewModbusFromConfig creates api.Meter from config
|
||||||
func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) {
|
func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/charger/openwb"
|
"github.com/andig/evcc/charger/openwb"
|
||||||
"github.com/andig/evcc/provider"
|
"github.com/andig/evcc/provider"
|
||||||
"github.com/andig/evcc/provider/mqtt"
|
"github.com/andig/evcc/provider/mqtt"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/meter/sma"
|
"github.com/andig/evcc/meter/sma"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ func init() {
|
||||||
registry.Add("sma", NewSMAFromConfig)
|
registry.Add("sma", NewSMAFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateSMA -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateSMA -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||||
|
|
||||||
// NewSMAFromConfig creates a SMA Meter from generic config
|
// NewSMAFromConfig creates a SMA Meter from generic config
|
||||||
func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) {
|
func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||||
|
|
@ -3,7 +3,7 @@ package meter
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/andig/evcc/internal/meter/sma"
|
"github.com/andig/evcc/meter/sma"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
"github.com/andig/evcc/api"
|
||||||
"github.com/andig/evcc/internal/meter/powerwall"
|
"github.com/andig/evcc/meter/powerwall"
|
||||||
"github.com/andig/evcc/util"
|
"github.com/andig/evcc/util"
|
||||||
"github.com/andig/evcc/util/request"
|
"github.com/andig/evcc/util/request"
|
||||||
)
|
)
|
||||||
|
|
@ -26,7 +26,7 @@ func init() {
|
||||||
registry.Add("tesla", NewTeslaFromConfig)
|
registry.Add("tesla", NewTeslaFromConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateTesla -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
//go:generate go run ../cmd/tools/decorate.go -f decorateTesla -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||||
|
|
||||||
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
|
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
|
||||||
func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
|
func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# STEP 1 build executable binary
|
|
||||||
# FROM golang:1.16-alpine as builder
|
|
||||||
|
|
||||||
# # Install git + SSL ca certificates.
|
|
||||||
# # Git is required for fetching the dependencies.
|
|
||||||
# # Ca-certificates is required to call HTTPS endpoints.
|
|
||||||
# RUN apk update && apk add --no-cache git ca-certificates tzdata alpine-sdk && update-ca-certificates
|
|
||||||
|
|
||||||
# WORKDIR /build
|
|
||||||
|
|
||||||
# # install go tools and cache modules
|
|
||||||
# COPY Makefile .
|
|
||||||
# COPY go.mod .
|
|
||||||
# COPY go.sum .
|
|
||||||
# RUN make install
|
|
||||||
# RUN go mod download
|
|
||||||
|
|
||||||
# # build
|
|
||||||
# COPY . .
|
|
||||||
# RUN make server
|
|
||||||
|
|
||||||
FROM alpine:3.13 as builder
|
|
||||||
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
|
|
||||||
|
|
||||||
# STEP 2 build a small image including module support
|
|
||||||
FROM alpine:3.13
|
|
||||||
|
|
||||||
ENV TZ=Europe/Berlin
|
|
||||||
|
|
||||||
# Import from builder
|
|
||||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
COPY soc-server /usr/local/bin/soc-server
|
|
||||||
|
|
||||||
CMD [ "soc-server" ]
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIE1DCCArwCCQC/TbpAPhcOQTANBgkqhkiG9w0BAQsFADAsMQswCQYDVQQGEwJE
|
|
||||||
RTEdMBsGCSqGSIb3DQEJARYOY3B1aWRsZUBnbXguZGUwHhcNMjEwMzMxMDc1ODAz
|
|
||||||
WhcNMjIwMzMxMDc1ODAzWjAsMQswCQYDVQQGEwJERTEdMBsGCSqGSIb3DQEJARYO
|
|
||||||
Y3B1aWRsZUBnbXguZGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+
|
|
||||||
noDTXbidh/5RFfsLlMJbfMn4Vu91oJb4MM41RQM8UOr3SRU/ZzcoLfWJe2ePpEDp
|
|
||||||
ZL4piobs8EIjFcWU2C/8La0rygbcDDqUJkW/nViH0Drs5ctgJl/uYeiDLnZiSZO+
|
|
||||||
PBoOA0trXxqlJPxuEseJbFaLvVE81DsLBnltDPqhJZEUJPEUIjtfStOhQM0f5YoO
|
|
||||||
Greewk7P5LUkTAuTIz+xk6uKnC1YF89SKmdPMH26Rcy6q1IqhEQG3tjUliJZiKS9
|
|
||||||
eQlnp9gskqNGop9gEsmFIMnhlrTUsVU2q38zLDFcmNDbNkRvv4Tf+7jHo4tLs02m
|
|
||||||
/fMTikdMNCRMOcJKbdobcwXq4Ghvc85xeM4/8wIQHEDFbLzmxi/4ibFfJjOfQOe9
|
|
||||||
VpK+TbeSBTxBw42CH9V0TUOz/LELYkMrJ+zfwMCvTV4Eodmc0gjwiy9aIYdpZXKF
|
|
||||||
ueqGGljhVCq2XqkaSATFu5CsdPZrfUszLiAVqpuVTMjy3VQ9ICzz1Bd5ICBRFt6l
|
|
||||||
fDsvfB6SgmB9r1bC+yr/tqe/unzUPajL5mZwn7Jev0uOl6mBNmQpcIEne+9cKedf
|
|
||||||
vPCv/tUQpwpz6blGacEtTxuM+fWDk+CLpPnId/4PEnIQWRFE2aXQ4nrJSwu3O5+k
|
|
||||||
vuZNWety8MOSkuQr0toH//p3VNHOtT7L4JWSlDfTZQIDAQABMA0GCSqGSIb3DQEB
|
|
||||||
CwUAA4ICAQANoLbh4Di1A485ggnKfp9ykfVvg+NduGm+eqr27be033N6IJ7fuKmF
|
|
||||||
7Ki1E+sp0IJDMdyikbqoHdbroHu2chwp/1GzFxhx3Vo7kswS/ehSxWpHjhtrGjAF
|
|
||||||
tSDUFR9q4C+km2A3k1ZNyd9C1w+R9Lr16j9lBQoGWmUgFsRf8ED16FMMSK/1mCC0
|
|
||||||
1VSydYnYhKmPZxMByTozGTV97wihA0XqXtadbkQoyvcnEuarBvEX3mPA3effqcEG
|
|
||||||
rreeDp1yzYtQRW85ZASaqgF5CYKhe9NekurZ5Jd+2mQPYRWPFpIFsSHoOLqbfcN1
|
|
||||||
/S0Si6LZWq0Mi8cbSi/zq+Eh7Q0w+EXP6Goh0A7M4e1dKt+cfGQpeyeg3cF49Cda
|
|
||||||
RrTkSv2Sl2jbrShqAsG/HNhyBaI/gtLGi9tig9wAoV2zjGw1Ehs0GcwFq2g/f6Zv
|
|
||||||
W7rdZIBZT1RIQLczuwsEhv6cFrSM5OU2f5fuKnmeI6uvyz0jRqiMncYRjwI6LotB
|
|
||||||
sWmdTWi6xsctYirJ3Yip5Tqm01asyUuIeiT+eQ6I9CfHLiGBXEzxlK5PrFqfD7gK
|
|
||||||
LfB1gCN7SAlxKGepmFMwfF+fDrsurL2T2ePxaaNBLjRAzmpSpH0NedMd+eanuHXV
|
|
||||||
a/iOL2XmVis7iuFyk5M2XtLFfYBff+mxSPA8d9u5kCpoel0tx3iLmw==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package ca
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed ca-cert.pem
|
|
||||||
var cert []byte
|
|
||||||
|
|
||||||
func PEM() []byte {
|
|
||||||
copy := bytes.NewBuffer(cert)
|
|
||||||
return copy.Bytes()
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set +ex
|
|
||||||
|
|
||||||
rm *.pem
|
|
||||||
|
|
||||||
# 1. Generate CA's private key and self-signed certificate
|
|
||||||
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
|
||||||
|
|
||||||
echo "CA's self-signed certificate"
|
|
||||||
openssl x509 -in ca-cert.pem -noout -text
|
|
||||||
|
|
||||||
# 2. Generate web server's private key and certificate signing request (CSR)
|
|
||||||
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
|
||||||
|
|
||||||
# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
|
|
||||||
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
|
|
||||||
|
|
||||||
echo "Server's signed certificate"
|
|
||||||
openssl x509 -in server-cert.pem -noout -text
|
|
||||||
|
|
||||||
# 4. Generate client's private key and certificate signing request (CSR)
|
|
||||||
openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
|
||||||
|
|
||||||
# 5. Use CA's private key to sign client's CSR and get back the signed certificate
|
|
||||||
openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf
|
|
||||||
|
|
||||||
echo "Client's signed certificate"
|
|
||||||
openssl x509 -in client-cert.pem -noout -text
|
|
||||||
|
|
||||||
rm *-req.pem
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
_ "embed"
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed server-cert.pem
|
|
||||||
var cert []byte
|
|
||||||
|
|
||||||
//--go:embed server-key.pem
|
|
||||||
var key []byte
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
key, err = base64.StdEncoding.DecodeString(util.Getenv("SERVER_KEY"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PEM() []byte {
|
|
||||||
copy := bytes.NewBuffer(cert)
|
|
||||||
return copy.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Certificate() (tls.Certificate, error) {
|
|
||||||
return tls.X509KeyPair(cert, key)
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIE+TCCAuGgAwIBAgIJALu7hrWUWtC8MA0GCSqGSIb3DQEBBQUAMCwxCzAJBgNV
|
|
||||||
BAYTAkRFMR0wGwYJKoZIhvcNAQkBFg5jcHVpZGxlQGdteC5kZTAeFw0yMTAzMzEw
|
|
||||||
NzU4MDVaFw0yMTA1MzAwNzU4MDVaMCwxCzAJBgNVBAYTAkRFMR0wGwYJKoZIhvcN
|
|
||||||
AQkBFg5jcHVpZGxlQGdteC5kZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
|
|
||||||
ggIBALzv8oy3yl+sH+7fezx6aDJWf77aCaZmBrQyupXmd8InJazRx8ScmpO+LJ7i
|
|
||||||
ZtVA1YuHbL2qZaV5SBk15UkRaVRRNqVitQmaRFYDrrAtDMosOhVNGOSorGnrlOqP
|
|
||||||
eu8LD/bWwZ57NqWE2z7ZTnqNNdgX1urcvzs7jmopKl4i71sKHAJ1jdzEYl+7l7r1
|
|
||||||
HdtKU4K6axQ2BBP6dHB9v0wOJQzfER76mSWe+hotCTdIiGnZhaIO5NoW31i3KkPi
|
|
||||||
277bLse5wGGbD1FvzLPaWf0HyruxARPs49kxuxnYtJ+KUDk5LXpDC6MM2QcUdV/8
|
|
||||||
HVkTadONW/gVFKWS7u21NrWH2342K0lz4FyNEwiQGmTCnyT4A9DQmdfWirnXsf4M
|
|
||||||
Jkag7fWqK/dEFIJK14RHZx+i53UBIoLu6mTT+MrHzdXRNewg9jlSwOYROG80DEMh
|
|
||||||
WIbTcYqUk9XPSUzzaeg2FuP7ZFEmpbsltsztaEQikd5PO9Dkl+Qzm5eiQMwNxJX5
|
|
||||||
bgb6eLQOI+HOtQHSWVTBXeM7Vf0JCfA7RAvKgRk4lK6JGijzGBSLuCLzXAPygiqb
|
|
||||||
59KgYf2EDaSmjzU+ZCLDR6l81SUDVnQj75obQt/TPBoVgwlCeOfFvLGGXY6tzJrp
|
|
||||||
8yH59MWpkRB4yQImTzmnoZ5klUjwuShi2bWLFiI0+FmXNuitAgMBAAGjHjAcMBoG
|
|
||||||
A1UdEQQTMBGCCSouZXZjYy5pb4cEAAAAADANBgkqhkiG9w0BAQUFAAOCAgEAlmh0
|
|
||||||
fR34BT1LTZcTZjFgk3a0/MaZ4zK8zdlwtQFHSlOh46t+TL+jps2bmLu9IlZxLF8E
|
|
||||||
lgSzLu5bWeP8H9HuBgji41RMtwvNUqSFUUMn08Dh/78lFISneqyJZ2CeRzwPYyYs
|
|
||||||
5xi7DBXitFbvtWj5GP3NQaDhsXAPoXtKXuqab3IE6eSwTJOxm5PJnvVdRTFSRQJW
|
|
||||||
W3qQFICQxMm6Oif+yCGZLgROj7sJWOOKJecevtAxYebOCWx5nxbXxAvOY3wiRerG
|
|
||||||
xsS3pT6Q5bVo5OxUjPeRb3Fe969JJEMqGaaGTGGtYP2IVkwarwzKBtcpwgG3NJYv
|
|
||||||
EYG/MEFkpBl6yYJRRB5D7EyPcBRcDHn53sJqnP+Y8Xq40tjfWhxZ9I+N8VWK9nwS
|
|
||||||
a9CKUHlwpB6aT/J1GNMiU2RXhQWgDv8qma6Vik7RH5ZmDMmtG18GERHTNdiGKk9b
|
|
||||||
v5xTphWMHl0NtcGPbPoc8PCrIVkb0frGAX5g6UVCHFfSkiUwLDV1zPhv/tLBa5hX
|
|
||||||
ZzW0wJzs6nDMj9NleyN1B1UY3mbyNKS2Ti9M9NBNEc+IEUdyj36RNmuvDmVNYTZ+
|
|
||||||
eYk28CFfkVT7XQjI1hIpBtu5JqBqic5Kzxg/Xwjmt1izpQBy8C7FFMb0Zs3uvrjo
|
|
||||||
FHk0eyJ/i830ZKCw8Ovx1fgMElZz8AZb/17aeRY=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
subjectAltName=DNS:*.evcc.io,IP:0.0.0.0
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/soc/server/auth/sponsor"
|
|
||||||
"github.com/andig/evcc/util"
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Issuer = "evcc.io"
|
|
||||||
TokenExpiry = 365 // days
|
|
||||||
tokenSecret = util.Getenv("JWT_TOKEN_SECRET")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mux sync.Mutex
|
|
||||||
updated time.Time
|
|
||||||
authorized = make(map[string]bool)
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateInterval = 5 * time.Minute
|
|
||||||
|
|
||||||
type Claims struct {
|
|
||||||
UsernameDeprecated string `json:"username,omitempty"`
|
|
||||||
Username string `json:"usr,omitempty"`
|
|
||||||
SponsorExempt bool `json:"spe,omitempty"`
|
|
||||||
jwt.StandardClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyFunc(token *jwt.Token) (interface{}, error) {
|
|
||||||
return []byte(tokenSecret), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AuthorizedToken(claims Claims) (string, error) {
|
|
||||||
claims.Issuer = Issuer
|
|
||||||
claims.IssuedAt = time.Now().Unix()
|
|
||||||
claims.ExpiresAt = time.Now().Add(24 * time.Hour * time.Duration(TokenExpiry)).Unix()
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
return token.SignedString([]byte(tokenSecret))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseToken(token string) (*Claims, error) {
|
|
||||||
jwt, err := jwt.ParseWithClaims(token, &Claims{}, keyFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, ok := jwt.Claims.(*Claims)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("claims invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
return claims, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsSponsor(login string) (bool, error) {
|
|
||||||
mux.Lock()
|
|
||||||
defer mux.Unlock()
|
|
||||||
|
|
||||||
if _, ok := authorized[login]; !ok {
|
|
||||||
if time.Since(updated) < updateInterval {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
all, err := sponsor.Get(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updated = time.Now()
|
|
||||||
|
|
||||||
for _, s := range all {
|
|
||||||
authorized[s.Login] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := authorized[login]
|
|
||||||
return ok, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
package sponsor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/util"
|
|
||||||
"github.com/shurcooL/githubv4"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var client *githubv4.Client
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
token := util.Getenv("GITHUB_TOKEN")
|
|
||||||
|
|
||||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
|
||||||
tc := oauth2.NewClient(context.Background(), ts)
|
|
||||||
|
|
||||||
client = githubv4.NewClient(tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source: https://github.com/tj/sponsors-api/blob/master/server.go
|
|
||||||
|
|
||||||
// Sponsor model
|
|
||||||
type Sponsor struct {
|
|
||||||
Name string
|
|
||||||
Login string
|
|
||||||
AvatarURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// sponsorships query: https://docs.github.com/en/graphql/reference/objects
|
|
||||||
type sponsorships struct {
|
|
||||||
Viewer struct {
|
|
||||||
Login string
|
|
||||||
SponsorshipsAsMaintainer struct {
|
|
||||||
PageInfo struct {
|
|
||||||
EndCursor string
|
|
||||||
HasNextPage bool
|
|
||||||
}
|
|
||||||
|
|
||||||
Edges []struct {
|
|
||||||
Node struct {
|
|
||||||
Sponsor Sponsor
|
|
||||||
}
|
|
||||||
Cursor string
|
|
||||||
}
|
|
||||||
} `graphql:"sponsorshipsAsMaintainer(first: 100, after: $cursor, includePrivate: true)"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the list of sponsors
|
|
||||||
func Get(ctx context.Context) ([]Sponsor, error) {
|
|
||||||
var sponsors []Sponsor
|
|
||||||
var q sponsorships
|
|
||||||
var cursor string
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := client.Query(ctx, &q, map[string]interface{}{
|
|
||||||
"cursor": githubv4.String(cursor),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, edge := range q.Viewer.SponsorshipsAsMaintainer.Edges {
|
|
||||||
sponsor := edge.Node.Sponsor
|
|
||||||
sponsors = append(sponsors, sponsor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !q.Viewer.SponsorshipsAsMaintainer.PageInfo.HasNextPage {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = q.Viewer.SponsorshipsAsMaintainer.PageInfo.EndCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
return sponsors, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
compile "github.com/andig/evcc/server"
|
|
||||||
"github.com/andig/evcc/soc/server/server"
|
|
||||||
"github.com/andig/evcc/soc/server/ui"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.Printf("soc-server %s (%s)", compile.Version, compile.Commit)
|
|
||||||
|
|
||||||
go ui.Run()
|
|
||||||
go server.Run()
|
|
||||||
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/soc/proto/pb"
|
|
||||||
"github.com/andig/evcc/soc/server/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthServer struct {
|
|
||||||
pb.UnimplementedAuthServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AuthServer) IsAuthorized(ctx context.Context, r *pb.AuthRequest) (*pb.AuthReply, error) {
|
|
||||||
authorized, _, claims, err := isAuthorized(r)
|
|
||||||
|
|
||||||
// track auth requests
|
|
||||||
log.Printf(claims.Subject+": %v", authorized)
|
|
||||||
|
|
||||||
res := &pb.AuthReply{Authorized: authorized}
|
|
||||||
if err == nil {
|
|
||||||
res.Subject = claims.Subject
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type tokenizer interface {
|
|
||||||
GetToken() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAuthorized(r tokenizer) (bool, string, *auth.Claims, error) {
|
|
||||||
token := r.GetToken()
|
|
||||||
|
|
||||||
claims, err := auth.ParseToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return false, token, claims, err
|
|
||||||
}
|
|
||||||
|
|
||||||
authorized := claims.SponsorExempt
|
|
||||||
if !authorized {
|
|
||||||
authorized, err = auth.IsSponsor(claims.Subject)
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorized, token, claims, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/soc/cert/ca"
|
|
||||||
"github.com/andig/evcc/soc/cert/server"
|
|
||||||
"github.com/andig/evcc/soc/proto/pb"
|
|
||||||
"github.com/andig/evcc/util"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
port = util.Getenv("GRPC_PORT", "8080")
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
if tlsConfig, err = loadTLSCredentials(); err != nil {
|
|
||||||
log.Fatalf("cannot load TLS credentials: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerMetrics()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTLSCredentials() (*tls.Config, error) {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
if !certPool.AppendCertsFromPEM(ca.PEM()) {
|
|
||||||
return nil, fmt.Errorf("failed to add client CA's certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load server's certificate and private key
|
|
||||||
serverCert, err := server.Certificate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the credentials and return it
|
|
||||||
config := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{serverCert},
|
|
||||||
ClientAuth: tls.VerifyClientCertIfGiven,
|
|
||||||
ClientCAs: certPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run() {
|
|
||||||
log.Println("grpc:", ":"+port)
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", ":"+port)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverOptions := []grpc.ServerOption{}
|
|
||||||
if tlsConfig != nil {
|
|
||||||
serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
|
||||||
}
|
|
||||||
grpcServer := grpc.NewServer(serverOptions...)
|
|
||||||
|
|
||||||
pb.RegisterVehicleServer(grpcServer, &VehicleServer{
|
|
||||||
registry: make(map[string][]*VehicleContainer),
|
|
||||||
})
|
|
||||||
pb.RegisterAuthServer(grpcServer, &AuthServer{})
|
|
||||||
|
|
||||||
log.Fatal(grpcServer.Serve(listener))
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
promActiveVehicles *prometheus.GaugeVec
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerMetrics() {
|
|
||||||
promActiveVehicles = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: "soc",
|
|
||||||
Name: "active_vehicles",
|
|
||||||
}, []string{"token", "brand"})
|
|
||||||
|
|
||||||
if err := prometheus.Register(promActiveVehicles); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveVehiclesMetric(token, typ string, delta int) {
|
|
||||||
g, err := promActiveVehicles.GetMetricWith(prometheus.Labels{
|
|
||||||
"token": token,
|
|
||||||
"brand": typ,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get metrics:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Add(float64(delta))
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/api"
|
|
||||||
"github.com/andig/evcc/internal/vehicle"
|
|
||||||
"github.com/andig/evcc/soc/proto/pb"
|
|
||||||
"github.com/andig/evcc/util/cloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
var vehicleID int64
|
|
||||||
|
|
||||||
type VehicleContainer struct {
|
|
||||||
id int64
|
|
||||||
hash []byte
|
|
||||||
vehicle api.Vehicle
|
|
||||||
}
|
|
||||||
|
|
||||||
type VehicleServer struct {
|
|
||||||
registry map[string][]*VehicleContainer
|
|
||||||
pb.UnimplementedVehicleServer
|
|
||||||
}
|
|
||||||
|
|
||||||
type vehicler interface {
|
|
||||||
tokenizer
|
|
||||||
GetVehicleId() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VehicleServer) vehicle(r vehicler) (api.Vehicle, error) {
|
|
||||||
token := r.GetToken()
|
|
||||||
vehicles, ok := s.registry[token]
|
|
||||||
if !ok {
|
|
||||||
return nil, cloud.ErrVehicleNotAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
id := r.GetVehicleId()
|
|
||||||
for _, c := range vehicles {
|
|
||||||
if c.id == id {
|
|
||||||
return c.vehicle, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, cloud.ErrVehicleNotAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringMapToInterface(in map[string]string) map[string]interface{} {
|
|
||||||
res := make(map[string]interface{})
|
|
||||||
|
|
||||||
for k, v := range in {
|
|
||||||
res[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VehicleServer) addVehicleToRegistry(token, typ string, config map[string]string, v api.Vehicle) int64 {
|
|
||||||
id := atomic.AddInt64(&vehicleID, 1)
|
|
||||||
|
|
||||||
// sort config keys
|
|
||||||
var keys []string
|
|
||||||
for k := range config {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
// hash config
|
|
||||||
h := sha256.New()
|
|
||||||
_, _ = h.Write([]byte(typ))
|
|
||||||
for _, k := range keys {
|
|
||||||
_, _ = h.Write([]byte(k))
|
|
||||||
_, _ = h.Write([]byte(config[k]))
|
|
||||||
}
|
|
||||||
hash := h.Sum(nil)
|
|
||||||
|
|
||||||
// find vehicle by hash and update it
|
|
||||||
for _, c := range s.registry[token] {
|
|
||||||
if bytes.Equal(c.hash, hash) {
|
|
||||||
c.vehicle = v
|
|
||||||
c.id = id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register new vehicle
|
|
||||||
c := VehicleContainer{
|
|
||||||
id: id,
|
|
||||||
hash: hash,
|
|
||||||
vehicle: v,
|
|
||||||
}
|
|
||||||
s.registry[token] = append(s.registry[token], &c)
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
_, _ = h.Write([]byte(token))
|
|
||||||
thash := fmt.Sprintf("%x", h.Sum(nil))
|
|
||||||
|
|
||||||
updateActiveVehiclesMetric(thash, typ, 1)
|
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VehicleServer) New(ctx context.Context, r *pb.NewRequest) (*pb.NewReply, error) {
|
|
||||||
authorized, token, claims, err := isAuthorized(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !authorized {
|
|
||||||
return nil, cloud.ErrNotAuthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := r.GetType()
|
|
||||||
config := r.GetConfig()
|
|
||||||
|
|
||||||
// track vehicle create
|
|
||||||
log.Println(claims.Subject+":", typ)
|
|
||||||
|
|
||||||
v, err := vehicle.NewFromConfig(typ, stringMapToInterface(config))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
id := s.addVehicleToRegistry(token, typ, config, v)
|
|
||||||
|
|
||||||
res := pb.NewReply{
|
|
||||||
VehicleId: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VehicleServer) SoC(ctx context.Context, r *pb.SoCRequest) (*pb.SoCReply, error) {
|
|
||||||
v, err := s.vehicle(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
soc, err := v.SoC()
|
|
||||||
res := pb.SoCReply{
|
|
||||||
Soc: soc,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en" class="h-100">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>evcc Sponsorship</title>
|
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
||||||
integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
|
|
||||||
integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bd-placeholder-img {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
text-anchor: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.bd-placeholder-img-lg {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Custom default button */
|
|
||||||
.btn-secondary,
|
|
||||||
.btn-secondary:hover,
|
|
||||||
.btn-secondary:focus {
|
|
||||||
color: #333;
|
|
||||||
text-shadow: none;
|
|
||||||
/* Prevent inheritance from `body` */
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
|
|
||||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-container {
|
|
||||||
max-width: 42em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-masthead .nav-link {
|
|
||||||
padding: .25rem 0;
|
|
||||||
font-weight: 700;
|
|
||||||
color: rgba(255, 255, 255, .5);
|
|
||||||
background-color: transparent;
|
|
||||||
border-bottom: .25rem solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-masthead .nav-link:hover,
|
|
||||||
.nav-masthead .nav-link:focus {
|
|
||||||
border-bottom-color: rgba(255, 255, 255, .25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-masthead .nav-link+.nav-link {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-masthead .active {
|
|
||||||
color: #fff;
|
|
||||||
border-bottom-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bd-placeholder-img {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
text-anchor: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.bd-placeholder-img-lg {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="d-flex h-100 text-center text-white bg-dark">
|
|
||||||
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
|
|
||||||
|
|
||||||
<header class="mb-auto">
|
|
||||||
<div>
|
|
||||||
<h3 class="float-md-start mb-0">evcc Sponsorship</h3>
|
|
||||||
<nav class="nav nav-masthead justify-content-center float-md-end">
|
|
||||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
|
||||||
<a class="nav-link" href="/privacy">Impressum & Datenschutz</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="px-3">
|
|
||||||
{{if .Content}}
|
|
||||||
{{.Content}}
|
|
||||||
{{else}}
|
|
||||||
<h1>Willkommen</h1>
|
|
||||||
<p class="lead">
|
|
||||||
Die Freischaltung der Sonderfunktionen für evcc Sponsoren erfordert eine einmalige Anmeldung um evcc mit dem
|
|
||||||
Github Konto des evcc Sponsors zu verknüpfen. Mit der Verknüpfung wird das Registrierungstoken erzeugt.
|
|
||||||
</p>
|
|
||||||
<p class="lead">
|
|
||||||
Das Registrierungstoken gibt Zugang zu den folgenden Funktionen:
|
|
||||||
<p>Nutzung des evcc-soc Moduls mit OpenWB</p>
|
|
||||||
<p>Nutzung des evcc Easee Moduls</p>
|
|
||||||
</p>
|
|
||||||
<p class="lead">
|
|
||||||
<a href="/login" class="btn btn-lg btn-secondary fw-bold border-white bg-white">Anmelden</a>
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="mt-auto text-white-50">
|
|
||||||
<p>Cover template by <a href="https://twitter.com/mdo" class="text-white">@mdo</a>.</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en" class="h-100">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Impressum und Datenschutzerklärung</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Impressum</h1>
|
|
||||||
<h2>Diensteanbieter</h2>
|
|
||||||
<p>Andreas Götz</p>
|
|
||||||
<p>Gubiner Str. 15</p>
|
|
||||||
<p>30880 Laatzen</p>
|
|
||||||
|
|
||||||
<h3>Kontaktmöglichkeiten</h3>
|
|
||||||
<p>E-Mail-Adresse: <a href="mailto:info@evcc.io">info@evcc.io</a></p>
|
|
||||||
|
|
||||||
<h1>Datenschutzerklärung</h1>
|
|
||||||
<h2>Einleitung</h2>
|
|
||||||
<p>Mit der folgenden Datenschutzerklärung möchten wir Sie darüber aufklären, welche Arten Ihrer personenbezogenen
|
|
||||||
Daten (nachfolgend auch kurz als "Daten“ bezeichnet) wir zu welchen Zwecken und in welchem Umfang verarbeiten. Die
|
|
||||||
Datenschutzerklärung gilt für alle von uns durchgeführten Verarbeitungen personenbezogener Daten, sowohl im
|
|
||||||
Rahmen der Erbringung unserer Leistungen als auch insbesondere auf unseren Webseiten, in mobilen Applikationen sowie
|
|
||||||
innerhalb externer Onlinepräsenzen, wie z.B. unserer Social-Media-Profile (nachfolgend zusammenfassend
|
|
||||||
bezeichnet als "Onlineangebot“).</p>
|
|
||||||
<p>Die verwendeten Begriffe sind nicht geschlechtsspezifisch.</p>
|
|
||||||
<p>Stand: 26. April 2021</p>
|
|
||||||
<h2>Inhaltsübersicht</h2>
|
|
||||||
<ul class="index">
|
|
||||||
<li><a class="index-link" href="#m14">Einleitung</a></li>
|
|
||||||
<li><a class="index-link" href="#m3">Verantwortlicher</a></li>
|
|
||||||
<li><a class="index-link" href="#mOverview">Übersicht der Verarbeitungen</a></li>
|
|
||||||
<li><a class="index-link" href="#m13">Maßgebliche Rechtsgrundlagen</a></li>
|
|
||||||
<li><a class="index-link" href="#m27">Sicherheitsmaßnahmen</a></li>
|
|
||||||
<li><a class="index-link" href="#m25">Übermittlung von personenbezogenen Daten</a></li>
|
|
||||||
<li><a class="index-link" href="#m24">Datenverarbeitung in Drittländern</a></li>
|
|
||||||
<li><a class="index-link" href="#m317">Geschäftliche Leistungen</a></li>
|
|
||||||
<li><a class="index-link" href="#m29">Bereitstellung des Onlineangebotes</a></li>
|
|
||||||
<li><a class="index-link" href="#m12">Löschung von Daten</a></li>
|
|
||||||
<li><a class="index-link" href="#m15">Änderung und Aktualisierung der Datenschutzerklärung</a></li>
|
|
||||||
<li><a class="index-link" href="#m10">Rechte der betroffenen Personen</a></li>
|
|
||||||
<li><a class="index-link" href="#m42">Begriffsdefinitionen</a></li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="m3">Verantwortlicher</h2>
|
|
||||||
<p>Andreas Götz<br>Gubiner Str. 15<br>30880 Laatzen</p>
|
|
||||||
<p>E-Mail-Adresse: <a href="mailto:info@evcc.io">info@evcc.io</a>.</p>
|
|
||||||
<h2 id="mOverview">Übersicht der Verarbeitungen</h2>
|
|
||||||
<p>Die nachfolgende Übersicht fasst die Arten der verarbeiteten Daten und die Zwecke ihrer Verarbeitung zusammen und
|
|
||||||
verweist auf die betroffenen Personen.</p>
|
|
||||||
<h3>Arten der verarbeiteten Daten</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Bestandsdaten (Github Profil und Sponsorship Level).</li>
|
|
||||||
<li>Meta-/Kommunikationsdaten (IP-Adressen, Hostnamen).</li>
|
|
||||||
<li>Vertragsdaten (Fahrzeugtyp und Zugangsdaten für Fahrzeug-APIs).</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Kategorien betroffener Personen</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Interessenten</li>
|
|
||||||
<li>Kunden</li>
|
|
||||||
<li>Nutzer</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Zwecke der Verarbeitung</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Bereitstellung unseres Onlineangebotes.</li>
|
|
||||||
<li>Verwaltung und Beantwortung von Anfragen.</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="m13">Maßgebliche Rechtsgrundlagen</h3>
|
|
||||||
<p>Im Folgenden erhalten Sie eine Übersicht der Rechtsgrundlagen der DSGVO, auf deren Basis wir personenbezogenen
|
|
||||||
Daten verarbeiten. Bitte nehmen Sie zur Kenntnis, dass neben den Regelungen der DSGVO nationale Datenschutzvorgaben
|
|
||||||
in Ihrem bzw. unserem Wohn- oder Sitzland gelten können. Sollten ferner im Einzelfall speziellere Rechtsgrundlagen
|
|
||||||
maßgeblich sein, teilen wir Ihnen diese in der Datenschutzerklärung mit.</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Einwilligung (Art. 6 Abs. 1 S. 1 lit. a. DSGVO)</strong> - Die betroffene Person hat ihre
|
|
||||||
Einwilligung in die Verarbeitung der sie betreffenden personenbezogenen Daten für einen spezifischen Zweck oder
|
|
||||||
mehrere bestimmte Zwecke gegeben.</li>
|
|
||||||
<li><strong>Vertragserfüllung und vorvertragliche Anfragen (Art. 6 Abs. 1 S. 1 lit. b. DSGVO)</strong> - Die
|
|
||||||
Verarbeitung ist für die Erfüllung eines Vertrags, dessen Vertragspartei die betroffene Person ist, oder zur
|
|
||||||
Durchführung vorvertraglicher Maßnahmen erforderlich, die auf Anfrage der betroffenen Person erfolgen.</li>
|
|
||||||
<li><strong>Rechtliche Verpflichtung (Art. 6 Abs. 1 S. 1 lit. c. DSGVO)</strong> - Die Verarbeitung ist zur
|
|
||||||
Erfüllung einer rechtlichen Verpflichtung erforderlich, der der Verantwortliche unterliegt.</li>
|
|
||||||
<li><strong>Berechtigte Interessen (Art. 6 Abs. 1 S. 1 lit. f. DSGVO)</strong> - Die Verarbeitung ist zur
|
|
||||||
Wahrung der berechtigten Interessen des Verantwortlichen oder eines Dritten erforderlich, sofern nicht die
|
|
||||||
Interessen oder Grundrechte und Grundfreiheiten der betroffenen Person, die den Schutz personenbezogener Daten
|
|
||||||
erfordern, überwiegen.</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Nationale Datenschutzregelungen in Deutschland</strong>: Zusätzlich zu den Datenschutzregelungen der
|
|
||||||
Datenschutz-Grundverordnung gelten nationale Regelungen zum Datenschutz in Deutschland. Hierzu gehört
|
|
||||||
insbesondere das Gesetz zum Schutz vor Missbrauch personenbezogener Daten bei der Datenverarbeitung
|
|
||||||
(Bundesdatenschutzgesetz – BDSG). Das BDSG enthält insbesondere Spezialregelungen zum Recht auf Auskunft, zum Recht
|
|
||||||
auf Löschung, zum Widerspruchsrecht, zur Verarbeitung besonderer Kategorien personenbezogener Daten, zur
|
|
||||||
Verarbeitung für andere Zwecke und zur Übermittlung sowie automatisierten Entscheidungsfindung im Einzelfall
|
|
||||||
einschließlich Profiling.
|
|
||||||
Des Weiteren regelt es die Datenverarbeitung für Zwecke des Beschäftigungsverhältnisses (§ 26 BDSG), insbesondere im
|
|
||||||
Hinblick auf die Begründung, Durchführung oder Beendigung von Beschäftigungsverhältnissen sowie die Einwilligung
|
|
||||||
von Beschäftigten. Ferner können Landesdatenschutzgesetze der einzelnen Bundesländer zur Anwendung gelangen.</p>
|
|
||||||
<h2 id="m27">Sicherheitsmaßnahmen</h2>
|
|
||||||
<p>Wir treffen nach Maßgabe der gesetzlichen Vorgaben unter Berücksichtigung des Stands der Technik, der
|
|
||||||
Implementierungskosten und der Art, des Umfangs, der Umstände und der Zwecke der Verarbeitung sowie der
|
|
||||||
unterschiedlichen Eintrittswahrscheinlichkeiten und des Ausmaßes der Bedrohung der Rechte und Freiheiten
|
|
||||||
natürlicher Personen geeignete technische und organisatorische Maßnahmen, um ein dem Risiko angemessenes
|
|
||||||
Schutzniveau zu gewährleisten.</p>
|
|
||||||
<p>Zu den Maßnahmen gehören insbesondere die Sicherung der Vertraulichkeit, Integrität und Verfügbarkeit von Daten
|
|
||||||
durch Kontrolle des physischen und elektronischen Zugangs zu den Daten als auch des sie betreffenden Zugriffs, der
|
|
||||||
Eingabe, der Weitergabe, der Sicherung der Verfügbarkeit und ihrer Trennung. Des Weiteren haben wir Verfahren
|
|
||||||
eingerichtet, die eine Wahrnehmung von Betroffenenrechten, die Löschung von Daten und Reaktionen auf die
|
|
||||||
Gefährdung der Daten gewährleisten. Ferner berücksichtigen wir den Schutz personenbezogener Daten bereits bei der
|
|
||||||
Entwicklung bzw. Auswahl von Hardware, Software sowie Verfahren entsprechend dem Prinzip des Datenschutzes, durch
|
|
||||||
Technikgestaltung und durch datenschutzfreundliche Voreinstellungen.</p>
|
|
||||||
<h2 id="m25">Übermittlung von personenbezogenen Daten</h2>
|
|
||||||
<p>Im Rahmen unserer Verarbeitung von personenbezogenen Daten kommt es vor, dass die Daten an andere Stellen,
|
|
||||||
Unternehmen, rechtlich selbstständige Organisationseinheiten oder Personen übermittelt oder sie ihnen gegenüber
|
|
||||||
offengelegt werden. Zu den Empfängern dieser Daten können z.B. mit IT-Aufgaben beauftragte Dienstleister oder
|
|
||||||
Anbieter von Diensten und Inhalten, die in eine Webseite eingebunden werden, gehören. In solchen Fall beachten
|
|
||||||
wir die gesetzlichen Vorgaben und schließen insbesondere entsprechende Verträge bzw. Vereinbarungen, die dem Schutz
|
|
||||||
Ihrer Daten dienen, mit den Empfängern Ihrer Daten ab.</p>
|
|
||||||
<h2 id="m24">Datenverarbeitung in Drittländern</h2>
|
|
||||||
<p>Sofern wir Daten in einem Drittland (d.h., außerhalb der Europäischen Union (EU), des Europäischen
|
|
||||||
Wirtschaftsraums (EWR)) verarbeiten oder die Verarbeitung im Rahmen der Inanspruchnahme von Diensten Dritter oder
|
|
||||||
der Offenlegung bzw. Übermittlung von Daten an andere Personen, Stellen oder Unternehmen stattfindet, erfolgt dies
|
|
||||||
nur im Einklang mit den gesetzlichen Vorgaben. </p>
|
|
||||||
<p>Vorbehaltlich ausdrücklicher Einwilligung oder vertraglich oder gesetzlich erforderlicher Übermittlung
|
|
||||||
verarbeiten oder lassen wir die Daten nur in Drittländern mit einem anerkannten Datenschutzniveau, vertraglichen
|
|
||||||
Verpflichtung durch sogenannte Standardschutzklauseln der EU-Kommission, beim Vorliegen von Zertifizierungen oder
|
|
||||||
verbindlicher internen Datenschutzvorschriften verarbeiten (Art. 44 bis 49 DSGVO, Informationsseite der
|
|
||||||
EU-Kommission: <a
|
|
||||||
href="https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection_de"
|
|
||||||
target="_blank">https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection_de</a>).
|
|
||||||
</p>
|
|
||||||
<h2 id="m29">Bereitstellung des Onlineangebotes</h2>
|
|
||||||
<p>Das Onlineangebote wird auf Servern von Prospect One (Karmelicka 5/5, Krakow, 31-133, Polen) betrieben. Wir
|
|
||||||
verweisen
|
|
||||||
auf die entsprechende <a href="https://appfleet.com/privacy-policy"> Datenschutzerklärung</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Wir verwenden Github für die Bereitstellung der Sponsoring Funktion sowie für die Generierung des Login Tokens.
|
|
||||||
Wir verweisen auf die entsprechende <a
|
|
||||||
href="https://docs.github.com/en/github/site-policy/github-privacy-statement"> Datenschutzerklärung</a>
|
|
||||||
</p>
|
|
||||||
<h2 id="m12">Löschung von Daten</h2>
|
|
||||||
<p>Die von uns verarbeiteten Daten werden nach Maßgabe der gesetzlichen Vorgaben gelöscht, sobald deren zur
|
|
||||||
Verarbeitung erlaubten Einwilligungen widerrufen werden oder sonstige Erlaubnisse entfallen (z.B., wenn der Zweck
|
|
||||||
der Verarbeitung dieser Daten entfallen ist oder sie für den Zweck nicht erforderlich sind).</p>
|
|
||||||
<p>Sofern die Daten nicht gelöscht werden, weil sie für andere und gesetzlich zulässige Zwecke erforderlich sind,
|
|
||||||
wird deren Verarbeitung auf diese Zwecke beschränkt. D.h., die Daten werden gesperrt und nicht für andere Zwecke
|
|
||||||
verarbeitet. Das gilt z.B. für Daten, die aus handels- oder steuerrechtlichen Gründen aufbewahrt werden müssen
|
|
||||||
oder deren Speicherung zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der
|
|
||||||
Rechte einer anderen natürlichen oder juristischen Person erforderlich ist.</p>
|
|
||||||
<p>Unsere Datenschutzhinweise können ferner weitere Angaben zu der Aufbewahrung und Löschung von Daten beinhalten,
|
|
||||||
die für die jeweiligen Verarbeitungen vorrangig gelten.</p>
|
|
||||||
<h2 id="m15">Änderung und Aktualisierung der Datenschutzerklärung</h2>
|
|
||||||
<p>Wir bitten Sie, sich regelmäßig über den Inhalt unserer Datenschutzerklärung zu informieren. Wir passen die
|
|
||||||
Datenschutzerklärung an, sobald die Änderungen der von uns durchgeführten Datenverarbeitungen dies erforderlich
|
|
||||||
machen. Wir informieren Sie, sobald durch die Änderungen eine Mitwirkungshandlung Ihrerseits (z.B. Einwilligung)
|
|
||||||
oder eine sonstige individuelle Benachrichtigung erforderlich wird.</p>
|
|
||||||
<p>Sofern wir in dieser Datenschutzerklärung Adressen und Kontaktinformationen von Unternehmen und Organisationen
|
|
||||||
angeben, bitten wir zu beachten, dass die Adressen sich über die Zeit ändern können und bitten die Angaben vor
|
|
||||||
Kontaktaufnahme zu prüfen.</p>
|
|
||||||
<h2 id="m10">Rechte der betroffenen Personen</h2>
|
|
||||||
<p>Ihnen stehen als Betroffene nach der DSGVO verschiedene Rechte zu, die sich insbesondere aus Art. 15 bis 21 DSGVO
|
|
||||||
ergeben:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Widerspruchsrecht: Sie haben das Recht, aus Gründen, die sich aus Ihrer besonderen Situation
|
|
||||||
ergeben, jederzeit gegen die Verarbeitung der Sie betreffenden personenbezogenen Daten, die aufgrund von Art. 6
|
|
||||||
Abs. 1 lit. e oder f DSGVO erfolgt, Widerspruch einzulegen; dies gilt auch für ein auf diese Bestimmungen
|
|
||||||
gestütztes Profiling. Werden die Sie betreffenden personenbezogenen Daten verarbeitet, um Direktwerbung
|
|
||||||
zu betreiben, haben Sie das Recht, jederzeit Widerspruch gegen die Verarbeitung der Sie betreffenden
|
|
||||||
personenbezogenen Daten zum Zwecke derartiger Werbung einzulegen; dies gilt auch für das Profiling,
|
|
||||||
soweit es mit solcher Direktwerbung in Verbindung steht.</strong></li>
|
|
||||||
<li><strong>Widerrufsrecht bei Einwilligungen:</strong> Sie haben das Recht, erteilte Einwilligungen jederzeit
|
|
||||||
zu widerrufen.</li>
|
|
||||||
<li><strong>Auskunftsrecht:</strong> Sie haben das Recht, eine Bestätigung darüber zu verlangen, ob betreffende
|
|
||||||
Daten verarbeitet werden und auf Auskunft über diese Daten sowie auf weitere Informationen und Kopie der
|
|
||||||
Daten entsprechend den gesetzlichen Vorgaben.</li>
|
|
||||||
<li><strong>Recht auf Berichtigung:</strong> Sie haben entsprechend den gesetzlichen Vorgaben das Recht, die
|
|
||||||
Vervollständigung der Sie betreffenden Daten oder die Berichtigung der Sie betreffenden unrichtigen Daten zu
|
|
||||||
verlangen.</li>
|
|
||||||
<li><strong>Recht auf Löschung und Einschränkung der Verarbeitung:</strong> Sie haben nach Maßgabe der
|
|
||||||
gesetzlichen Vorgaben das Recht, zu verlangen, dass Sie betreffende Daten unverzüglich gelöscht werden, bzw.
|
|
||||||
alternativ nach Maßgabe der gesetzlichen Vorgaben eine Einschränkung der Verarbeitung der Daten zu verlangen.</li>
|
|
||||||
<li><strong>Recht auf Datenübertragbarkeit:</strong> Sie haben das Recht, Sie betreffende Daten, die Sie uns
|
|
||||||
bereitgestellt haben, nach Maßgabe der gesetzlichen Vorgaben in einem strukturierten, gängigen und
|
|
||||||
maschinenlesbaren Format zu erhalten oder deren Übermittlung an einen anderen Verantwortlichen zu fordern.
|
|
||||||
</li>
|
|
||||||
<li><strong>Beschwerde bei Aufsichtsbehörde:</strong> Sie haben unbeschadet eines anderweitigen
|
|
||||||
verwaltungsrechtlichen oder gerichtlichen Rechtsbehelfs das Recht auf Beschwerde bei einer Aufsichtsbehörde,
|
|
||||||
insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthaltsorts, ihres Arbeitsplatzes oder des Orts des
|
|
||||||
mutmaßlichen Verstoßes, wenn Sie der Ansicht sind, dass die Verarbeitung der Sie betreffenden
|
|
||||||
personenbezogenen Daten gegen die Vorgaben der DSGVO verstößt.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="m42">Begriffsdefinitionen</h2>
|
|
||||||
<p>In diesem Abschnitt erhalten Sie eine Übersicht über die in dieser Datenschutzerklärung verwendeten
|
|
||||||
Begrifflichkeiten. Viele der Begriffe sind dem Gesetz entnommen und vor allem im Art. 4 DSGVO definiert. Die
|
|
||||||
gesetzlichen Definitionen sind verbindlich. Die nachfolgenden Erläuterungen sollen dagegen vor allem dem
|
|
||||||
Verständnis dienen. Die Begriffe sind alphabetisch sortiert.</p>
|
|
||||||
<ul class="glossary">
|
|
||||||
<li><strong>Personenbezogene Daten:</strong> "Personenbezogene Daten“ sind alle Informationen, die sich auf eine
|
|
||||||
identifizierte oder identifizierbare natürliche Person (im Folgenden "betroffene Person“) beziehen; als
|
|
||||||
identifizierbar wird eine natürliche Person angesehen, die direkt oder indirekt, insbesondere mittels
|
|
||||||
Zuordnung zu einer Kennung wie einem Namen, zu einer Kennnummer, zu Standortdaten, zu einer Online-Kennung (z.B.
|
|
||||||
Cookie) oder zu einem oder mehreren besonderen Merkmalen identifiziert werden kann, die Ausdruck der physischen,
|
|
||||||
physiologischen, genetischen, psychischen, wirtschaftlichen, kulturellen oder sozialen Identität dieser
|
|
||||||
natürlichen Person sind. </li>
|
|
||||||
<li><strong>Verantwortlicher:</strong> Als "Verantwortlicher“ wird die natürliche oder juristische Person,
|
|
||||||
Behörde, Einrichtung oder andere Stelle, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der
|
|
||||||
Verarbeitung von personenbezogenen Daten entscheidet, bezeichnet. </li>
|
|
||||||
<li><strong>Verarbeitung:</strong> "Verarbeitung" ist jeder mit oder ohne Hilfe automatisierter Verfahren
|
|
||||||
ausgeführte Vorgang oder jede solche Vorgangsreihe im Zusammenhang mit personenbezogenen Daten. Der Begriff
|
|
||||||
reicht weit und umfasst praktisch jeden Umgang mit Daten, sei es das Erheben, das Auswerten, das Speichern,
|
|
||||||
das Übermitteln oder das Löschen. </li>
|
|
||||||
</ul>
|
|
||||||
<p class="seal"><a href="https://datenschutz-generator.de/?l=de"
|
|
||||||
title="Rechtstext von Dr. Schwenke - für weitere Informationen bitte anklicken." target="_blank"
|
|
||||||
rel="noopener noreferrer nofollow">Erstellt mit kostenlosem Datenschutz-Generator.de von Dr. Thomas
|
|
||||||
Schwenke</a>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,297 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"net/http/pprof"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/andig/evcc/internal/vehicle"
|
|
||||||
"github.com/andig/evcc/soc/server/auth"
|
|
||||||
"github.com/andig/evcc/util"
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"github.com/thoas/go-funk"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed index.html
|
|
||||||
var indexHtml string
|
|
||||||
|
|
||||||
//go:embed privacy.html
|
|
||||||
var privacyHtml string
|
|
||||||
|
|
||||||
var (
|
|
||||||
// login ui and callback
|
|
||||||
redirectURL = util.Getenv("REDIRECT_URL")
|
|
||||||
|
|
||||||
// SSL termination
|
|
||||||
sslCertDir = util.Getenv("SSL_CERT_DIR", "certs")
|
|
||||||
|
|
||||||
// github oauth application
|
|
||||||
clientID = util.Getenv("GITHUB_CLIENT_ID")
|
|
||||||
clientSecret = util.Getenv("GITHUB_CLIENT_SECRET")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
indexTpl, privacyTpl *template.Template
|
|
||||||
|
|
||||||
oauthState = randomString()
|
|
||||||
oauthConfig *oauth2.Config
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ProfileURL = "https://api.github.com/user"
|
|
||||||
EmailURL = "https://api.github.com/user/emails"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Bio string `json:"bio"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Login string `json:"login"`
|
|
||||||
Picture string `json:"avatar_url"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
clientID = util.Getenv("GITHUB_CLIENT_ID")
|
|
||||||
clientSecret = util.Getenv("GITHUB_CLIENT_SECRET")
|
|
||||||
|
|
||||||
oauthConfig = &oauth2.Config{
|
|
||||||
RedirectURL: redirectURL,
|
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
Scopes: []string{},
|
|
||||||
Endpoint: github.Endpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
indexTpl = template.Must(template.New("index").Parse(indexHtml))
|
|
||||||
privacyTpl = template.Must(template.New("privacy").Parse(privacyHtml))
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomString() string {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
|
||||||
length := 16
|
|
||||||
var b strings.Builder
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
b.WriteRune(chars[rand.Intn(len(chars))])
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMain(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = indexTpl.Execute(w, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePrivacy(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = privacyTpl.Execute(w, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func templateError(w http.ResponseWriter, r *http.Request, err string) {
|
|
||||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
|
||||||
"Content": template.HTML(`
|
|
||||||
<h1>Fehler</h1>
|
|
||||||
<p class="lead">` + err + `</p>`),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
url := oauthConfig.AuthCodeURL(oauthState)
|
|
||||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Query().Get("error") != "" {
|
|
||||||
err := r.URL.Query().Get("error_description")
|
|
||||||
templateError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := getUserInfo(r.FormValue("state"), r.FormValue("code"))
|
|
||||||
if err != nil {
|
|
||||||
templateError(w, r, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authorized, err := auth.IsSponsor(user.Login)
|
|
||||||
if err != nil {
|
|
||||||
templateError(w, r, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authMap := map[bool]string{false: "rejected", true: "authorized"}
|
|
||||||
log.Println(user.Login+":", authMap[authorized])
|
|
||||||
|
|
||||||
if !authorized {
|
|
||||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
|
||||||
"Content": template.HTML(`
|
|
||||||
<h1>Inaktiv</h1>
|
|
||||||
<p class="lead">Github Sponsorship für evcc ist inaktiv. Um erweiterte Funktionen zu nutzen, ist es notwendig, evcc zu unterstützen.</p>
|
|
||||||
<p class="lead">evcc bei Github <a href="https://github.com/sponsors/andig" class="text-warning">unterstützen</a>.</p>`),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
claims := auth.Claims{
|
|
||||||
Username: user.Name,
|
|
||||||
StandardClaims: jwt.StandardClaims{
|
|
||||||
Subject: user.Login,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := auth.AuthorizedToken(claims)
|
|
||||||
if err != nil {
|
|
||||||
templateError(w, r, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
|
||||||
"Content": template.HTML(fmt.Sprintf(`
|
|
||||||
<h1>Aktiv</h1>
|
|
||||||
<p class="lead">Github Sponsorship für evcc ist aktiv. Das folgende Registrierungstoken kann für den Zugriff auf evcc genutzt werden.</p>
|
|
||||||
<p class="lead">Der Code ist %d Tage gültig und kann jederzeit neu erzeugt werden.</p>
|
|
||||||
<p class="lead"><code>`+jwt+`</code></p>`, auth.TokenExpiry),
|
|
||||||
)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserInfo(state string, code string) (*User, error) {
|
|
||||||
if state != oauthState {
|
|
||||||
return nil, fmt.Errorf("invalid oauth state")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
token, err := oauthConfig.Exchange(ctx, code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("code exchange failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var user User
|
|
||||||
req, err := http.NewRequest("GET", ProfileURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
|
||||||
response, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("GitHub API responded with a %d trying to fetch user information", response.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
bits, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bits, &user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeFilter = []string{"custom", "cloud", "niu", "tesla"}
|
|
||||||
|
|
||||||
var typeMap = map[string]string{
|
|
||||||
"bmw": "BMW",
|
|
||||||
"citroen": "Citroën",
|
|
||||||
"skoda": "Škoda",
|
|
||||||
"enyaq": "Škoda Enyaq",
|
|
||||||
"vw": "VW",
|
|
||||||
"id": "VW ID",
|
|
||||||
}
|
|
||||||
|
|
||||||
type typeDesc struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleVehicles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
types := make([]typeDesc, 0)
|
|
||||||
|
|
||||||
for _, id := range vehicle.Types() {
|
|
||||||
if funk.ContainsString(typeFilter, id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
vt := typeDesc{ID: id, Name: strings.Title(id)}
|
|
||||||
if name, ok := typeMap[id]; ok {
|
|
||||||
vt.Name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
types = append(types, vt)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(types, func(i, j int) bool {
|
|
||||||
return strings.Compare(types[i].Name, types[j].Name) < 0
|
|
||||||
})
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
|
||||||
_ = json.NewEncoder(w).Encode(types)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run() {
|
|
||||||
mux := &http.ServeMux{}
|
|
||||||
mux.HandleFunc("/", handleMain)
|
|
||||||
mux.HandleFunc("/privacy", handlePrivacy)
|
|
||||||
mux.HandleFunc("/login", handleLogin)
|
|
||||||
mux.HandleFunc("/callback", handleCallback)
|
|
||||||
mux.HandleFunc("/api/vehicles", handleVehicles)
|
|
||||||
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
|
||||||
|
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
||||||
// mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
||||||
// mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
||||||
// mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
||||||
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: ":http",
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
if sslHosts := strings.TrimSpace(os.Getenv("SSL_HOSTS")); sslHosts != "" {
|
|
||||||
log.Println("sslHosts:", sslHosts)
|
|
||||||
log.Println("sslCertDir:", sslCertDir)
|
|
||||||
|
|
||||||
certManager := autocert.Manager{
|
|
||||||
Prompt: autocert.AcceptTOS,
|
|
||||||
HostPolicy: autocert.HostWhitelist(strings.Split(sslHosts, " ")...),
|
|
||||||
Cache: autocert.DirCache(sslCertDir),
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Fatal(http.ListenAndServe(":http", certManager.HTTPHandler(nil)))
|
|
||||||
}()
|
|
||||||
|
|
||||||
s.Addr = ":https"
|
|
||||||
s.TLSConfig = certManager.TLSConfig()
|
|
||||||
|
|
||||||
log.Fatal(s.ListenAndServeTLS("", ""))
|
|
||||||
} else {
|
|
||||||
log.Fatal(s.ListenAndServe())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIE1DCCArwCCQC/TbpAPhcOQTANBgkqhkiG9w0BAQsFADAsMQswCQYDVQQGEwJE
|
|
||||||
RTEdMBsGCSqGSIb3DQEJARYOY3B1aWRsZUBnbXguZGUwHhcNMjEwMzMxMDc1ODAz
|
|
||||||
WhcNMjIwMzMxMDc1ODAzWjAsMQswCQYDVQQGEwJERTEdMBsGCSqGSIb3DQEJARYO
|
|
||||||
Y3B1aWRsZUBnbXguZGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+
|
|
||||||
noDTXbidh/5RFfsLlMJbfMn4Vu91oJb4MM41RQM8UOr3SRU/ZzcoLfWJe2ePpEDp
|
|
||||||
ZL4piobs8EIjFcWU2C/8La0rygbcDDqUJkW/nViH0Drs5ctgJl/uYeiDLnZiSZO+
|
|
||||||
PBoOA0trXxqlJPxuEseJbFaLvVE81DsLBnltDPqhJZEUJPEUIjtfStOhQM0f5YoO
|
|
||||||
Greewk7P5LUkTAuTIz+xk6uKnC1YF89SKmdPMH26Rcy6q1IqhEQG3tjUliJZiKS9
|
|
||||||
eQlnp9gskqNGop9gEsmFIMnhlrTUsVU2q38zLDFcmNDbNkRvv4Tf+7jHo4tLs02m
|
|
||||||
/fMTikdMNCRMOcJKbdobcwXq4Ghvc85xeM4/8wIQHEDFbLzmxi/4ibFfJjOfQOe9
|
|
||||||
VpK+TbeSBTxBw42CH9V0TUOz/LELYkMrJ+zfwMCvTV4Eodmc0gjwiy9aIYdpZXKF
|
|
||||||
ueqGGljhVCq2XqkaSATFu5CsdPZrfUszLiAVqpuVTMjy3VQ9ICzz1Bd5ICBRFt6l
|
|
||||||
fDsvfB6SgmB9r1bC+yr/tqe/unzUPajL5mZwn7Jev0uOl6mBNmQpcIEne+9cKedf
|
|
||||||
vPCv/tUQpwpz6blGacEtTxuM+fWDk+CLpPnId/4PEnIQWRFE2aXQ4nrJSwu3O5+k
|
|
||||||
vuZNWety8MOSkuQr0toH//p3VNHOtT7L4JWSlDfTZQIDAQABMA0GCSqGSIb3DQEB
|
|
||||||
CwUAA4ICAQANoLbh4Di1A485ggnKfp9ykfVvg+NduGm+eqr27be033N6IJ7fuKmF
|
|
||||||
7Ki1E+sp0IJDMdyikbqoHdbroHu2chwp/1GzFxhx3Vo7kswS/ehSxWpHjhtrGjAF
|
|
||||||
tSDUFR9q4C+km2A3k1ZNyd9C1w+R9Lr16j9lBQoGWmUgFsRf8ED16FMMSK/1mCC0
|
|
||||||
1VSydYnYhKmPZxMByTozGTV97wihA0XqXtadbkQoyvcnEuarBvEX3mPA3effqcEG
|
|
||||||
rreeDp1yzYtQRW85ZASaqgF5CYKhe9NekurZ5Jd+2mQPYRWPFpIFsSHoOLqbfcN1
|
|
||||||
/S0Si6LZWq0Mi8cbSi/zq+Eh7Q0w+EXP6Goh0A7M4e1dKt+cfGQpeyeg3cF49Cda
|
|
||||||
RrTkSv2Sl2jbrShqAsG/HNhyBaI/gtLGi9tig9wAoV2zjGw1Ehs0GcwFq2g/f6Zv
|
|
||||||
W7rdZIBZT1RIQLczuwsEhv6cFrSM5OU2f5fuKnmeI6uvyz0jRqiMncYRjwI6LotB
|
|
||||||
sWmdTWi6xsctYirJ3Yip5Tqm01asyUuIeiT+eQ6I9CfHLiGBXEzxlK5PrFqfD7gK
|
|
||||||
LfB1gCN7SAlxKGepmFMwfF+fDrsurL2T2ePxaaNBLjRAzmpSpH0NedMd+eanuHXV
|
|
||||||
a/iOL2XmVis7iuFyk5M2XtLFfYBff+mxSPA8d9u5kCpoel0tx3iLmw==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue