multiSSH3 6.32__tar.gz → 6.34__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 (108) hide show
  1. {multissh3-6.32/multiSSH3.egg-info → multissh3-6.34}/PKG-INFO +16 -12
  2. {multissh3-6.32 → multissh3-6.34}/README.md +15 -11
  3. {multissh3-6.32 → multissh3-6.34}/mssh_complete.py +116 -10
  4. {multissh3-6.32 → multissh3-6.34/multiSSH3.egg-info}/PKG-INFO +16 -12
  5. {multissh3-6.32 → multissh3-6.34}/multiSSH3.egg-info/SOURCES.txt +7 -0
  6. {multissh3-6.32 → multissh3-6.34}/multiSSH3.py +314 -47
  7. {multissh3-6.32 → multissh3-6.34}/tests/complete_shell_harness.py +18 -2
  8. {multissh3-6.32 → multissh3-6.34}/tests/conftest.py +14 -0
  9. {multissh3-6.32 → multissh3-6.34}/tests/data/run_command_golden.json +189 -34
  10. {multissh3-6.32 → multissh3-6.34}/tests/test_cli_branches.py +1 -1
  11. {multissh3-6.32 → multissh3-6.34}/tests/test_cli_processing.py +2 -1
  12. multissh3-6.34/tests/test_closed_output.py +60 -0
  13. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_diagnose.py +4 -3
  14. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_engine.py +1 -1
  15. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_keys.py +84 -8
  16. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_picker.py +45 -0
  17. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_shells.py +41 -2
  18. multissh3-6.34/tests/test_complete_start.py +102 -0
  19. {multissh3-6.32 → multissh3-6.34}/tests/test_config_cli.py +2 -2
  20. {multissh3-6.32 → multissh3-6.34}/tests/test_config_values.py +7 -5
  21. {multissh3-6.32 → multissh3-6.34}/tests/test_copy_id.py +2 -2
  22. {multissh3-6.32 → multissh3-6.34}/tests/test_curses_event_loop.py +94 -0
  23. {multissh3-6.32 → multissh3-6.34}/tests/test_expand_hostnames.py +15 -0
  24. {multissh3-6.32 → multissh3-6.34}/tests/test_history_replay.py +6 -0
  25. {multissh3-6.32 → multissh3-6.34}/tests/test_identities_only.py +1 -1
  26. {multissh3-6.32 → multissh3-6.34}/tests/test_ipmi_logins.py +18 -7
  27. {multissh3-6.32 → multissh3-6.34}/tests/test_main_lifecycle.py +2 -1
  28. multissh3-6.34/tests/test_no_hosts.py +46 -0
  29. multissh3-6.34/tests/test_password_handling.py +153 -0
  30. multissh3-6.34/tests/test_passwords_out_of_ps.py +109 -0
  31. {multissh3-6.32 → multissh3-6.34}/tests/test_run_command_branches.py +5 -3
  32. {multissh3-6.32 → multissh3-6.34}/tests/test_run_command_characterization.py +14 -8
  33. {multissh3-6.32 → multissh3-6.34}/tests/test_run_on_hosts_modes.py +7 -4
  34. {multissh3-6.32 → multissh3-6.34}/tests/test_runtime_branch_matrix.py +3 -2
  35. {multissh3-6.32 → multissh3-6.34}/tests/test_sshpass_exit.py +1 -1
  36. multissh3-6.34/tests/test_stopped_commands.py +287 -0
  37. multissh3-6.34/tests/test_underscore_settings.py +83 -0
  38. multissh3-6.34/tests/test_wide_wrap.py +78 -0
  39. multissh3-6.32/tests/test_stopped_commands.py +0 -123
  40. {multissh3-6.32 → multissh3-6.34}/LICENSE +0 -0
  41. {multissh3-6.32 → multissh3-6.34}/MANIFEST.in +0 -0
  42. {multissh3-6.32 → multissh3-6.34}/multiSSH3.egg-info/dependency_links.txt +0 -0
  43. {multissh3-6.32 → multissh3-6.34}/multiSSH3.egg-info/entry_points.txt +0 -0
  44. {multissh3-6.32 → multissh3-6.34}/multiSSH3.egg-info/top_level.txt +0 -0
  45. {multissh3-6.32 → multissh3-6.34}/pytest.ini +0 -0
  46. {multissh3-6.32 → multissh3-6.34}/setup.cfg +0 -0
  47. {multissh3-6.32 → multissh3-6.34}/setup.py +0 -0
  48. {multissh3-6.32 → multissh3-6.34}/tests/bench_complete.py +0 -0
  49. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.bash5.1 +0 -0
  50. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.bash5.3 +0 -0
  51. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.centos7 +0 -0
  52. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.current +0 -0
  53. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.legacy +0 -0
  54. {multissh3-6.32 → multissh3-6.34}/tests/shells/Dockerfile.modern +0 -0
  55. {multissh3-6.32 → multissh3-6.34}/tests/shells/run_matrix.sh +0 -0
  56. {multissh3-6.32 → multissh3-6.34}/tests/test_cli_option_words.py +0 -0
  57. {multissh3-6.32 → multissh3-6.34}/tests/test_color_helpers.py +0 -0
  58. {multissh3-6.32 → multissh3-6.34}/tests/test_command_terminal.py +0 -0
  59. {multissh3-6.32 → multissh3-6.34}/tests/test_compact_hostnames.py +0 -0
  60. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_blesh.py +0 -0
  61. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_cli.py +0 -0
  62. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_context.py +0 -0
  63. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_editor.py +0 -0
  64. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_eval.py +0 -0
  65. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_hosts.py +0 -0
  66. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_hoststate.py +0 -0
  67. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_index.py +0 -0
  68. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_lex.py +0 -0
  69. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_mssh_helpers.py +0 -0
  70. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_password_spellings.py +0 -0
  71. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_probe.py +0 -0
  72. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_store.py +0 -0
  73. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_suggest.py +0 -0
  74. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_terminal.py +0 -0
  75. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_untrusted.py +0 -0
  76. {multissh3-6.32 → multissh3-6.34}/tests/test_complete_zsh_options.py +0 -0
  77. {multissh3-6.32 → multissh3-6.34}/tests/test_config_validate.py +0 -0
  78. {multissh3-6.32 → multissh3-6.34}/tests/test_control_master_config.py +0 -0
  79. {multissh3-6.32 → multissh3-6.34}/tests/test_curses_branches.py +0 -0
  80. {multissh3-6.32 → multissh3-6.34}/tests/test_curses_tui.py +0 -0
  81. {multissh3-6.32 → multissh3-6.34}/tests/test_curses_wide_chars.py +0 -0
  82. {multissh3-6.32 → multissh3-6.34}/tests/test_file_trust.py +0 -0
  83. {multissh3-6.32 → multissh3-6.34}/tests/test_harness_smoke.py +0 -0
  84. {multissh3-6.32 → multissh3-6.34}/tests/test_helpers_extra.py +0 -0
  85. {multissh3-6.32 → multissh3-6.34}/tests/test_host.py +0 -0
  86. {multissh3-6.32 → multissh3-6.34}/tests/test_host_validity.py +0 -0
  87. {multissh3-6.32 → multissh3-6.34}/tests/test_hosts_file.py +0 -0
  88. {multissh3-6.32 → multissh3-6.34}/tests/test_ipmi_lockout_wait.py +0 -0
  89. {multissh3-6.32 → multissh3-6.34}/tests/test_key_path_home.py +0 -0
  90. {multissh3-6.32 → multissh3-6.34}/tests/test_login_drop_retry.py +0 -0
  91. {multissh3-6.32 → multissh3-6.34}/tests/test_narrow_terminal.py +0 -0
  92. {multissh3-6.32 → multissh3-6.34}/tests/test_option_no_effect.py +0 -0
  93. {multissh3-6.32 → multissh3-6.34}/tests/test_options_after_command.py +0 -0
  94. {multissh3-6.32 → multissh3-6.34}/tests/test_output_merge.py +0 -0
  95. {multissh3-6.32 → multissh3-6.34}/tests/test_output_robustness.py +0 -0
  96. {multissh3-6.32 → multissh3-6.34}/tests/test_packaging.py +0 -0
  97. {multissh3-6.32 → multissh3-6.34}/tests/test_run_command.py +0 -0
  98. {multissh3-6.32 → multissh3-6.34}/tests/test_run_command_exit.py +0 -0
  99. {multissh3-6.32 → multissh3-6.34}/tests/test_run_on_hosts.py +0 -0
  100. {multissh3-6.32 → multissh3-6.34}/tests/test_runtime_dir.py +0 -0
  101. {multissh3-6.32 → multissh3-6.34}/tests/test_smoke_optional.py +0 -0
  102. {multissh3-6.32 → multissh3-6.34}/tests/test_ssh_error_text.py +0 -0
  103. {multissh3-6.32 → multissh3-6.34}/tests/test_ssh_timeouts.py +0 -0
  104. {multissh3-6.32 → multissh3-6.34}/tests/test_start_run_on_hosts.py +0 -0
  105. {multissh3-6.32 → multissh3-6.34}/tests/test_stream_reader.py +0 -0
  106. {multissh3-6.32 → multissh3-6.34}/tests/test_tui_ssh_notices.py +0 -0
  107. {multissh3-6.32 → multissh3-6.34}/tests/test_unavailable_hosts.py +0 -0
  108. {multissh3-6.32 → multissh3-6.34}/tests/vt_grid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.32
3
+ Version: 6.34
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
@@ -182,7 +182,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
182
182
  >
183
183
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
184
184
  >
185
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
185
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
186
186
 
187
187
  ### Generating / Storing Config File
188
188
  To store / generate a config file with the current command line options, you can use
@@ -336,6 +336,8 @@ When hostname need to be resolved, mssh will check in the following order:
336
336
 
337
337
  An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
338
338
 
339
+ A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
340
+
339
341
  Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
340
342
 
341
343
 
@@ -490,7 +492,7 @@ mssh --completion uninstall # remove it again
490
492
  mssh --completion bash # or print the script (bash, zsh or fish)
491
493
  ```
492
494
 
493
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
495
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
494
496
 
495
497
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
496
498
 
@@ -551,7 +553,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
551
553
  | Environment variable | Effect |
552
554
  | --- | --- |
553
555
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
554
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
556
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
555
557
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
556
558
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
557
559
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -584,8 +586,8 @@ mssh -q -- web1 grep -r TODO /srv
584
586
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
585
587
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
586
588
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
587
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
588
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
589
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
590
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
589
591
 
590
592
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
591
593
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -711,15 +713,15 @@ mssh -f ./data/ allServers /tmp/data/
711
713
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
712
714
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
713
715
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
714
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
716
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
715
717
 
716
718
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
717
719
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
718
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
720
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
719
721
 
720
722
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
721
723
  - How `--ipmi` reaches a host:
722
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
724
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
723
725
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
724
726
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
725
727
  - Any other value fails with `IPMI method ... not supported!`.
@@ -876,7 +878,7 @@ mssh -f ./data/ allServers /tmp/data/
876
878
 
877
879
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
878
880
  - The file to store command history.
879
- - By default, mssh store command history in `~/.mssh_history`.
881
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
880
882
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
881
883
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
882
884
 
@@ -970,7 +972,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
970
972
 
971
973
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
972
974
 
973
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
975
+ Below is a sample help message output from multiSSH3 6.34 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
974
976
 
975
977
  ```bash
976
978
  $ mssh -h
@@ -996,7 +998,7 @@ options:
996
998
  -u, --username USERNAME
997
999
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
998
1000
  -p, --password PASSWORD
999
- The password to use to connect to the hosts via ssh, (default: none)
1001
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
1000
1002
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
1001
1003
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
1002
1004
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1370,6 +1372,8 @@ __DEBUG_MODE = False
1370
1372
 
1371
1373
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1372
1374
 
1375
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1376
+
1373
1377
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1374
1378
 
1375
1379
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -156,7 +156,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
156
156
  >
157
157
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
158
158
  >
159
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
159
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
160
160
 
161
161
  ### Generating / Storing Config File
162
162
  To store / generate a config file with the current command line options, you can use
@@ -310,6 +310,8 @@ When hostname need to be resolved, mssh will check in the following order:
310
310
 
311
311
  An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
312
312
 
313
+ A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
314
+
313
315
  Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
314
316
 
315
317
 
@@ -464,7 +466,7 @@ mssh --completion uninstall # remove it again
464
466
  mssh --completion bash # or print the script (bash, zsh or fish)
465
467
  ```
466
468
 
467
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
469
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
468
470
 
469
471
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
470
472
 
@@ -525,7 +527,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
525
527
  | Environment variable | Effect |
526
528
  | --- | --- |
527
529
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
528
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
530
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
529
531
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
530
532
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
531
533
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -558,8 +560,8 @@ mssh -q -- web1 grep -r TODO /srv
558
560
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
559
561
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
560
562
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
561
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
562
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
563
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
564
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
563
565
 
564
566
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
565
567
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -685,15 +687,15 @@ mssh -f ./data/ allServers /tmp/data/
685
687
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
686
688
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
687
689
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
688
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
690
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
689
691
 
690
692
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
691
693
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
692
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
694
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
693
695
 
694
696
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
695
697
  - How `--ipmi` reaches a host:
696
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
698
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
697
699
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
698
700
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
699
701
  - Any other value fails with `IPMI method ... not supported!`.
@@ -850,7 +852,7 @@ mssh -f ./data/ allServers /tmp/data/
850
852
 
851
853
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
852
854
  - The file to store command history.
853
- - By default, mssh store command history in `~/.mssh_history`.
855
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
854
856
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
855
857
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
856
858
 
@@ -944,7 +946,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
944
946
 
945
947
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
946
948
 
947
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
949
+ Below is a sample help message output from multiSSH3 6.34 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
948
950
 
949
951
  ```bash
950
952
  $ mssh -h
@@ -970,7 +972,7 @@ options:
970
972
  -u, --username USERNAME
971
973
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
972
974
  -p, --password PASSWORD
973
- The password to use to connect to the hosts via ssh, (default: none)
975
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
974
976
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
975
977
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
976
978
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1344,6 +1346,8 @@ __DEBUG_MODE = False
1344
1346
 
1345
1347
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1346
1348
 
1349
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1350
+
1347
1351
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1348
1352
 
1349
1353
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -16,7 +16,7 @@ import sys
16
16
  import time
17
17
  from bisect import bisect_left
18
18
 
19
- version = '0.5'
19
+ version = '0.7'
20
20
  __version__ = version
21
21
  PROTOCOL = 'MSSHC1'
22
22
  MSSH_NAMES = ('mssh', 'mssh3', 'multissh', 'multissh3', 'multiSSH3')
@@ -2577,12 +2577,9 @@ def _run_name(host, typed_user, line_user, own_user=''):
2577
2577
  The name mssh gives a host in this run: what it records a failure under in unavailable_hosts.csv
2578
2578
  and looks up to skip the host. The user typed with the host, else -u (line_user), else none.
2579
2579
  own_user: the user an alias member is written with. -u does not replace it, a user typed with the
2580
- alias goes in front of it (for root@dbs with dbs=postgres@db1 mssh runs root@postgres@db1).
2580
+ alias does (for root@dbs with dbs=postgres@db1 mssh runs root@db1).
2581
2581
  '''
2582
- if own_user:
2583
- host = own_user + '@' + host
2584
- line_user = ''
2585
- user = typed_user or line_user
2582
+ user = typed_user or own_user or line_user
2586
2583
  return user + '@' + host if user else host
2587
2584
 
2588
2585
  # characters that can start an expansion the shell makes before mssh sees the word
@@ -5769,6 +5766,42 @@ class LineEditor(object):
5769
5766
  self.draw(term, prompt, style)
5770
5767
 
5771
5768
  #%% ------------ Shell glue ----------------
5769
+ # zsh and fish run the picker through this POSIX sh: sh -c START_SCRIPT sh SECONDS PICKER-COMMAND...
5770
+ # The picker creates MSSH_COMPLETE_CLAIM just before it takes the terminal (_claim_terminal). It is
5771
+ # given SECONDS to do so: then, or on Ctrl-C while it starts, the script creates it first and stops the
5772
+ # picker, which has not touched the terminal (a hung file system can keep it from starting, deaf to
5773
+ # Ctrl-C). mkdir succeeds for one of the two only. Exit status: the picker's, 124 when it was too slow,
5774
+ # 130 on Ctrl-C. The picker runs in the background of this sh, in the shell's process group: it can
5775
+ # read the terminal. Its answer goes through a file: a process it leaves behind when stopped cannot
5776
+ # keep the shell's command substitution open. bash waits for its "ready" line instead (_mssh_complete_start).
5777
+ START_SCRIPT = r"""limit=$1
5778
+ shift
5779
+ dir=$(mktemp -d 2>/dev/null) || exec "$@"
5780
+ exec 3<&0
5781
+ MSSH_COMPLETE_CLAIM=$dir/claim "$@" <&3 3<&- >"$dir/answer" &
5782
+ pid=$!
5783
+ exec 3<&-
5784
+ stop() {
5785
+ mkdir "$dir/claim" 2>/dev/null && : > "$dir/$1" && kill -9 "$pid"
5786
+ }
5787
+ # the sleeper: nothing of it may keep the shell's command substitution open, and Ctrl-C is the
5788
+ # trap's (bash does not make a background subshell ignore it, as dash and busybox do)
5789
+ { trap '' INT; sleep "$limit"; stop late; } </dev/null >/dev/null 2>&1 &
5790
+ sleeper=$!
5791
+ trap 'stop stopped' INT
5792
+ while :; do
5793
+ wait "$pid"
5794
+ status=$?
5795
+ kill -0 "$pid" 2>/dev/null || break
5796
+ done
5797
+ kill "$sleeper" 2>/dev/null
5798
+ [ -e "$dir/stopped" ] && status=130
5799
+ [ -e "$dir/late" ] && status=124
5800
+ cat "$dir/answer" 2>/dev/null
5801
+ rm -rf "$dir"
5802
+ exit "$status"
5803
+ """
5804
+
5772
5805
  GLUE_MARK = 'MSSH_COMPLETE_GLUE'
5773
5806
 
5774
5807
  BASH_GLUE = r'''# MSSH_COMPLETE_GLUE @VERSION@ bash: autocomplete for mssh (multiSSH3), protocol MSSHC1
@@ -6489,6 +6522,18 @@ _mssh_complete_engine() {
6489
6522
  @ENGINE@ "$@"
6490
6523
  }
6491
6524
 
6525
+ typeset -g _mssh_complete_start_script=@START@
6526
+
6527
+ _mssh_complete_start_pick() {
6528
+ # the picker, reading the request on stdin: MSSH_COMPLETE_START_TIMEOUT seconds (2) at most to take
6529
+ # the terminal, then as long as the operator takes. 124 when too slow, 130 on Ctrl-C while it
6530
+ # starts: see START_SCRIPT in mssh_complete.py
6531
+ emulate -L zsh
6532
+ local limit=${MSSH_COMPLETE_START_TIMEOUT:-2}
6533
+ [[ $limit == (<->|<->.<->|.<->) ]] || limit=2
6534
+ command sh -c "$_mssh_complete_start_script" sh "$limit" @ENGINE@ pick
6535
+ }
6536
+
6492
6537
  _mssh_complete_keep_terminal() {
6493
6538
  # after each `exec {fd}< <(...)`: before zsh 5.8 the process substitution takes the terminal from the
6494
6539
  # shell (Ctrl-C then goes to it, and the shell's next reads can fail with "error on TTY read"); a
@@ -6517,7 +6562,10 @@ _mssh_complete_ctrl_r() {
6517
6562
  emulate -L zsh
6518
6563
  local resp line text= cursor= st
6519
6564
  if _mssh_complete_is_mssh "$BUFFER"; then
6520
- resp=$(_mssh_complete_request 'pick=1 nul=0' "$LBUFFER" "$RBUFFER" | _mssh_complete_engine pick 2>/dev/null)
6565
+ # Ctrl-C while the picker starts stops it (130), and keeps the line: without a trap of its own
6566
+ # (local: emulate -L), zsh would end the widget and drop the line
6567
+ trap : INT
6568
+ resp=$(_mssh_complete_request 'pick=1 nul=0' "$LBUFFER" "$RBUFFER" | _mssh_complete_start_pick 2>/dev/null)
6521
6569
  st=$?
6522
6570
  if (( st == 0 )) && [[ $resp == MSSHC1$'\n'*$'\n'E ]]; then
6523
6571
  for line in "${(@f)resp}"; do
@@ -6538,6 +6586,8 @@ _mssh_complete_ctrl_r() {
6538
6586
  [[ -n ${_mssh_complete_warn_reason-} ]] && zle -M -- "$_mssh_complete_warn_reason"
6539
6587
  return 0
6540
6588
  fi
6589
+ # Ctrl-C while it started: the line stays as it is. Too slow (124): what Ctrl-R did before
6590
+ (( st == 130 )) && return 0
6541
6591
  fi
6542
6592
  [[ -n $_mssh_complete_previous ]] && zle $_mssh_complete_previous
6543
6593
  return 0
@@ -7064,6 +7114,7 @@ switch "$FISH_VERSION"
7064
7114
  if not set -q __mssh_complete_keys
7065
7115
  set -g __mssh_complete_keys 1
7066
7116
  ''' + _fish_nested(_FISH_QUERY, 3) + r'''
7117
+ set -g __mssh_complete_start_script @START@
7067
7118
  function __mssh_complete_is_mssh
7068
7119
  string match -qr '^\s*(\S*/)?(mssh|mssh3|multissh|multissh3|multiSSH3)\s' -- "$argv[1]"
7069
7120
  end
@@ -7076,10 +7127,14 @@ switch "$FISH_VERSION"
7076
7127
  set -l keep 1000
7077
7128
  set -q MSSH_COMPLETE_SHELL_HISTORY; and set keep $MSSH_COMPLETE_SHELL_HISTORY
7078
7129
  # the history builtin: the operator's history function or alias (one showing times) would change the lines
7130
+ # MSSH_COMPLETE_START_TIMEOUT seconds (2) at most for the picker to take the terminal, then as
7131
+ # long as the operator takes: 124 when too slow, 130 on Ctrl-C while it starts (START_SCRIPT)
7132
+ set -l limit 2
7133
+ string match -qr '^(\d+|\d+\.\d*|\.\d+)$' -- "$MSSH_COMPLETE_START_TIMEOUT"; and set limit $MSSH_COMPLETE_START_TIMEOUT
7079
7134
  set -l lines (begin
7080
7135
  command printf '%s\0' MSSHC1 fish $FISH_VERSION "pick=1 nul=0 term=$TERM" "$before" "$after" ''
7081
7136
  builtin history search -n $keep 2>/dev/null
7082
- end | @ENGINE@ pick 2>/dev/null)
7137
+ end | command sh -c $__mssh_complete_start_script sh $limit @ENGINE@ pick 2>/dev/null)
7083
7138
  set -l st $status
7084
7139
  if test $st -eq 0; and test (count $lines) -ge 3; and test "$lines[1]" = MSSHC1; and test "$lines[-1]" = E
7085
7140
  for line in $lines[2..-2]
@@ -7090,7 +7145,8 @@ switch "$FISH_VERSION"
7090
7145
  end
7091
7146
  end
7092
7147
  commandline -f repaint
7093
- test $st -le 1; and return
7148
+ # Ctrl-C while it started: the line stays as it is. Too slow (124): what Ctrl-R did before
7149
+ test $st -le 1; or test $st -eq 130; and return
7094
7150
  end
7095
7151
  set -l previous __mssh_complete_previous_$mode
7096
7152
  for command in $$previous
@@ -7213,6 +7269,7 @@ def render_glue(shell, command=None):
7213
7269
  command = engine_command() if command is None else command
7214
7270
  quote = _fish_quote if shell.startswith('fish') else shlex.quote
7215
7271
  text = _GLUES[shell].replace('@VERSION@', version).replace('@ENGINE@', ' '.join(quote(word) for word in command))
7272
+ text = text.replace('@START@', quote(START_SCRIPT))
7216
7273
  return _zsh_guard(text) if shell == 'zsh' else text
7217
7274
 
7218
7275
  #%% ------------ Printing to a reader that may go ----------------
@@ -7826,8 +7883,38 @@ def uninstall(shell, home=None, environ=None):
7826
7883
  return 1, messages + list(error.args)
7827
7884
  if len(messages) == (1 if paths.get('note') else 0):
7828
7885
  messages.append('nothing to remove')
7886
+ if shell == 'zsh':
7887
+ for dump in _zsh_dumps_naming_mssh(home, environ):
7888
+ messages.append("zsh's completion dump {0} still maps mssh to _mssh, which is gone: "
7889
+ "remove it (rm {0}) and zsh makes a new one".format(dump))
7829
7890
  return 0, messages
7830
7891
 
7892
+ def _zsh_dumps_naming_mssh(home, environ):
7893
+ '''
7894
+ The zsh completion dumps (compinit's .zcompdump, oh-my-zsh's .zcompdump-HOST-VERSION) that map mssh
7895
+ to _mssh. A dump written while the glue was loaded keeps that after it is removed, and zsh then
7896
+ answers TAB with "command not found: _mssh". They are zsh's: they are named, not changed.
7897
+ '''
7898
+ candidates = [environ.get('ZSH_COMPDUMP')]
7899
+ for directory in (environ.get('ZDOTDIR'), home):
7900
+ try:
7901
+ names = sorted(os.listdir(directory)) if directory else []
7902
+ except OSError:
7903
+ names = []
7904
+ candidates.extend(os.path.join(directory, name) for name in names
7905
+ if name.startswith('.zcompdump') and not name.endswith('.zwc'))
7906
+ found = []
7907
+ for path in candidates:
7908
+ if not path or path in found:
7909
+ continue
7910
+ try:
7911
+ with open(path, encoding='utf-8', errors='replace') as dump:
7912
+ if "'_mssh'" in dump.read(4 << 20):
7913
+ found.append(path)
7914
+ except OSError:
7915
+ pass
7916
+ return found
7917
+
7831
7918
  def prepare_index():
7832
7919
  '''
7833
7920
  Build the index now, for `mssh --completion install`: the first TAB in a new shell then answers
@@ -8235,6 +8322,25 @@ def _ready():
8235
8322
  except OSError:
8236
8323
  return False
8237
8324
 
8325
+ def _claim_terminal():
8326
+ '''
8327
+ Create the directory MSSH_COMPLETE_CLAIM names (zsh and fish set it), just before taking the terminal.
8328
+
8329
+ The shell waits only so long (MSSH_COMPLETE_START_TIMEOUT) for the picker to take it, and creates
8330
+ that directory itself when it stops waiting (also on Ctrl-C): mkdir succeeds for one of the two
8331
+ only, so a picker slow to start never draws after the shell went on. True when the terminal is
8332
+ ours (also without MSSH_COMPLETE_CLAIM, as bash runs it).
8333
+ '''
8334
+ path = os.environ.get('MSSH_COMPLETE_CLAIM')
8335
+ if not path:
8336
+ return True
8337
+ try:
8338
+ os.mkdir(path, 0o700)
8339
+ return True
8340
+ except OSError as e:
8341
+ _debug('pick: the shell went on ({!r}): leaving the terminal alone'.format(e))
8342
+ return False
8343
+
8238
8344
  def _cmd_pick():
8239
8345
  '''
8240
8346
  mssh-complete pick: the Ctrl-R picker. Reads a request on stdin, draws on /dev/tty, prints the new
@@ -8261,7 +8367,7 @@ def _cmd_pick():
8261
8367
  line = before = None
8262
8368
  if prompt is not None:
8263
8369
  line, before = prompt + request.before + request.after, prompt + request.before
8264
- if request.flags.get('ready') == '1' and not _ready():
8370
+ if not _claim_terminal() or (request.flags.get('ready') == '1' and not _ready()):
8265
8371
  return 2
8266
8372
  with Terminal() as term:
8267
8373
  outcome = picker.run(term, line, before)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.32
3
+ Version: 6.34
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
@@ -182,7 +182,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
182
182
  >
183
183
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
184
184
  >
185
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
185
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
186
186
 
187
187
  ### Generating / Storing Config File
188
188
  To store / generate a config file with the current command line options, you can use
@@ -336,6 +336,8 @@ When hostname need to be resolved, mssh will check in the following order:
336
336
 
337
337
  An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
338
338
 
339
+ A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
340
+
339
341
  Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
340
342
 
341
343
 
@@ -490,7 +492,7 @@ mssh --completion uninstall # remove it again
490
492
  mssh --completion bash # or print the script (bash, zsh or fish)
491
493
  ```
492
494
 
493
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
495
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
494
496
 
495
497
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
496
498
 
@@ -551,7 +553,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
551
553
  | Environment variable | Effect |
552
554
  | --- | --- |
553
555
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
554
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
556
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
555
557
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
556
558
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
557
559
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -584,8 +586,8 @@ mssh -q -- web1 grep -r TODO /srv
584
586
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
585
587
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
586
588
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
587
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
588
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
589
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
590
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
589
591
 
590
592
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
591
593
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -711,15 +713,15 @@ mssh -f ./data/ allServers /tmp/data/
711
713
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
712
714
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
713
715
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
714
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
716
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
715
717
 
716
718
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
717
719
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
718
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
720
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
719
721
 
720
722
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
721
723
  - How `--ipmi` reaches a host:
722
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
724
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
723
725
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
724
726
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
725
727
  - Any other value fails with `IPMI method ... not supported!`.
@@ -876,7 +878,7 @@ mssh -f ./data/ allServers /tmp/data/
876
878
 
877
879
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
878
880
  - The file to store command history.
879
- - By default, mssh store command history in `~/.mssh_history`.
881
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
880
882
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
881
883
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
882
884
 
@@ -970,7 +972,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
970
972
 
971
973
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
972
974
 
973
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
975
+ Below is a sample help message output from multiSSH3 6.34 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
974
976
 
975
977
  ```bash
976
978
  $ mssh -h
@@ -996,7 +998,7 @@ options:
996
998
  -u, --username USERNAME
997
999
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
998
1000
  -p, --password PASSWORD
999
- The password to use to connect to the hosts via ssh, (default: none)
1001
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
1000
1002
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
1001
1003
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
1002
1004
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1370,6 +1372,8 @@ __DEBUG_MODE = False
1370
1372
 
1371
1373
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1372
1374
 
1375
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1376
+
1373
1377
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1374
1378
 
1375
1379
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.