warlock-manager 2.2.5__tar.gz → 2.2.6__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.
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/PKG-INFO +1 -1
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/pyproject.toml +1 -1
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cmd.py +29 -2
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/base_app.py +0 -1
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/cmd.py +72 -14
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/firewall.py +12 -1
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/PKG-INFO +1 -1
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/LICENSE +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/README.md +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/setup.cfg +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_app.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_base_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_base_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cli_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cli_formatter.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_config_key.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_ini_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_sensitive_data_filter.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_socket_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_config_ark_spawn_entities.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_save.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_version.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/manual_app.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/steam_app.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/base_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/cli_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/config_key.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/ini_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/json_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/properties_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/unreal_config.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/formatters/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/formatters/cli_formatter.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/app.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/app_runner.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/cache.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/download.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/get_wan_ip.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/java.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/meta.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/ports.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/sensitive_data_filter.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/tui.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/utils.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/version.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/base_mod.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/warlock_nexus_mod.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/nexus/nexus.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/__init__.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/base_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/http_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/rcon_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/socket_service.py +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/SOURCES.txt +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/dependency_links.txt +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/requires.txt +0 -0
- {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/top_level.txt +0 -0
|
@@ -3,16 +3,34 @@ from warlock_manager.libs.cmd import Cmd
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class TestCmd(unittest.TestCase):
|
|
6
|
-
def
|
|
6
|
+
def test_exists(self):
|
|
7
|
+
"""
|
|
8
|
+
Test if commands exist and do not exist
|
|
9
|
+
:return:
|
|
10
|
+
"""
|
|
7
11
|
cmd = Cmd(["echo"])
|
|
8
12
|
self.assertTrue(cmd.exists)
|
|
9
13
|
|
|
10
|
-
def test_exists_false(self):
|
|
11
14
|
cmd = Cmd(["nonexistentbinary12345"])
|
|
12
15
|
self.assertFalse(cmd.exists)
|
|
13
16
|
self.assertFalse(cmd.success)
|
|
14
17
|
|
|
18
|
+
def test_exists_sudo(self):
|
|
19
|
+
"""
|
|
20
|
+
Test exists functionality when used with sudo
|
|
21
|
+
:return:
|
|
22
|
+
"""
|
|
23
|
+
cmd = Cmd(["true"]).sudo('nobody')
|
|
24
|
+
self.assertTrue(cmd.exists)
|
|
25
|
+
|
|
26
|
+
cmd = Cmd(["nonexistentbinary12345"]).sudo('nobody')
|
|
27
|
+
self.assertFalse(cmd.exists)
|
|
28
|
+
|
|
15
29
|
def test_text(self):
|
|
30
|
+
"""
|
|
31
|
+
Test that .text returns the output of the command
|
|
32
|
+
:return:
|
|
33
|
+
"""
|
|
16
34
|
cmd = Cmd(["echo", "hello world"])
|
|
17
35
|
self.assertEqual(cmd.text, "hello world")
|
|
18
36
|
|
|
@@ -42,6 +60,15 @@ class TestCmd(unittest.TestCase):
|
|
|
42
60
|
with self.assertRaises(Exception):
|
|
43
61
|
_ = cmd.json
|
|
44
62
|
|
|
63
|
+
def test_cwd(self):
|
|
64
|
+
"""
|
|
65
|
+
Test that the cwd is set and used correctly
|
|
66
|
+
|
|
67
|
+
:return:
|
|
68
|
+
"""
|
|
69
|
+
cmd = Cmd(["pwd"]).cwd("/tmp")
|
|
70
|
+
self.assertEqual(cmd.text, "/tmp")
|
|
71
|
+
|
|
45
72
|
|
|
46
73
|
if __name__ == "__main__":
|
|
47
74
|
unittest.main()
|
|
@@ -88,7 +88,6 @@ class BaseApp(ABC):
|
|
|
88
88
|
'api', # Game supports baseline API features
|
|
89
89
|
'cmd', # Game supports commands sent via the API
|
|
90
90
|
'create_service', # Game supports creating new services
|
|
91
|
-
'mods', # Game supports mods
|
|
92
91
|
}
|
|
93
92
|
"""
|
|
94
93
|
List of features available in this game
|
|
@@ -3,6 +3,7 @@ import subprocess
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
|
+
import pwd
|
|
6
7
|
|
|
7
8
|
from warlock_manager.libs import cache
|
|
8
9
|
|
|
@@ -39,7 +40,7 @@ class Cmd:
|
|
|
39
40
|
CompletedProcess: The result of the command execution
|
|
40
41
|
"""
|
|
41
42
|
|
|
42
|
-
self.executable: str | None = cmd[0] if len(cmd) > 0 else None
|
|
43
|
+
self.executable: str | None = self.cmd[0] if len(self.cmd) > 0 else None
|
|
43
44
|
"""
|
|
44
45
|
str: The executable name of the command
|
|
45
46
|
"""
|
|
@@ -65,7 +66,12 @@ class Cmd:
|
|
|
65
66
|
These commands are NOT persistent across calls!
|
|
66
67
|
"""
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
self._cwd: str | None = None
|
|
70
|
+
"""
|
|
71
|
+
The current working directory for this command
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def sudo(self, runas: str | int) -> 'Cmd':
|
|
69
75
|
"""
|
|
70
76
|
Run this command as another user using sudo.
|
|
71
77
|
|
|
@@ -78,55 +84,73 @@ class Cmd:
|
|
|
78
84
|
:return:
|
|
79
85
|
"""
|
|
80
86
|
if isinstance(runas, str):
|
|
81
|
-
|
|
87
|
+
# Get the name of the user owning the current process
|
|
88
|
+
# use pwd instead of os.getlogin to address CI tests on 3.13
|
|
89
|
+
current_user = pwd.getpwuid(os.geteuid()).pw_name
|
|
90
|
+
if current_user == runas:
|
|
82
91
|
# If we're already running as this user, no need to prefix with sudo
|
|
83
|
-
return
|
|
92
|
+
return self
|
|
84
93
|
prefix = ['sudo', '-u', runas]
|
|
85
94
|
else:
|
|
86
95
|
if os.geteuid() == runas:
|
|
87
96
|
# If we're already running as this user, no need to prefix with sudo
|
|
88
|
-
return
|
|
97
|
+
return self
|
|
89
98
|
prefix = ['sudo', '-u', '#%s' % runas]
|
|
90
99
|
|
|
91
100
|
self.cmd = prefix + self.cmd
|
|
92
101
|
self.result = None
|
|
102
|
+
return self
|
|
93
103
|
|
|
94
|
-
def use_stdout(self):
|
|
104
|
+
def use_stdout(self) -> 'Cmd':
|
|
95
105
|
"""
|
|
96
106
|
Set this command to use stdout for output instead of stderr.
|
|
97
107
|
:return:
|
|
98
108
|
"""
|
|
99
109
|
self.uses = 'stdout'
|
|
110
|
+
return self
|
|
100
111
|
|
|
101
|
-
def use_stderr(self):
|
|
112
|
+
def use_stderr(self) -> 'Cmd':
|
|
102
113
|
"""
|
|
103
114
|
Set this command to use stderr for output instead of stdout.
|
|
104
115
|
:return:
|
|
105
116
|
"""
|
|
106
117
|
self.uses = 'stderr'
|
|
118
|
+
return self
|
|
107
119
|
|
|
108
|
-
def stream_output(self):
|
|
120
|
+
def stream_output(self) -> 'Cmd':
|
|
109
121
|
"""
|
|
110
122
|
Set this command to stream to stdout/stderr directly. Useful for long-running commands.
|
|
111
123
|
:return:
|
|
112
124
|
"""
|
|
113
125
|
self.uses = None
|
|
126
|
+
return self
|
|
114
127
|
|
|
115
|
-
def is_cacheable(self, expires: int = 3600):
|
|
128
|
+
def is_cacheable(self, expires: int = 3600) -> 'Cmd':
|
|
116
129
|
"""
|
|
117
130
|
Set this command as cacheable for N seconds.
|
|
118
131
|
:param expires:
|
|
119
132
|
:return:
|
|
120
133
|
"""
|
|
121
134
|
self.cacheable = expires
|
|
135
|
+
return self
|
|
122
136
|
|
|
123
|
-
def is_memory_cacheable(self, expires: int = 2):
|
|
137
|
+
def is_memory_cacheable(self, expires: int = 2) -> 'Cmd':
|
|
124
138
|
"""
|
|
125
139
|
Set this command as cacheable in memory for N seconds.
|
|
126
140
|
:param expires:
|
|
127
141
|
:return:
|
|
128
142
|
"""
|
|
129
143
|
self.memory_cacheable = expires
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def cwd(self, path: str | None) -> 'Cmd':
|
|
147
|
+
"""
|
|
148
|
+
Set the current working directory for this command.
|
|
149
|
+
:param path:
|
|
150
|
+
:return:
|
|
151
|
+
"""
|
|
152
|
+
self._cwd = path
|
|
153
|
+
return self
|
|
130
154
|
|
|
131
155
|
@property
|
|
132
156
|
def exists(self) -> bool:
|
|
@@ -230,6 +254,7 @@ class Cmd:
|
|
|
230
254
|
self.cmd,
|
|
231
255
|
capture_output=capture_output,
|
|
232
256
|
check=False,
|
|
257
|
+
cwd=self._cwd,
|
|
233
258
|
encoding='utf-8'
|
|
234
259
|
)
|
|
235
260
|
except FileNotFoundError as e:
|
|
@@ -265,21 +290,54 @@ class Cmd:
|
|
|
265
290
|
|
|
266
291
|
return self.result
|
|
267
292
|
|
|
268
|
-
def extend(self, args: list):
|
|
293
|
+
def extend(self, args: list) -> 'Cmd':
|
|
269
294
|
"""
|
|
270
295
|
Extend the command with additional arguments.
|
|
271
296
|
:param args:
|
|
272
297
|
"""
|
|
273
298
|
self.cmd = self.cmd + args
|
|
274
299
|
self.result = None
|
|
300
|
+
return self
|
|
275
301
|
|
|
276
|
-
def append(self, arg: str):
|
|
302
|
+
def append(self, arg: str) -> 'Cmd':
|
|
277
303
|
"""
|
|
278
304
|
Append a single argument to the command.
|
|
279
305
|
:param arg:
|
|
280
306
|
"""
|
|
281
307
|
self.cmd.append(arg)
|
|
282
308
|
self.result = None
|
|
309
|
+
return self
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class PipeCmd(Cmd):
|
|
313
|
+
"""
|
|
314
|
+
Convenience wrapper for piping command output to a parent process
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
def run(self):
|
|
318
|
+
"""
|
|
319
|
+
Run the command in the background using nohup. Caches the result so subsequent calls don't re-run the command.
|
|
320
|
+
|
|
321
|
+
:return:
|
|
322
|
+
"""
|
|
323
|
+
if self.result is None:
|
|
324
|
+
|
|
325
|
+
if self.cacheable is not False:
|
|
326
|
+
logging.warning('Piped commands cannot be cached!')
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
logging.debug('Running piped command: %s' % ' '.join(self.cmd))
|
|
330
|
+
self.result = subprocess.Popen(
|
|
331
|
+
self.cmd,
|
|
332
|
+
stdout=subprocess.PIPE,
|
|
333
|
+
stderr=subprocess.PIPE
|
|
334
|
+
)
|
|
335
|
+
except FileNotFoundError as e:
|
|
336
|
+
self.result = CmdFakeResponse('', str(e), 127)
|
|
337
|
+
except OSError as e:
|
|
338
|
+
self.result = CmdFakeResponse('', str(e), 1)
|
|
339
|
+
|
|
340
|
+
return self.result
|
|
283
341
|
|
|
284
342
|
|
|
285
343
|
class BackgroundCmd(Cmd):
|
|
@@ -299,11 +357,11 @@ class BackgroundCmd(Cmd):
|
|
|
299
357
|
logging.warning('Background commands cannot be cached!')
|
|
300
358
|
|
|
301
359
|
try:
|
|
360
|
+
logging.debug('Running background command: %s' % ' '.join(self.cmd))
|
|
302
361
|
self.result = subprocess.Popen(
|
|
303
362
|
self.cmd,
|
|
304
363
|
stdout=subprocess.DEVNULL,
|
|
305
|
-
stderr=subprocess.DEVNULL
|
|
306
|
-
preexec_fn=lambda: logging.debug('Running background command: %s' % ' '.join(self.cmd))
|
|
364
|
+
stderr=subprocess.DEVNULL
|
|
307
365
|
)
|
|
308
366
|
except FileNotFoundError as e:
|
|
309
367
|
self.result = CmdFakeResponse('', str(e), 127)
|
|
@@ -95,9 +95,15 @@ class Firewall:
|
|
|
95
95
|
logging.error(f"Invalid port number: {port}")
|
|
96
96
|
return False
|
|
97
97
|
|
|
98
|
+
if protocol.lower() not in ['tcp', 'udp']:
|
|
99
|
+
logging.error(f"Invalid protocol: {protocol}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
98
102
|
firewall = cls.get_available()
|
|
99
103
|
|
|
100
104
|
if firewall == 'ufw':
|
|
105
|
+
# UFW requires the protocol to be all lowercase.
|
|
106
|
+
protocol = protocol.lower()
|
|
101
107
|
logging.info(f"Allowing {port}/{protocol} via UFW")
|
|
102
108
|
cmd = Cmd(['ufw', 'allow', f'{port}/{protocol}'])
|
|
103
109
|
if comment:
|
|
@@ -139,6 +145,10 @@ class Firewall:
|
|
|
139
145
|
logging.error(f"Invalid port number: {port}")
|
|
140
146
|
return False
|
|
141
147
|
|
|
148
|
+
if protocol.lower() not in ['tcp', 'udp']:
|
|
149
|
+
logging.error(f"Invalid protocol: {protocol}")
|
|
150
|
+
return False
|
|
151
|
+
|
|
142
152
|
firewall = cls.get_available()
|
|
143
153
|
|
|
144
154
|
if firewall == 'ufw':
|
|
@@ -183,7 +193,8 @@ class Firewall:
|
|
|
183
193
|
ufw_check = Cmd(['ufw', 'status'])
|
|
184
194
|
ufw_check.is_memory_cacheable(3)
|
|
185
195
|
result = ufw_check.text
|
|
186
|
-
|
|
196
|
+
# UFW requires the protocol to be all lowercase.
|
|
197
|
+
port_proto = f"{port}/{protocol}".lower()
|
|
187
198
|
for line in result.splitlines():
|
|
188
199
|
if port_proto in line and "ALLOW" in line and ("Anywhere" in line or "Anywhere (v6)" in line):
|
|
189
200
|
return True
|
|
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
|
|
File without changes
|
{warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_config_ark_spawn_entities.py
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
{warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/sensitive_data_filter.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
{warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|