This commit is contained in:
Aaron Bieber 2023-01-10 15:13:54 -07:00
commit 0f70db9ead
No known key found for this signature in database
6 changed files with 226 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
result
pots
.direnv

26
flake.lock Normal file
View File

@ -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
}

38
flake.nix Normal file
View File

@ -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 ];
};
});
};
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module suah.dev/pots
go 1.19

155
main.go Normal file
View File

@ -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, `<a href="https://suah.dev/pots">suah.dev/pots</a>`)
})
s := &http.Server{
Addr: *listen,
Handler: mux,
}
log.Fatal(s.ListenAndServe())
}