serverwatcher 3.7.1__tar.gz → 3.8.1__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.7.1/src/serverwatcher.egg-info → serverwatcher-3.8.1}/PKG-INFO +1 -1
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/pyproject.toml +1 -1
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/config/messages.py +2 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/defaultconfigs/messages.yaml +2 -0
- serverwatcher-3.8.1/src/serverwatcher/helpers/__init__.py +5 -0
- serverwatcher-3.8.1/src/serverwatcher/helpers/loadconfigs.py +15 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/watcher.py +8 -41
- {serverwatcher-3.7.1 → serverwatcher-3.8.1/src/serverwatcher.egg-info}/PKG-INFO +1 -1
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher.egg-info/SOURCES.txt +3 -2
- serverwatcher-3.7.1/src/serverwatcher/schema.py +0 -70
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/LICENSE +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/README.md +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/setup.cfg +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/__init__.py +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/config/__init__.py +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/config/global_config.py +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/config/watcher.py +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/defaultconfigs/global.yaml +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher/defaultconfigs/watcher.yaml +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher.egg-info/dependency_links.txt +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher.egg-info/requires.txt +0 -0
- {serverwatcher-3.7.1 → serverwatcher-3.8.1}/src/serverwatcher.egg-info/top_level.txt +0 -0
|
@@ -25,8 +25,10 @@ broadcast_second:
|
|
|
25
25
|
|
|
26
26
|
logging:
|
|
27
27
|
log_start: "ServerWatcher is running!"
|
|
28
|
+
log_status_check: "Checking server status..."
|
|
28
29
|
log_validation_fail: "Validation FAILED."
|
|
29
30
|
log_validation_ok: "All validation checks succeeded."
|
|
31
|
+
log_shutdown: "Shutting down ServerWatcher."
|
|
30
32
|
log_immediate_restart: "Restarting immediately."
|
|
31
33
|
log_no_restart: "The server does not need to restart."
|
|
32
34
|
log_scheduled: "Restart needed, but anti-restart factors outweigh it."
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
def load_or_default(path: str, default_path: str, schema):
|
|
4
|
+
|
|
5
|
+
abs_path = os.path.join(BASE_DIR, path)
|
|
6
|
+
abs_default = os.path.join(PACKAGE_DIR, default_path)
|
|
7
|
+
|
|
8
|
+
if not os.path.exists(abs_path):
|
|
9
|
+
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
|
10
|
+
with open(abs_default, "r") as src, open(abs_path, "w") as dst:
|
|
11
|
+
dst.write(src.read())
|
|
12
|
+
|
|
13
|
+
raw = load_yaml(abs_path)
|
|
14
|
+
raw = flatten_nested(raw)
|
|
15
|
+
return map_to_dataclass(raw, schema)
|
|
@@ -17,27 +17,12 @@ from hungerlib.addons import (
|
|
|
17
17
|
|
|
18
18
|
from serverwatcher.schema import flatten_nested
|
|
19
19
|
from serverwatcher.config import GlobalConfig, MessagesConfig, WatcherConfig
|
|
20
|
+
from serverwatcher.helpers import load_or_default
|
|
20
21
|
|
|
21
22
|
# Set directory
|
|
22
23
|
BASE_DIR = os.getcwd()
|
|
23
24
|
PACKAGE_DIR = os.path.dirname(__file__)
|
|
24
25
|
|
|
25
|
-
# Load configs
|
|
26
|
-
def load_or_default(path: str, default_path: str, schema):
|
|
27
|
-
|
|
28
|
-
abs_path = os.path.join(BASE_DIR, path)
|
|
29
|
-
abs_default = os.path.join(PACKAGE_DIR, default_path)
|
|
30
|
-
|
|
31
|
-
if not os.path.exists(abs_path):
|
|
32
|
-
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
|
33
|
-
with open(abs_default, "r") as src, open(abs_path, "w") as dst:
|
|
34
|
-
dst.write(src.read())
|
|
35
|
-
|
|
36
|
-
raw = load_yaml(abs_path)
|
|
37
|
-
raw = flatten_nested(raw)
|
|
38
|
-
return map_to_dataclass(raw, schema)
|
|
39
|
-
|
|
40
|
-
|
|
41
26
|
|
|
42
27
|
# Main watcher
|
|
43
28
|
class ServerWatcher:
|
|
@@ -47,34 +32,26 @@ class ServerWatcher:
|
|
|
47
32
|
"defaultconfigs/global.yaml",
|
|
48
33
|
GlobalConfig
|
|
49
34
|
)
|
|
50
|
-
|
|
51
35
|
self.messages: MessagesConfig = load_or_default(
|
|
52
36
|
"config/messages.yaml",
|
|
53
37
|
"defaultconfigs/messages.yaml",
|
|
54
38
|
MessagesConfig
|
|
55
39
|
)
|
|
56
|
-
|
|
57
40
|
self.cfg: WatcherConfig = load_or_default(
|
|
58
41
|
"config/watcher.yaml",
|
|
59
42
|
"defaultconfigs/watcher.yaml",
|
|
60
43
|
WatcherConfig
|
|
61
44
|
)
|
|
62
|
-
|
|
63
|
-
# panel
|
|
64
45
|
self.panel = Panel(
|
|
65
46
|
name=self.global_cfg.panel_name,
|
|
66
47
|
url=self.global_cfg.panel_url,
|
|
67
48
|
api_key=self.global_cfg.panel_api_key,
|
|
68
49
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# origin
|
|
72
50
|
self.origin = GenericServer(
|
|
73
51
|
name="Origin",
|
|
74
52
|
panel=self.panel,
|
|
75
53
|
server_id=self.global_cfg.origin_server_id
|
|
76
54
|
)
|
|
77
|
-
|
|
78
55
|
self.server = MinecraftServer(
|
|
79
56
|
name=self.global_cfg.server_name,
|
|
80
57
|
panel=self.panel,
|
|
@@ -85,21 +62,15 @@ class ServerWatcher:
|
|
|
85
62
|
rcon_password=self.global_cfg.rcon_password,
|
|
86
63
|
tpsCommand=self.global_cfg.tps_command,
|
|
87
64
|
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# logger
|
|
91
65
|
logger_name = self.cfg.logger_name_template.format(
|
|
92
66
|
server_name=self.global_cfg.server_name
|
|
93
67
|
)
|
|
94
|
-
|
|
95
68
|
self.log = HungerLogger(
|
|
96
69
|
name=logger_name,
|
|
97
70
|
server=self.server,
|
|
98
71
|
log_path=self.cfg.log_path,
|
|
99
72
|
console_backspaces=self.cfg.console_backspaces,
|
|
100
73
|
)
|
|
101
|
-
|
|
102
|
-
# timezone
|
|
103
74
|
self.tz = ZoneInfo(self.cfg.timezone)
|
|
104
75
|
|
|
105
76
|
# format messages with prefix
|
|
@@ -108,7 +79,7 @@ class ServerWatcher:
|
|
|
108
79
|
|
|
109
80
|
# simple shutdown
|
|
110
81
|
def shutdown(self):
|
|
111
|
-
self.log.info("
|
|
82
|
+
self.log.info(f"{self.messages.log_shutdown}")
|
|
112
83
|
raise SystemExit
|
|
113
84
|
|
|
114
85
|
# restart logic
|
|
@@ -118,7 +89,7 @@ class ServerWatcher:
|
|
|
118
89
|
self.log.info(f"{self.messages.restart_action_sent}")
|
|
119
90
|
time.sleep(self.cfg.restart_wait_seconds)
|
|
120
91
|
|
|
121
|
-
self.log.warn("
|
|
92
|
+
self.log.warn(f"{self.messages.log_status_check}")
|
|
122
93
|
alive = waitForOnline(
|
|
123
94
|
self.server,
|
|
124
95
|
timeout=self.cfg.restart_online_timeout,
|
|
@@ -127,9 +98,7 @@ class ServerWatcher:
|
|
|
127
98
|
|
|
128
99
|
if alive:
|
|
129
100
|
self.log.info(f"{self.messages.server_back_online}")
|
|
130
|
-
self.server.sendBroadcast(
|
|
131
|
-
f"{self.messages.server_back_online_broadcast}"
|
|
132
|
-
)
|
|
101
|
+
self.server.sendBroadcast(f"{self.messages.server_back_online_broadcast}")
|
|
133
102
|
self.origin.enableSchedule(self.cfg.origin_disable_schedule_id)
|
|
134
103
|
else:
|
|
135
104
|
self.log.error(f"{self.messages.server_failed_restart}")
|
|
@@ -143,9 +112,7 @@ class ServerWatcher:
|
|
|
143
112
|
time_str = local_time.strftime("%I:%M %p")
|
|
144
113
|
|
|
145
114
|
# Broadcast the scheduled restart time
|
|
146
|
-
self.server.sendBroadcast(
|
|
147
|
-
self.fmt(self.messages.broadcast_restart_at, time=time_str)
|
|
148
|
-
)
|
|
115
|
+
self.server.sendBroadcast(self.fmt({self.messages.broadcast_restart_at, time=time_str}))
|
|
149
116
|
|
|
150
117
|
# Collect all minute_* keys
|
|
151
118
|
minute_keys = [
|
|
@@ -184,10 +151,10 @@ class ServerWatcher:
|
|
|
184
151
|
|
|
185
152
|
# main evaluation logic
|
|
186
153
|
def evaluate(self):
|
|
187
|
-
self.log.info(self.messages.log_start)
|
|
154
|
+
self.log.info(f"{self.messages.log_start}")
|
|
188
155
|
|
|
189
156
|
if not validateAll(self.panel, self.server):
|
|
190
|
-
self.log.error(self.messages.log_validation_fail)
|
|
157
|
+
self.log.error(f"{self.messages.log_validation_fail}")
|
|
191
158
|
self.shutdown()
|
|
192
159
|
|
|
193
160
|
self.server.refresh()
|
|
@@ -255,7 +222,7 @@ class ServerWatcher:
|
|
|
255
222
|
gap = abs(pro - anti)
|
|
256
223
|
|
|
257
224
|
if pro == 0:
|
|
258
|
-
self.log.info(self.messages.log_no_restart)
|
|
225
|
+
self.log.info(f"{self.messages.log_no_restart}")
|
|
259
226
|
return
|
|
260
227
|
|
|
261
228
|
if pro > anti and snap.players == 0:
|
|
@@ -2,7 +2,6 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/serverwatcher/__init__.py
|
|
5
|
-
src/serverwatcher/schema.py
|
|
6
5
|
src/serverwatcher/watcher.py
|
|
7
6
|
src/serverwatcher.egg-info/PKG-INFO
|
|
8
7
|
src/serverwatcher.egg-info/SOURCES.txt
|
|
@@ -15,4 +14,6 @@ src/serverwatcher/config/messages.py
|
|
|
15
14
|
src/serverwatcher/config/watcher.py
|
|
16
15
|
src/serverwatcher/defaultconfigs/global.yaml
|
|
17
16
|
src/serverwatcher/defaultconfigs/messages.yaml
|
|
18
|
-
src/serverwatcher/defaultconfigs/watcher.yaml
|
|
17
|
+
src/serverwatcher/defaultconfigs/watcher.yaml
|
|
18
|
+
src/serverwatcher/helpers/__init__.py
|
|
19
|
+
src/serverwatcher/helpers/loadconfigs.py
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any, List
|
|
2
|
-
|
|
3
|
-
from hungerlib.addons import validate_required_keys
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def validate_config_schema(raw: Dict[str, Any]) -> list[str]:
|
|
7
|
-
errors: List[str] = []
|
|
8
|
-
|
|
9
|
-
# top-level sections
|
|
10
|
-
errors += validate_required_keys(raw, ["panel", "origin", "server", "watcher"], "root")
|
|
11
|
-
|
|
12
|
-
panel = raw.get("panel", {})
|
|
13
|
-
origin = raw.get("origin", {})
|
|
14
|
-
server = raw.get("server", {})
|
|
15
|
-
watcher = raw.get("watcher", {})
|
|
16
|
-
|
|
17
|
-
errors += validate_required_keys(panel, ["name", "url", "api_key"], "panel")
|
|
18
|
-
errors += validate_required_keys(origin, ["server_id"], "origin")
|
|
19
|
-
errors += validate_required_keys(
|
|
20
|
-
server,
|
|
21
|
-
["name", "server_id", "domain", "port", "rcon_port", "rcon_password", "tps_command"],
|
|
22
|
-
"server",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# simple sanity checks (optional but nice)
|
|
26
|
-
if "port" in server and not isinstance(server["port"], int):
|
|
27
|
-
errors.append("[server] 'port' must be an integer")
|
|
28
|
-
if "rcon_port" in server and not isinstance(server["rcon_port"], int):
|
|
29
|
-
errors.append("[server] 'rcon_port' must be an integer")
|
|
30
|
-
|
|
31
|
-
# watcher numeric sanity
|
|
32
|
-
if "ram_threshold" in watcher and watcher["ram_threshold"] <= 0:
|
|
33
|
-
errors.append("[watcher] 'ram_threshold' must be > 0")
|
|
34
|
-
if "cpu_threshold" in watcher and watcher["cpu_threshold"] <= 0:
|
|
35
|
-
errors.append("[watcher] 'cpu_threshold' must be > 0")
|
|
36
|
-
|
|
37
|
-
return errors
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def validate_messages_schema(raw: Dict[str, Any]) -> list[str]:
|
|
41
|
-
# you can make this as strict as you want; for now just ensure prefix exists
|
|
42
|
-
errors: List[str] = []
|
|
43
|
-
if "prefix" not in raw:
|
|
44
|
-
errors.append("[messages] Missing required key: 'prefix'")
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|