diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..88c030e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: goreleaser + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Fetch all tags + run: git fetch --force --tags + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.18 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 241aa67..426d46b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ -.idea/ \ No newline at end of file +.idea/ +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..f23378a --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,35 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + main: ./cmd/arcmon + binary: arcmon + goos: + - linux + - windows + - darwin +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + files: + - src: systemd/arcmon.service + strip_parent: true +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/README.md b/README.md index cc87c26..b4f67a1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# ArcDPS Monitor +# ArcDPS Monitor [![Go](https://github.com/mythwright/arc-monitor/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/mythwright/arc-monitor/actions/workflows/build.yml) A very simple daemon to notifiy a Discord Webhook whenever ArcDPS gets updated (within a set interval) diff --git a/cmd/arcmon/main.go b/cmd/arcmon/main.go index 4caae03..72b287b 100644 --- a/cmd/arcmon/main.go +++ b/cmd/arcmon/main.go @@ -1,4 +1,4 @@ -package arcmon +package main import ( "bytes" @@ -8,8 +8,11 @@ import ( "net" "net/http" "os" + "os/signal" "path/filepath" "strings" + "sync" + "syscall" "time" "github.com/sirupsen/logrus" @@ -24,38 +27,52 @@ const ( ) type ArcDPSVersion struct { - Timestamp time.Time `yaml:"timestamp"` - CheckSum string `yaml:"check_sum"` + Timestamp time.Time `yaml:"timestamp"` + CheckSum string `yaml:"check_sum"` + sync.RWMutex `yaml:"-"` } func main() { - f, err := os.Open(filepath.Dir(os.TempDir() + "/arcdps.yaml")) + if os.Getenv("DISCORD_WEBHOOK") == "" { + logrus.Fatalf("missing DISCORD_WEBHOOK env variable") + } + + f, err := os.OpenFile(filepath.Join(".", "arcdps.yml"), os.O_RDWR|os.O_CREATE, 0755) if err != nil { - if err != os.ErrNotExist { - logrus.Fatalf("err opening temp file: %v\n", err) - } - f, err = os.CreateTemp("", "/arcdps.yml") - if err != nil { - logrus.Fatalf("temp file not exists and unable to create new one: %v\n", err) + if !strings.Contains(err.Error(), "no such") { + logrus.Fatalf("err opening tracking file: %v\n", err) } } + logrus.Infof("using: %s", f.Name()) + arcdps := &ArcDPSVersion{} - if err := yaml.NewDecoder(f).Decode(&arcdps); err != nil { + if err := yaml.NewDecoder(f).Decode(&arcdps); err != nil && err != io.EOF { logrus.Fatalf("unable to decode arcdps.yml: %v", err) } - s := NewServer() - s.Tick() - + s := NewServer(arcdps) + ctx, cncl := context.WithCancel(context.Background()) + go s.Tick(ctx) + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGTERM, syscall.SIGKILL, os.Interrupt) + <-sig + cncl() + logrus.Infof("shutting down") + f.Seek(0, 0) //rewind file descriptor + if err := yaml.NewEncoder(f).Encode(arcdps); err != nil { + logrus.Fatalf("unable to save file: (%v)", err) + } + f.Close() } type Server struct { http *http.Client webhookURL string + arcdps *ArcDPSVersion } -func NewServer() *Server { +func NewServer(arcdps *ArcDPSVersion) *Server { return &Server{ http: &http.Client{ Transport: &http.Transport{ @@ -64,11 +81,48 @@ func NewServer() *Server { Timeout: 5 * time.Second, }, webhookURL: os.Getenv("DISCORD_WEBHOOK"), + arcdps: arcdps, } } -func (s *Server) Tick() { - +func (s *Server) Tick(ctx context.Context) { + ticker := time.NewTicker(DefaultTickDuration) + logrus.Infof("Starting Check Ticker") + for { + select { + case <-ticker.C: + check, err := s.GetChecksum(ctx) + if err != nil { + logrus.Errorf("Failed getting checksum: (%v)", err) + continue + } + version, err := s.GetVersion(ctx) + if err != nil { + logrus.Errorf("Failed getting version: (%v)", err) + continue + } + if s.arcdps.CheckSum == "" { + logrus.Infof("Setting initial version") + s.arcdps.CheckSum = check + s.arcdps.Timestamp = version + continue + } + if s.arcdps.CheckSum != check { + // new version + if err := s.SendWebHook(ctx, + fmt.Sprintf("`%s`", check), + fmt.Sprintf("`%s`", version.String()), + ); err != nil { + logrus.Errorf("unable to send webhook: (%v)\n", err) + } + s.arcdps.CheckSum = check + s.arcdps.Timestamp = version + } + case <-ctx.Done(): + ticker.Stop() + return + } + } } func (s *Server) GetChecksum(ctx context.Context) (string, error) { @@ -128,19 +182,26 @@ func (s *Server) GetVersion(ctx context.Context) (time.Time, error) { } func (s *Server) SendWebHook(ctx context.Context, checksum, time string) error { - payload := bytes.NewBufferString(fmt.Sprintf(PayloadJSON, checksum, time)) + payload := bytes.NewBufferString(fmt.Sprintf(PayloadJSON, checksum, time, DefaultTickDuration.String())) req, err := http.NewRequestWithContext(ctx, "POST", s.webhookURL, payload) if err != nil { return err } + req.Header.Set("Content-Type", "application/json") + resp, err := s.http.Do(req) if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode > 299 { - return fmt.Errorf("bad response from Discord: %d", resp.StatusCode) + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("bad response from Discord: %d (%s)", resp.StatusCode, string(body)) } return nil } @@ -148,15 +209,15 @@ func (s *Server) SendWebHook(ctx context.Context, checksum, time string) error { var ( PayloadJSON = ` { - "content": null, "embeds": [ { "title": "ArcDPS has updated!", "color": 12124160, "fields": [ { - "name": "CheckSum", - "value": "%s" + "name": "Checksum", + "value": "%s", + "inline": true }, { "name": "Timestamp Version", @@ -165,12 +226,15 @@ var ( }, { "name": "Direct Download Link", - "value": "https://www.deltaconnected.com/arcdps/x64/d3d9.dll", - "inline": true + "value": "https://www.deltaconnected.com/arcdps/x64/d3d9.dll" } ], + "author": { + "name": "ArcDPS Monitor", + "icon_url": "https://wiki.guildwars2.com/images/0/03/Specter_icon_(highres).png" + }, "footer": { - "text": "ArcDPS Monitor" + "text": "This bot checks every %s" } } ] diff --git a/go.mod b/go.mod index 061986b..8af80db 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/mythwright/arc-monitor -go 1.17 +go 1.18 require ( - github.com/sirupsen/logrus v1.8.1 // indirect - golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + github.com/sirupsen/logrus v1.8.1 + gopkg.in/yaml.v2 v2.4.0 ) + +require golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect diff --git a/go.sum b/go.sum index b0f6d6c..671057d 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/systemd/arcmon.service b/systemd/arcmon.service new file mode 100644 index 0000000..117d971 --- /dev/null +++ b/systemd/arcmon.service @@ -0,0 +1,23 @@ +[Unit] +Description=ArcMon Daemon + +Wants=network.target +After=network.target + +[Service] +User=arcmon +Group=arcmon +Restart=on-failure + +ProtectHome=true +ProtectSystem=full +PrivateDevices=true +NoNewPrivileges=true +PrivateTmp=true +InaccessibleDirectories=/root /sys /srv -/opt /media -/lost+found +ReadWriteDirectories=/var/arcmon +WorkingDirectory=/var/arcmon +ExecStart=arcmon + +[Install] +WantedBy=multi-user.target \ No newline at end of file