warlock-manager 2.2.0__tar.gz → 2.2.2__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.0 → warlock_manager-2.2.2}/PKG-INFO +1 -1
  2. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/pyproject.toml +1 -1
  3. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/apps/base_app.py +23 -7
  4. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/apps/steam_app.py +44 -10
  5. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/base_config.py +25 -11
  6. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/app_runner.py +3 -3
  7. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/services/base_service.py +42 -19
  8. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager.egg-info/PKG-INFO +1 -1
  9. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/LICENSE +0 -0
  10. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/README.md +0 -0
  11. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/setup.cfg +0 -0
  12. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_app.py +0 -0
  13. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_base_config.py +0 -0
  14. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_base_service.py +0 -0
  15. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_cli_config.py +0 -0
  16. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_cli_formatter.py +0 -0
  17. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_cmd.py +0 -0
  18. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_config_key.py +0 -0
  19. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_ini_config.py +0 -0
  20. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_sensitive_data_filter.py +0 -0
  21. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_socket_service.py +0 -0
  22. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_unreal_config.py +0 -0
  23. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_unreal_config_ark_spawn_entities.py +0 -0
  24. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_unreal_save.py +0 -0
  25. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/tests/test_version.py +0 -0
  26. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/__init__.py +0 -0
  27. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/apps/__init__.py +0 -0
  28. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/apps/manual_app.py +0 -0
  29. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/__init__.py +0 -0
  30. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/cli_config.py +0 -0
  31. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/config_key.py +0 -0
  32. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/ini_config.py +0 -0
  33. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/json_config.py +0 -0
  34. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/properties_config.py +0 -0
  35. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/config/unreal_config.py +0 -0
  36. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/formatters/__init__.py +0 -0
  37. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/formatters/cli_formatter.py +0 -0
  38. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/__init__.py +0 -0
  39. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/app.py +0 -0
  40. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/cache.py +0 -0
  41. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/cmd.py +0 -0
  42. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/download.py +0 -0
  43. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/firewall.py +0 -0
  44. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/get_wan_ip.py +0 -0
  45. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/java.py +0 -0
  46. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/meta.py +0 -0
  47. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/ports.py +0 -0
  48. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/sensitive_data_filter.py +0 -0
  49. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/tui.py +0 -0
  50. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/utils.py +0 -0
  51. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/libs/version.py +0 -0
  52. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/mods/__init__.py +0 -0
  53. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/mods/base_mod.py +0 -0
  54. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/mods/warlock_nexus_mod.py +0 -0
  55. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/nexus/nexus.py +0 -0
  56. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/services/__init__.py +0 -0
  57. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/services/http_service.py +0 -0
  58. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/services/rcon_service.py +0 -0
  59. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager/services/socket_service.py +0 -0
  60. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager.egg-info/SOURCES.txt +0 -0
  61. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager.egg-info/dependency_links.txt +0 -0
  62. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/warlock_manager.egg-info/requires.txt +0 -0
  63. {warlock_manager-2.2.0 → warlock_manager-2.2.2}/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.0
3
+ Version: 2.2.2
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.0"
7
+ version = "2.2.2"
8
8
  description = "Dependency library for game-server management applications."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -193,7 +193,7 @@ class BaseApp(ABC):
193
193
  logging.warning('Invalid option: %s, not present in game configuration!' % option)
194
194
  return ''
195
195
 
196
- def option_value_updated(self, option: str, previous_value, new_value):
196
+ def option_value_updated(self, option: str, previous_value, new_value) -> bool | None:
197
197
  """
198
198
  Handle any special actions needed when an option value is updated
199
199
  :param option:
@@ -203,7 +203,7 @@ class BaseApp(ABC):
203
203
  """
204
204
  pass
205
205
 
206
- def set_option(self, option: str, value: str | int | bool):
206
+ def set_option(self, option: str, value: str | int | bool) -> bool:
207
207
  """
208
208
  Set a configuration option in the game config
209
209
  :param option:
@@ -211,19 +211,35 @@ class BaseApp(ABC):
211
211
  :return:
212
212
  """
213
213
  for config in self.configs.values():
214
- if option in config.options:
214
+ opt = config.get_config(option)
215
+ if opt is not None:
216
+ # Convert input value to a _system_ value to ensure '0' matches False
217
+ new_value = opt.to_system_type(value)
215
218
  previous_value = config.get_value(option)
216
- if previous_value == value:
219
+ if previous_value == new_value:
217
220
  # No change
218
- return
221
+ return True
219
222
 
220
223
  config.set_value(option, value)
221
224
  config.save()
222
225
 
223
- self.option_value_updated(option, previous_value, value)
224
- return
226
+ # Allow the extending service to handle any special actions needed for this option update
227
+ post_result = self.option_value_updated(option, previous_value, new_value)
228
+ if post_result is True:
229
+ # Post-update either returned a successful operation or nothing at all, either is fine.
230
+ logging.info('Updated option %s to %s and ran post-update actions successfully' % (option, value))
231
+ return True
232
+ elif post_result is None:
233
+ logging.debug('Post-update returned None, this is fine as it indicates no post-actions taken')
234
+ logging.info('Updated option %s to %s' % (option, value))
235
+ return True
236
+ else:
237
+ # Post-update explictly returned False, this indicates a problem occurred.
238
+ logging.warning('Configuration saved, but unable to complete post-update actions!')
239
+ return False
225
240
 
226
241
  logging.warning('Invalid option: %s, not present in game configuration!' % option)
242
+ return False
227
243
 
228
244
  def get_option_options(self, option: str):
229
245
  """
@@ -6,7 +6,8 @@ from abc import ABC
6
6
  import logging
7
7
 
8
8
  from .base_app import BaseApp
9
- from ..libs.cmd import Cmd
9
+ from warlock_manager.libs.cmd import Cmd
10
+ from warlock_manager.libs import utils
10
11
 
11
12
 
12
13
  def guess_steamcmd_path() -> str:
@@ -209,7 +210,12 @@ def steamcmd_parse_manifest(manifest_content):
209
210
 
210
211
  class SteamApp(BaseApp, ABC):
211
212
  """
212
- Game application manager
213
+ Game application manager for Steam-based games
214
+
215
+ Expects the game to have the following configuration keys:
216
+
217
+ * Steam Branch - Branch to install the game from (default: public)
218
+ * Steam Branch Password - Password for the Steam branch (optional)
213
219
  """
214
220
 
215
221
  def __init__(self):
@@ -262,7 +268,7 @@ class SteamApp(BaseApp, ABC):
262
268
 
263
269
  # Run the steamcmd command
264
270
  cmd = Cmd(command)
265
- cmd.sudo(self.get_app_uid())
271
+ cmd.sudo(utils.get_app_uid())
266
272
  cmd.is_cacheable()
267
273
 
268
274
  # Output from command should be Steam manifest format, parse it
@@ -293,13 +299,38 @@ class SteamApp(BaseApp, ABC):
293
299
 
294
300
  return list(info['depots']['branches'].keys())
295
301
 
302
+ def option_value_updated(self, option: str, previous_value, new_value):
303
+ """
304
+ Handle any special actions needed when an option value is updated
305
+
306
+ :param option:
307
+ :param previous_value:
308
+ :param new_value:
309
+ :return:
310
+ """
311
+ if option == 'Steam Branch':
312
+ # If the steam branch is updated, update the game binaries automatically.
313
+ self.update()
314
+
315
+ def get_option_options(self, option: str):
316
+ """
317
+ Get the list of possible options for a configuration option
318
+ :param option:
319
+ :return:
320
+ """
321
+ if option == 'Steam Branch':
322
+ # Steam branch should be pulled automatically from Steam
323
+ return self.get_steam_branches()
324
+ else:
325
+ return super().get_option_options(option)
326
+
296
327
  def check_update_available(self) -> bool:
297
328
  """
298
329
  Check if a SteamCMD update is available for this game
299
330
 
300
331
  :return:
301
332
  """
302
- app_manifest = os.path.join(self.get_app_directory(), 'AppFiles', 'steamapps', 'appmanifest_%s.acf' % self.steam_id)
333
+ app_manifest = os.path.join(utils.get_app_directory(), 'AppFiles', 'steamapps', 'appmanifest_%s.acf' % self.steam_id)
303
334
 
304
335
  if not os.path.exists(app_manifest):
305
336
  print(f"App manifest file {app_manifest} does not exist.", file=sys.stderr)
@@ -377,21 +408,24 @@ class SteamApp(BaseApp, ABC):
377
408
  cmd = Cmd([
378
409
  guess_steamcmd_path(),
379
410
  '+force_install_dir',
380
- os.path.join(self.get_app_directory(), 'AppFiles'),
411
+ os.path.join(utils.get_app_directory(), 'AppFiles'),
381
412
  '+login',
382
413
  'anonymous',
383
414
  '+app_update',
384
415
  self.steam_id,
385
416
  ])
386
- cmd.sudo(self.get_app_uid())
417
+ cmd.sudo(utils.get_app_uid())
387
418
  cmd.stream_output()
388
419
 
389
- if self.steam_branch != 'public':
420
+ branch = self.get_option_value('Steam Branch')
421
+ branch_password = self.get_option_value('Steam Branch Password')
422
+
423
+ if branch != 'public':
390
424
  cmd.append('-beta')
391
- cmd.append(self.steam_branch)
392
- if self.steam_branch_password != '':
425
+ cmd.append(branch)
426
+ if branch_password != '':
393
427
  cmd.append('-betapassword')
394
- cmd.append(self.steam_branch_password)
428
+ cmd.append(branch_password)
395
429
 
396
430
  cmd.append('validate')
397
431
  cmd.append('+quit')
@@ -64,11 +64,11 @@ class BaseConfig(ABC):
64
64
  :param value:
65
65
  :return:
66
66
  """
67
- if name not in self.options:
67
+ opt = self.get_config(name)
68
+ if opt is None:
68
69
  print('Invalid option: %s, not available in configuration!' % (name, ), file=sys.stderr)
69
70
  return ''
70
71
 
71
- opt = self.options[name]
72
72
  if opt.val_type == 'bool':
73
73
  if value == '':
74
74
  # Allow empty values to defer to default
@@ -117,18 +117,29 @@ class BaseConfig(ABC):
117
117
  """
118
118
  pass
119
119
 
120
+ def get_config(self, name: str) -> ConfigKey | None:
121
+ """
122
+ Get the raw configuration key object for the given name, or None if not found
123
+
124
+ :param name:
125
+ :return:
126
+ """
127
+ if name in self.options:
128
+ return self.options[name]
129
+ else:
130
+ return None
131
+
120
132
  def get_default(self, name: str) -> config_types:
121
133
  """
122
134
  Get the default value of a configuration option
123
135
  :param name:
124
136
  :return:
125
137
  """
126
- if name not in self.options:
138
+ opt = self.get_config(name)
139
+ if opt is None:
127
140
  print('Invalid option: %s, not available in configuration!' % (name, ), file=sys.stderr)
128
141
  return ''
129
142
 
130
- opt = self.options[name]
131
-
132
143
  return opt.to_system_type(opt.default)
133
144
 
134
145
  def get_type(self, name: str) -> str:
@@ -138,11 +149,12 @@ class BaseConfig(ABC):
138
149
  :param name:
139
150
  :return:
140
151
  """
141
- if name not in self.options:
152
+ opt = self.get_config(name)
153
+ if opt is None:
142
154
  print('Invalid option: %s, not available in configuration!' % (name, ), file=sys.stderr)
143
155
  return ''
144
156
 
145
- return self.options[name].val_type
157
+ return opt.val_type
146
158
 
147
159
  def get_help(self, name: str) -> str:
148
160
  """
@@ -151,11 +163,12 @@ class BaseConfig(ABC):
151
163
  :param name:
152
164
  :return:
153
165
  """
154
- if name not in self.options:
166
+ opt = self.get_config(name)
167
+ if opt is None:
155
168
  print('Invalid option: %s, not available in configuration!' % (name, ), file=sys.stderr)
156
169
  return ''
157
170
 
158
- return self.options[name].help
171
+ return opt.help
159
172
 
160
173
  def get_options(self, name: str):
161
174
  """
@@ -164,11 +177,12 @@ class BaseConfig(ABC):
164
177
  :param name:
165
178
  :return:
166
179
  """
167
- if name not in self.options:
180
+ opt = self.get_config(name)
181
+ if opt is None:
168
182
  print('Invalid option: %s, not available in configuration!' % (name, ), file=sys.stderr)
169
183
  return None
170
184
 
171
- return self.options[name].options
185
+ return opt.options
172
186
 
173
187
  def exists(self) -> bool:
174
188
  """
@@ -506,10 +506,10 @@ def app_runner(game: BaseApp):
506
506
  :return:
507
507
  """
508
508
  if service and isinstance(service, BaseService):
509
- service.set_option(option, value)
509
+ ret = service.set_option(option, value)
510
510
  else:
511
- game.set_option(option, value)
512
- sys.exit(0)
511
+ ret = game.set_option(option, value)
512
+ sys.exit(0 if ret else 1)
513
513
 
514
514
  if game.mod_handler is not None:
515
515
  @app.command()
@@ -169,7 +169,7 @@ class BaseService(ABC):
169
169
  print('Invalid option: %s, not present in service configuration!' % option, file=sys.stderr)
170
170
  return ''
171
171
 
172
- def option_value_updated(self, option: str, previous_value, new_value):
172
+ def option_value_updated(self, option: str, previous_value, new_value) -> bool | None:
173
173
  """
174
174
  Handle any special actions needed when an option value is updated
175
175
 
@@ -193,7 +193,7 @@ class BaseService(ABC):
193
193
  print('Invalid option: %s, not present in service configuration!' % option, file=sys.stderr)
194
194
  return 'Options'
195
195
 
196
- def set_option(self, option: str, value: str | int | bool):
196
+ def set_option(self, option: str, value: str | int | bool) -> bool:
197
197
  """
198
198
  Set a configuration option in the service config
199
199
 
@@ -202,21 +202,35 @@ class BaseService(ABC):
202
202
  :return:
203
203
  """
204
204
  for config in self.configs.values():
205
- if option in config.options:
205
+ opt = config.get_config(option)
206
+ if opt is not None:
207
+ # Convert input value to a _system_ value to ensure '0' matches False
208
+ new_value = opt.to_system_type(value)
206
209
  previous_value = config.get_value(option)
207
- if previous_value == value and config.has_value(option):
210
+ if previous_value == new_value and config.has_value(option):
208
211
  # No change
209
- return
212
+ return True
210
213
 
211
214
  config.set_value(option, value)
212
215
  config.save()
213
216
 
214
217
  # Allow the extending service to handle any special actions needed for this option update
215
- self.option_value_updated(option, previous_value, value)
216
- logging.info('Updated option %s to %s' % (option, value))
217
- return
218
+ post_result = self.option_value_updated(option, previous_value, new_value)
219
+ if post_result is True:
220
+ # Post-update either returned a successful operation or nothing at all, either is fine.
221
+ logging.info('Updated option %s to %s and ran post-update actions successfully' % (option, value))
222
+ return True
223
+ elif post_result is None:
224
+ logging.debug('Post-update returned None, this is fine as it indicates no post-actions taken')
225
+ logging.info('Updated option %s to %s' % (option, value))
226
+ return True
227
+ else:
228
+ # Post-update explictly returned False, this indicates a problem occurred.
229
+ logging.warning('Configuration saved, but unable to complete post-update actions!')
230
+ return False
218
231
 
219
232
  logging.warning('Invalid option: %s, not present in service configuration!' % option)
233
+ return False
220
234
 
221
235
  def option_has_value(self, option: str) -> bool:
222
236
  """
@@ -360,7 +374,7 @@ class BaseService(ABC):
360
374
  :return:
361
375
  """
362
376
  code = Cmd(['systemctl', 'show', '-p', 'ExecMainStatus', self.service]).text[15:]
363
- return int(code)
377
+ return -1 if code == '' else int(code)
364
378
 
365
379
  @abstractmethod
366
380
  def get_game_pid(self) -> int:
@@ -1169,14 +1183,15 @@ class BaseService(ABC):
1169
1183
  """
1170
1184
  Get the backup directory for this game service, which is the directory that contains backups of the game files
1171
1185
 
1172
- If the game is registered as a multi-binary game, each service is contained within its own directory,
1173
- otherwise all instances share Backups.
1186
+ All game services are contained in their own separate directory to make management easier.
1174
1187
 
1175
1188
  :return:
1176
1189
  """
1177
- base = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'Backups')
1178
- if self.game.multi_binary:
1179
- base = os.path.join(base, self.service)
1190
+ base = os.path.join(
1191
+ os.path.dirname(os.path.realpath(sys.argv[0])),
1192
+ 'Backups',
1193
+ self.service
1194
+ )
1180
1195
 
1181
1196
  return base
1182
1197
 
@@ -1212,6 +1227,18 @@ class BaseService(ABC):
1212
1227
  'loader': self.get_loader(),
1213
1228
  }
1214
1229
 
1230
+ def build_environment_file(self):
1231
+ """
1232
+ Build the environment file for this service
1233
+
1234
+ :return:
1235
+ """
1236
+ with open(self._env_file, 'w') as f:
1237
+ env = self.get_environment()
1238
+ for key in env:
1239
+ f.write('%s=%s\n' % (key, env[key]))
1240
+ self.game.ensure_file_ownership(self._env_file)
1241
+
1215
1242
  def build_systemd_config(self):
1216
1243
  """
1217
1244
  Build and save the systemd service file for this service
@@ -1234,12 +1261,8 @@ class BaseService(ABC):
1234
1261
  logging.info('Created systemd service file for %s at %s' % (self.service, self._service_file))
1235
1262
 
1236
1263
  # Save the environmental variable file for this service
1237
- with open(self._env_file, 'w') as f:
1238
- env = self.get_environment()
1239
- for key in env:
1240
- f.write('%s=%s\n' % (key, env[key]))
1264
+ self.build_environment_file()
1241
1265
  logging.info('Created environment file for %s at %s' % (self.service, self._env_file))
1242
- self.game.ensure_file_ownership(self._env_file)
1243
1266
 
1244
1267
  # Grab the ports from this service and try to automatically update them to the next available port
1245
1268
  # NOTICE, this will only check against services within this same game,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warlock-manager
3
- Version: 2.2.0
3
+ Version: 2.2.2
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