-
Notifications
You must be signed in to change notification settings - Fork 774
/
main.go
188 lines (167 loc) · 5.24 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path"
"regexp"
"strings"
"time"
sdk "agones.dev/agones/sdks/go"
"github.com/hpcloud/tail"
)
// logLocation is the path to the location of the SuperTuxKart log file
const logLocation = "/.config/supertuxkart/config-0.10/server_config.log"
// main intercepts the log file of the SuperTuxKart gameserver and uses it
// to determine if the game server is ready or not.
func main() {
log.SetPrefix("[wrapper] ")
input := flag.String("i", "", "the command and arguments to execute the server binary")
// Since player tracking is not on by default, it is behind this flag.
// If it is off, still log messages about players, but don't actually call the player tracking functions.
enablePlayerTracking := flag.Bool("player-tracking", false, "If true, player tracking will be enabled.")
flag.Parse()
log.Println("Connecting to Agones with the SDK")
s, err := sdk.NewSDK()
if err != nil {
log.Fatalf("could not connect to SDK: %v", err)
}
if *enablePlayerTracking {
if err = s.Alpha().SetPlayerCapacity(8); err != nil {
log.Fatalf("could not set play count: %v", err)
}
}
log.Println("Starting health checking")
go doHealth(s)
log.Println("Starting wrapper for SuperTuxKart")
log.Printf("Command being run for SuperTuxKart server: %s \n", *input)
cmdString := strings.Split(*input, " ")
command, args := cmdString[0], cmdString[1:]
cmd := exec.Command(command, args...) // #nosec
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
log.Fatalf("error starting cmd: %v", err)
}
// SuperTuxKart refuses to output to foreground, so we're going to
// poll the server log.
home, err := os.UserHomeDir()
if err != nil {
log.Fatalf("could not get home dir: %v", err)
}
t := &tail.Tail{}
// Loop to make sure the log has been created. Sometimes it takes a few seconds
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
t, err = tail.TailFile(path.Join(home, logLocation), tail.Config{Follow: true})
if err != nil {
log.Print(err)
continue
} else {
break
}
}
defer t.Cleanup()
for line := range t.Lines {
// Don't use the logger here. This would add multiple prefixes to the logs. We just want
// to show the supertuxkart logs as they are, and layer the wrapper logs in with them.
fmt.Println(line.Text)
action, player := handleLogLine(line.Text)
switch action {
case "READY":
if err := s.Ready(); err != nil {
log.Fatal("failed to mark server ready")
}
case "PLAYERJOIN":
if player == nil {
log.Print("could not determine player")
break
}
if *enablePlayerTracking {
result, err := s.Alpha().PlayerConnect(*player)
if err != nil {
log.Print(err)
} else {
log.Print(result)
}
}
case "PLAYERLEAVE":
if player == nil {
log.Print("could not determine player")
break
}
if *enablePlayerTracking {
result, err := s.Alpha().PlayerDisconnect(*player)
if err != nil {
log.Print(err)
} else {
log.Print(result)
}
}
case "SHUTDOWN":
if err := s.Shutdown(); err != nil {
log.Fatal(err)
}
os.Exit(0)
}
}
log.Fatal("tail ended")
}
// doHealth sends the regular Health Pings
func doHealth(sdk *sdk.SDK) {
tick := time.Tick(2 * time.Second)
for {
if err := sdk.Health(); err != nil {
log.Fatalf("could not send health ping: %v", err)
}
<-tick
}
}
// handleLogLine compares the log line to a series of regexes to determine if any action should be taken.
// TODO: This could probably be handled better with a custom type rather than just (string, *string)
func handleLogLine(line string) (string, *string) {
// The various regexes that match server lines
playerJoin := regexp.MustCompile(`ServerLobby: New player (.+) with online id [0-9][0-9]?`)
playerLeave := regexp.MustCompile(`ServerLobby: (.+) disconnected$`)
noMorePlayers := regexp.MustCompile(`STKHost.+There are now 0 peers\.$`)
serverStart := regexp.MustCompile(`Listening has been started`)
// Start the server
if serverStart.MatchString(line) {
log.Print("server ready")
return "READY", nil
}
// Player tracking
if playerJoin.MatchString(line) {
matches := playerJoin.FindSubmatch([]byte(line))
player := string(matches[1])
log.Printf("Player %s joined\n", player)
return "PLAYERJOIN", &player
}
if playerLeave.MatchString(line) {
matches := playerLeave.FindSubmatch([]byte(line))
player := string(matches[1])
log.Printf("Player %s disconnected", player)
return "PLAYERLEAVE", &player
}
// All the players left, send a shutdown
if noMorePlayers.MatchString(line) {
log.Print("server has no more players. shutting down")
return "SHUTDOWN", nil
}
return "", nil
}