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.
Files changed (63) hide show
  1. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/PKG-INFO +1 -1
  2. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/pyproject.toml +1 -1
  3. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cmd.py +29 -2
  4. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/base_app.py +0 -1
  5. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/cmd.py +72 -14
  6. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/firewall.py +12 -1
  7. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/PKG-INFO +1 -1
  8. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/LICENSE +0 -0
  9. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/README.md +0 -0
  10. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/setup.cfg +0 -0
  11. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_app.py +0 -0
  12. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_base_config.py +0 -0
  13. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_base_service.py +0 -0
  14. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cli_config.py +0 -0
  15. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_cli_formatter.py +0 -0
  16. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_config_key.py +0 -0
  17. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_ini_config.py +0 -0
  18. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_sensitive_data_filter.py +0 -0
  19. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_socket_service.py +0 -0
  20. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_config.py +0 -0
  21. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_config_ark_spawn_entities.py +0 -0
  22. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_unreal_save.py +0 -0
  23. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/tests/test_version.py +0 -0
  24. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/__init__.py +0 -0
  25. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/__init__.py +0 -0
  26. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/manual_app.py +0 -0
  27. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/apps/steam_app.py +0 -0
  28. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/__init__.py +0 -0
  29. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/base_config.py +0 -0
  30. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/cli_config.py +0 -0
  31. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/config_key.py +0 -0
  32. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/ini_config.py +0 -0
  33. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/json_config.py +0 -0
  34. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/properties_config.py +0 -0
  35. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/config/unreal_config.py +0 -0
  36. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/formatters/__init__.py +0 -0
  37. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/formatters/cli_formatter.py +0 -0
  38. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/__init__.py +0 -0
  39. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/app.py +0 -0
  40. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/app_runner.py +0 -0
  41. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/cache.py +0 -0
  42. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/download.py +0 -0
  43. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/get_wan_ip.py +0 -0
  44. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/java.py +0 -0
  45. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/meta.py +0 -0
  46. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/ports.py +0 -0
  47. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/sensitive_data_filter.py +0 -0
  48. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/tui.py +0 -0
  49. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/utils.py +0 -0
  50. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/libs/version.py +0 -0
  51. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/__init__.py +0 -0
  52. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/base_mod.py +0 -0
  53. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/mods/warlock_nexus_mod.py +0 -0
  54. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/nexus/nexus.py +0 -0
  55. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/__init__.py +0 -0
  56. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/base_service.py +0 -0
  57. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/http_service.py +0 -0
  58. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/rcon_service.py +0 -0
  59. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager/services/socket_service.py +0 -0
  60. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/SOURCES.txt +0 -0
  61. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/dependency_links.txt +0 -0
  62. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/requires.txt +0 -0
  63. {warlock_manager-2.2.5 → warlock_manager-2.2.6}/warlock_manager.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warlock-manager
3
- Version: 2.2.5
3
+ Version: 2.2.6
4
4
  Summary: Dependency library for game-server management applications.
5
5
  Author-email: Charlie Powell <cdp1337@bitsnbytes.dev>
6
6
  Maintainer-email: Charlie Powell <cdp1337@bitsnbytes.dev>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "warlock-manager"
7
- version = "2.2.5"
7
+ version = "2.2.6"
8
8
  description = "Dependency library for game-server management applications."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -3,16 +3,34 @@ from warlock_manager.libs.cmd import Cmd
3
3
 
4
4
 
5
5
  class TestCmd(unittest.TestCase):
6
- def test_exists_true(self):
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
- def sudo(self, runas: str | int):
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
- if os.getlogin() == runas:
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
- port_proto = f"{port}/{protocol}"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warlock-manager
3
- Version: 2.2.5
3
+ Version: 2.2.6
4
4
  Summary: Dependency library for game-server management applications.
5
5
  Author-email: Charlie Powell <cdp1337@bitsnbytes.dev>
6
6
  Maintainer-email: Charlie Powell <cdp1337@bitsnbytes.dev>
File without changes