serverwatcher 3.9__tar.gz → 4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {serverwatcher-3.9/src/serverwatcher.egg-info → serverwatcher-4.0}/PKG-INFO +1 -1
- {serverwatcher-3.9 → serverwatcher-4.0}/pyproject.toml +1 -1
- {serverwatcher-3.9 → serverwatcher-4.0}/src/serverwatcher/__init__.py +6 -2
- serverwatcher-4.0/src/serverwatcher/defaultconfigs/global.yaml +16 -0
- serverwatcher-4.0/src/serverwatcher/defaultconfigs/messages.yaml +51 -0
- serverwatcher-4.0/src/serverwatcher/defaultconfigs/watcher.yaml +39 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/src/serverwatcher/watcher.py +49 -44
- {serverwatcher-3.9 → serverwatcher-4.0/src/serverwatcher.egg-info}/PKG-INFO +1 -1
- serverwatcher-4.0/src/serverwatcher.egg-info/SOURCES.txt +17 -0
- serverwatcher-3.9/src/serverwatcher.egg-info/SOURCES.txt +0 -14
- {serverwatcher-3.9 → serverwatcher-4.0}/LICENSE +0 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/README.md +0 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/setup.cfg +0 -0
- {serverwatcher-3.9/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/__init__.py +0 -0
- {serverwatcher-3.9/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/global_config.py +0 -0
- {serverwatcher-3.9/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/messages.py +0 -0
- {serverwatcher-3.9/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/watcher.py +0 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/src/serverwatcher.egg-info/dependency_links.txt +0 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/src/serverwatcher.egg-info/requires.txt +0 -0
- {serverwatcher-3.9 → serverwatcher-4.0}/src/serverwatcher.egg-info/top_level.txt +0 -0
|
@@ -7,9 +7,13 @@ except PackageNotFoundError:
|
|
|
7
7
|
__version__ = '0.0.0'
|
|
8
8
|
|
|
9
9
|
from .watcher import ServerWatcher
|
|
10
|
-
|
|
10
|
+
from .configclasses.global_config import GlobalConfig
|
|
11
|
+
from .configclasses.messages import MessagesConfig
|
|
12
|
+
from .configclasses.watcher import WatcherConfig
|
|
11
13
|
|
|
12
14
|
__all__ = [
|
|
13
|
-
# core utilities
|
|
14
15
|
'ServerWatcher',
|
|
16
|
+
'GlobalConfig',
|
|
17
|
+
'MessagesConfig',
|
|
18
|
+
'WatcherConfig',
|
|
15
19
|
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
panel:
|
|
2
|
+
panel_name: My Panel
|
|
3
|
+
panel_url: https://example.com
|
|
4
|
+
panel_api_key: CHANGE_ME
|
|
5
|
+
|
|
6
|
+
origin:
|
|
7
|
+
origin_server_id: CHANGE_ME
|
|
8
|
+
|
|
9
|
+
server:
|
|
10
|
+
server_name: My SMP
|
|
11
|
+
server_id: CHANGE_ME
|
|
12
|
+
server_domain: mc.example.com
|
|
13
|
+
server_port: 25565
|
|
14
|
+
rcon_port: 25575
|
|
15
|
+
rcon_password: password
|
|
16
|
+
tps_command: ticks
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
prefix: "<aqua>[Server Watcher]"
|
|
2
|
+
|
|
3
|
+
broadcast_restart_at: "{prefix} The server will restart at {time} CDT."
|
|
4
|
+
|
|
5
|
+
broadcast_minute:
|
|
6
|
+
minute_120: "{prefix} Restart in 2 hours!"
|
|
7
|
+
minute_60: "{prefix} Restart in 1 hour!"
|
|
8
|
+
minute_45: "{prefix} Restart in 45 minutes!"
|
|
9
|
+
minute_30: "{prefix} Restart in 30 minutes!"
|
|
10
|
+
minute_15: "{prefix} Restart in 15 minutes!"
|
|
11
|
+
minute_5: "{prefix} Restart in 5 minutes!"
|
|
12
|
+
minute_1: "{prefix} Restart in 1 minute!"
|
|
13
|
+
|
|
14
|
+
broadcast_second:
|
|
15
|
+
second_10: "{prefix} Restart in 10 seconds!"
|
|
16
|
+
second_9: "{prefix} Restart in 9 seconds!"
|
|
17
|
+
second_8: "{prefix} Restart in 8 seconds!"
|
|
18
|
+
second_7: "{prefix} Restart in 7 seconds!"
|
|
19
|
+
second_6: "{prefix} Restart in 6 seconds!"
|
|
20
|
+
second_5: "{prefix} Restart in 5 seconds!"
|
|
21
|
+
second_4: "{prefix} Restart in 4 seconds!"
|
|
22
|
+
second_3: "{prefix} Restart in 3 seconds!"
|
|
23
|
+
second_2: "{prefix} Restart in 2 seconds!"
|
|
24
|
+
second_1: "{prefix} Restart in 1 second!"
|
|
25
|
+
|
|
26
|
+
logging:
|
|
27
|
+
log_start: "ServerWatcher is running!"
|
|
28
|
+
log_status_check: "Checking server status..."
|
|
29
|
+
log_validation_fail: "Validation FAILED."
|
|
30
|
+
log_validation_ok: "All validation checks succeeded."
|
|
31
|
+
log_shutdown: "Shutting down ServerWatcher."
|
|
32
|
+
log_immediate_restart: "Restarting immediately."
|
|
33
|
+
log_no_restart: "The server does not need to restart."
|
|
34
|
+
log_scheduled: "Restart needed, but anti-restart factors outweigh it."
|
|
35
|
+
log_gap_low: "Gap {gap}. Scheduling restart in 2 hours."
|
|
36
|
+
log_gap_high: "Gap {gap}. Scheduling restart in 1 hour."
|
|
37
|
+
|
|
38
|
+
reasons:
|
|
39
|
+
reason_restart_soon: "The server is set to restart soon"
|
|
40
|
+
reason_ram: "RAM usage ({ram}) is higher than {threshold} GB"
|
|
41
|
+
reason_cpu: "CPU usage ({cpu}) is higher than {threshold}%"
|
|
42
|
+
reason_uptime: "Uptime {uptime} exceeds {threshold}h"
|
|
43
|
+
reason_tps: "TPS {tps} is lower than {threshold}"
|
|
44
|
+
reason_low_uptime: "Uptime {uptime} is shorter than 30m"
|
|
45
|
+
reason_players: "There {verb} {count} {plural} online"
|
|
46
|
+
|
|
47
|
+
restarts:
|
|
48
|
+
restart_action_sent: "Restart action sent. Waiting..."
|
|
49
|
+
server_back_online: "Server is back online!"
|
|
50
|
+
server_back_online_broadcast: "{prefix} <green>Restart successful!"
|
|
51
|
+
server_failed_restart: "Server failed to restart!"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
logging:
|
|
2
|
+
logger_name_template: "Server Watcher"
|
|
3
|
+
log_path: "/home/container/logs/"
|
|
4
|
+
timezone: "America/Chicago"
|
|
5
|
+
|
|
6
|
+
terminal_settings:
|
|
7
|
+
console_backspaces: 8
|
|
8
|
+
clear_terminal: True
|
|
9
|
+
|
|
10
|
+
schedules:
|
|
11
|
+
restart_soon_schedule_id: 0 # replace this with real schedule id
|
|
12
|
+
origin_disable_schedule_id: 0 # replace this with real schedule id
|
|
13
|
+
|
|
14
|
+
thresolds:
|
|
15
|
+
ram_threshold: 6
|
|
16
|
+
cpu_threshold: 150
|
|
17
|
+
uptime_hours_threshold: 12
|
|
18
|
+
tps_threshold: 19.5
|
|
19
|
+
|
|
20
|
+
weights:
|
|
21
|
+
weight_restart_soon: 3
|
|
22
|
+
weight_ram: 1
|
|
23
|
+
weight_cpu: 1
|
|
24
|
+
weight_uptime: 1
|
|
25
|
+
weight_tps: 1
|
|
26
|
+
weight_low_uptime: 5
|
|
27
|
+
weight_per_player: 1
|
|
28
|
+
|
|
29
|
+
gaps:
|
|
30
|
+
low_gap_minutes: 120
|
|
31
|
+
high_gap_minutes: 60
|
|
32
|
+
|
|
33
|
+
restart_intervals:
|
|
34
|
+
restart_wait_seconds: 45
|
|
35
|
+
restart_online_timeout: 120
|
|
36
|
+
restart_online_interval: 2
|
|
37
|
+
|
|
38
|
+
watcher_intervals:
|
|
39
|
+
watch_interval: 60
|
|
@@ -11,35 +11,47 @@ from hungerlib.addons import (
|
|
|
11
11
|
waitForOnline,
|
|
12
12
|
validateAll,
|
|
13
13
|
runCountdownEvents,
|
|
14
|
+
loadConfig,
|
|
14
15
|
)
|
|
15
|
-
from hungerlib.addons.configmap import load_or_default
|
|
16
16
|
|
|
17
|
-
from serverwatcher
|
|
18
|
-
|
|
19
|
-
# Set directory
|
|
20
|
-
BASE_DIR = os.getcwd()
|
|
21
|
-
PACKAGE_DIR = os.path.dirname(__file__)
|
|
17
|
+
from serverwatcher import GlobalConfig, MessagesConfig, WatcherConfig
|
|
22
18
|
|
|
23
19
|
|
|
24
20
|
class ServerWatcher:
|
|
25
21
|
def __init__(self):
|
|
26
|
-
# Load all configs in /config using configmap
|
|
27
|
-
cfgmap = load_or_default("config", skip_files=[])
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
self.
|
|
31
|
-
|
|
23
|
+
# load configs
|
|
24
|
+
self.global_cfg = loadConfig(
|
|
25
|
+
"config/global.yaml",
|
|
26
|
+
"/defaultconfigs/global.yaml",
|
|
27
|
+
GlobalConfig
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.messages = loadConfig(
|
|
31
|
+
"config/messages.yaml",
|
|
32
|
+
"/defaultconfigs/messages.yaml",
|
|
33
|
+
MessagesConfig
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self.cfg = loadConfig(
|
|
37
|
+
"config/watcher.yaml",
|
|
38
|
+
"/defaultconfigs/watcher.yaml",
|
|
39
|
+
WatcherConfig
|
|
40
|
+
)
|
|
32
41
|
|
|
42
|
+
# initialize panels and servers
|
|
33
43
|
self.panel = Panel(
|
|
34
44
|
name=self.global_cfg.panel_name,
|
|
35
45
|
url=self.global_cfg.panel_url,
|
|
36
46
|
api_key=self.global_cfg.panel_api_key,
|
|
37
47
|
)
|
|
48
|
+
|
|
38
49
|
self.origin = GenericServer(
|
|
39
50
|
name="Origin",
|
|
40
51
|
panel=self.panel,
|
|
41
52
|
server_id=self.global_cfg.origin_server_id
|
|
42
53
|
)
|
|
54
|
+
|
|
43
55
|
self.server = MinecraftServer(
|
|
44
56
|
name=self.global_cfg.server_name,
|
|
45
57
|
panel=self.panel,
|
|
@@ -50,34 +62,36 @@ class ServerWatcher:
|
|
|
50
62
|
rcon_password=self.global_cfg.rcon_password,
|
|
51
63
|
tpsCommand=self.global_cfg.tps_command,
|
|
52
64
|
)
|
|
65
|
+
|
|
53
66
|
logger_name = self.cfg.logger_name_template.format(
|
|
54
67
|
server_name=self.global_cfg.server_name
|
|
55
68
|
)
|
|
69
|
+
|
|
56
70
|
self.log = HungerLogger(
|
|
57
71
|
name=logger_name,
|
|
58
72
|
server=self.server,
|
|
59
73
|
log_path=self.cfg.log_path,
|
|
60
74
|
console_backspaces=self.cfg.console_backspaces,
|
|
61
75
|
)
|
|
76
|
+
|
|
62
77
|
self.tz = ZoneInfo(self.cfg.timezone)
|
|
63
78
|
|
|
64
|
-
#
|
|
79
|
+
# utility
|
|
65
80
|
def fmt(self, template: str, **kwargs):
|
|
66
81
|
return template.format(prefix=self.messages.prefix, **kwargs)
|
|
67
82
|
|
|
68
|
-
# simple shutdown
|
|
69
83
|
def shutdown(self):
|
|
70
|
-
self.log.info(
|
|
84
|
+
self.log.info(self.messages.log_shutdown)
|
|
71
85
|
raise SystemExit
|
|
72
86
|
|
|
73
87
|
# restart logic
|
|
74
88
|
def restart_and_wait(self):
|
|
75
89
|
self.origin.disableSchedule(self.cfg.restart_soon_schedule_id)
|
|
76
90
|
self.server.restart()
|
|
77
|
-
self.log.info(
|
|
91
|
+
self.log.info(self.messages.restart_action_sent)
|
|
78
92
|
time.sleep(self.cfg.restart_wait_seconds)
|
|
79
93
|
|
|
80
|
-
self.log.warn(
|
|
94
|
+
self.log.warn(self.messages.log_status_check)
|
|
81
95
|
alive = waitForOnline(
|
|
82
96
|
self.server,
|
|
83
97
|
timeout=self.cfg.restart_online_timeout,
|
|
@@ -85,13 +99,13 @@ class ServerWatcher:
|
|
|
85
99
|
)
|
|
86
100
|
|
|
87
101
|
if alive:
|
|
88
|
-
self.log.info(
|
|
89
|
-
self.server.sendBroadcast(
|
|
102
|
+
self.log.info(self.messages.server_back_online)
|
|
103
|
+
self.server.sendBroadcast(self.messages.server_back_online_broadcast)
|
|
90
104
|
self.origin.enableSchedule(self.cfg.origin_disable_schedule_id)
|
|
91
105
|
else:
|
|
92
|
-
self.log.error(
|
|
106
|
+
self.log.error(self.messages.server_failed_restart)
|
|
93
107
|
|
|
94
|
-
#
|
|
108
|
+
# scheduled restart
|
|
95
109
|
def schedule_restart(self, minutes):
|
|
96
110
|
info = snapSchedule(minimumMinutes=minutes)
|
|
97
111
|
scheduled = info["scheduled"]
|
|
@@ -99,50 +113,42 @@ class ServerWatcher:
|
|
|
99
113
|
local_time = scheduled.astimezone(self.tz)
|
|
100
114
|
time_str = local_time.strftime("%I:%M %p")
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Collect all minute_* keys
|
|
106
|
-
minute_keys = [
|
|
107
|
-
k for k in vars(self.messages)
|
|
108
|
-
if k.startswith("minute_")
|
|
109
|
-
]
|
|
116
|
+
self.server.sendBroadcast(
|
|
117
|
+
self.fmt(self.messages.broadcast_restart_at, time=time_str)
|
|
118
|
+
)
|
|
110
119
|
|
|
120
|
+
# minute_* callbacks
|
|
111
121
|
minute_callbacks = {
|
|
112
122
|
int(k.split("_")[1]): (
|
|
113
123
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
114
124
|
self.server.sendBroadcast(msg)
|
|
115
125
|
)
|
|
116
|
-
for k in
|
|
126
|
+
for k in vars(self.messages)
|
|
127
|
+
if k.startswith("minute_")
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
#
|
|
120
|
-
second_keys = [
|
|
121
|
-
k for k in vars(self.messages)
|
|
122
|
-
if k.startswith("second_")
|
|
123
|
-
]
|
|
124
|
-
|
|
130
|
+
# second_* callbacks
|
|
125
131
|
second_callbacks = {
|
|
126
132
|
int(k.split("_")[1]): (
|
|
127
133
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
128
134
|
self.server.sendBroadcast(msg)
|
|
129
135
|
)
|
|
130
|
-
for k in
|
|
136
|
+
for k in vars(self.messages)
|
|
137
|
+
if k.startswith("second_")
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
|
|
134
140
|
runCountdownEvents(
|
|
135
141
|
target_time=scheduled,
|
|
136
142
|
minute_callbacks=minute_callbacks,
|
|
137
143
|
second_callbacks=second_callbacks,
|
|
138
144
|
)
|
|
139
145
|
|
|
140
|
-
#
|
|
146
|
+
# evaluation logic
|
|
141
147
|
def evaluate(self):
|
|
142
|
-
self.log.info(
|
|
148
|
+
self.log.info(self.messages.log_start)
|
|
143
149
|
|
|
144
150
|
if not validateAll(self.panel, self.server):
|
|
145
|
-
self.log.error(
|
|
151
|
+
self.log.error(self.messages.log_validation_fail)
|
|
146
152
|
self.shutdown()
|
|
147
153
|
|
|
148
154
|
self.server.refresh()
|
|
@@ -209,8 +215,9 @@ class ServerWatcher:
|
|
|
209
215
|
|
|
210
216
|
gap = abs(pro - anti)
|
|
211
217
|
|
|
218
|
+
# decision
|
|
212
219
|
if pro == 0:
|
|
213
|
-
self.log.info(
|
|
220
|
+
self.log.info(self.messages.log_no_restart)
|
|
214
221
|
return
|
|
215
222
|
|
|
216
223
|
if pro > anti and snap.players == 0:
|
|
@@ -229,9 +236,7 @@ class ServerWatcher:
|
|
|
229
236
|
|
|
230
237
|
self.restart_and_wait()
|
|
231
238
|
|
|
232
|
-
#
|
|
233
|
-
# Main loop
|
|
234
|
-
# -----------------------------------------------------
|
|
239
|
+
# main loop
|
|
235
240
|
def run(self):
|
|
236
241
|
if self.cfg.clear_terminal:
|
|
237
242
|
clearTerminal()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/serverwatcher/__init__.py
|
|
5
|
+
src/serverwatcher/watcher.py
|
|
6
|
+
src/serverwatcher.egg-info/PKG-INFO
|
|
7
|
+
src/serverwatcher.egg-info/SOURCES.txt
|
|
8
|
+
src/serverwatcher.egg-info/dependency_links.txt
|
|
9
|
+
src/serverwatcher.egg-info/requires.txt
|
|
10
|
+
src/serverwatcher.egg-info/top_level.txt
|
|
11
|
+
src/serverwatcher/configclasses/__init__.py
|
|
12
|
+
src/serverwatcher/configclasses/global_config.py
|
|
13
|
+
src/serverwatcher/configclasses/messages.py
|
|
14
|
+
src/serverwatcher/configclasses/watcher.py
|
|
15
|
+
src/serverwatcher/defaultconfigs/global.yaml
|
|
16
|
+
src/serverwatcher/defaultconfigs/messages.yaml
|
|
17
|
+
src/serverwatcher/defaultconfigs/watcher.yaml
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
src/serverwatcher/__init__.py
|
|
5
|
-
src/serverwatcher/watcher.py
|
|
6
|
-
src/serverwatcher.egg-info/PKG-INFO
|
|
7
|
-
src/serverwatcher.egg-info/SOURCES.txt
|
|
8
|
-
src/serverwatcher.egg-info/dependency_links.txt
|
|
9
|
-
src/serverwatcher.egg-info/requires.txt
|
|
10
|
-
src/serverwatcher.egg-info/top_level.txt
|
|
11
|
-
src/serverwatcher/configmap/configclasses/__init__.py
|
|
12
|
-
src/serverwatcher/configmap/configclasses/global_config.py
|
|
13
|
-
src/serverwatcher/configmap/configclasses/messages.py
|
|
14
|
-
src/serverwatcher/configmap/configclasses/watcher.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|