From 0f70db9eada52548a6d3643af84254e479f5601f Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Tue, 10 Jan 2023 15:13:54 -0700 Subject: [PATCH] init --- .envrc | 1 + .gitignore | 3 ++ flake.lock | 26 +++++++++ flake.nix | 38 +++++++++++++ go.mod | 3 ++ main.go | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 go.mod create mode 100644 main.go diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f7a4f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +result +pots +.direnv diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..3c7eb22 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1673226411, + "narHash": "sha256-b6cGb5Ln7Zy80YO66+cbTyGdjZKtkoqB/iIIhDX9gRA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa1d74709f5dac623adb4d48fdfb27cc2c92a4d4", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c1eb246 --- /dev/null +++ b/flake.nix @@ -0,0 +1,38 @@ +{ + description = "pots: a tailscale pushover webhooker"; + + inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: + let + supportedSystems = + [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); + in { + packages = forAllSystems (system: + let pkgs = nixpkgsFor.${system}; + in { + pots = pkgs.buildGoModule { + pname = "pots"; + version = "v0.0.1"; + src = ./.; + vendorHash = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo="; + }; + }); + + defaultPackage = forAllSystems (system: self.packages.${system}.pots); + devShells = forAllSystems (system: + let pkgs = nixpkgsFor.${system}; + in { + default = pkgs.mkShell { + shellHook = '' + PS1='\u@\h:\@; ' + echo "Go `${pkgs.go}/bin/go version`" + ''; + nativeBuildInputs = with pkgs; [ git go gopls go-tools jo jq ]; + }; + }); + }; +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..467f2f0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module suah.dev/pots + +go 1.19 diff --git a/main.go b/main.go new file mode 100644 index 0000000..43b0cca --- /dev/null +++ b/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" + "path" + "strings" + "time" +) + +type LogEntry struct { + Timestamp string `json:"timestamp"` + Version int `json:"version"` + Type string `json:"type"` + Tailnet string `json:"tailnet"` + Message string `json:"message"` + Data Data `json:"data"` +} +type Data struct { + NodeID string `json:"nodeID,omitempty"` + DeviceName string `json:"deviceName,omitempty"` + ManagedBy string `json:"managedBy,omitempty"` + Actor string `json:"actor,omitempty"` + URL string `json:"url,omitempty"` +} + +type TSLogs []LogEntry + +// Push represents a message sent to the Pushover api +type Push struct { + Token string `json:"token"` + User string `json:"user"` + Message string `json:"message"` + Device string `json:"device,omitempty"` + Title string `json:"title"` + URL string `json:"url"` + URLTitle string `json:"url_title,omitempty"` + Priority int `json:"priority,omitempty"` + Sound string `json:"sound,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// PushResponse is a response from the Pushover api +type PushResponse struct { + Status int `json:"status,omitempty"` + Request string `json:"request,omitempty"` + User string `json:"user,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +type apiHandler struct{} + +var ( + appToken = os.Getenv("PUSHOVER_TOKEN") + userToken = os.Getenv("PUSHOVER_USER") + potsToken = os.Getenv("POTS_TOKEN") + client = *http.DefaultClient +) + +func sendPush(p *Push) error { + buf := new(bytes.Buffer) + + if err := json.NewEncoder(buf).Encode(p); err != nil { + return err + } + + req, err := http.NewRequest("POST", "https://api.pushover.net/1/messages.json", buf) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + + var resBody PushResponse + if err = json.NewDecoder(res.Body).Decode(&resBody); err != nil { + return err + } + + if len(resBody.Errors) > 0 { + return fmt.Errorf("%s", strings.Join(resBody.Errors, ", ")) + } + + return nil +} + +func (apiHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.Method != "POST" { + http.NotFound(w, req) + return + } + + token := path.Base(req.URL.Path) + if token != potsToken { + http.NotFound(w, req) + return + } + + decoder := json.NewDecoder(req.Body) + var tsLogs TSLogs + err := decoder.Decode(&tsLogs) + if err != nil { + http.Error(w, "invalid data", http.StatusBadRequest) + return + } + + for _, t := range tsLogs { + var push = &Push{ + Token: appToken, + User: userToken, + Timestamp: time.Now(), + Title: t.Message, + Message: t.Type, + URL: t.Data.URL, + } + err := sendPush(push) + if err != nil { + log.Println(err) + } + } +} + +func main() { + listen := flag.String("listen", ":8888", "listen string") + flag.Parse() + + mux := http.NewServeMux() + mux.Handle("/api/", apiHandler{}) + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + log.Println(req.URL.Path) + if req.URL.Path != "/" { + http.NotFound(w, req) + return + } + fmt.Fprintf(w, `suah.dev/pots`) + }) + + s := &http.Server{ + Addr: *listen, + Handler: mux, + } + + log.Fatal(s.ListenAndServe()) +}