1
0
mirror of https://github.com/mgerb/ServerStatus synced 2026-01-12 04:02:48 +00:00

9 Commits

Author SHA1 Message Date
56770b7b66 v0.7.0 2020-06-10 18:30:40 -05:00
f8d72bc297 feat: new features/improvements
- update dependencies
- add up/down time #14
- add workers with retry to help with spam
2020-06-10 18:28:41 -05:00
68bfed3f3b update readme 2019-01-19 20:30:27 -06:00
92d94ed4d4 feat(RCON): added RCON support for UDP servers 2019-01-19 20:13:55 -06:00
d0cf0eae78 Merge pull request #23 from mgerb/development
added docker support
2018-10-04 19:10:23 -05:00
642ccc7bf5 added docker support 2018-10-04 19:07:30 -05:00
bcb57c72a3 add polling interval to configurations - resolves #15 2018-07-18 20:22:37 -05:00
1cd9f139c2 Merge pull request #13 from mgerb/mitchell
added embedded messages resolves #9
2018-03-15 21:07:58 -05:00
8d26fba95d added embedded messages resolves #9 2018-03-15 21:06:08 -05:00
16 changed files with 352 additions and 71 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
/vendor
/dist

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
config.json config.json
dist dist
vendor

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM golang:1.11.1-alpine
WORKDIR /go/src/github.com/mgerb/ServerStatus
ADD . .
RUN apk add --no-cache git alpine-sdk
RUN go get -u github.com/golang/dep/cmd/dep
RUN dep ensure
RUN make linux
FROM alpine:3.8
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
WORKDIR /server-status
COPY --from=0 /go/src/github.com/mgerb/ServerStatus/dist/ServerStatus-linux .
ENTRYPOINT ./ServerStatus-linux

90
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,90 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:05eebdd5727fea23083fce0d98d307d70c86baed644178e81608aaa9f09ea469"
name = "github.com/Sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "60c74ad9be0d874af0ab0daef6ab07c5c5911f0d"
version = "v1.6.0"
[[projects]]
branch = "master"
digest = "1:b7e05c8da029b985907b15e126cde5f42f2814b08211a184dac436eeec6fe780"
name = "github.com/anvie/port-scanner"
packages = [
".",
"predictors",
"predictors/webserver",
]
pruneopts = "UT"
revision = "8159197d3770eb6dbf3a9706a6d40462ebb69cec"
[[projects]]
digest = "1:d87c9221a974263e3b369bfd3513707b2a53e27a6cd799d472f94dc6a6157e59"
name = "github.com/bwmarrin/discordgo"
packages = ["."]
pruneopts = "UT"
revision = "ed4d6904961d1688b3f5601b3d73e95a71046734"
version = "v0.20.3"
[[projects]]
digest = "1:6d29f02f0f01c627c2be40fb7347669a9ff2aa215cb97747294c1d13ffa74bdd"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "b65e62901fc1c0d968042419e74789f6af455eb9"
version = "v1.4.2"
[[projects]]
branch = "master"
digest = "1:50993a8fbb3042b88dfecb8f4473a42f13ac70d6fd86f64df525a28b357c2d5b"
name = "github.com/kidoman/go-steam"
packages = ["."]
pruneopts = "UT"
revision = "2e40e0d508cbac591bab4ae18b231153295f3a0a"
[[projects]]
digest = "1:09cb61dc19af93deae01587e2fdb1c081e0bf48f1a5ad5fa24f48750dc57dce8"
name = "github.com/konsorten/go-windows-terminal-sequences"
packages = ["."]
pruneopts = "UT"
revision = "edb144dfd453055e1e49a3d8b410a660b5a87613"
version = "v1.0.3"
[[projects]]
branch = "master"
digest = "1:1714bd928fd176237ccdea21695bf72801c1ee5f51bccefb69b87f0fd376a6aa"
name = "golang.org/x/crypto"
packages = [
"internal/subtle",
"nacl/secretbox",
"poly1305",
"salsa20/salsa",
]
pruneopts = "UT"
revision = "70a84ac30bf957c7df57edd1935d2081871515e1"
[[projects]]
branch = "master"
digest = "1:145abe7dfa46d17ef35e5126ed1cc87b9100e78b9f428c9460deea34bfeabafb"
name = "golang.org/x/sys"
packages = [
"cpu",
"internal/unsafeheader",
"unix",
]
pruneopts = "UT"
revision = "226ff32320da7b90d0b5bc2365f4e359c466fb78"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/anvie/port-scanner",
"github.com/bwmarrin/discordgo",
"github.com/kidoman/go-steam",
]
solver-name = "gps-cdcl"
solver-version = 1

42
Gopkg.toml Normal file
View File

@@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/anvie/port-scanner"
[[constraint]]
name = "github.com/bwmarrin/discordgo"
version = "0.20.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/kidoman/go-steam"

View File

@@ -1,8 +1,10 @@
{ {
"Token": "your bot token", "Token": "your bot token",
"RoomIDList":["room id list goes here"], "RoomIDList":["room id list goes here"],
"RoleToNotify": "@everyone", "RolesToNotify": ["<@&roleid>", "<@userid>"],
"GameStatus": "current playing game", "GameStatus": "current playing game",
"PollingInterval": 10,
"BotPrefix": "!",
"Servers": [ "Servers": [
{ {
"Name": "Your awesome server", "Name": "Your awesome server",

View File

@@ -2,32 +2,39 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"time"
) )
// Variables used for command line parameters // Variables used for command line parameters
var Config configStruct var Config configStruct
type configStruct struct { type configStruct struct {
Token string `json:"Token"` Token string `json:"Token"`
RoomIDList []string `json:"RoomIDList"` RoomIDList []string `json:"RoomIDList"`
RoleToNotify string `json:"RoleToNotify"` RolesToNotify []string `json:"RolesToNotify"`
Servers []server `json:"Servers"` Servers []Server `json:"Servers"`
GameStatus string `json:"GameStatus"` GameStatus string `json:"GameStatus"`
PollingInterval time.Duration `json:"PollingInterval"`
BotPrefix string `json:"BotPrefix"`
} }
type server struct { type Server struct {
Name string `json:"Name"` Name string `json:"Name"`
Address string `json:"Address"` Address string `json:"Address"`
Port int `json:"Port"` Port int `json:"Port"`
Online bool `json:"Online,omitempty"` Online bool `json:"Online,omitempty"`
// OnlineTimestamp - time of when the server last came online
OnlineTimestamp time.Time
OfflineTimestamp time.Time
} }
func Configure() { func Configure() {
log.Println("Reading config file...") fmt.Println("Reading config file...")
file, e := ioutil.ReadFile("./config.json") file, e := ioutil.ReadFile("./config.json")
@@ -36,12 +43,14 @@ func Configure() {
os.Exit(1) os.Exit(1)
} }
log.Printf("%s\n", string(file))
err := json.Unmarshal(file, &Config) err := json.Unmarshal(file, &Config)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
if Config.PollingInterval == 0 {
log.Fatal("Please set your PollingInterval > 0 in your config file.")
}
} }

5
docker-build.sh Executable file
View File

@@ -0,0 +1,5 @@
version=$(git describe --tags)
docker build -t mgerb/server-status:latest .
docker tag mgerb/server-status:latest mgerb/server-status:$version

7
docker-compose.yml Normal file
View File

@@ -0,0 +1,7 @@
version: "2"
services:
server-status:
image: mgerb/server-status:latest
volumes:
- ./config.json:/server-status/config.json

5
docker-push.sh Executable file
View File

@@ -0,0 +1,5 @@
version=$(git describe --tags)
docker push mgerb/server-status:latest;
docker push mgerb/server-status:$version;

17
main.go
View File

@@ -1,15 +1,18 @@
package main package main
import ( import (
"github.com/mgerb/serverstatus/bot" "fmt"
"github.com/mgerb/serverstatus/config"
"github.com/mgerb/serverstatus/serverstatus" "github.com/mgerb/ServerStatus/bot"
"github.com/mgerb/ServerStatus/config"
"github.com/mgerb/ServerStatus/serverstatus"
) )
// Variables used for command line parameters var version = "undefined"
var (
BotID string func init() {
) fmt.Println("Starting Server Status " + version)
}
func main() { func main() {
//read config file //read config file

View File

@@ -1,14 +1,16 @@
VERSION := $(shell git describe --tags)
run: run:
go run ./src/main.go go run ./src/main.go
linux: linux:
go build -o ./dist/ServerStatus-linux ./main.go GOOS=linux GOARCH=amd64 go build -o ./dist/ServerStatus-linux -ldflags="-X main.version=${VERSION}" ./main.go
mac: mac:
GOOS=darwin GOARCH=amd64 go build -o ./dist/ServerStatus-mac ./main.go GOOS=darwin GOARCH=amd64 go build -o ./dist/ServerStatus-mac -ldflags="-X main.version=${VERSION}" ./main.go
windows: windows:
GOOS=windows GOARCH=386 go build -o ./dist/ServerStatus-windows.exe ./main.go GOOS=windows GOARCH=386 go build -o ./dist/ServerStatus-windows.exe -ldflags="-X main.version=${VERSION}" ./main.go
clean: clean:
rm -rf ./dist rm -rf ./dist
@@ -16,4 +18,7 @@ clean:
copyfiles: copyfiles:
cp config.template.json ./dist/config.json cp config.template.json ./dist/config.json
zip:
zip -r dist.zip dist
all: linux mac windows copyfiles all: linux mac windows copyfiles

View File

@@ -1,24 +1,51 @@
## Server Status # Server Status
Monitors a list of servers and sends a chat notification when a server goes on or offline.
Scans a list of servers checking whether the ports are open or not. - **TCP** - should work with all servers
This bot will send a chat notification when the status of a server changes (goes on or offline). - **UDP** - [Source RCON Protocol](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) is supported
I originally made this bot to check if private World of Warcraft servers were up or not.
It's actually much more useful than that and can be used for most servers.
It has been brought to my attention that this bot currently does not work for Ark servers.
### Note - updated for Felmyst server!
The config template contains IP/Ports for the new Felmyst game and logon servers
## Configuration ## Configuration
- Download the latest release [here](https://github.com/mgerb/ServerStatus/releases) - Download the latest release [here](https://github.com/mgerb/ServerStatus/releases)
- Add your bot token as well as other configurations to config.json - Add your bot token as well as other configurations to **config.json**
- Execute the OS specific binary! - Execute the OS specific binary!
## Compiling from source ### Mentioning Roles/Users
- list of user/role ID's must be in the following format (see below for obtaining ID's)
- `<@userid>`
- `<@&roleid>`
### Polling Interval
The polling interval is how often the bot will try to ping the servers.
A good interval is 10 seconds, but this may need some adjustment if
it happens to be spamming notifications.
- time in seconds
- configurable in **config.json**
## With Docker
```
docker run -it -v /path/to/your/config.json:/server-status/config.json:ro mgerb/server-status
```
### Docker Compose
```
version: "2"
services:
server-status:
image: mgerb/server-status:latest
volumes:
- /path/to/your/config.json:/server-status/config.json
```
## Usage
To get the current status of your servers simply type `!ServerStatus` in chat.
![Server Status](./readme_files/screenshot1.png)
## Compiling from source
- Make sure Go and Make are installed - Make sure Go and Make are installed
- make all - make all
@@ -26,19 +53,6 @@ The config template contains IP/Ports for the new Felmyst game and logon servers
https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token
### How to get your room ID ### How to get your room ID
To get IDs, turn on Developer Mode in the Discord client (User Settings -> Appearance) and then right-click your name/icon anywhere in the client and select Copy ID. To get IDs, turn on Developer Mode in the Discord client (User Settings -> Appearance) and then right-click your name/icon anywhere in the client and select Copy ID.
<img src="https://camo.githubusercontent.com/9f759ec8b45a6e9dd2242bc64c82897c74f84a25/687474703a2f2f692e696d6775722e636f6d2f47684b70424d512e676966"/> <img src="./readme_files/screenshot2.gif"/>
## List server status in discord channel
`!ServerStatus`
```
Elysium PvP is online!
Zethkur PvP is online!
Anathema PvP is online!
Darrowshire PvE is online!
Elysium Authentication Server is online!
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

View File

@@ -1,23 +1,39 @@
package serverstatus package serverstatus
import ( import (
"fmt"
"log" "log"
"strconv"
"strings"
"sync"
"time" "time"
"github.com/anvie/port-scanner" portscanner "github.com/anvie/port-scanner"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/mgerb/serverstatus/bot" steam "github.com/kidoman/go-steam"
"github.com/mgerb/serverstatus/config" "github.com/mgerb/ServerStatus/bot"
"github.com/mgerb/ServerStatus/config"
) )
const (
red = 0xf4425c
green = 0x42f477
blue = 0x42adf4
)
// Start - start port scanner and bot listeners
func Start() { func Start() {
//set each server status as online to start //set each server status as online to start
for i, _ := range config.Config.Servers { for i := range config.Config.Servers {
config.Config.Servers[i].Online = true config.Config.Servers[i].Online = true
config.Config.Servers[i].OnlineTimestamp = time.Now()
config.Config.Servers[i].OfflineTimestamp = time.Now()
} }
err := bot.Session.UpdateStatus(0, config.Config.GameStatus) err := bot.Session.UpdateStatus(0, config.Config.GameStatus)
sendMessageToRooms(blue, "Server Status", "Bot started! Type !ServerStatus to see the status of your servers :smiley:", false)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@@ -36,32 +52,85 @@ func scanServers() {
for { for {
for index, server := range config.Config.Servers { // use waitgroup to scan all servers concurrently
prevServerUp := server.Online //set value to previous server status var wg sync.WaitGroup
serverScanner := portscanner.NewPortScanner(server.Address, time.Second*2, 1) for index := range config.Config.Servers {
serverUp := serverScanner.IsOpen(server.Port) //check if the port is open wg.Add(1)
go worker(index, &config.Config.Servers[index], &wg)
if serverUp && serverUp != prevServerUp {
sendMessage(config.Config.RoleToNotify + " " + server.Name + " is now online!")
} else if !serverUp && serverUp != prevServerUp {
sendMessage(config.Config.RoleToNotify + " " + server.Name + " went offline!")
}
config.Config.Servers[index].Online = serverUp
} }
time.Sleep(time.Second * 5) wg.Wait()
time.Sleep(time.Second * config.Config.PollingInterval)
} }
} }
func sendMessage(message string) { func worker(index int, server *config.Server, wg *sync.WaitGroup) {
defer wg.Done()
prevServerUp := server.Online //set value to previous server status
var serverUp bool
retryCounter := 0
// try reconnecting 5 times if failure persists (every 2 seconds)
for {
serverScanner := portscanner.NewPortScanner(server.Address, time.Second*2, 1)
serverUp = serverScanner.IsOpen(server.Port) //check if the port is open
if serverUp || retryCounter >= 5 {
break
}
retryCounter++
time.Sleep(time.Second * 2)
}
// if server isn't up check RCON protocol (UDP)
if !serverUp {
host := server.Address + ":" + strconv.Itoa(server.Port)
steamConnection, err := steam.Connect(host)
if err == nil {
defer steamConnection.Close()
_, err := steamConnection.Ping()
if err == nil {
serverUp = true
}
}
}
if serverUp && serverUp != prevServerUp {
server.OnlineTimestamp = time.Now()
sendMessageToRooms(green, server.Name, "Is now online :smiley:", true)
} else if !serverUp && serverUp != prevServerUp {
server.OfflineTimestamp = time.Now()
sendMessageToRooms(red, server.Name, "Has gone offline :frowning2:", true)
}
server.Online = serverUp
}
func sendMessageToRooms(color int, title, description string, mentionRoles bool) {
for _, roomID := range config.Config.RoomIDList { for _, roomID := range config.Config.RoomIDList {
bot.Session.ChannelMessageSend(roomID, message) if mentionRoles {
content := strings.Join(config.Config.RolesToNotify, " ")
bot.Session.ChannelMessageSend(roomID, content)
}
sendEmbeddedMessage(roomID, color, title, description)
} }
} }
// This function will be called every time a new func sendEmbeddedMessage(roomID string, color int, title, description string) {
embed := &discordgo.MessageEmbed{
Color: color,
Title: title,
Description: description,
}
bot.Session.ChannelMessageSendEmbed(roomID, embed)
}
// MessageHandler will be called every time a new
// message is created on any channel that the autenticated bot has access to. // message is created on any channel that the autenticated bot has access to.
func MessageHandler(s *discordgo.Session, m *discordgo.MessageCreate) { func MessageHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
@@ -70,13 +139,23 @@ func MessageHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
return return
} }
if m.Content == "!ServerStatus" { if m.Content == config.Config.BotPrefix+"ServerStatus" {
for _, server := range config.Config.Servers { for _, server := range config.Config.Servers {
if server.Online { if server.Online {
s.ChannelMessageSend(m.ChannelID, server.Name+" is online!") sendEmbeddedMessage(m.ChannelID, green, server.Name, "Online!\nUptime: "+fmtDuration(time.Since(server.OnlineTimestamp)))
} else { } else {
s.ChannelMessageSend(m.ChannelID, server.Name+" is down!") sendEmbeddedMessage(m.ChannelID, red, server.Name, "Offline!\nDowntime: "+fmtDuration(time.Since(server.OfflineTimestamp)))
} }
} }
} }
} }
func fmtDuration(d time.Duration) string {
days := int(d.Hours()) / 24
hours := int(d.Hours()) % 60
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
}