commit 605d340aa0d2a396ddf352ab690a137a783bdfa0 (tree)
parent 65297845e5358c5e1124d37d0fea3b6f00d680fd
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Sun, 15 Sep 2024 07:36:17 +0300
rewrite weather in go
Diffstat:
6 files changed, 142 insertions(+), 56 deletions(-)
diff --git a/flake.nix b/flake.nix
@@ -94,6 +94,7 @@
deploy-rs-pkg = null;
})
(_: super: {
+ weather = super.callPackage ./pkgs/weather { };
nicer = super.callPackage ./pkgs/nicer.nix { };
imapsync = super.callPackage ./pkgs/imapsync.nix { };
tmuxbash = super.callPackage ./pkgs/tmuxbash.nix { };
@@ -305,19 +306,20 @@
};
formatter = pkgs.nixfmt-rfc-style;
-
}
)
- // {
- packages.x86_64-linux.vanta-agent =
- let
- pkgs = import nixpkgs {
- inherit overlays;
- system = "x86_64-linux";
- };
- in
- pkgs.vanta-agent;
- };
+ // (
+ let
+ pkgs = import nixpkgs {
+ inherit overlays;
+ system = "x86_64-linux";
+ };
+ in
+ {
+ packages.x86_64-linux.vanta-agent = pkgs.vanta-agent;
+ packages.x86_64-linux.weather = pkgs.weather;
+ }
+ );
}
diff --git a/hosts/fwminex/configuration.nix b/hosts/fwminex/configuration.nix
@@ -95,8 +95,8 @@ in
bash
];
serviceConfig = {
- type = "notify";
- ExecStart = "${pkgs.systemd}/bin/systemd-socket-activate -a --inetd -l ${toString myData.ports.exporters.weather} ${../../pkgs/weather/main}";
+ type = "simple";
+ ExecStart = "${pkgs.weather}/bin/weather";
ProtectSystem = "strict";
};
};
diff --git a/pkgs/weather/default.nix b/pkgs/weather/default.nix
@@ -0,0 +1,6 @@
+{ buildGoModule }:
+buildGoModule {
+ name = "weather";
+ src = ./.;
+ vendorHash = null;
+}
diff --git a/pkgs/weather/go.mod b/pkgs/weather/go.mod
@@ -0,0 +1,3 @@
+module git.jakstys.lt/motiejus/config/pkgs/weather
+
+go 1.19
diff --git a/pkgs/weather/main b/pkgs/weather/main
@@ -1,43 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-STATION=vilniaus-ams
-TODAY=${1:-$(date +%F)}
-
-export TZ=UTC
-sed -e 's/$/\r/' <<EOF
-HTTP/1.0 200 OK
-Content-Type: text/plain; version=0.0.4; charset=utf-8; escaping=values
-
-EOF
-
-get() { jq -r ".$1 // 0" <<< "$2"; }
-
-while IFS= read -r obs; do
- #{
- # "observationTimeUtc": "2024-09-13 12:00:00",
- # "airTemperature": 20.9,
- # "feelsLikeTemperature": 20.9,
- # "windSpeed": 1.4,
- # "windGust": 5.7,
- # "windDirection": 103,
- # "cloudCover": 88,
- # "seaLevelPressure": 1010.3,
- # "relativeHumidity": 82,
- # "precipitation": 0,
- # "conditionCode": "rain"
- #}
- ts=$(date +%s000 --date="$(get observationTimeUtc "$obs")")
- cat <<EOF
-weather_station_air_temperature_celsius{station="$STATION"} $(get airTemperature "$obs") $ts
-weather_station_air_feels_like_celsius{station="$STATION"} $(get feelsLikeTemperature "$obs") $ts
-weather_station_wind_speed_ms{station="$STATION"} $(get windSpeed "$obs") $ts
-weather_station_wind_gust_ms{station="$STATION"} $(get windGust "$obs") $ts
-weather_station_wind_direction_degrees{station="$STATION"} $(get windDirection "$obs") $ts
-weather_station_cloud_cover_percent{station="$STATION"} $(get cloudCover "$obs") $ts
-weather_station_sea_level_pressure_hpa{station="$STATION"} $(get seaLevelPressure "$obs") $ts
-weather_station_relative_humidity_percent{station="$STATION"} $(get relativeHumidity "$obs") $ts
-weather_station_precipitation_mm{station="$STATION"} $(get precipitation "$obs") $ts
-weather_station_condition{station="$STATION",code="$(get conditionCode "$obs")"} 1 $ts
-EOF
-done < <(curl -f -s "https://api.meteo.lt/v1/stations/$STATION/observations/$TODAY" | jq -c '.observations[]')
diff --git a/pkgs/weather/main.go b/pkgs/weather/main.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "text/template"
+ "time"
+)
+
+const (
+ _listen = ":9011"
+ _urlTemplate = "https://api.meteo.lt/v1/stations/%s/observations/%s"
+ _station = "vilniaus-ams"
+ _promTemplate = `weather_station_air_temperature_celsius{station="{{ .Station }}"} {{ .AirTemperature }} {{ .TS }}
+weather_station_air_feels_like_celsius{station="{{ .Station }}"} {{ .FeelsLikeTemperature }} {{ .TS }}
+weather_station_wind_speed_ms{station="{{ .Station }}"} {{ .WindSpeed }} {{ .TS }}
+weather_station_wind_gust_ms{station="{{ .Station }}"} {{ .WindGust }} {{ .TS }}
+weather_station_wind_direction_degrees{station="{{ .Station }}"} {{ .WindDirection }} {{ .TS }}{{ if .CloudCover }}
+weather_station_cloud_cover_percent{station="{{ .Station }}"} {{ .CloudCover }} {{ .TS }}{{ end }}
+weather_station_sea_level_pressure_hpa{station="{{ .Station }}"} {{ .SeaLevelPressure }} {{ .TS }}
+weather_station_relative_humidity_percent{station="{{ .Station }}"} {{ .RelativeHumidity }} {{ .TS }}
+weather_station_precipitation_mm{station="{{ .Station }}"} {{ .Precipitation }} {{ .TS }}{{ if .ConditionCode }}
+weather_station_condition{station="{{ .Station }}",code="{{ .ConditionCode }}"} 1 {{ .TS }}{{ end }}
+`
+)
+
+var tpl = template.Must(template.New("prom").Parse(_promTemplate))
+
+func main() {
+ log.Printf("Listening on %s\n", _listen)
+ log.Fatal((&http.Server{
+ Addr: _listen,
+ Handler: http.HandlerFunc(handler),
+ }).ListenAndServe())
+}
+
+type observation struct {
+ ObservationTimeUtc string `json:"observationTimeUtc"`
+ AirTemperature float64 `json:"airTemperature"`
+ FeelsLikeTemperature float64 `json:"feelsLikeTemperature"`
+ WindSpeed float64 `json:"windSpeed"`
+ WindGust float64 `json:"windGust"`
+ WindDirection float64 `json:"windDirection"`
+ CloudCover *float64 `json:"cloudCover"`
+ SeaLevelPressure float64 `json:"seaLevelPressure"`
+ RelativeHumidity float64 `json:"relativeHumidity"`
+ Precipitation float64 `json:"precipitation"`
+ ConditionCode *string `json:"conditionCode"`
+
+ // template variables
+ TS int64
+ Station string
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ observations, err := getObservations(time.Now().UTC(), _station)
+ if err != nil {
+ log.Printf("Error getting observations: %v\n", err)
+ http.Error(w, fmt.Sprintf("Internal error: %v", err.Error()), 500)
+ return
+ }
+ w.Header().Add("Content-Type", "text/plain; version=0.0.4")
+
+ bw := bufio.NewWriter(w)
+ defer bw.Flush()
+
+ for _, observation := range observations {
+
+ ts, err := time.ParseInLocation(
+ time.DateTime,
+ observation.ObservationTimeUtc,
+ time.UTC,
+ )
+ if err != nil {
+ log.Printf("error parsing time %q: %v\n", observation.ObservationTimeUtc, err)
+ return
+ }
+ observation.TS = ts.UnixMilli()
+ observation.Station = _station
+
+ if err := tpl.Execute(bw, observation); err != nil {
+ log.Printf("error executing template: %v", err)
+ return
+ }
+ }
+ bw.WriteString("\n")
+}
+
+func getObservations(date time.Time, station string) ([]observation, error) {
+ url := fmt.Sprintf(_urlTemplate, station, date.Format(time.DateOnly))
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("get %q: %w", url, err)
+ }
+
+ defer func() {
+ io.Copy(io.Discard, resp.Body)
+ resp.Body.Close()
+ }()
+
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("got non-200 http status code %d", resp.StatusCode)
+ }
+
+ decoder := json.NewDecoder(resp.Body)
+ var incoming struct {
+ Observations []observation `json:"observations"`
+ }
+ if err := decoder.Decode(&incoming); err != nil {
+ return nil, fmt.Errorf("json decode: %w", err)
+ }
+
+ return incoming.Observations, nil
+}