1
0
mirror of https://github.com/mgerb/ServerStatus synced 2026-01-09 18:52:50 +00:00

18 Commits

Author SHA1 Message Date
25ab128a58 update docker files 2024-03-03 16:09:20 -06:00
Ethorbit
e4320bcecb Make docker image unprivileged 2024-03-03 16:00:31 -06:00
50a06b98be Updates and maintenance
- switch from dep to go modules
- update to discordgo v0.27.1
- fix offline/online message bug
2024-03-03 11:32:08 -06:00
Ethorbit
ed232fc785 Fix outdated broken bot
discordgo upgraded from 0.20.0 to 0.24.0 which fixed the bot starting with a websocket error and remaining offline. Additionally, the command prefix and MessageHandling commands have been replaced with Discord's newer slash commands.
2024-03-03 11:24:22 -06:00
12c20b928c v0.8.0 2020-06-13 12:53:51 -05:00
2fdc0eddd3 update docker files
closes #27
2020-06-13 12:52:52 -05:00
18b1a0b6e3 fix: add retry to RCON servers
closes #28
2020-06-13 12:52:32 -05:00
bf79d376f3 fix: date format
closes #29
2020-06-13 12:47:00 -05:00
8f430b5982 update readme 2020-06-10 18:53:49 -05:00
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 340 additions and 92 deletions

2
.dockerignore Normal file
View File

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

1
.gitignore vendored
View File

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

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM golang:1.22-alpine3.19
WORKDIR /go/src/github.com/mgerb/ServerStatus
ADD . .
RUN apk add --no-cache git alpine-sdk
RUN go get
RUN make linux
FROM alpine:3.19
ARG UNAME="server-status"
ARG GNAME="server-status"
ARG UID=1000
ARG GID=1000
WORKDIR /server-status
COPY --from=0 /go/src/github.com/mgerb/ServerStatus/dist/ServerStatus-linux .
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
RUN addgroup -g ${GID} "${GNAME}"
RUN adduser -D -u ${UID} -G "${GNAME}" "${UNAME}" &&\
chown "${UNAME}":"${GNAME}" -R /server-status/
USER ${UNAME}
ENTRYPOINT ./ServerStatus-linux

View File

@@ -1,17 +1,19 @@
{
"Token": "your bot token",
"RoomIDList":["room id list goes here"],
"RoleToNotify": "@everyone",
"GameStatus": "current playing game",
"Servers": [
{
"Name": "Your awesome server",
"Address": "game.server.com",
"Port": 80
}, {
"Name": "Another awesome server",
"Address": "awesome.server.com",
"Port": 8080
}
]
"Token": "your bot token",
"RoomIDList": ["room id list goes here"],
"RolesToNotify": ["<@&roleid>", "<@userid>"],
"GameStatus": "current playing game",
"PollingInterval": 10,
"Servers": [
{
"Name": "Your awesome server",
"Address": "game.server.com",
"Port": 80
},
{
"Name": "Another awesome server",
"Address": "awesome.server.com",
"Port": 8080
}
]
}

View File

@@ -2,32 +2,38 @@ package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"time"
)
// Variables used for command line parameters
var Config configStruct
type configStruct struct {
Token string `json:"Token"`
RoomIDList []string `json:"RoomIDList"`
RoleToNotify string `json:"RoleToNotify"`
Servers []server `json:"Servers"`
GameStatus string `json:"GameStatus"`
Token string `json:"Token"`
RoomIDList []string `json:"RoomIDList"`
RolesToNotify []string `json:"RolesToNotify"`
Servers []Server `json:"Servers"`
GameStatus string `json:"GameStatus"`
PollingInterval time.Duration `json:"PollingInterval"`
}
type server struct {
type Server struct {
Name string `json:"Name"`
Address string `json:"Address"`
Port int `json:"Port"`
Online bool `json:"Online,omitempty"`
// OnlineTimestamp - time of when the server last came online
OnlineTimestamp time.Time
OfflineTimestamp time.Time
}
func Configure() {
log.Println("Reading config file...")
fmt.Println("Reading config file...")
file, e := ioutil.ReadFile("./config.json")
@@ -36,12 +42,14 @@ func Configure() {
os.Exit(1)
}
log.Printf("%s\n", string(file))
err := json.Unmarshal(file, &Config)
if err != nil {
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: "3"
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
go.mod Normal file
View File

@@ -0,0 +1,17 @@
module github.com/mgerb/ServerStatus
go 1.22.0
require (
github.com/anvie/port-scanner v0.0.0-20180225151059-8159197d3770
github.com/bwmarrin/discordgo v0.27.1
github.com/kidoman/go-steam v0.0.0-20141221015629-2e40e0d508cb
)
require (
github.com/Sirupsen/logrus v1.0.6 // indirect
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
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
)

19
go.sum Normal file
View File

@@ -0,0 +1,19 @@
github.com/Sirupsen/logrus v1.0.6 h1:HCAGQRk48dRVPA5Y+Yh0qdCSTzPOyU1tBJ7Q9YzotII=
github.com/Sirupsen/logrus v1.0.6/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U=
github.com/anvie/port-scanner v0.0.0-20180225151059-8159197d3770 h1:1KEvfMGAjISVzk3Ti6pfaOgtoC3naoU0LfiJooZDNO8=
github.com/anvie/port-scanner v0.0.0-20180225151059-8159197d3770/go.mod h1:QGzdstKeoHmMWwi9oNHZ7DQzEj9pi7H42171pkj9htk=
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.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/kidoman/go-steam v0.0.0-20141221015629-2e40e0d508cb h1:+3H4rb1CvcN/BEuBlk774uxxab272M6YU9nb9p/GZm8=
github.com/kidoman/go-steam v0.0.0-20141221015629-2e40e0d508cb/go.mod h1:PKiM4eL8SN6mJ38F9m6ZJpVtJKnmSxOxBsi2p3TOru4=
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 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
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=

21
main.go
View File

@@ -1,15 +1,18 @@
package main
import (
"github.com/mgerb/serverstatus/bot"
"github.com/mgerb/serverstatus/config"
"github.com/mgerb/serverstatus/serverstatus"
"fmt"
"github.com/mgerb/ServerStatus/bot"
"github.com/mgerb/ServerStatus/config"
"github.com/mgerb/ServerStatus/serverstatus"
)
// Variables used for command line parameters
var (
BotID string
)
var version = "undefined"
func init() {
fmt.Println("Starting Server Status " + version)
}
func main() {
//read config file
@@ -19,13 +22,13 @@ func main() {
bot.Connect(config.Config.Token)
// add handlers
bot.AddHandler(serverstatus.MessageHandler)
bot.AddHandler(serverstatus.InteractionHandler)
//start websocket to listen for messages
bot.Start()
//start server status task
serverstatus.Start()
serverstatus.Start()
// Simple way to keep program running until CTRL-C is pressed.
<-make(chan struct{})

View File

@@ -1,14 +1,16 @@
VERSION := $(shell git describe --tags)
run:
go run ./src/main.go
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:
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:
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:
rm -rf ./dist
@@ -16,4 +18,7 @@ clean:
copyfiles:
cp config.template.json ./dist/config.json
zip:
zip -r dist.zip dist
all: linux mac windows copyfiles

View File

@@ -1,24 +1,60 @@
## 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.
This bot will send a chat notification when the status of a server changes (goes on or offline).
## Features
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.
- send channel notifications
- track server up/down time
- **TCP** - should work with all servers
- **UDP** - [Source RCON Protocol](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) is supported
- [Docker](https://hub.docker.com/r/mgerb/server-status)
It has been brought to my attention that this bot currently does not work for Ark servers.
### Want to see more features?
### Note - updated for Felmyst server!
The config template contains IP/Ports for the new Felmyst game and logon servers
[Submit a new issue](https://github.com/mgerb/ServerStatus/issues/new/choose)
## Configuration
- 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!
## 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 `/server-status` in chat.
![Server Status](./readme_files/screenshot1.png)
## Compiling from source
- Make sure Go and Make are installed
- make all
@@ -26,19 +62,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
### 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.
<img src="https://camo.githubusercontent.com/9f759ec8b45a6e9dd2242bc64c82897c74f84a25/687474703a2f2f692e696d6775722e636f6d2f47684b70424d512e676966"/>
## 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!
```
<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,22 +1,56 @@
package serverstatus
import (
"fmt"
"log"
"strconv"
"strings"
"sync"
"time"
"github.com/anvie/port-scanner"
portscanner "github.com/anvie/port-scanner"
"github.com/bwmarrin/discordgo"
"github.com/mgerb/serverstatus/bot"
"github.com/mgerb/serverstatus/config"
steam "github.com/kidoman/go-steam"
"github.com/mgerb/ServerStatus/bot"
"github.com/mgerb/ServerStatus/config"
)
const (
red = 0xf4425c
green = 0x42f477
blue = 0x42adf4
)
// Start - add command, start port scanner and bot listeners
func Start() {
//set each server status as online to start
for i, _ := range config.Config.Servers {
config.Config.Servers[i].Online = true
//add command
_, err := bot.Session.ApplicationCommandCreate(bot.Session.State.User.ID, "", &discordgo.ApplicationCommand{
Name: "server-status",
Description: "Get the status of the servers.",
})
if err != nil {
log.Panicf("Cannot create status command '%v'", err)
}
err := bot.Session.UpdateStatus(0, config.Config.GameStatus)
//set each server status as online to start
for i := range config.Config.Servers {
config.Config.Servers[i].Online = true
config.Config.Servers[i].OnlineTimestamp = time.Now()
config.Config.Servers[i].OfflineTimestamp = time.Now()
}
err = bot.Session.UpdateStatusComplex(discordgo.UpdateStatusData{
Status: "online",
Activities: []*discordgo.Activity{
&discordgo.Activity{
Type: discordgo.ActivityTypeGame,
Name: config.Config.GameStatus,
},
},
})
sendMessageToRooms(blue, "Server Status", "Bot started! Type /server-status to see the status of your servers :smiley:", false)
if err != nil {
log.Println(err)
@@ -36,47 +70,138 @@ func scanServers() {
for {
for index, server := range config.Config.Servers {
prevServerUp := server.Online //set value to previous server status
// use waitgroup to scan all servers concurrently
var wg sync.WaitGroup
serverScanner := portscanner.NewPortScanner(server.Address, time.Second*2, 1)
serverUp := serverScanner.IsOpen(server.Port) //check if the port is open
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
for index := range config.Config.Servers {
wg.Add(1)
go worker(&config.Config.Servers[index], &wg)
}
time.Sleep(time.Second * 5)
wg.Wait()
time.Sleep(time.Second * config.Config.PollingInterval)
}
}
func sendMessage(message string) {
func worker(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 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 || retryCounter >= 5 {
break
}
retryCounter++
time.Sleep(time.Second * 2)
}
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 {
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
// message is created on any channel that the autenticated bot has access to.
func MessageHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
func sendEmbeddedMessage(roomID string, color int, title, description string) {
// Ignore all messages created by the bot itself
if m.Author.ID == bot.BotID {
return
embed := &discordgo.MessageEmbed{
Color: color,
Title: title,
Description: description,
}
if m.Content == "!ServerStatus" {
bot.Session.ChannelMessageSendEmbed(roomID, embed)
}
// InteractionHandler will be called every time an interaction from a user occurs
// Command interaction handling requires bot command scope
func InteractionHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
// A user is calling us with our status command
if i.ApplicationCommandData().Name == "server-status" {
online := ""
offline := ""
for _, server := range config.Config.Servers {
if server.Online {
s.ChannelMessageSend(m.ChannelID, server.Name+" is online!")
online = online + server.Name + " : " + fmtDuration(time.Since(server.OnlineTimestamp)) + "\n"
} else {
s.ChannelMessageSend(m.ChannelID, server.Name+" is down!")
offline = offline + server.Name + " : " + fmtDuration(time.Since(server.OfflineTimestamp)) + "\n"
}
}
embeds := []*discordgo.MessageEmbed{}
if online != "" {
embeds = append(embeds, &discordgo.MessageEmbed{
Title: ":white_check_mark: Online",
Color: green,
Description: online,
})
}
if offline != "" {
embeds = append(embeds, &discordgo.MessageEmbed{
Title: ":x: Offline",
Color: red,
Description: offline,
})
}
// Only one message can be an interaction response. Messages can only contain up to 10 embeds.
// Our message will therefore instead be two embeds (online and offline), each with a list of servers in text.
// Embed descriptions can be ~4096 characters, so no limits should get hit with this.
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: embeds,
},
})
}
}
func fmtDuration(d time.Duration) string {
days := int(d.Hours()) / 24
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
}