serverwatcher 3.3__tar.gz → 3.5__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.3/src/serverwatcher.egg-info → serverwatcher-3.5}/PKG-INFO +1 -1
- {serverwatcher-3.3 → serverwatcher-3.5}/pyproject.toml +1 -1
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/__init__.py +2 -1
- serverwatcher-3.5/src/serverwatcher/config/__init__.py +9 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/config/watcher.py +4 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/defaultconfigs/global.yaml +1 -1
- serverwatcher-3.5/src/serverwatcher/defaultconfigs/watcher.yaml +39 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/schema.py +25 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/watcher.py +27 -41
- {serverwatcher-3.3 → serverwatcher-3.5/src/serverwatcher.egg-info}/PKG-INFO +1 -1
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher.egg-info/SOURCES.txt +1 -0
- serverwatcher-3.3/src/serverwatcher/defaultconfigs/watcher.yaml +0 -28
- {serverwatcher-3.3 → serverwatcher-3.5}/LICENSE +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/README.md +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/setup.cfg +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/config/global_config.py +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/config/messages.py +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher/defaultconfigs/messages.yaml +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher.egg-info/dependency_links.txt +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher.egg-info/requires.txt +0 -0
- {serverwatcher-3.3 → serverwatcher-3.5}/src/serverwatcher.egg-info/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ except PackageNotFoundError:
|
|
|
7
7
|
__version__ = '0.0.0'
|
|
8
8
|
|
|
9
9
|
from .watcher import ServerWatcher
|
|
10
|
-
from .schema import validate_config_schema, validate_messages_schema
|
|
10
|
+
from .schema import validate_config_schema, validate_messages_schema flatten_nested
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
@@ -15,4 +15,5 @@ __all__ = [
|
|
|
15
15
|
'ServerWatcher',
|
|
16
16
|
'validate_config_schema',
|
|
17
17
|
'validate_messages_schema',
|
|
18
|
+
'flatten_nested',
|
|
18
19
|
]
|
|
@@ -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
|
|
@@ -43,3 +43,28 @@ def validate_messages_schema(raw: Dict[str, Any]) -> list[str]:
|
|
|
43
43
|
if "prefix" not in raw:
|
|
44
44
|
errors.append("[messages] Missing required key: 'prefix'")
|
|
45
45
|
return errors
|
|
46
|
+
|
|
47
|
+
def flatten_nested(raw: dict) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
Recursively flattens a nested YAML dict into a single-level dict.
|
|
50
|
+
Section names are ignored; only leaf keys matter.
|
|
51
|
+
"""
|
|
52
|
+
flat = {}
|
|
53
|
+
|
|
54
|
+
def walk(node):
|
|
55
|
+
if isinstance(node, dict):
|
|
56
|
+
for v in node.values():
|
|
57
|
+
walk(v)
|
|
58
|
+
else:
|
|
59
|
+
# ignore non-dict nodes at this level
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def collect(node):
|
|
63
|
+
for key, value in node.items():
|
|
64
|
+
if isinstance(value, dict):
|
|
65
|
+
collect(value)
|
|
66
|
+
else:
|
|
67
|
+
flat[key] = value
|
|
68
|
+
|
|
69
|
+
collect(raw)
|
|
70
|
+
return flat
|
|
@@ -15,19 +15,13 @@ from hungerlib.addons import (
|
|
|
15
15
|
map_to_dataclass,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# BASE DIRECTORY OF THIS PACKAGE
|
|
24
|
-
# BASE_DIR = os.path.dirname(__file__) # OLD VERSION, may use later
|
|
25
|
-
BASE_DIR = os.getcwd() # NEW VERSION
|
|
18
|
+
from serverwatcher.config import GlobalConfig, MessagesConfig, WatcherConfig
|
|
19
|
+
|
|
20
|
+
# Set directory
|
|
21
|
+
BASE_DIR = os.getcwd()
|
|
26
22
|
PACKAGE_DIR = os.path.dirname(__file__)
|
|
27
23
|
|
|
28
|
-
#
|
|
29
|
-
# Utility: load config or copy default from defaultconfigs/
|
|
30
|
-
# ---------------------------------------------------------
|
|
24
|
+
# Load configs
|
|
31
25
|
def load_or_default(path: str, default_path: str, schema):
|
|
32
26
|
"""
|
|
33
27
|
Loads YAML from path. If missing, copies default_path → path.
|
|
@@ -43,15 +37,14 @@ def load_or_default(path: str, default_path: str, schema):
|
|
|
43
37
|
dst.write(src.read())
|
|
44
38
|
|
|
45
39
|
raw = load_yaml(abs_path)
|
|
40
|
+
raw = flatten_nested(raw)
|
|
46
41
|
return map_to_dataclass(raw, schema)
|
|
42
|
+
|
|
47
43
|
|
|
48
44
|
|
|
49
|
-
#
|
|
50
|
-
# Main Watcher
|
|
51
|
-
# ---------------------------------------------------------
|
|
45
|
+
# Main watcher
|
|
52
46
|
class ServerWatcher:
|
|
53
47
|
def __init__(self):
|
|
54
|
-
# Load all 3 configs
|
|
55
48
|
self.global_cfg: GlobalConfig = load_or_default(
|
|
56
49
|
"config/global.yaml",
|
|
57
50
|
"defaultconfigs/global.yaml",
|
|
@@ -70,7 +63,7 @@ class ServerWatcher:
|
|
|
70
63
|
WatcherConfig
|
|
71
64
|
)
|
|
72
65
|
|
|
73
|
-
#
|
|
66
|
+
# panel
|
|
74
67
|
p = self.global_cfg.panel
|
|
75
68
|
self.panel = Panel(
|
|
76
69
|
name=p["name"],
|
|
@@ -78,7 +71,7 @@ class ServerWatcher:
|
|
|
78
71
|
api_key=p["api_key"],
|
|
79
72
|
)
|
|
80
73
|
|
|
81
|
-
#
|
|
74
|
+
# origin
|
|
82
75
|
o = self.global_cfg.origin
|
|
83
76
|
self.origin = GenericServer(
|
|
84
77
|
name="Origin",
|
|
@@ -86,7 +79,7 @@ class ServerWatcher:
|
|
|
86
79
|
server_id=o["server_id"],
|
|
87
80
|
)
|
|
88
81
|
|
|
89
|
-
#
|
|
82
|
+
# server
|
|
90
83
|
s = self.global_cfg.server
|
|
91
84
|
self.server = MinecraftServer(
|
|
92
85
|
name=s["name"],
|
|
@@ -99,7 +92,7 @@ class ServerWatcher:
|
|
|
99
92
|
tpsCommand=s["tps_command"],
|
|
100
93
|
)
|
|
101
94
|
|
|
102
|
-
#
|
|
95
|
+
# logger
|
|
103
96
|
logger_name = self.cfg.logger_name_template.format(
|
|
104
97
|
server_name=s["name"]
|
|
105
98
|
)
|
|
@@ -111,25 +104,19 @@ class ServerWatcher:
|
|
|
111
104
|
console_backspaces=self.cfg.console_backspaces,
|
|
112
105
|
)
|
|
113
106
|
|
|
114
|
-
#
|
|
107
|
+
# timezone
|
|
115
108
|
self.tz = ZoneInfo(self.cfg.timezone)
|
|
116
109
|
|
|
117
|
-
#
|
|
118
|
-
# Utility: format messages with prefix
|
|
119
|
-
# -----------------------------------------------------
|
|
110
|
+
# format messages with prefix
|
|
120
111
|
def fmt(self, template: str, **kwargs):
|
|
121
112
|
return template.format(prefix=self.messages.prefix, **kwargs)
|
|
122
113
|
|
|
123
|
-
#
|
|
124
|
-
# Shutdown
|
|
125
|
-
# -----------------------------------------------------
|
|
114
|
+
# simple shutdown
|
|
126
115
|
def shutdown(self):
|
|
127
116
|
self.log.info("Shutting down ServerWatcher.")
|
|
128
117
|
raise SystemExit
|
|
129
118
|
|
|
130
|
-
#
|
|
131
|
-
# Restart logic
|
|
132
|
-
# -----------------------------------------------------
|
|
119
|
+
# restart logic
|
|
133
120
|
def restart_and_wait(self):
|
|
134
121
|
self.origin.disableSchedule(self.cfg.restart_soon_schedule_id)
|
|
135
122
|
self.server.restart()
|
|
@@ -152,9 +139,7 @@ class ServerWatcher:
|
|
|
152
139
|
else:
|
|
153
140
|
self.log.error(f"{self.messages.server_failed_restart}")
|
|
154
141
|
|
|
155
|
-
#
|
|
156
|
-
# Schedule restart
|
|
157
|
-
# -----------------------------------------------------
|
|
142
|
+
# schedule restart
|
|
158
143
|
def schedule_restart(self, minutes):
|
|
159
144
|
info = snapSchedule(minimumMinutes=minutes)
|
|
160
145
|
scheduled = info["scheduled"]
|
|
@@ -184,9 +169,7 @@ class ServerWatcher:
|
|
|
184
169
|
second_callbacks=second_callbacks,
|
|
185
170
|
)
|
|
186
171
|
|
|
187
|
-
#
|
|
188
|
-
# Main evaluation logic
|
|
189
|
-
# -----------------------------------------------------
|
|
172
|
+
# main evaluation logic
|
|
190
173
|
def evaluate(self):
|
|
191
174
|
self.log.info(self.messages.log_start)
|
|
192
175
|
|
|
@@ -202,7 +185,7 @@ class ServerWatcher:
|
|
|
202
185
|
restart_reasons = []
|
|
203
186
|
no_restart_reasons = []
|
|
204
187
|
|
|
205
|
-
#
|
|
188
|
+
# pro-restart
|
|
206
189
|
if self.server.getSchedule(self.cfg.restart_soon_schedule_id)["is_active"]:
|
|
207
190
|
restart_reasons.append(self.messages.reason_restart_soon)
|
|
208
191
|
pro += self.cfg.weight_restart_soon
|
|
@@ -211,7 +194,7 @@ class ServerWatcher:
|
|
|
211
194
|
restart_reasons.append(
|
|
212
195
|
self.fmt(self.messages.reason_ram, ram=snap.ram, threshold=self.cfg.ram_threshold)
|
|
213
196
|
)
|
|
214
|
-
pro += round(snap.ram, 0) - 5
|
|
197
|
+
pro += int(round(snap.ram, 0) - 5)
|
|
215
198
|
|
|
216
199
|
if snap.cpu >= self.cfg.cpu_threshold:
|
|
217
200
|
restart_reasons.append(
|
|
@@ -232,7 +215,7 @@ class ServerWatcher:
|
|
|
232
215
|
)
|
|
233
216
|
pro += self.cfg.weight_tps
|
|
234
217
|
|
|
235
|
-
#
|
|
218
|
+
# anti-restart
|
|
236
219
|
if snap.uptime // 60 < 30:
|
|
237
220
|
no_restart_reasons.append(
|
|
238
221
|
self.fmt(self.messages.reason_low_uptime, uptime=snap.uptime_formatted)
|
|
@@ -247,7 +230,7 @@ class ServerWatcher:
|
|
|
247
230
|
)
|
|
248
231
|
anti += snap.players * self.cfg.weight_per_player
|
|
249
232
|
|
|
250
|
-
#
|
|
233
|
+
# logging
|
|
251
234
|
for r in restart_reasons:
|
|
252
235
|
self.log.warn(f"- {r}")
|
|
253
236
|
for r in no_restart_reasons:
|
|
@@ -282,7 +265,10 @@ class ServerWatcher:
|
|
|
282
265
|
# Main loop
|
|
283
266
|
# -----------------------------------------------------
|
|
284
267
|
def run(self):
|
|
285
|
-
|
|
268
|
+
if self.cfg.clear_terminal:
|
|
269
|
+
clearTerminal()
|
|
286
270
|
while True:
|
|
271
|
+
if self.cfg.clear_terminal:
|
|
272
|
+
clearTerminal()
|
|
287
273
|
self.evaluate()
|
|
288
|
-
time.sleep(
|
|
274
|
+
time.sleep(self.cfg.watch_interval)
|
|
@@ -9,6 +9,7 @@ src/serverwatcher.egg-info/SOURCES.txt
|
|
|
9
9
|
src/serverwatcher.egg-info/dependency_links.txt
|
|
10
10
|
src/serverwatcher.egg-info/requires.txt
|
|
11
11
|
src/serverwatcher.egg-info/top_level.txt
|
|
12
|
+
src/serverwatcher/config/__init__.py
|
|
12
13
|
src/serverwatcher/config/global_config.py
|
|
13
14
|
src/serverwatcher/config/messages.py
|
|
14
15
|
src/serverwatcher/config/watcher.py
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
restart_soon_schedule_id: 13
|
|
2
|
-
origin_disable_schedule_id: 11
|
|
3
|
-
|
|
4
|
-
ram_threshold: 6
|
|
5
|
-
cpu_threshold: 150
|
|
6
|
-
uptime_hours_threshold: 12
|
|
7
|
-
tps_threshold: 19.5
|
|
8
|
-
|
|
9
|
-
weight_restart_soon: 3
|
|
10
|
-
weight_ram: 1
|
|
11
|
-
weight_cpu: 1
|
|
12
|
-
weight_uptime: 1
|
|
13
|
-
weight_tps: 1
|
|
14
|
-
|
|
15
|
-
weight_low_uptime: 5
|
|
16
|
-
weight_per_player: 1
|
|
17
|
-
|
|
18
|
-
low_gap_minutes: 120
|
|
19
|
-
high_gap_minutes: 60
|
|
20
|
-
|
|
21
|
-
restart_wait_seconds: 45
|
|
22
|
-
restart_online_timeout: 120
|
|
23
|
-
restart_online_interval: 2
|
|
24
|
-
|
|
25
|
-
logger_name_template: "Server Watcher"
|
|
26
|
-
log_path: "/home/container/logs/"
|
|
27
|
-
console_backspaces: 8
|
|
28
|
-
timezone: "America/Chicago"
|
|
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
|