multiSSH3 5.43__tar.gz → 5.45__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.

Potentially problematic release.


This version of multiSSH3 might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: multiSSH3
3
- Version: 5.43
3
+ Version: 5.45
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: multiSSH3
3
- Version: 5.43
3
+ Version: 5.45
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -33,6 +33,7 @@ import getpass
33
33
  import uuid
34
34
  import tempfile
35
35
  import math
36
+ from itertools import count
36
37
 
37
38
  try:
38
39
  # Check if functiools.cache is available
@@ -45,10 +46,18 @@ except AttributeError:
45
46
  # If neither is available, use a dummy decorator
46
47
  def cache_decorator(func):
47
48
  return func
48
- version = '5.43'
49
+ version = '5.45'
49
50
  VERSION = version
50
51
 
51
- CONFIG_FILE = '/etc/multiSSH3.config.json'
52
+ CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
53
+ '~/multiSSH3.config.json',
54
+ '~/.multiSSH3.config.json',
55
+ '~/.config/multiSSH3/multiSSH3.config.json',
56
+ '/etc/multiSSH3.d/multiSSH3.config.json',
57
+ '/etc/multiSSH3.config.json'] # The first one has the highest priority
58
+
59
+
60
+ # TODO: Add terminal TUI with history support
52
61
 
53
62
  # ------------ Pre Helper Functions ----------------
54
63
  def eprint(*args, **kwargs):
@@ -149,8 +158,8 @@ def getIP(hostname: str,local=False):
149
158
  except:
150
159
  return None
151
160
 
152
- __host_i_lock = threading.Lock()
153
- __host_i_counter = -1
161
+
162
+ _i_counter = count()
154
163
  def _get_i():
155
164
  '''
156
165
  Get the global counter for the host objects
@@ -158,15 +167,11 @@ def _get_i():
158
167
  Returns:
159
168
  int: The global counter for the host objects
160
169
  '''
161
- global __host_i_counter
162
- global __host_i_lock
163
- with __host_i_lock:
164
- __host_i_counter += 1
165
- return __host_i_counter
170
+ return next(_i_counter)
166
171
 
167
172
  # ------------ Host Object ----------------
168
173
  class Host:
169
- def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,shell=False,i = _get_i(),uuid=uuid.uuid4(),ip = None):
174
+ def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,shell=False,i = -1,uuid=uuid.uuid4(),ip = None):
170
175
  self.name = name # the name of the host (hostname or IP address)
171
176
  self.command = command # the command to run on the host
172
177
  self.returncode = None # the return code of the command
@@ -185,7 +190,7 @@ class Host:
185
190
  self.extraargs = extraargs # extra arguments to be passed to ssh
186
191
  self.resolvedName = None # the resolved IP address of the host
187
192
  # also store a globally unique integer i from 0
188
- self.i = i
193
+ self.i = i if i != -1 else _get_i()
189
194
  self.uuid = uuid
190
195
  self.identity_file = identity_file
191
196
  self.ip = ip if ip else getIP(name)
@@ -224,44 +229,42 @@ def load_config_file(config_file):
224
229
  return {}
225
230
  return config
226
231
 
227
- __configs_from_file = load_config_file(CONFIG_FILE)
228
-
229
- __build_in_default_config = {
230
- 'AUTHOR': 'Yufei Pan',
231
- 'AUTHOR_EMAIL': 'pan@zopyr.us',
232
- 'DEFAULT_HOSTS': 'all',
233
- 'DEFAULT_USERNAME': None,
234
- 'DEFAULT_PASSWORD': '',
235
- 'DEFAULT_IDENTITY_FILE': None,
236
- 'DEDAULT_SSH_KEY_SEARCH_PATH': '~/.ssh/',
237
- 'DEFAULT_USE_KEY': False,
238
- 'DEFAULT_EXTRA_ARGS': None,
239
- 'DEFAULT_ONE_ON_ONE': False,
240
- 'DEFAULT_SCP': False,
241
- 'DEFAULT_FILE_SYNC': False,
242
- 'DEFAULT_TIMEOUT': 50,
243
- 'DEFAULT_CLI_TIMEOUT': 0,
244
- 'DEFAULT_REPEAT': 1,
245
- 'DEFAULT_INTERVAL': 0,
246
- 'DEFAULT_IPMI': False,
247
- 'DEFAULT_IPMI_INTERFACE_IP_PREFIX': '',
248
- 'DEFAULT_INTERFACE_IP_PREFIX': None,
249
- 'DEFAULT_NO_WATCH': False,
250
- 'DEFAULT_CURSES_MINIMUM_CHAR_LEN': 40,
251
- 'DEFAULT_CURSES_MINIMUM_LINE_LEN': 1,
252
- 'DEFAULT_SINGLE_WINDOW': False,
253
- 'DEFAULT_ERROR_ONLY': False,
254
- 'DEFAULT_NO_OUTPUT': False,
255
- 'DEFAULT_NO_ENV': False,
256
- 'DEFAULT_ENV_FILE': '/etc/profile.d/hosts.sh',
257
- 'DEFAULT_MAX_CONNECTIONS': 4 * os.cpu_count(),
258
- 'DEFAULT_JSON_MODE': False,
259
- 'DEFAULT_PRINT_SUCCESS_HOSTS': False,
260
- 'DEFAULT_GREPPABLE_MODE': False,
261
- 'DEFAULT_SKIP_UNREACHABLE': True,
262
- 'DEFAULT_SKIP_HOSTS': '',
263
- 'SSH_STRICT_HOST_KEY_CHECKING': False,
264
- 'ERROR_MESSAGES_TO_IGNORE': [
232
+ if True:
233
+ AUTHOR = 'Yufei Pan'
234
+ AUTHOR_EMAIL = 'pan@zopyr.us'
235
+ DEFAULT_HOSTS = 'all'
236
+ DEFAULT_USERNAME = None
237
+ DEFAULT_PASSWORD = ''
238
+ DEFAULT_IDENTITY_FILE = None
239
+ DEDAULT_SSH_KEY_SEARCH_PATH = '~/.ssh/'
240
+ DEFAULT_USE_KEY = False
241
+ DEFAULT_EXTRA_ARGS = None
242
+ DEFAULT_ONE_ON_ONE = False
243
+ DEFAULT_SCP = False
244
+ DEFAULT_FILE_SYNC = False
245
+ DEFAULT_TIMEOUT = 50
246
+ DEFAULT_CLI_TIMEOUT = 0
247
+ DEFAULT_REPEAT = 1
248
+ DEFAULT_INTERVAL = 0
249
+ DEFAULT_IPMI = False
250
+ DEFAULT_IPMI_INTERFACE_IP_PREFIX = ''
251
+ DEFAULT_INTERFACE_IP_PREFIX = None
252
+ DEFAULT_NO_WATCH = False
253
+ DEFAULT_CURSES_MINIMUM_CHAR_LEN = 40
254
+ DEFAULT_CURSES_MINIMUM_LINE_LEN = 1
255
+ DEFAULT_SINGLE_WINDOW = False
256
+ DEFAULT_ERROR_ONLY = False
257
+ DEFAULT_NO_OUTPUT = False
258
+ DEFAULT_NO_ENV = False
259
+ DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
260
+ DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
261
+ DEFAULT_JSON_MODE = False
262
+ DEFAULT_PRINT_SUCCESS_HOSTS = False
263
+ DEFAULT_GREPPABLE_MODE = False
264
+ DEFAULT_SKIP_UNREACHABLE = True
265
+ DEFAULT_SKIP_HOSTS = ''
266
+ SSH_STRICT_HOST_KEY_CHECKING = False
267
+ ERROR_MESSAGES_TO_IGNORE = [
265
268
  'Pseudo-terminal will not be allocated because stdin is not a terminal',
266
269
  'Connection to .* closed',
267
270
  'Warning: Permanently added',
@@ -269,80 +272,34 @@ __build_in_default_config = {
269
272
  'disabling multiplexing',
270
273
  'Killed by signal',
271
274
  'Connection reset by peer',
272
- ],
273
- '_DEFAULT_CALLED': True,
274
- '_DEFAULT_RETURN_UNFINISHED': False,
275
- '_DEFAULT_UPDATE_UNREACHABLE_HOSTS': True,
276
- '_DEFAULT_NO_START': False,
277
- '_etc_hosts': {},
278
- '_sshpassPath': None,
279
- '_sshPath': None,
280
- '_scpPath': None,
281
- '_ipmitoolPath': None,
282
- '_rsyncPath': None,
283
- '_shellPath': None,
284
- '__ERROR_MESSAGES_TO_IGNORE_REGEX':None,
285
- '__DEBUG_MODE': False,
286
- }
275
+ ]
276
+ _DEFAULT_CALLED = True
277
+ _DEFAULT_RETURN_UNFINISHED = False
278
+ _DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
279
+ _DEFAULT_NO_START = False
280
+ _etc_hosts = {}
281
+ _sshpassPath = None
282
+ _sshPath = None
283
+ _scpPath = None
284
+ _ipmitoolPath = None
285
+ _rsyncPath = None
286
+ _shellPath = None
287
+ __ERROR_MESSAGES_TO_IGNORE_REGEX =None
288
+ __DEBUG_MODE = False
287
289
 
288
290
  # Load Config Based Default Global variables
289
291
  if True:
290
- AUTHOR = __configs_from_file.get('AUTHOR', __build_in_default_config['AUTHOR'])
291
- AUTHOR_EMAIL = __configs_from_file.get('AUTHOR_EMAIL', __build_in_default_config['AUTHOR_EMAIL'])
292
-
293
- DEFAULT_HOSTS = __configs_from_file.get('DEFAULT_HOSTS', __build_in_default_config['DEFAULT_HOSTS'])
294
- DEFAULT_ENV_FILE = __configs_from_file.get('DEFAULT_ENV_FILE', __build_in_default_config['DEFAULT_ENV_FILE'])
295
- DEFAULT_USERNAME = __configs_from_file.get('DEFAULT_USERNAME', __build_in_default_config['DEFAULT_USERNAME'])
296
- DEFAULT_PASSWORD = __configs_from_file.get('DEFAULT_PASSWORD', __build_in_default_config['DEFAULT_PASSWORD'])
297
- DEFAULT_IDENTITY_FILE = __configs_from_file.get('DEFAULT_IDENTITY_FILE', __build_in_default_config['DEFAULT_IDENTITY_FILE'])
298
- DEDAULT_SSH_KEY_SEARCH_PATH = __configs_from_file.get('DEDAULT_SSH_KEY_SEARCH_PATH', __build_in_default_config['DEDAULT_SSH_KEY_SEARCH_PATH'])
299
- DEFAULT_USE_KEY = __configs_from_file.get('DEFAULT_USE_KEY', __build_in_default_config['DEFAULT_USE_KEY'])
300
- DEFAULT_EXTRA_ARGS = __configs_from_file.get('DEFAULT_EXTRA_ARGS', __build_in_default_config['DEFAULT_EXTRA_ARGS'])
301
- DEFAULT_ONE_ON_ONE = __configs_from_file.get('DEFAULT_ONE_ON_ONE', __build_in_default_config['DEFAULT_ONE_ON_ONE'])
302
- DEFAULT_SCP = __configs_from_file.get('DEFAULT_SCP', __build_in_default_config['DEFAULT_SCP'])
303
- DEFAULT_FILE_SYNC = __configs_from_file.get('DEFAULT_FILE_SYNC', __build_in_default_config['DEFAULT_FILE_SYNC'])
304
- DEFAULT_TIMEOUT = __configs_from_file.get('DEFAULT_TIMEOUT', __build_in_default_config['DEFAULT_TIMEOUT'])
305
- DEFAULT_CLI_TIMEOUT = __configs_from_file.get('DEFAULT_CLI_TIMEOUT', __build_in_default_config['DEFAULT_CLI_TIMEOUT'])
306
- DEFAULT_REPEAT = __configs_from_file.get('DEFAULT_REPEAT', __build_in_default_config['DEFAULT_REPEAT'])
307
- DEFAULT_INTERVAL = __configs_from_file.get('DEFAULT_INTERVAL', __build_in_default_config['DEFAULT_INTERVAL'])
308
- DEFAULT_IPMI = __configs_from_file.get('DEFAULT_IPMI', __build_in_default_config['DEFAULT_IPMI'])
309
- DEFAULT_IPMI_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_IPMI_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_IPMI_INTERFACE_IP_PREFIX'])
310
- DEFAULT_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_INTERFACE_IP_PREFIX'])
311
- DEFAULT_NO_WATCH = __configs_from_file.get('DEFAULT_NO_WATCH', __build_in_default_config['DEFAULT_NO_WATCH'])
312
- DEFAULT_CURSES_MINIMUM_CHAR_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_CHAR_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_CHAR_LEN'])
313
- DEFAULT_CURSES_MINIMUM_LINE_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_LINE_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_LINE_LEN'])
314
- DEFAULT_SINGLE_WINDOW = __configs_from_file.get('DEFAULT_SINGLE_WINDOW', __build_in_default_config['DEFAULT_SINGLE_WINDOW'])
315
- DEFAULT_ERROR_ONLY = __configs_from_file.get('DEFAULT_ERROR_ONLY', __build_in_default_config['DEFAULT_ERROR_ONLY'])
316
- DEFAULT_NO_OUTPUT = __configs_from_file.get('DEFAULT_NO_OUTPUT', __build_in_default_config['DEFAULT_NO_OUTPUT'])
317
- DEFAULT_NO_ENV = __configs_from_file.get('DEFAULT_NO_ENV', __build_in_default_config['DEFAULT_NO_ENV'])
318
- DEFAULT_MAX_CONNECTIONS = __configs_from_file.get('DEFAULT_MAX_CONNECTIONS', __build_in_default_config['DEFAULT_MAX_CONNECTIONS'])
319
- if not DEFAULT_MAX_CONNECTIONS:
320
- DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
321
- DEFAULT_JSON_MODE = __configs_from_file.get('DEFAULT_JSON_MODE', __build_in_default_config['DEFAULT_JSON_MODE'])
322
- DEFAULT_PRINT_SUCCESS_HOSTS = __configs_from_file.get('DEFAULT_PRINT_SUCCESS_HOSTS', __build_in_default_config['DEFAULT_PRINT_SUCCESS_HOSTS'])
323
- DEFAULT_GREPPABLE_MODE = __configs_from_file.get('DEFAULT_GREPPABLE_MODE', __build_in_default_config['DEFAULT_GREPPABLE_MODE'])
324
- DEFAULT_SKIP_UNREACHABLE = __configs_from_file.get('DEFAULT_SKIP_UNREACHABLE', __build_in_default_config['DEFAULT_SKIP_UNREACHABLE'])
325
- DEFAULT_SKIP_HOSTS = __configs_from_file.get('DEFAULT_SKIP_HOSTS', __build_in_default_config['DEFAULT_SKIP_HOSTS'])
326
-
327
- SSH_STRICT_HOST_KEY_CHECKING = __configs_from_file.get('SSH_STRICT_HOST_KEY_CHECKING', __build_in_default_config['SSH_STRICT_HOST_KEY_CHECKING'])
328
-
329
- ERROR_MESSAGES_TO_IGNORE = __configs_from_file.get('ERROR_MESSAGES_TO_IGNORE', __build_in_default_config['ERROR_MESSAGES_TO_IGNORE'])
330
-
331
- _DEFAULT_CALLED = __configs_from_file.get('_DEFAULT_CALLED', __build_in_default_config['_DEFAULT_CALLED'])
332
- _DEFAULT_RETURN_UNFINISHED = __configs_from_file.get('_DEFAULT_RETURN_UNFINISHED', __build_in_default_config['_DEFAULT_RETURN_UNFINISHED'])
333
- _DEFAULT_UPDATE_UNREACHABLE_HOSTS = __configs_from_file.get('_DEFAULT_UPDATE_UNREACHABLE_HOSTS', __build_in_default_config['_DEFAULT_UPDATE_UNREACHABLE_HOSTS'])
334
- _DEFAULT_NO_START = __configs_from_file.get('_DEFAULT_NO_START', __build_in_default_config['_DEFAULT_NO_START'])
335
-
292
+ __configs_from_file = {}
293
+ for config_file in reversed(CONFIG_FILE_CHAIN.copy()):
294
+ __configs_from_file.update(load_config_file(os.path.expanduser(config_file)))
295
+ globals().update(__configs_from_file)
336
296
  # form the regex from the list
337
- __ERROR_MESSAGES_TO_IGNORE_REGEX = __configs_from_file.get('__ERROR_MESSAGES_TO_IGNORE_REGEX', __build_in_default_config['__ERROR_MESSAGES_TO_IGNORE_REGEX'])
338
297
  if __ERROR_MESSAGES_TO_IGNORE_REGEX:
339
- eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX from config file, ignoring ERROR_MESSAGES_TO_IGNORE')
340
- __ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__configs_from_file['__ERROR_MESSAGES_TO_IGNORE_REGEX'])
298
+ eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX, ignoring ERROR_MESSAGES_TO_IGNORE')
299
+ __ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__ERROR_MESSAGES_TO_IGNORE_REGEX)
341
300
  else:
342
301
  __ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
343
302
 
344
- __DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config['__DEBUG_MODE'])
345
-
346
303
  # Load mssh Functional Global Variables
347
304
  if True:
348
305
  __global_suppress_printout = False
@@ -355,7 +312,6 @@ if True:
355
312
  __ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
356
313
  __keyPressesIn = [[]]
357
314
  _emo = False
358
- _etc_hosts = __configs_from_file.get('_etc_hosts', __build_in_default_config['_etc_hosts'])
359
315
  __curses_global_color_pairs = {(-1,-1):1}
360
316
  __curses_current_color_pair_index = 2 # Start from 1, as 0 is the default color pair
361
317
  __curses_color_table = {}
@@ -394,12 +350,11 @@ if __curses_available:
394
350
  _binPaths = {}
395
351
  def check_path(program_name):
396
352
  global __configs_from_file
397
- global __build_in_default_config
398
353
  global _binPaths
399
354
  config_key = f'_{program_name}Path'
400
355
  program_path = (
401
356
  __configs_from_file.get(config_key) or
402
- __build_in_default_config.get(config_key) or
357
+ globals().get(config_key) or
403
358
  shutil.which(program_name)
404
359
  )
405
360
  if program_path:
@@ -2715,14 +2670,45 @@ def generate_default_config(args):
2715
2670
  'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
2716
2671
  }
2717
2672
 
2718
- def write_default_config(args,CONFIG_FILE,backup = True):
2719
- if backup and os.path.exists(CONFIG_FILE):
2720
- os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
2673
+ def write_default_config(args,CONFIG_FILE = None):
2721
2674
  default_config = generate_default_config(args)
2722
2675
  # apply the updated defualt_config to __configs_from_file and write that to file
2723
2676
  __configs_from_file.update(default_config)
2724
- with open(CONFIG_FILE,'w') as f:
2725
- json.dump(__configs_from_file,f,indent=4)
2677
+ if not CONFIG_FILE:
2678
+ print(json.dumps(__configs_from_file, indent=4))
2679
+ return
2680
+ backup = True
2681
+ if os.path.exists(CONFIG_FILE):
2682
+ eprint(f"Warning: {CONFIG_FILE!r} already exists, what to do? (o/b/n)")
2683
+ eprint(f"o: Overwrite the file")
2684
+ eprint(f"b: Rename the current config file at {CONFIG_FILE!r}.bak forcefully and write the new config file (default)")
2685
+ eprint(f"n: Do nothing")
2686
+ inStr = input_with_timeout_and_countdown(10)
2687
+ if (not inStr) or inStr.lower().strip().startswith('b'):
2688
+ backup = True
2689
+ elif inStr.lower().strip().startswith('o'):
2690
+ backup = False
2691
+ else:
2692
+ eprint("Aborted")
2693
+ sys.exit(1)
2694
+ try:
2695
+ if backup and os.path.exists(CONFIG_FILE):
2696
+ os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
2697
+ except Exception as e:
2698
+ eprint(f"Error: Unable to backup the config file: {e!r}")
2699
+ eprint(f"Do you want to continue writing the new config file to {CONFIG_FILE!r}? (y/n)")
2700
+ inStr = input_with_timeout_and_countdown(10)
2701
+ if not inStr or not inStr.lower().strip().startswith('y'):
2702
+ eprint("Aborted")
2703
+ sys.exit(1)
2704
+ try:
2705
+ with open(CONFIG_FILE,'w') as f:
2706
+ json.dump(__configs_from_file,f,indent=4)
2707
+ eprint(f"Config file written to {CONFIG_FILE!r}")
2708
+ except Exception as e:
2709
+ eprint(f"Error: Unable to write to the config file: {e!r}")
2710
+ eprint(f'Printing the config file to stdout:')
2711
+ print(json.dumps(__configs_from_file, indent=4))
2726
2712
 
2727
2713
  # ------------ Wrapper Block ----------------
2728
2714
  def main():
@@ -2734,11 +2720,12 @@ def main():
2734
2720
  global _binPaths
2735
2721
  global _env_file
2736
2722
  global __DEBUG_MODE
2723
+ global __configs_from_file
2737
2724
  _emo = False
2738
2725
  # We handle the signal
2739
2726
  signal.signal(signal.SIGINT, signal_handler)
2740
2727
  # We parse the arguments
2741
- parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file: {CONFIG_FILE}')
2728
+ parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file chain: {CONFIG_FILE_CHAIN!r}')
2742
2729
  parser.add_argument('hosts', metavar='hosts', type=str, nargs='?', help=f'Hosts to run the command on, use "," to seperate hosts. (default: {DEFAULT_HOSTS})',default=DEFAULT_HOSTS)
2743
2730
  parser.add_argument('commands', metavar='commands', type=str, nargs='*',default=None,help='the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host name.')
2744
2731
  parser.add_argument('-u','--username', type=str,help=f'The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified. (default: {DEFAULT_USERNAME})',default=DEFAULT_USERNAME)
@@ -2775,7 +2762,9 @@ def main():
2775
2762
  group.add_argument("-nsu","--no_skip_unreachable",dest = 'skip_unreachable', action='store_false', help=f"Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {not DEFAULT_SKIP_UNREACHABLE})", default=not DEFAULT_SKIP_UNREACHABLE)
2776
2763
 
2777
2764
  parser.add_argument("-sh","--skip_hosts", type=str, help=f"Skip the hosts in the list. (default: {DEFAULT_SKIP_HOSTS if DEFAULT_SKIP_HOSTS else 'None'})", default=DEFAULT_SKIP_HOSTS)
2778
- parser.add_argument('--store_config_file', action='store_true', help=f'Store / generate the default config file from command line argument and current config at {CONFIG_FILE}')
2765
+ parser.add_argument('--generate_config_file', action='store_true', help=f'Store / generate the default config file from command line argument and current config at --config_file / stdout')
2766
+ parser.add_argument('--config_file', type=str,nargs='?', help=f'Additional config file to use, will pioritize over config chains. When using with store_config_file, will store the resulting config file at this location. Use without a path will use multiSSH3.config.json',const='multiSSH3.config.json',default=None)
2767
+ parser.add_argument('--store_config_file',type = str,nargs='?',help=f'Store the default config file from command line argument and current config. Same as --store_config_file --config_file=<path>',const='multiSSH3.config.json')
2779
2768
  parser.add_argument('--debug', action='store_true', help='Print debug information')
2780
2769
  parser.add_argument('-ci','--copy_id', action='store_true', help='Copy the ssh id to the hosts')
2781
2770
  parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
@@ -2796,33 +2785,26 @@ def main():
2796
2785
  eprint(f"Warning: Unknown arguments, treating all as commands: {unknown!r}")
2797
2786
  args.commands += unknown
2798
2787
 
2799
-
2800
-
2801
- if args.store_config_file:
2802
- try:
2803
- if os.path.exists(CONFIG_FILE):
2804
- eprint(f"Warning: {CONFIG_FILE!r} already exists, what to do? (o/b/n)")
2805
- eprint(f"o: Overwrite the file")
2806
- eprint(f"b: Rename the current config file at {CONFIG_FILE!r}.bak forcefully and write the new config file (default)")
2807
- eprint(f"n: Do nothing")
2808
- inStr = input_with_timeout_and_countdown(10)
2809
- if (not inStr) or inStr.lower().strip().startswith('b'):
2810
- write_default_config(args,CONFIG_FILE,backup = True)
2811
- eprint(f"Config file written to {CONFIG_FILE!r}")
2812
- elif inStr.lower().strip().startswith('o'):
2813
- write_default_config(args,CONFIG_FILE,backup = False)
2814
- eprint(f"Config file written to {CONFIG_FILE!r}")
2815
- else:
2816
- write_default_config(args,CONFIG_FILE,backup = True)
2817
- eprint(f"Config file written to {CONFIG_FILE!r}")
2818
- except Exception as e:
2819
- eprint(f"Error while writing config file: {e!r}")
2820
- import traceback
2821
- eprint(traceback.format_exc())
2822
- if not args.commands:
2823
- with open(CONFIG_FILE,'r') as f:
2788
+ if args.generate_config_file or args.store_config_file:
2789
+ if args.store_config_file:
2790
+ configFileToWriteTo = args.store_config_file
2791
+ if args.config_file:
2792
+ if os.path.exists(args.config_file):
2793
+ __configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
2794
+ else:
2795
+ eprint(f"Warning: Pre store config file {args.config_file!r} not found.")
2796
+ else:
2797
+ configFileToWriteTo = args.config_file
2798
+ write_default_config(args,configFileToWriteTo)
2799
+ if not args.commands and configFileToWriteTo:
2800
+ with open(configFileToWriteTo,'r') as f:
2824
2801
  eprint(f"Config file content: \n{f.read()}")
2825
2802
  sys.exit(0)
2803
+ if args.config_file:
2804
+ if os.path.exists(args.config_file):
2805
+ __configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
2806
+ else:
2807
+ eprint(f"Warning: Config file {args.config_file!r} not found, ignoring it.")
2826
2808
 
2827
2809
  _env_file = args.env_file
2828
2810
  __DEBUG_MODE = args.debug
File without changes
File without changes
File without changes