diff --git a/docker/compose.with-database-grafana.yml b/docker/compose.with-database-grafana.yml index 2a63282..d72d7e7 100644 --- a/docker/compose.with-database-grafana.yml +++ b/docker/compose.with-database-grafana.yml @@ -26,7 +26,10 @@ services: restart: always environment: DB_URL: ${DB_URL} - REMOTE_URL: ${REMOTE_URL} + OPENDTU_ADDRESS: ${OPENDTU_ADDRESS} + OPENDTU_AUTH: ${OPENDTU_AUTH} + OPENDTU_USERNAME: ${OPENDTU_USERNAME} + OPENDTU_PASSWORD: ${OPENDTU_PASSWORD} TIMESCALEDB_ENABLED: ${TIMESCALEDB_ENABLED} TZ: ${TZ} depends_on: diff --git a/docker/compose.with-database.yml b/docker/compose.with-database.yml index 867bbd9..277ee63 100644 --- a/docker/compose.with-database.yml +++ b/docker/compose.with-database.yml @@ -26,7 +26,10 @@ services: image: git.hollander.online/energy/opendtu-logger:latest environment: DB_URL: ${DB_URL} - REMOTE_URL: ${REMOTE_URL} + OPENDTU_ADDRESS: ${OPENDTU_ADDRESS} + OPENDTU_AUTH: ${OPENDTU_AUTH} + OPENDTU_USERNAME: ${OPENDTU_USERNAME} + OPENDTU_PASSWORD: ${OPENDTU_PASSWORD} TIMESCALEDB_ENABLED: ${TIMESCALEDB_ENABLED} TZ: ${TZ} depends_on: diff --git a/docker/compose.yml b/docker/compose.yml index dc0fbe3..2c29835 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -5,7 +5,10 @@ services: image: git.hollander.online/energy/opendtu-logger:latest environment: DB_URL: ${DB_URL} - REMOTE_URL: ${REMOTE_URL} + OPENDTU_ADDRESS: ${OPENDTU_ADDRESS} + OPENDTU_AUTH: ${OPENDTU_AUTH} + OPENDTU_USERNAME: ${OPENDTU_USERNAME} + OPENDTU_PASSWORD: ${OPENDTU_PASSWORD} TIMESCALEDB_ENABLED: ${TIMESCALEDB_ENABLED} TZ: ${TZ} depends_on: diff --git a/docker/example.with-database.env b/docker/example.with-database.env index a283d89..5131f51 100644 --- a/docker/example.with-database.env +++ b/docker/example.with-database.env @@ -1,9 +1,13 @@ +# OpenDTU +OPENDTU_ADDRESS="192.168.1.89:80" +OPENDTU_AUTH=false +OPENDTU_USERNAME= +OPENDTU_PASSWORD= # OpenDTU Logger -REMOTE_URL="192.168.1.89:80" DB_URL="host=timescaledb port=5432 user=postgres password=secret dbname=opendtu_logger sslmode=disable" TIMESCALEDB_ENABLED=true TZ="Europe/Amsterdam" # Database configuration PG_USER=postgres PG_PASSWORD= -PG_DB=opendtu_logger \ No newline at end of file +PG_DB=opendtu_logger diff --git a/main.go b/main.go index 8bd6657..2d3921f 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,4 @@ // TODO: Storage optimisation: Map inverter serial to shorter serial. Use that for referring. -// TODO: Use username and password provided using Basic Authentication. // TODO: Record Inverter struct data only on-change. // Idea: Make a full admin / config GUI and only configure through this utility. // Idea: Gather settings only on start-up. @@ -9,6 +8,7 @@ package main import ( "database/sql" + "encoding/base64" "encoding/json" "fmt" "io/fs" @@ -149,10 +149,13 @@ type InverterSettingsData struct { // Config settings struct type Config struct { - DB string `json:"db"` - OpenDTU string `json:"opendtu"` - TimescaleDB bool `json:"timescaledb"` - TZ string `json:"tz"` + DB string `json:"db"` + OpenDTUAddress string `json:"opendtu_address"` + OpenDTUAuth bool `json:"opendtu_auth"` + OpenDTUUser string `json:"opendtu_username"` + OpenDTUPassword string `json:"opendtu_password"` + TimescaleDB bool `json:"timescaledb"` + TZ string `json:"tz"` } var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) @@ -173,15 +176,50 @@ func loadConfig() Config { if err != nil { log.Fatalf("Error parsing config file: %v", err) } + if config.DB == "" { + log.Fatal("db connection settings are not set") + } + if config.OpenDTUAddress == "" { + log.Fatal("opendtu_address is not set") + } + if config.OpenDTUAuth { + if config.OpenDTUUser == "" { + log.Fatal("opendtu_username is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set username") + } + if config.OpenDTUPassword == "" { + log.Fatal("opendtu_password is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set password") + } + } } else { + logger.Info("JSON config file not found. Falling back to environment variables.") // Fallback to environment variables config.DB = os.Getenv("DB_URL") if config.DB == "" { log.Fatal("DB_URL environment variable is not set.") } - config.OpenDTU = os.Getenv("REMOTE_URL") - if config.OpenDTU == "" { - log.Fatal("REMOTE_URL environment variable is not set.") + config.OpenDTUAddress = os.Getenv("OPENDTU_ADDRESS") + if config.OpenDTUAddress == "" { + log.Fatal("OPENDTU_ADDRESS environment variable is not set.") + } + + openDTUAuthStr := os.Getenv("OPENDTU_AUTH") + if openDTUAuthStr != "" { + openDTUAuth, err := strconv.ParseBool(openDTUAuthStr) + if err != nil { + log.Fatalf("Error parsing OPENDTU_AUTH: %v", err) + } + config.OpenDTUAuth = openDTUAuth + } + if config.OpenDTUAuth { + config.OpenDTUUser = os.Getenv("OPENDTU_USERNAME") + if config.OpenDTUUser == "" { + log.Fatal("OPENDTU_USERNAME environment variable is not set.") + } + config.OpenDTUPassword = os.Getenv("OPENDTU_PASSWORD") + if config.OpenDTUPassword == "" { + log.Fatal("OPENDTU_PASSWORD environment variable is not set.") + } + } timescaleDBStr := os.Getenv("TIMESCALEDB_ENABLED") @@ -194,6 +232,10 @@ func loadConfig() Config { } config.TZ = os.Getenv("TZ") } + _, err = time.LoadLocation(config.TZ) + if err != nil { + logger.Warn("invalid timezone") + } return config } @@ -218,10 +260,16 @@ func main() { migrateDB(db) // Create WebSocket URL from config variable - wsURL := "ws://" + config.OpenDTU + "/livedata" + wsURL := "ws://" + config.OpenDTUAddress + "/livedata" + + // Create headers with optional Basic Auth + headers := http.Header{} + if config.OpenDTUAuth { + headers.Set("Authorization", basicAuth(config.OpenDTUUser, config.OpenDTUPassword)) + } // Establish WebSocket connection - c, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + c, _, err := websocket.DefaultDialer.Dial(wsURL, headers) if err != nil { log.Fatal(err) } @@ -424,15 +472,33 @@ func migrateFS(db *sql.DB, migrationFS fs.FS, dir string) error { } func queryEventsEndpoint(inverterSerial string) (*EventsResponse, error) { - remoteURL := config.OpenDTU - endpoint := fmt.Sprintf("http://"+remoteURL+"/api/eventlog/status?inv=%s", inverterSerial) + endpoint := fmt.Sprintf("http://"+config.OpenDTUAddress+"/api/eventlog/status?inv=%s", inverterSerial) - resp, err := http.Get(endpoint) + // Create a new HTTP request + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return nil, err + } + + if config.OpenDTUAuth { + // Add Basic Auth header + req.Header.Add("Authorization", basicAuth(config.OpenDTUUser, config.OpenDTUPassword)) + } + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() + // Check for HTTP errors + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP request failed with status: %s", resp.Status) + } + + // Decode the response var eventsResponse EventsResponse if err := json.NewDecoder(resp.Body).Decode(&eventsResponse); err != nil { return nil, err @@ -508,6 +574,12 @@ func updateEvents(db *sql.DB, inverterSerial string, events *EventsResponse) { } } +// basicAuth generates the Basic Auth header value +func basicAuth(username, password string) string { + credentials := username + ":" + password + return "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials)) +} + // TODO: finish this function. // func updateInverterConfig(db *sql.DB) { // // Periodically query the /api/inverter/list @@ -526,8 +598,8 @@ func updateEvents(db *sql.DB, inverterSerial string, events *EventsResponse) { // } // func queryConfigEndpoint() (*InverterSettingsData, error) { -// remoteURL := os.Getenv("REMOTE_URL") -// endpoint := fmt.Sprintf("http://" + remoteURL + "/api/inverter/list") +// openDTUAddress := os.Getenv("OPENDTU_ADDRESS") +// endpoint := fmt.Sprintf("http://" + openDTUAddress + "/api/inverter/list") // resp, err := http.Get(endpoint) // if err != nil { diff --git a/systemd/opendtu-logger.service b/systemd/opendtu-logger.service index 9c14fb6..60fa257 100644 --- a/systemd/opendtu-logger.service +++ b/systemd/opendtu-logger.service @@ -16,7 +16,10 @@ Type=simple User=opendtu-logger Group=opendtu-logger -Environment="REMOTE_URL=opendtu.local:80" +Environment="OPENDTU_ADDRESS=opendtu.local:80" +Environment="OPENDTU_AUTH=false" +Environment="OPENDTU_USERNAME=admin" +Environment="OPENDTU_PASSWORD=your_super_secret_password" Environment="DB_URL=host=localhost port=5432 user=postgres password=secret dbname=dtu sslmode=disable" Environment="TIMESCALEDB_ENABLED=true" Environment="TZ=Europe/Amsterdam"