serverwatcher 3.9.1__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.1/src/serverwatcher.egg-info → serverwatcher-4.0}/PKG-INFO +1 -1
- {serverwatcher-3.9.1 → serverwatcher-4.0}/pyproject.toml +1 -1
- {serverwatcher-3.9.1 → 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.1 → serverwatcher-4.0}/src/serverwatcher/watcher.py +48 -42
- {serverwatcher-3.9.1 → 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.1/src/serverwatcher.egg-info/SOURCES.txt +0 -14
- {serverwatcher-3.9.1 → serverwatcher-4.0}/LICENSE +0 -0
- {serverwatcher-3.9.1 → serverwatcher-4.0}/README.md +0 -0
- {serverwatcher-3.9.1 → serverwatcher-4.0}/setup.cfg +0 -0
- {serverwatcher-3.9.1/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/__init__.py +0 -0
- {serverwatcher-3.9.1/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/global_config.py +0 -0
- {serverwatcher-3.9.1/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/messages.py +0 -0
- {serverwatcher-3.9.1/src/serverwatcher/configmap → serverwatcher-4.0/src/serverwatcher}/configclasses/watcher.py +0 -0
- {serverwatcher-3.9.1 → serverwatcher-4.0}/src/serverwatcher.egg-info/dependency_links.txt +0 -0
- {serverwatcher-3.9.1 → serverwatcher-4.0}/src/serverwatcher.egg-info/requires.txt +0 -0
- {serverwatcher-3.9.1 → 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,34 +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
|
-
|
|
18
|
-
BASE_DIR = os.getcwd()
|
|
19
|
-
PACKAGE_DIR = os.path.dirname(__file__)
|
|
17
|
+
from serverwatcher import GlobalConfig, MessagesConfig, WatcherConfig
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
class ServerWatcher:
|
|
23
21
|
def __init__(self):
|
|
24
|
-
# Load all configs in /config using configmap
|
|
25
|
-
cfgmap = load_or_default("config", skip_files=[])
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
self.
|
|
29
|
-
|
|
23
|
+
# load configs
|
|
24
|
+
self.global_cfg = loadConfig(
|
|
25
|
+
"config/global.yaml",
|
|
26
|
+
"/defaultconfigs/global.yaml",
|
|
27
|
+
GlobalConfig
|
|
28
|
+
)
|
|
30
29
|
|
|
30
|
+
self.messages = loadConfig(
|
|
31
|
+
"config/messages.yaml",
|
|
32
|
+
"/defaultconfigs/messages.yaml",
|
|
33
|
+
MessagesConfig
|
|
34
|
+
)
|
|
31
35
|
|
|
36
|
+
self.cfg = loadConfig(
|
|
37
|
+
"config/watcher.yaml",
|
|
38
|
+
"/defaultconfigs/watcher.yaml",
|
|
39
|
+
WatcherConfig
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# initialize panels and servers
|
|
32
43
|
self.panel = Panel(
|
|
33
44
|
name=self.global_cfg.panel_name,
|
|
34
45
|
url=self.global_cfg.panel_url,
|
|
35
46
|
api_key=self.global_cfg.panel_api_key,
|
|
36
47
|
)
|
|
48
|
+
|
|
37
49
|
self.origin = GenericServer(
|
|
38
50
|
name="Origin",
|
|
39
51
|
panel=self.panel,
|
|
40
52
|
server_id=self.global_cfg.origin_server_id
|
|
41
53
|
)
|
|
54
|
+
|
|
42
55
|
self.server = MinecraftServer(
|
|
43
56
|
name=self.global_cfg.server_name,
|
|
44
57
|
panel=self.panel,
|
|
@@ -49,34 +62,36 @@ class ServerWatcher:
|
|
|
49
62
|
rcon_password=self.global_cfg.rcon_password,
|
|
50
63
|
tpsCommand=self.global_cfg.tps_command,
|
|
51
64
|
)
|
|
65
|
+
|
|
52
66
|
logger_name = self.cfg.logger_name_template.format(
|
|
53
67
|
server_name=self.global_cfg.server_name
|
|
54
68
|
)
|
|
69
|
+
|
|
55
70
|
self.log = HungerLogger(
|
|
56
71
|
name=logger_name,
|
|
57
72
|
server=self.server,
|
|
58
73
|
log_path=self.cfg.log_path,
|
|
59
74
|
console_backspaces=self.cfg.console_backspaces,
|
|
60
75
|
)
|
|
76
|
+
|
|
61
77
|
self.tz = ZoneInfo(self.cfg.timezone)
|
|
62
78
|
|
|
63
|
-
#
|
|
79
|
+
# utility
|
|
64
80
|
def fmt(self, template: str, **kwargs):
|
|
65
81
|
return template.format(prefix=self.messages.prefix, **kwargs)
|
|
66
82
|
|
|
67
|
-
# simple shutdown
|
|
68
83
|
def shutdown(self):
|
|
69
|
-
self.log.info(
|
|
84
|
+
self.log.info(self.messages.log_shutdown)
|
|
70
85
|
raise SystemExit
|
|
71
86
|
|
|
72
87
|
# restart logic
|
|
73
88
|
def restart_and_wait(self):
|
|
74
89
|
self.origin.disableSchedule(self.cfg.restart_soon_schedule_id)
|
|
75
90
|
self.server.restart()
|
|
76
|
-
self.log.info(
|
|
91
|
+
self.log.info(self.messages.restart_action_sent)
|
|
77
92
|
time.sleep(self.cfg.restart_wait_seconds)
|
|
78
93
|
|
|
79
|
-
self.log.warn(
|
|
94
|
+
self.log.warn(self.messages.log_status_check)
|
|
80
95
|
alive = waitForOnline(
|
|
81
96
|
self.server,
|
|
82
97
|
timeout=self.cfg.restart_online_timeout,
|
|
@@ -84,13 +99,13 @@ class ServerWatcher:
|
|
|
84
99
|
)
|
|
85
100
|
|
|
86
101
|
if alive:
|
|
87
|
-
self.log.info(
|
|
88
|
-
self.server.sendBroadcast(
|
|
102
|
+
self.log.info(self.messages.server_back_online)
|
|
103
|
+
self.server.sendBroadcast(self.messages.server_back_online_broadcast)
|
|
89
104
|
self.origin.enableSchedule(self.cfg.origin_disable_schedule_id)
|
|
90
105
|
else:
|
|
91
|
-
self.log.error(
|
|
106
|
+
self.log.error(self.messages.server_failed_restart)
|
|
92
107
|
|
|
93
|
-
#
|
|
108
|
+
# scheduled restart
|
|
94
109
|
def schedule_restart(self, minutes):
|
|
95
110
|
info = snapSchedule(minimumMinutes=minutes)
|
|
96
111
|
scheduled = info["scheduled"]
|
|
@@ -98,50 +113,42 @@ class ServerWatcher:
|
|
|
98
113
|
local_time = scheduled.astimezone(self.tz)
|
|
99
114
|
time_str = local_time.strftime("%I:%M %p")
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# Collect all minute_* keys
|
|
105
|
-
minute_keys = [
|
|
106
|
-
k for k in vars(self.messages)
|
|
107
|
-
if k.startswith("minute_")
|
|
108
|
-
]
|
|
116
|
+
self.server.sendBroadcast(
|
|
117
|
+
self.fmt(self.messages.broadcast_restart_at, time=time_str)
|
|
118
|
+
)
|
|
109
119
|
|
|
120
|
+
# minute_* callbacks
|
|
110
121
|
minute_callbacks = {
|
|
111
122
|
int(k.split("_")[1]): (
|
|
112
123
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
113
124
|
self.server.sendBroadcast(msg)
|
|
114
125
|
)
|
|
115
|
-
for k in
|
|
126
|
+
for k in vars(self.messages)
|
|
127
|
+
if k.startswith("minute_")
|
|
116
128
|
}
|
|
117
129
|
|
|
118
|
-
#
|
|
119
|
-
second_keys = [
|
|
120
|
-
k for k in vars(self.messages)
|
|
121
|
-
if k.startswith("second_")
|
|
122
|
-
]
|
|
123
|
-
|
|
130
|
+
# second_* callbacks
|
|
124
131
|
second_callbacks = {
|
|
125
132
|
int(k.split("_")[1]): (
|
|
126
133
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
127
134
|
self.server.sendBroadcast(msg)
|
|
128
135
|
)
|
|
129
|
-
for k in
|
|
136
|
+
for k in vars(self.messages)
|
|
137
|
+
if k.startswith("second_")
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
|
|
133
140
|
runCountdownEvents(
|
|
134
141
|
target_time=scheduled,
|
|
135
142
|
minute_callbacks=minute_callbacks,
|
|
136
143
|
second_callbacks=second_callbacks,
|
|
137
144
|
)
|
|
138
145
|
|
|
139
|
-
#
|
|
146
|
+
# evaluation logic
|
|
140
147
|
def evaluate(self):
|
|
141
|
-
self.log.info(
|
|
148
|
+
self.log.info(self.messages.log_start)
|
|
142
149
|
|
|
143
150
|
if not validateAll(self.panel, self.server):
|
|
144
|
-
self.log.error(
|
|
151
|
+
self.log.error(self.messages.log_validation_fail)
|
|
145
152
|
self.shutdown()
|
|
146
153
|
|
|
147
154
|
self.server.refresh()
|
|
@@ -208,8 +215,9 @@ class ServerWatcher:
|
|
|
208
215
|
|
|
209
216
|
gap = abs(pro - anti)
|
|
210
217
|
|
|
218
|
+
# decision
|
|
211
219
|
if pro == 0:
|
|
212
|
-
self.log.info(
|
|
220
|
+
self.log.info(self.messages.log_no_restart)
|
|
213
221
|
return
|
|
214
222
|
|
|
215
223
|
if pro > anti and snap.players == 0:
|
|
@@ -228,9 +236,7 @@ class ServerWatcher:
|
|
|
228
236
|
|
|
229
237
|
self.restart_and_wait()
|
|
230
238
|
|
|
231
|
-
#
|
|
232
|
-
# Main loop
|
|
233
|
-
# -----------------------------------------------------
|
|
239
|
+
# main loop
|
|
234
240
|
def run(self):
|
|
235
241
|
if self.cfg.clear_terminal:
|
|
236
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
|