config

NixOS config
Log | Files | Refs | README | LICENSE

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:
Mflake.nix | 24+++++++++++++-----------
Mhosts/fwminex/configuration.nix | 4++--
Apkgs/weather/default.nix | 6++++++
Apkgs/weather/go.mod | 3+++
Dpkgs/weather/main | 43-------------------------------------------
Apkgs/weather/main.go | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 +}