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

6 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
15 changed files with 206 additions and 49 deletions

2
.dockerignore Normal file
View File

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

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

65
Gopkg.lock generated
View File

@@ -1,41 +1,90 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # 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]] [[projects]]
branch = "master" branch = "master"
digest = "1:b7e05c8da029b985907b15e126cde5f42f2814b08211a184dac436eeec6fe780"
name = "github.com/anvie/port-scanner" name = "github.com/anvie/port-scanner"
packages = [ packages = [
".", ".",
"predictors", "predictors",
"predictors/webserver" "predictors/webserver",
] ]
pruneopts = "UT"
revision = "8159197d3770eb6dbf3a9706a6d40462ebb69cec" revision = "8159197d3770eb6dbf3a9706a6d40462ebb69cec"
[[projects]] [[projects]]
digest = "1:d87c9221a974263e3b369bfd3513707b2a53e27a6cd799d472f94dc6a6157e59"
name = "github.com/bwmarrin/discordgo" name = "github.com/bwmarrin/discordgo"
packages = ["."] packages = ["."]
revision = "4a33b9bc7c56cfdb9bb244e33e83cb3941fe2bdc" pruneopts = "UT"
version = "v0.18.0" revision = "ed4d6904961d1688b3f5601b3d73e95a71046734"
version = "v0.20.3"
[[projects]] [[projects]]
digest = "1:6d29f02f0f01c627c2be40fb7347669a9ff2aa215cb97747294c1d13ffa74bdd"
name = "github.com/gorilla/websocket" name = "github.com/gorilla/websocket"
packages = ["."] packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" pruneopts = "UT"
version = "v1.2.0" revision = "b65e62901fc1c0d968042419e74789f6af455eb9"
version = "v1.4.2"
[[projects]] [[projects]]
branch = "master" 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" name = "golang.org/x/crypto"
packages = [ packages = [
"internal/subtle",
"nacl/secretbox", "nacl/secretbox",
"poly1305", "poly1305",
"salsa20/salsa" "salsa20/salsa",
] ]
revision = "c4a91bd4f524f10d064139674cf55852e055ad01" 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] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "0832269100c492c595c4fe5f02e10d70889b9fa4d6a869c3ad59584cda0c5d31" input-imports = [
"github.com/anvie/port-scanner",
"github.com/bwmarrin/discordgo",
"github.com/kidoman/go-steam",
]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@@ -31,8 +31,12 @@
[[constraint]] [[constraint]]
name = "github.com/bwmarrin/discordgo" name = "github.com/bwmarrin/discordgo"
version = "0.18.0" version = "0.20.0"
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/kidoman/go-steam"

View File

@@ -4,6 +4,7 @@
"RolesToNotify": ["<@&roleid>", "<@userid>"], "RolesToNotify": ["<@&roleid>", "<@userid>"],
"GameStatus": "current playing game", "GameStatus": "current playing game",
"PollingInterval": 10, "PollingInterval": 10,
"BotPrefix": "!",
"Servers": [ "Servers": [
{ {
"Name": "Your awesome server", "Name": "Your awesome server",

View File

@@ -16,16 +16,20 @@ type configStruct struct {
Token string `json:"Token"` Token string `json:"Token"`
RoomIDList []string `json:"RoomIDList"` RoomIDList []string `json:"RoomIDList"`
RolesToNotify []string `json:"RolesToNotify"` 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"` 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() {

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;

View File

@@ -8,11 +8,6 @@ import (
"github.com/mgerb/ServerStatus/serverstatus" "github.com/mgerb/ServerStatus/serverstatus"
) )
// Variables used for command line parameters
var (
BotID string
)
var version = "undefined" var version = "undefined"
func init() { func init() {

View File

@@ -4,7 +4,7 @@ run:
go run ./src/main.go go run ./src/main.go
linux: linux:
go build -o ./dist/ServerStatus-linux -ldflags="-X main.version=${VERSION}" ./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 -ldflags="-X main.version=${VERSION}" ./main.go GOOS=darwin GOARCH=amd64 go build -o ./dist/ServerStatus-mac -ldflags="-X main.version=${VERSION}" ./main.go

View File

@@ -1,21 +1,18 @@
# Server Status # Server Status
Scans a list of TCP servers checking checking which are currently online. Monitors a list of servers and sends a chat notification when a server goes on or offline.
This bot will send a chat notification when the status of a server changes (goes on or offline).
I originally made this bot to check if private World of Warcraft servers were up or not. - **TCP** - should work with all servers
It's actually much more useful than that and can be used for most servers. - **UDP** - [Source RCON Protocol](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) is supported
NOTE: This bot currently does not work for UDP 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!
### Mentioning Roles/Users ### Mentioning Roles/Users
- you must first get your role/user id - list of user/role ID's must be in the following format (see below for obtaining ID's)
- for user `<@userid>` - `<@userid>`
- for role `<@&roleid>` - `<@&roleid>`
### Polling Interval ### Polling Interval
The polling interval is how often the bot will try to ping the servers. The polling interval is how often the bot will try to ping the servers.
@@ -23,12 +20,30 @@ A good interval is 10 seconds, but this may need some adjustment if
it happens to be spamming notifications. it happens to be spamming notifications.
- time in seconds - time in seconds
- configurable in `config.json` - 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 ## Usage
To get the current status of your servers simply type `!ServerStatus` in chat. To get the current status of your servers simply type `!ServerStatus` in chat.
![Server Status](https://i.imgur.com/ZzQSBJp.png) ![Server Status](./readme_files/screenshot1.png)
## Compiling from source ## Compiling from source
- Make sure Go and Make are installed - Make sure Go and Make are installed
@@ -40,5 +55,4 @@ https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-
### 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"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

View File

@@ -1,12 +1,16 @@
package serverstatus package serverstatus
import ( import (
"fmt"
"log" "log"
"strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/anvie/port-scanner" portscanner "github.com/anvie/port-scanner"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
steam "github.com/kidoman/go-steam"
"github.com/mgerb/ServerStatus/bot" "github.com/mgerb/ServerStatus/bot"
"github.com/mgerb/ServerStatus/config" "github.com/mgerb/ServerStatus/config"
) )
@@ -22,6 +26,8 @@ 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)
@@ -46,36 +52,74 @@ 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 {
sendMessageToRooms(green, server.Name, "Is now online :smiley:", true)
} else if !serverUp && serverUp != prevServerUp {
sendMessageToRooms(red, server.Name, "Has gone offline :frowning2:", true)
} }
config.Config.Servers[index].Online = serverUp wg.Wait()
}
time.Sleep(time.Second * config.Config.PollingInterval) time.Sleep(time.Second * config.Config.PollingInterval)
} }
} }
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) { func sendMessageToRooms(color int, title, description string, mentionRoles bool) {
for _, roomID := range config.Config.RoomIDList { for _, roomID := range config.Config.RoomIDList {
if mentionRoles { if mentionRoles {
content := strings.Join(config.Config.RolesToNotify, " ") content := strings.Join(config.Config.RolesToNotify, " ")
bot.Session.ChannelMessageSend(roomID, content) bot.Session.ChannelMessageSend(roomID, content)
} }
sendEmbededMessage(roomID, color, title, description) sendEmbeddedMessage(roomID, color, title, description)
} }
} }
func sendEmbededMessage(roomID string, color int, title, description string) { func sendEmbeddedMessage(roomID string, color int, title, description string) {
embed := &discordgo.MessageEmbed{ embed := &discordgo.MessageEmbed{
Color: color, Color: color,
@@ -95,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 {
sendEmbededMessage(m.ChannelID, green, server.Name, "Online!") sendEmbeddedMessage(m.ChannelID, green, server.Name, "Online!\nUptime: "+fmtDuration(time.Since(server.OnlineTimestamp)))
} else { } else {
sendEmbededMessage(m.ChannelID, red, server.Name, "Offline!") 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)
}