From dfec6f5cdc2751ed82a0a54432f4241dc175c7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Doma=C5=84ski?= Date: Thu, 25 Jul 2024 21:20:00 +0200 Subject: [PATCH] [project] init --- .dockerignore | 4 ++ .gitignore | 25 ++++++++++++ Dockerfile | 18 +++++++++ Readme.md | 0 cmd/main.go | 15 +++++++ docker-compose.yaml | 9 +++++ env.template | 4 ++ go.mod | 15 +++++++ go.sum | 16 ++++++++ src/checker.go | 84 +++++++++++++++++++++++++++++++++++++++ src/config.go | 40 +++++++++++++++++++ src/discordInteraction.go | 26 ++++++++++++ src/ipTools.go | 38 ++++++++++++++++++ 13 files changed, 294 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Readme.md create mode 100644 cmd/main.go create mode 100644 docker-compose.yaml create mode 100644 env.template create mode 100644 go.mod create mode 100644 go.sum create mode 100644 src/checker.go create mode 100644 src/config.go create mode 100644 src/discordInteraction.go create mode 100644 src/ipTools.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3f9436d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +!src +!cmd +!go.mod +!go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f72f89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e87e63 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.22-alpine as builder + +# build app +WORKDIR /opt/serverchecker/src +COPY . /opt/serverchecker/src + +RUN go build -o /opt/serverchecker/serverchecker cmd/main.go +RUN chmod a+x /opt/serverchecker/serverchecker + +# target image +FROM golang:1.22-alpine +RUN mkdir -p /opt/serverchecker + +COPY --from=builder /opt/serverchecker/serverchecker /opt/serverchecker/serverchecker +RUN crontab -l | { cat; echo "0 * * * * /opt/serverchecker/serverchecker"; } | crontab - + + +CMD ["crond", "-f", "-l", "2"] diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..e69de29 diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..b48d36f --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "git.domandoman.xyz/doman/serverchecker/src" +) + +func main() { + config, err := src.GetConfig() + + if err != nil { + panic(err) + } + + src.RunCheck(config) +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..5855078 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,9 @@ +services: + app: + image: serverchecker + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + env_file: + - .env diff --git a/env.template b/env.template new file mode 100644 index 0000000..c39597b --- /dev/null +++ b/env.template @@ -0,0 +1,4 @@ +DISCORD_TOKEN="" +DISCORD_CHANNEL_ID="" +CHECK_DOMAINS="[\"google.com\"]" +CHECK_NOTIFY_EXPIRATION_DAYS=7 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..702fb20 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module git.domandoman.xyz/doman/serverchecker + +go 1.22.5 + +require ( + git.domandoman.xyz/doman/certchecker v0.0.2 + github.com/bwmarrin/discordgo v0.28.1 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d7c86b7 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +git.domandoman.xyz/doman/certchecker v0.0.2 h1:ems0cG8ULb/mSabkrsvTixXhHcZMiHrGggo4UeyOjY0= +git.domandoman.xyz/doman/certchecker v0.0.2/go.mod h1:Ovlm5r/JfAr4LewAvXVDax6TnzBN8GwglTcYCJaWnzQ= +github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= +github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/checker.go b/src/checker.go new file mode 100644 index 0000000..77fb852 --- /dev/null +++ b/src/checker.go @@ -0,0 +1,84 @@ +package src + +import ( + "fmt" + "git.domandoman.xyz/doman/certchecker" + "strings" + "sync" + "time" +) + +type Errors struct { + errors []error + mutex sync.Mutex +} + +func (errors *Errors) append(err error) { + errors.mutex.Lock() + errors.errors = append(errors.errors, err) + errors.mutex.Unlock() +} + +func RunCheck(config *Config) { + localIp, err := getIp() + + if err != nil { + panic(err) + } + + wg := sync.WaitGroup{} + errors := Errors{} + + for _, domain := range config.Domains { + wg.Add(2) + + go func() { + defer wg.Done() + + err := certchecker.Check(domain, config.NotifyExpirationDays) + + if err != nil { + errors.append(fmt.Errorf("%s: %s", domain, err)) + } + }() + + go func() { + defer wg.Done() + + domainIp, err := getDomainIp(domain) + + if err != nil { + errors.append(fmt.Errorf("%s: %s", domain, err)) + } + + if domainIp != localIp { + errors.append(fmt.Errorf("%s: DNS mismatch (%s != %s)", domain, domainIp, localIp)) + } + }() + } + + wg.Wait() + + if len(errors.errors) == 0 { + fmt.Println("No errors found") + return + } + + builder := strings.Builder{} + + header := fmt.Sprintf("@here\nDuring check at: %s several errors were found:\n", time.Now().Format(time.RFC3339)) + builder.WriteString(header) + + for _, err := range errors.errors { + builder.WriteString(err.Error()) + builder.WriteString("\n") + } + + message := builder.String() + fmt.Println(message) + err = sendDiscordMessage(config, message) + + if err != nil { + panic(err) + } +} diff --git a/src/config.go b/src/config.go new file mode 100644 index 0000000..317ac44 --- /dev/null +++ b/src/config.go @@ -0,0 +1,40 @@ +package src + +import ( + "encoding/json" + "github.com/joho/godotenv" + "os" + "strconv" +) + +type Config struct { + Domains []string + NotifyExpirationDays int + DiscordToken string + DiscordChannelID string +} + +func GetConfig() (*Config, error) { + godotenv.Load() + + domainsEncoded := os.Getenv("CHECK_DOMAINS") + domains := []string{} + err := json.Unmarshal([]byte(domainsEncoded), &domains) + + if err != nil { + return nil, err + } + + notifyExpirationDays, err := strconv.Atoi(os.Getenv("CHECK_NOTIFY_EXPIRATION_DAYS")) + + if err != nil { + return nil, err + } + + return &Config{ + Domains: domains, + NotifyExpirationDays: notifyExpirationDays, + DiscordToken: os.Getenv("DISCORD_TOKEN"), + DiscordChannelID: os.Getenv("DISCORD_CHANNEL_ID"), + }, nil +} diff --git a/src/discordInteraction.go b/src/discordInteraction.go new file mode 100644 index 0000000..0a7f7e3 --- /dev/null +++ b/src/discordInteraction.go @@ -0,0 +1,26 @@ +package src + +import ( + "github.com/bwmarrin/discordgo" +) + +func sendDiscordMessage(config *Config, message string) error { + discord, err := discordgo.New("Bot " + config.DiscordToken) + + if err != nil { + return err + } + + err = discord.Open() + if err != nil { + return err + } + + _, err = discord.ChannelMessageSend(config.DiscordChannelID, message) + + if err != nil { + return err + } + + return nil +} diff --git a/src/ipTools.go b/src/ipTools.go new file mode 100644 index 0000000..f7f355b --- /dev/null +++ b/src/ipTools.go @@ -0,0 +1,38 @@ +package src + +import ( + "encoding/json" + "io" + "net" + "net/http" +) + +type IP struct { + Query string +} + +func getIp() (string, error) { + req, err := http.Get("http://ip-api.com/json/") + if err != nil { + return "", err + } + defer req.Body.Close() + + body, err := io.ReadAll(req.Body) + if err != nil { + return "", err + } + + var ip IP + json.Unmarshal(body, &ip) + + return ip.Query, nil +} + +func getDomainIp(domain string) (string, error) { + ips, err := net.LookupIP(domain) + if err != nil { + return "", err + } + return ips[0].String(), nil +}