machineconfig 2.0__py3-none-any.whl → 2.2__py3-none-any.whl
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 machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/cloud_manager.py +0 -3
- machineconfig/cluster/data_transfer.py +0 -1
- machineconfig/cluster/file_manager.py +0 -1
- machineconfig/cluster/job_params.py +0 -3
- machineconfig/cluster/loader_runner.py +0 -3
- machineconfig/cluster/remote_machine.py +0 -1
- machineconfig/cluster/script_notify_upon_completion.py +0 -1
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +5 -6
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
- machineconfig/cluster/sessions_managers/wt_local.py +36 -110
- machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
- machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
- machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
- machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
- machineconfig/cluster/templates/cli_click.py +0 -1
- machineconfig/cluster/templates/cli_gooey.py +0 -2
- machineconfig/cluster/templates/cli_trogon.py +0 -1
- machineconfig/cluster/templates/run_cloud.py +0 -1
- machineconfig/cluster/templates/run_cluster.py +0 -1
- machineconfig/cluster/templates/run_remote.py +0 -1
- machineconfig/cluster/templates/utils.py +27 -46
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
- machineconfig/jobs/python/check_installations.py +2 -1
- machineconfig/jobs/python/create_bootable_media.py +0 -2
- machineconfig/jobs/python/python_ve_symlink.py +9 -11
- machineconfig/jobs/python/tasks.py +0 -1
- machineconfig/jobs/python/vscode/api.py +5 -5
- machineconfig/jobs/python/vscode/link_ve.py +13 -14
- machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
- machineconfig/jobs/python/vscode/sync_code.py +9 -13
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
- machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
- machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
- machineconfig/jobs/python_custom_installers/dev/code.py +12 -32
- machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
- machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
- machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
- machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
- machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
- machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
- machineconfig/jobs/python_custom_installers/docker.py +12 -21
- machineconfig/jobs/python_custom_installers/gh.py +11 -19
- machineconfig/jobs/python_custom_installers/hx.py +32 -16
- machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_generic_installers/config.json +1 -1
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
- machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
- machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
- machineconfig/jobs/windows/start_terminal.ps1 +1 -1
- machineconfig/profile/create.py +38 -26
- machineconfig/profile/create_hardlinks.py +29 -20
- machineconfig/profile/shell.py +56 -32
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/cloud/init.sh +2 -2
- machineconfig/scripts/linux/checkout_versions +1 -1
- machineconfig/scripts/linux/choose_wezterm_theme +1 -1
- machineconfig/scripts/linux/cloud_copy +1 -1
- machineconfig/scripts/linux/cloud_manager +1 -1
- machineconfig/scripts/linux/cloud_mount +1 -1
- machineconfig/scripts/linux/cloud_repo_sync +1 -1
- machineconfig/scripts/linux/cloud_sync +1 -1
- machineconfig/scripts/linux/croshell +1 -1
- machineconfig/scripts/linux/devops +7 -7
- machineconfig/scripts/linux/fire +1 -1
- machineconfig/scripts/linux/fire_agents +3 -2
- machineconfig/scripts/linux/ftpx +1 -1
- machineconfig/scripts/linux/gh_models +1 -1
- machineconfig/scripts/linux/kill_process +1 -1
- machineconfig/scripts/linux/mcinit +1 -1
- machineconfig/scripts/linux/repos +1 -1
- machineconfig/scripts/linux/scheduler +1 -1
- machineconfig/scripts/linux/start_slidev +1 -1
- machineconfig/scripts/linux/start_terminals +1 -1
- machineconfig/scripts/linux/url2md +1 -1
- machineconfig/scripts/linux/warp-cli.sh +122 -0
- machineconfig/scripts/linux/wifi_conn +1 -1
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__init__.py +0 -0
- machineconfig/scripts/python/ai/generate_files.py +83 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
- machineconfig/scripts/python/ai/mcinit.py +14 -7
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +7 -7
- machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
- machineconfig/scripts/python/cloud_copy.py +23 -14
- machineconfig/scripts/python/cloud_mount.py +36 -24
- machineconfig/scripts/python/cloud_repo_sync.py +40 -27
- machineconfig/scripts/python/cloud_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +40 -29
- machineconfig/scripts/python/devops.py +45 -27
- machineconfig/scripts/python/devops_add_identity.py +15 -25
- machineconfig/scripts/python/devops_add_ssh_key.py +8 -8
- machineconfig/scripts/python/devops_backup_retrieve.py +18 -16
- machineconfig/scripts/python/devops_devapps_install.py +25 -20
- machineconfig/scripts/python/devops_update_repos.py +232 -59
- machineconfig/scripts/python/dotfile.py +17 -15
- machineconfig/scripts/python/fire_agents.py +48 -22
- machineconfig/scripts/python/fire_jobs.py +93 -58
- machineconfig/scripts/python/ftpx.py +26 -15
- machineconfig/scripts/python/get_zellij_cmd.py +8 -7
- machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
- machineconfig/scripts/python/helpers/helpers2.py +27 -16
- machineconfig/scripts/python/helpers/helpers4.py +45 -32
- machineconfig/scripts/python/helpers/helpers5.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +32 -10
- machineconfig/scripts/python/mount_nfs.py +9 -16
- machineconfig/scripts/python/mount_nw_drive.py +10 -5
- machineconfig/scripts/python/mount_ssh.py +9 -7
- machineconfig/scripts/python/repos.py +216 -58
- machineconfig/scripts/python/snapshot.py +0 -1
- machineconfig/scripts/python/start_slidev.py +11 -6
- machineconfig/scripts/python/start_terminals.py +22 -16
- machineconfig/scripts/python/viewer_template.py +0 -1
- machineconfig/scripts/python/wifi_conn.py +49 -75
- machineconfig/scripts/python/wsl_windows_transfer.py +9 -7
- machineconfig/scripts/windows/checkout_version.ps1 +1 -3
- machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
- machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
- machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
- machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
- machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
- machineconfig/scripts/windows/croshell.ps1 +2 -2
- machineconfig/scripts/windows/devops.ps1 +1 -4
- machineconfig/scripts/windows/dotfile.ps1 +1 -3
- machineconfig/scripts/windows/fire.ps1 +1 -1
- machineconfig/scripts/windows/ftpx.ps1 +2 -2
- machineconfig/scripts/windows/gpt.ps1 +1 -1
- machineconfig/scripts/windows/kill_process.ps1 +1 -2
- machineconfig/scripts/windows/mcinit.ps1 +1 -1
- machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
- machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/pomodoro.ps1 +1 -1
- machineconfig/scripts/windows/py2exe.ps1 +1 -3
- machineconfig/scripts/windows/repos.ps1 +1 -1
- machineconfig/scripts/windows/scheduler.ps1 +1 -1
- machineconfig/scripts/windows/snapshot.ps1 +2 -2
- machineconfig/scripts/windows/start_slidev.ps1 +1 -1
- machineconfig/scripts/windows/start_terminals.ps1 +1 -1
- machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
- machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
- machineconfig/settings/lf/linux/lfrc +2 -1
- machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
- machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
- machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
- machineconfig/settings/svim/linux/init.toml +1 -1
- machineconfig/settings/svim/windows/init.toml +1 -1
- machineconfig/setup_linux/web_shortcuts/croshell.sh +3 -52
- machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
- machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
- machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
- machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +58 -13
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +45 -37
- machineconfig/utils/ai/generate_file_checklist.py +8 -10
- machineconfig/utils/ai/url2md.py +4 -2
- machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
- machineconfig/utils/cloud/onedrive/transaction.py +63 -98
- machineconfig/utils/code.py +62 -41
- machineconfig/utils/installer.py +29 -35
- machineconfig/utils/installer_utils/installer_abc.py +11 -11
- machineconfig/utils/installer_utils/installer_class.py +155 -74
- machineconfig/utils/links.py +112 -31
- machineconfig/utils/notifications.py +211 -0
- machineconfig/utils/options.py +41 -42
- machineconfig/utils/path.py +13 -6
- machineconfig/utils/path_reduced.py +614 -311
- machineconfig/utils/procs.py +48 -42
- machineconfig/utils/scheduling.py +0 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +146 -85
- machineconfig/utils/terminal.py +84 -37
- machineconfig/utils/upgrade_packages.py +91 -0
- machineconfig/utils/utils2.py +39 -50
- machineconfig/utils/utils5.py +195 -116
- machineconfig/utils/ve.py +13 -5
- {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/METADATA +14 -13
- {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/RECORD +212 -237
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
- machineconfig/jobs/python/archive/python_tools.txt +0 -12
- machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/update.py +0 -3
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/linux/activate_ve +0 -87
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/windows/activate_ve.ps1 +0 -54
- machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
- machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
- machineconfig/utils/utils.py +0 -95
- /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
- {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
- {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
machineconfig/utils/ssh.py
CHANGED
|
@@ -1,37 +1,44 @@
|
|
|
1
|
-
|
|
2
1
|
from typing import Optional, Any, Union, List
|
|
3
2
|
import os
|
|
4
3
|
from dataclasses import dataclass
|
|
5
4
|
import rich.console
|
|
6
5
|
from machineconfig.utils.terminal import Terminal, Response, MACHINE
|
|
7
|
-
from machineconfig.utils.path_reduced import
|
|
6
|
+
from machineconfig.utils.path_reduced import PathExtended, PLike, OPLike
|
|
8
7
|
from machineconfig.utils.utils2 import pprint
|
|
8
|
+
# from machineconfig.utils.ve import get_ve_activate_line
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
@dataclass
|
|
11
12
|
class Scout:
|
|
12
|
-
source_full:
|
|
13
|
-
source_rel2home:
|
|
13
|
+
source_full: PathExtended
|
|
14
|
+
source_rel2home: PathExtended
|
|
14
15
|
exists: bool
|
|
15
16
|
is_dir: bool
|
|
16
|
-
files: Optional[List[
|
|
17
|
+
files: Optional[List[PathExtended]]
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
def scout(source: PLike, z: bool = False, r: bool = False) -> Scout:
|
|
18
|
-
source_full =
|
|
21
|
+
source_full = PathExtended(source).expanduser().absolute()
|
|
19
22
|
source_rel2home = source_full.collapseuser()
|
|
20
23
|
exists = source_full.exists()
|
|
21
24
|
is_dir = source_full.is_dir() if exists else False
|
|
22
25
|
if z and exists:
|
|
23
|
-
try:
|
|
26
|
+
try:
|
|
27
|
+
source_full = source_full.zip()
|
|
24
28
|
except Exception as ex:
|
|
25
29
|
raise Exception(f"Could not zip {source_full} due to {ex}") from ex # type: ignore # pylint: disable=W0719
|
|
26
30
|
source_rel2home = source_full.zip()
|
|
27
31
|
if r and exists and is_dir:
|
|
28
32
|
files = [item.collapseuser() for item in source_full.search(folders=False, r=True)]
|
|
29
|
-
else:
|
|
33
|
+
else:
|
|
34
|
+
files = None
|
|
30
35
|
return Scout(source_full=source_full, source_rel2home=source_rel2home, exists=exists, is_dir=is_dir, files=files)
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
34
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self, host: Optional[str] = None, username: Optional[str] = None, hostname: Optional[str] = None, sshkey: Optional[str] = None, pwd: Optional[str] = None, port: int = 22, ve: Optional[str] = ".venv", compress: bool = False
|
|
41
|
+
): # https://stackoverflow.com/questions/51027192/execute-command-script-using-different-shell-in-ssh-paramiko
|
|
35
42
|
self.pwd = pwd
|
|
36
43
|
self.ve = ve
|
|
37
44
|
self.compress = compress # Defaults: (1) use localhost if nothing provided.
|
|
@@ -44,26 +51,33 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
44
51
|
import platform
|
|
45
52
|
import paramiko # type: ignore
|
|
46
53
|
import getpass
|
|
54
|
+
|
|
47
55
|
if isinstance(host, str):
|
|
48
56
|
try:
|
|
49
57
|
import paramiko.config as pconfig
|
|
50
|
-
|
|
58
|
+
|
|
59
|
+
config = pconfig.SSHConfig.from_path(str(PathExtended.home().joinpath(".ssh/config")))
|
|
51
60
|
config_dict = config.lookup(host)
|
|
52
61
|
self.hostname = config_dict["hostname"]
|
|
53
62
|
self.username = config_dict["user"]
|
|
54
63
|
self.host = host
|
|
55
64
|
self.port = int(config_dict.get("port", port))
|
|
56
65
|
tmp = config_dict.get("identityfile", sshkey)
|
|
57
|
-
if isinstance(tmp, list):
|
|
58
|
-
|
|
66
|
+
if isinstance(tmp, list):
|
|
67
|
+
sshkey = tmp[0]
|
|
68
|
+
else:
|
|
69
|
+
sshkey = tmp
|
|
59
70
|
self.proxycommand = config_dict.get("proxycommand", None)
|
|
60
71
|
if sshkey is not None:
|
|
61
72
|
tmp = config.lookup("*").get("identityfile", sshkey)
|
|
62
|
-
if isinstance(tmp, list):
|
|
63
|
-
|
|
73
|
+
if isinstance(tmp, list):
|
|
74
|
+
sshkey = tmp[0]
|
|
75
|
+
else:
|
|
76
|
+
sshkey = tmp
|
|
64
77
|
except (FileNotFoundError, KeyError):
|
|
65
78
|
assert "@" in host or ":" in host, f"Host must be in the form of `username@hostname:port` or `username@hostname` or `hostname:port`, but it is: {host}"
|
|
66
|
-
if "@" in host:
|
|
79
|
+
if "@" in host:
|
|
80
|
+
self.username, self.hostname = host.split("@")
|
|
67
81
|
else:
|
|
68
82
|
self.username = username or getpass.getuser()
|
|
69
83
|
self.hostname = host
|
|
@@ -77,12 +91,11 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
77
91
|
print(f"Provided values: host={host}, username={username}, hostname={hostname}")
|
|
78
92
|
raise ValueError("Either host or username and hostname must be provided.")
|
|
79
93
|
|
|
80
|
-
self.sshkey = str(
|
|
94
|
+
self.sshkey = str(PathExtended(sshkey).expanduser().absolute()) if sshkey is not None else None # no need to pass sshkey if it was configured properly already
|
|
81
95
|
self.ssh = paramiko.SSHClient()
|
|
82
96
|
self.ssh.load_system_host_keys()
|
|
83
97
|
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
84
98
|
pprint(dict(host=self.host, hostname=self.hostname, username=self.username, password="***", port=self.port, key_filename=self.sshkey, ve=self.ve), title="SSHing To")
|
|
85
|
-
|
|
86
99
|
sock = paramiko.ProxyCommand(self.proxycommand) if self.proxycommand is not None else None
|
|
87
100
|
try:
|
|
88
101
|
if pwd is None:
|
|
@@ -91,38 +104,36 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
91
104
|
else:
|
|
92
105
|
allow_agent = False
|
|
93
106
|
look_for_keys = False
|
|
94
|
-
self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock,
|
|
95
|
-
allow_agent=allow_agent, look_for_keys=look_for_keys) # type: ignore
|
|
107
|
+
self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock, allow_agent=allow_agent, look_for_keys=look_for_keys) # type: ignore
|
|
96
108
|
except Exception as _err:
|
|
97
109
|
rich.console.Console().print_exception()
|
|
98
110
|
self.pwd = getpass.getpass(f"Enter password for {self.username}@{self.hostname}: ")
|
|
99
|
-
self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
try: self.sftp: Optional[paramiko.SFTPClient] = self.ssh.open_sftp()
|
|
111
|
+
self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock, allow_agent=False, look_for_keys=False) # type: ignore
|
|
112
|
+
try:
|
|
113
|
+
self.sftp: Optional[paramiko.SFTPClient] = self.ssh.open_sftp()
|
|
103
114
|
except Exception as err:
|
|
104
115
|
self.sftp = None
|
|
105
116
|
print(f"""⚠️ WARNING: Failed to open SFTP connection to {hostname}.
|
|
106
117
|
Error Details: {err}\nData transfer may be affected!""")
|
|
118
|
+
|
|
107
119
|
def view_bar(slf: Any, a: Any, b: Any):
|
|
108
120
|
slf.total = int(b)
|
|
109
121
|
slf.update(int(a - slf.n)) # update pbar with increment
|
|
110
122
|
from tqdm import tqdm
|
|
111
|
-
self.tqdm_wrap = type(
|
|
123
|
+
self.tqdm_wrap = type("TqdmWrap", (tqdm,), {"view_bar": view_bar})
|
|
112
124
|
self._local_distro: Optional[str] = None
|
|
113
125
|
self._remote_distro: Optional[str] = None
|
|
114
126
|
self._remote_machine: Optional[MACHINE] = None
|
|
115
127
|
self.terminal_responses: list[Response] = []
|
|
116
128
|
self.platform = platform
|
|
117
|
-
self.remote_env_cmd = rf"""~/venvs/{self.ve}/Scripts/Activate.ps1""" if self.get_remote_machine() == "Windows" else rf"""source ~/venvs/{self.ve}/bin/activate"""
|
|
118
|
-
self.local_env_cmd = rf"""~/venvs/{self.ve}/Scripts/Activate.ps1""" if self.platform.system() == "Windows" else rf"""source ~/venvs/{self.ve}/bin/activate""" # works for both cmd and pwsh
|
|
119
|
-
def __getstate__(self): return {attr: self.__getattribute__(attr) for attr in ["username", "hostname", "host", "port", "sshkey", "compress", "pwd", "ve"]}
|
|
120
|
-
def __setstate__(self, state: dict[str, Any]): SSH(**state)
|
|
121
129
|
def get_remote_machine(self) -> MACHINE:
|
|
122
130
|
if self._remote_machine is None:
|
|
123
|
-
if
|
|
124
|
-
|
|
131
|
+
if self.run("$env:OS", verbose=False, desc="Testing Remote OS Type").op == "Windows_NT" or self.run("echo %OS%", verbose=False, desc="Testing Remote OS Type Again").op == "Windows_NT":
|
|
132
|
+
self._remote_machine = "Windows"
|
|
133
|
+
else:
|
|
134
|
+
self._remote_machine = "Linux"
|
|
125
135
|
return self._remote_machine # echo %OS% TODO: uname on linux
|
|
136
|
+
|
|
126
137
|
def get_local_distro(self) -> str:
|
|
127
138
|
if self._local_distro is None:
|
|
128
139
|
command = """uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """
|
|
@@ -133,105 +144,150 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
133
144
|
return self._local_distro
|
|
134
145
|
def get_remote_distro(self):
|
|
135
146
|
if self._remote_distro is None:
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
res = self.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
|
|
148
|
+
self._remote_distro = res.op_if_successfull_or_default() or ""
|
|
138
149
|
return self._remote_distro
|
|
139
|
-
|
|
150
|
+
|
|
151
|
+
def restart_computer(self):
|
|
152
|
+
self.run("Restart-Computer -Force" if self.get_remote_machine() == "Windows" else "sudo reboot")
|
|
153
|
+
|
|
140
154
|
def send_ssh_key(self):
|
|
141
155
|
self.copy_from_here("~/.ssh/id_rsa.pub")
|
|
142
156
|
assert self.get_remote_machine() == "Windows"
|
|
143
157
|
code_url = "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/refs/heads/main/src/machineconfig/setup_windows/openssh-server_add-sshkey.ps1"
|
|
144
|
-
code =
|
|
158
|
+
code = PathExtended(code_url).download().read_text(encoding="utf-8")
|
|
145
159
|
self.run(code)
|
|
160
|
+
|
|
146
161
|
def copy_env_var(self, name: str):
|
|
147
162
|
assert self.get_remote_machine() == "Linux"
|
|
148
163
|
return self.run(f"{name} = {os.environ[name]}; export {name}")
|
|
149
|
-
|
|
164
|
+
|
|
165
|
+
def get_remote_repr(self, add_machine: bool = False) -> str:
|
|
166
|
+
return f"{self.username}@{self.hostname}:{self.port}" + (f" [{self.get_remote_machine()}][{self.get_remote_distro()}]" if add_machine else "")
|
|
167
|
+
|
|
150
168
|
def get_local_repr(self, add_machine: bool = False) -> str:
|
|
151
169
|
import getpass
|
|
170
|
+
|
|
152
171
|
return f"{getpass.getuser()}@{self.platform.node()}" + (f" [{self.platform.system()}][{self.get_local_distro()}]" if add_machine else "")
|
|
153
|
-
|
|
172
|
+
|
|
173
|
+
def __repr__(self):
|
|
174
|
+
return f"local {self.get_local_repr(add_machine=True)} >>> SSH TO >>> remote {self.get_remote_repr(add_machine=True)}"
|
|
175
|
+
|
|
154
176
|
def run_locally(self, command: str):
|
|
155
177
|
print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.platform.node()} Command: {command}""")
|
|
156
178
|
res = Response(cmd=command)
|
|
157
179
|
res.output.returncode = os.system(command)
|
|
158
180
|
return res
|
|
159
|
-
def get_ssh_conn_str(self, cmd: str = ""):
|
|
160
|
-
|
|
161
|
-
def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False
|
|
162
|
-
cmd = (self.remote_env_cmd + "; " + cmd) if env_prefix else cmd
|
|
181
|
+
def get_ssh_conn_str(self, cmd: str = ""):
|
|
182
|
+
return "ssh " + (f" -i {self.sshkey}" if self.sshkey else "") + self.get_remote_repr().replace(":", " -p ") + (f" -t {cmd} " if cmd != "" else " ")
|
|
183
|
+
def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False) -> Response:
|
|
163
184
|
raw = self.ssh.exec_command(cmd)
|
|
164
185
|
res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=cmd, desc=desc) # type: ignore
|
|
165
|
-
if not verbose:
|
|
166
|
-
|
|
186
|
+
if not verbose:
|
|
187
|
+
res.capture().print_if_unsuccessful(desc=desc, strict_err=strict_err, strict_returncode=strict_returncode, assert_success=False)
|
|
188
|
+
else:
|
|
189
|
+
res.print()
|
|
167
190
|
self.terminal_responses.append(res)
|
|
168
191
|
return res
|
|
169
192
|
def run_py(self, cmd: str, desc: str = "", return_obj: bool = False, verbose: bool = True, strict_err: bool = False, strict_returncode: bool = False) -> Union[Any, Response]:
|
|
170
193
|
assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
|
|
171
|
-
if not return_obj:
|
|
194
|
+
if not return_obj:
|
|
195
|
+
return self.run(
|
|
196
|
+
cmd=f"""uv run --with machineconfig -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode)
|
|
172
197
|
assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
|
|
173
|
-
source_file = self.run_py(f"""{cmd}\npath = Save.pickle(obj=obj, path=P.tmpfile(suffix='.pkl'))\nprint(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split(
|
|
174
|
-
res = self.copy_to_here(source=source_file, target=
|
|
198
|
+
source_file = self.run_py(f"""{cmd}\npath = Save.pickle(obj=obj, path=P.tmpfile(suffix='.pkl'))\nprint(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
|
|
199
|
+
res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
|
|
175
200
|
import pickle
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[
|
|
179
|
-
if init:
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
return pickle.loads(res.read_bytes())
|
|
202
|
+
|
|
203
|
+
def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[PathExtended, list[PathExtended]]:
|
|
204
|
+
if init:
|
|
205
|
+
print(f"{'⬆️' * 5} [SFTP UPLOAD] FROM `{source}` TO `{target}`") # TODO: using return_obj do all tests required in one go.
|
|
206
|
+
source_obj = PathExtended(source).expanduser().absolute()
|
|
207
|
+
if not source_obj.exists():
|
|
208
|
+
raise RuntimeError(f"Meta.SSH Error: source `{source_obj}` does not exist!")
|
|
182
209
|
if target is None:
|
|
183
|
-
target =
|
|
210
|
+
target = PathExtended(source_obj).expanduser().absolute().collapseuser(strict=True)
|
|
184
211
|
assert target.is_relative_to("~"), "If target is not specified, source must be relative to home."
|
|
185
|
-
if z:
|
|
212
|
+
if z:
|
|
213
|
+
target += ".zip"
|
|
186
214
|
if not z and source_obj.is_dir():
|
|
187
|
-
if r is False:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
215
|
+
if r is False:
|
|
216
|
+
raise RuntimeError(f"Meta.SSH Error: source `{source_obj}` is a directory! either set `r=True` for recursive sending or raise `z=True` flag to zip it first.")
|
|
217
|
+
source_list: list[PathExtended] = source_obj.search("*", folders=False, files=True, r=True)
|
|
218
|
+
remote_root = (
|
|
219
|
+
self.run_py(
|
|
220
|
+
f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())",
|
|
221
|
+
desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}",
|
|
222
|
+
verbose=False,
|
|
223
|
+
).op
|
|
224
|
+
or ""
|
|
225
|
+
)
|
|
226
|
+
for idx, item in enumerate(source_list):
|
|
227
|
+
print(f" {idx + 1:03d}. {item}")
|
|
191
228
|
for item in source_list:
|
|
192
|
-
a__target =
|
|
229
|
+
a__target = PathExtended(remote_root).joinpath(item.relative_to(source_obj))
|
|
193
230
|
self.copy_from_here(source=item, target=a__target)
|
|
194
231
|
return list(source_list)
|
|
195
232
|
if z:
|
|
196
233
|
print("🗜️ ZIPPING ...")
|
|
197
|
-
source_obj =
|
|
198
|
-
remotepath =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
234
|
+
source_obj = PathExtended(source_obj).expanduser().zip(content=True) # .append(f"_{randstr()}", inplace=True) # eventually, unzip will raise content flag, so this name doesn't matter.
|
|
235
|
+
remotepath = (
|
|
236
|
+
self.run_py(
|
|
237
|
+
f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.parent.create())",
|
|
238
|
+
desc=f"Creating Target directory `{PathExtended(target).parent.as_posix()}` @ {self.get_remote_repr()}",
|
|
239
|
+
verbose=False,
|
|
240
|
+
).op
|
|
241
|
+
or ""
|
|
242
|
+
)
|
|
243
|
+
remotepath = PathExtended(remotepath.split("\n")[-1]).joinpath(PathExtended(target).name)
|
|
244
|
+
print(f"""📤 [SFTP UPLOAD] Sending file: {repr(PathExtended(source_obj))} ==> Remote Path: {remotepath.as_posix()}""")
|
|
245
|
+
with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
|
|
246
|
+
self.sftp.put(localpath=PathExtended(source_obj).expanduser(), remotepath=remotepath.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
|
|
202
247
|
if z:
|
|
203
248
|
_resp = self.run_py(f"""P(r'{remotepath.as_posix()}').expanduser().unzip(content=False, inplace=True, overwrite={overwrite})""", desc=f"UNZIPPING {remotepath.as_posix()}", verbose=False, strict_err=True, strict_returncode=True)
|
|
204
249
|
source_obj.delete(sure=True)
|
|
205
250
|
print("\n")
|
|
206
251
|
return source_obj
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if
|
|
210
|
-
|
|
252
|
+
|
|
253
|
+
def copy_to_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, init: bool = True) -> PathExtended:
|
|
254
|
+
if init:
|
|
255
|
+
print(f"{'⬇️' * 5} SFTP DOWNLOADING FROM `{source}` TO `{target}`")
|
|
256
|
+
if not z and self.run_py(f"print(P(r'{source}').expanduser().absolute().is_dir())", desc=f"Check if source `{source}` is a dir", verbose=False, strict_returncode=True, strict_err=True).op.split("\n")[-1] == "True":
|
|
257
|
+
if r is False:
|
|
258
|
+
raise RuntimeError(f"source `{source}` is a directory! either set r=True for recursive sending or raise zip_first flag.")
|
|
211
259
|
source_list = self.run_py(f"obj=P(r'{source}').search(folders=False, r=True).collapseuser(strict=False)", desc="Searching for files in source", return_obj=True, verbose=False)
|
|
212
260
|
assert isinstance(source_list, List), f"Could not resolve source path {source} due to error"
|
|
213
261
|
for file in source_list:
|
|
214
|
-
self.copy_to_here(source=file.as_posix(), target=
|
|
262
|
+
self.copy_to_here(source=file.as_posix(), target=PathExtended(target).joinpath(PathExtended(file).relative_to(source)) if target else None, r=False)
|
|
215
263
|
if z:
|
|
216
264
|
tmp: Response = self.run_py(f"print(P(r'{source}').expanduser().zip(inplace=False, verbose=False))", desc=f"Zipping source file {source}", verbose=False)
|
|
217
265
|
tmp2 = tmp.op2path(strict_returncode=True, strict_err=True)
|
|
218
|
-
if not isinstance(tmp2,
|
|
219
|
-
|
|
266
|
+
if not isinstance(tmp2, PathExtended):
|
|
267
|
+
raise RuntimeError(f"Could not zip {source} due to {tmp.err}")
|
|
268
|
+
else:
|
|
269
|
+
source = tmp2
|
|
220
270
|
if target is None:
|
|
221
|
-
tmpx = self.run_py(f"print(P(r'{
|
|
222
|
-
if isinstance(tmpx,
|
|
223
|
-
|
|
271
|
+
tmpx = self.run_py(f"print(P(r'{PathExtended(source).as_posix()}').collapseuser(strict=False).as_posix())", desc="Finding default target via relative source path", strict_returncode=True, strict_err=True, verbose=False).op2path()
|
|
272
|
+
if isinstance(tmpx, PathExtended):
|
|
273
|
+
target = tmpx
|
|
274
|
+
else:
|
|
275
|
+
raise RuntimeError(f"Could not resolve target path {target} due to error")
|
|
224
276
|
assert target.is_relative_to("~"), f"If target is not specified, source must be relative to home.\n{target=}"
|
|
225
|
-
target_obj =
|
|
277
|
+
target_obj = PathExtended(target).expanduser().absolute()
|
|
226
278
|
target_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
227
|
-
if z and
|
|
279
|
+
if z and ".zip" not in target_obj.suffix:
|
|
280
|
+
target_obj += ".zip"
|
|
228
281
|
if "~" in str(source):
|
|
229
282
|
tmp3 = self.run_py(f"print(P(r'{source}').expanduser())", desc="# Resolving source path address by expanding user", strict_returncode=True, strict_err=True, verbose=False).op2path()
|
|
230
|
-
if isinstance(tmp3,
|
|
231
|
-
|
|
232
|
-
|
|
283
|
+
if isinstance(tmp3, PathExtended):
|
|
284
|
+
source = tmp3
|
|
285
|
+
else:
|
|
286
|
+
raise RuntimeError(f"Could not resolve source path {source} due to")
|
|
287
|
+
else:
|
|
288
|
+
source = PathExtended(source)
|
|
233
289
|
print(f"""📥 [DOWNLOAD] Receiving: {source} ==> Local Path: {target_obj}""")
|
|
234
|
-
with self.tqdm_wrap(ascii=True, unit=
|
|
290
|
+
with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar: # type: ignore # pylint: disable=E1129
|
|
235
291
|
assert self.sftp is not None, f"Could not establish SFTP connection to {self.hostname}."
|
|
236
292
|
self.sftp.get(remotepath=source.as_posix(), localpath=str(target_obj), callback=pbar.view_bar) # type: ignore
|
|
237
293
|
if z:
|
|
@@ -239,27 +295,32 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
239
295
|
self.run_py(f"P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True, verbose=False)
|
|
240
296
|
print("\n")
|
|
241
297
|
return target_obj
|
|
242
|
-
|
|
298
|
+
|
|
299
|
+
def receieve(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False) -> PathExtended:
|
|
243
300
|
scout = self.run_py(cmd=f"obj=scout(r'{source}', z={z}, r={r})", desc=f"Scouting source `{source}` path on remote", return_obj=True, verbose=False)
|
|
244
301
|
assert isinstance(scout, Scout)
|
|
245
302
|
if not z and scout.is_dir and scout.files is not None:
|
|
246
303
|
if r:
|
|
247
|
-
tmp: list[
|
|
304
|
+
tmp: list[PathExtended] = [self.receieve(source=file.as_posix(), target=PathExtended(target).joinpath(PathExtended(file).relative_to(source)) if target else None, r=False) for file in scout.files]
|
|
248
305
|
return tmp[0]
|
|
249
|
-
else:
|
|
306
|
+
else:
|
|
307
|
+
print("Source is a directory! either set `r=True` for recursive sending or raise `zip_first=True` flag.")
|
|
250
308
|
if target:
|
|
251
|
-
target =
|
|
309
|
+
target = PathExtended(target).expanduser().absolute()
|
|
252
310
|
else:
|
|
253
311
|
target = scout.source_rel2home.expanduser().absolute()
|
|
254
312
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
-
if z and
|
|
313
|
+
if z and ".zip" not in target.suffix:
|
|
314
|
+
target += ".zip"
|
|
256
315
|
source = scout.source_full
|
|
257
|
-
with self.tqdm_wrap(ascii=True, unit=
|
|
316
|
+
with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
|
|
317
|
+
self.sftp.get(remotepath=source.as_posix(), localpath=target.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
|
|
258
318
|
if z:
|
|
259
319
|
target = target.unzip(inplace=True, content=True)
|
|
260
320
|
self.run_py(f"P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True)
|
|
261
321
|
print("\n")
|
|
262
322
|
return target
|
|
323
|
+
|
|
263
324
|
# def print_summary(self):
|
|
264
325
|
# import polars as pl
|
|
265
326
|
# df = pl.DataFrame(List(self.terminal_responses).apply(lambda rsp: dict(desc=rsp.desc, err=rsp.err, returncode=rsp.returncode)).list)
|