machineconfig 6.82__py3-none-any.whl → 7.98__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.
Files changed (294) hide show
  1. machineconfig/cluster/remote/cloud_manager.py +1 -1
  2. machineconfig/cluster/sessions_managers/utils/maker.py +25 -13
  3. machineconfig/cluster/sessions_managers/wt_local.py +16 -221
  4. machineconfig/cluster/sessions_managers/wt_local_manager.py +55 -193
  5. machineconfig/cluster/sessions_managers/wt_remote_manager.py +42 -198
  6. machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
  7. machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
  8. machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
  9. machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
  10. machineconfig/cluster/sessions_managers/zellij_local_manager.py +3 -1
  11. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +3 -2
  12. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +2 -2
  13. machineconfig/jobs/installer/custom/boxes.py +2 -2
  14. machineconfig/jobs/installer/custom/hx.py +75 -18
  15. machineconfig/jobs/installer/custom/yazi.py +119 -0
  16. machineconfig/jobs/installer/custom_dev/brave.py +5 -3
  17. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  18. machineconfig/jobs/installer/custom_dev/code.py +4 -1
  19. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  20. machineconfig/jobs/installer/custom_dev/nerdfont.py +1 -1
  21. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +27 -22
  22. machineconfig/jobs/installer/custom_dev/sysabc.py +139 -0
  23. machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
  24. machineconfig/jobs/installer/custom_dev/winget.py +10 -14
  25. machineconfig/jobs/installer/installer_data.json +1287 -216
  26. machineconfig/jobs/installer/linux_scripts/q.sh +10 -7
  27. machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
  28. machineconfig/jobs/installer/package_groups.py +58 -89
  29. machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
  30. machineconfig/logger.py +0 -1
  31. machineconfig/profile/create_helper.py +43 -16
  32. machineconfig/profile/create_links.py +2 -1
  33. machineconfig/profile/create_links_export.py +64 -18
  34. machineconfig/profile/create_shell_profile.py +78 -127
  35. machineconfig/profile/mapper.toml +15 -8
  36. machineconfig/scripts/__init__.py +0 -4
  37. machineconfig/scripts/linux/wrap_mcfg +46 -0
  38. machineconfig/scripts/nu/wrap_mcfg.nu +69 -0
  39. machineconfig/scripts/python/agents.py +52 -37
  40. machineconfig/scripts/python/ai/initai.py +1 -1
  41. machineconfig/scripts/python/ai/scripts/command_runner.ps1 +33 -0
  42. machineconfig/scripts/python/ai/{command_runner → scripts}/command_runner.sh +1 -1
  43. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Thinking-Beast-Mode.chatmode.md → agents/Thinking-Beast-Mode.agent.md} +0 -1
  44. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md → agents/Ultimate-Transparent-Thinking-Beast-Mode.agent.md} +0 -1
  45. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/deepResearch.chatmode.md → agents/deepResearch.agent.md} +2 -2
  46. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +5 -5
  47. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -0
  48. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/watch_exec.prompt.md +20 -0
  49. machineconfig/scripts/python/ai/solutions/generic.py +1 -1
  50. machineconfig/scripts/python/ai/{generate_files.py → utils/generate_files.py} +2 -2
  51. machineconfig/scripts/python/ai/{vscode_tasks.py → utils/vscode_tasks.py} +7 -2
  52. machineconfig/scripts/python/croshell.py +77 -78
  53. machineconfig/scripts/python/devops.py +39 -21
  54. machineconfig/scripts/python/devops_navigator.py +0 -4
  55. machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
  56. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  57. machineconfig/scripts/python/fire_jobs.py +84 -115
  58. machineconfig/scripts/python/ftpx.py +42 -16
  59. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  60. machineconfig/scripts/python/helpers/qr_code.py +166 -0
  61. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  62. machineconfig/scripts/python/helpers/run_py_script.py +79 -0
  63. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  64. machineconfig/scripts/python/helpers/tmp_py_scripts/a.py +26 -0
  65. machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_crush.json +1 -1
  66. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.py +39 -0
  67. machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_cursor_agents.py +3 -4
  68. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_gemini.py +55 -0
  69. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +30 -0
  70. machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_help_launch.py +32 -13
  71. machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_helper_types.py +11 -14
  72. machineconfig/scripts/python/helpers_agents/templates/prompt.txt +10 -0
  73. machineconfig/scripts/python/helpers_agents/templates/template.sh +32 -0
  74. machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
  75. machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
  76. machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
  77. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
  78. machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
  79. machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
  80. machineconfig/scripts/python/helpers_devops/cli_config.py +46 -61
  81. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +67 -55
  82. machineconfig/scripts/python/helpers_devops/cli_nw.py +157 -16
  83. machineconfig/scripts/python/helpers_devops/cli_repos.py +55 -21
  84. machineconfig/scripts/python/helpers_devops/cli_self.py +98 -48
  85. machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
  86. machineconfig/scripts/python/helpers_devops/cli_share_server.py +80 -42
  87. machineconfig/scripts/python/helpers_devops/{cli_terminal.py → cli_share_terminal.py} +15 -17
  88. machineconfig/scripts/python/helpers_devops/cli_utils.py +3 -128
  89. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  90. machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
  91. machineconfig/scripts/python/helpers_devops/themes/choose_wezterm_theme.py +1 -1
  92. machineconfig/scripts/python/{helpers_fire/helpers4.py → helpers_fire_command/file_wrangler.py} +56 -20
  93. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +26 -16
  94. machineconfig/scripts/python/helpers_msearch/__init__.py +5 -0
  95. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfg +3 -3
  96. machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +59 -0
  97. machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
  98. machineconfig/scripts/python/helpers_network/address.py +132 -0
  99. machineconfig/scripts/python/{nw → helpers_network}/devops_add_ssh_key.py +24 -5
  100. machineconfig/scripts/python/{nw → helpers_network}/mount_nfs +0 -1
  101. machineconfig/scripts/python/{nw → helpers_network}/mount_nfs.py +2 -2
  102. machineconfig/scripts/python/{nw → helpers_network}/mount_ssh.py +1 -1
  103. machineconfig/scripts/python/{nw → helpers_network}/ssh_debug_linux.py +7 -7
  104. machineconfig/scripts/python/{nw → helpers_network}/ssh_debug_windows.py +4 -4
  105. machineconfig/scripts/python/{nw → helpers_network}/wifi_conn.py +1 -53
  106. machineconfig/scripts/python/{nw → helpers_network}/wsl_windows_transfer.py +3 -2
  107. machineconfig/scripts/python/helpers_repos/clone.py +0 -1
  108. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +46 -19
  109. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  110. machineconfig/scripts/python/helpers_repos/grource.py +1 -1
  111. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  112. machineconfig/scripts/python/helpers_repos/repo_analyzer_1.py +160 -0
  113. machineconfig/scripts/python/helpers_repos/{count_lines.py → repo_analyzer_2.py} +113 -192
  114. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +20 -13
  115. machineconfig/scripts/python/helpers_utils/download.py +150 -0
  116. machineconfig/scripts/python/helpers_utils/path.py +185 -0
  117. machineconfig/scripts/python/interactive.py +19 -26
  118. machineconfig/scripts/python/{mcfg.py → mcfg_entry.py} +10 -0
  119. machineconfig/scripts/python/msearch.py +71 -0
  120. machineconfig/scripts/python/sessions.py +94 -25
  121. machineconfig/scripts/python/terminal.py +133 -0
  122. machineconfig/scripts/python/utils.py +28 -30
  123. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  124. machineconfig/scripts/windows/wrap_mcfg.ps1 +63 -0
  125. machineconfig/settings/broot/conf.toml +1 -1
  126. machineconfig/settings/helix/config.toml +16 -0
  127. machineconfig/settings/helix/languages.toml +13 -4
  128. machineconfig/settings/helix/yazi-picker.sh +12 -0
  129. machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
  130. machineconfig/settings/lf/linux/exe/previewer.sh +3 -2
  131. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  132. machineconfig/settings/lf/windows/lfrc +14 -16
  133. machineconfig/settings/marimo/marimo.toml +1 -1
  134. machineconfig/settings/marimo/snippets/globalize.py +34 -0
  135. machineconfig/settings/shells/bash/init.sh +43 -11
  136. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +1 -1
  137. machineconfig/settings/shells/nushell/config.nu +2 -32
  138. machineconfig/settings/shells/nushell/env.nu +45 -6
  139. machineconfig/settings/shells/nushell/init.nu +314 -0
  140. machineconfig/settings/shells/pwsh/init.ps1 +40 -14
  141. machineconfig/settings/shells/starship/starship.toml +16 -0
  142. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  143. machineconfig/settings/shells/wt/settings.json +14 -5
  144. machineconfig/settings/shells/zsh/init.sh +17 -19
  145. machineconfig/settings/television/cable_unix/alias.toml +8 -0
  146. machineconfig/settings/television/cable_unix/aws-buckets.toml +14 -0
  147. machineconfig/settings/television/cable_unix/aws-instances.toml +13 -0
  148. machineconfig/settings/television/cable_unix/bash-history.toml +8 -0
  149. machineconfig/settings/television/cable_unix/channels.toml +19 -0
  150. machineconfig/settings/television/cable_unix/dirs.toml +13 -0
  151. machineconfig/settings/television/cable_unix/distrobox-list.toml +42 -0
  152. machineconfig/settings/television/cable_unix/docker-images.toml +13 -0
  153. machineconfig/settings/television/cable_unix/dotfiles.toml +11 -0
  154. machineconfig/settings/television/cable_unix/env.toml +17 -0
  155. machineconfig/settings/television/cable_unix/files.toml +11 -0
  156. machineconfig/settings/television/cable_unix/fish-history.toml +8 -0
  157. machineconfig/settings/television/cable_unix/git-branch.toml +11 -0
  158. machineconfig/settings/television/cable_unix/git-diff.toml +10 -0
  159. machineconfig/settings/television/cable_unix/git-log.toml +12 -0
  160. machineconfig/settings/television/cable_unix/git-reflog.toml +12 -0
  161. machineconfig/settings/television/cable_unix/git-repos.toml +16 -0
  162. machineconfig/settings/television/cable_unix/guix.toml +20 -0
  163. machineconfig/settings/television/cable_unix/just-recipes.toml +18 -0
  164. machineconfig/settings/television/cable_unix/k8s-deployments.toml +36 -0
  165. machineconfig/settings/television/cable_unix/k8s-pods.toml +50 -0
  166. machineconfig/settings/television/cable_unix/k8s-services.toml +36 -0
  167. machineconfig/settings/television/cable_unix/man-pages.toml +24 -0
  168. machineconfig/settings/television/cable_unix/nu-history.toml +7 -0
  169. machineconfig/settings/television/cable_unix/procs.toml +20 -0
  170. machineconfig/settings/television/cable_unix/text.toml +17 -0
  171. machineconfig/settings/television/cable_unix/tldr.toml +18 -0
  172. machineconfig/settings/television/cable_unix/zsh-history.toml +9 -0
  173. machineconfig/settings/television/cable_windows/alias.toml +7 -0
  174. machineconfig/settings/television/cable_windows/dirs.toml +13 -0
  175. machineconfig/settings/television/cable_windows/docker-images.toml +13 -0
  176. machineconfig/settings/television/cable_windows/dotfiles.toml +11 -0
  177. machineconfig/settings/television/cable_windows/env.toml +17 -0
  178. machineconfig/settings/television/cable_windows/files.toml +14 -0
  179. machineconfig/settings/television/cable_windows/git-branch.toml +11 -0
  180. machineconfig/settings/television/cable_windows/git-diff.toml +10 -0
  181. machineconfig/settings/television/cable_windows/git-log.toml +11 -0
  182. machineconfig/settings/television/cable_windows/git-reflog.toml +11 -0
  183. machineconfig/settings/television/cable_windows/git-repos.toml +15 -0
  184. machineconfig/settings/television/cable_windows/nu-history.toml +7 -0
  185. machineconfig/settings/television/cable_windows/pwsh-history.toml +6 -0
  186. machineconfig/settings/television/cable_windows/text.toml +17 -0
  187. machineconfig/settings/yazi/init.lua +61 -0
  188. machineconfig/settings/yazi/keymap_linux.toml +94 -0
  189. machineconfig/settings/yazi/keymap_windows.toml +78 -0
  190. machineconfig/settings/yazi/shell/yazi_cd.ps1 +33 -0
  191. machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
  192. machineconfig/settings/yazi/theme.toml +4 -0
  193. machineconfig/settings/yazi/yazi_linux.toml +84 -0
  194. machineconfig/settings/yazi/yazi_windows.toml +58 -0
  195. machineconfig/setup_linux/__init__.py +2 -1
  196. machineconfig/setup_linux/web_shortcuts/interactive.sh +27 -12
  197. machineconfig/setup_linux/web_shortcuts/live_from_github.sh +31 -0
  198. machineconfig/setup_mac/__init__.py +2 -3
  199. machineconfig/setup_mac/apps_gui.sh +248 -0
  200. machineconfig/setup_windows/__init__.py +3 -3
  201. machineconfig/setup_windows/uv.ps1 +8 -1
  202. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +26 -11
  203. machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +30 -0
  204. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +17 -0
  205. machineconfig/utils/accessories.py +7 -4
  206. machineconfig/utils/code.py +99 -32
  207. machineconfig/utils/files/ascii_art.py +1 -1
  208. machineconfig/utils/files/headers.py +3 -2
  209. machineconfig/utils/installer_utils/github_release_bulk.py +156 -119
  210. machineconfig/utils/installer_utils/install_from_url.py +183 -0
  211. machineconfig/utils/installer_utils/installer_class.py +42 -99
  212. machineconfig/utils/installer_utils/installer_cli.py +175 -0
  213. machineconfig/utils/installer_utils/installer_helper.py +129 -0
  214. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +36 -85
  215. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +16 -61
  216. machineconfig/utils/io.py +69 -1
  217. machineconfig/utils/links.py +56 -38
  218. machineconfig/utils/meta.py +33 -18
  219. machineconfig/utils/options.py +46 -18
  220. machineconfig/utils/options_tv.py +119 -0
  221. machineconfig/utils/path_extended.py +44 -95
  222. machineconfig/utils/path_helper.py +76 -23
  223. machineconfig/utils/procs.py +1 -1
  224. machineconfig/utils/scheduler.py +20 -53
  225. machineconfig/utils/scheduling.py +0 -2
  226. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  227. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  228. machineconfig/utils/ssh.py +159 -412
  229. machineconfig/utils/ssh_utils/abc.py +5 -0
  230. machineconfig/utils/ssh_utils/copy_from_here.py +111 -0
  231. machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
  232. machineconfig/utils/ssh_utils/utils.py +142 -0
  233. machineconfig/utils/ssh_utils/wsl.py +210 -0
  234. machineconfig/utils/terminal.py +1 -0
  235. machineconfig/utils/upgrade_packages.py +104 -28
  236. machineconfig/utils/ve.py +12 -4
  237. machineconfig-7.98.dist-info/METADATA +132 -0
  238. {machineconfig-6.82.dist-info → machineconfig-7.98.dist-info}/RECORD +259 -196
  239. {machineconfig-6.82.dist-info → machineconfig-7.98.dist-info}/entry_points.txt +4 -1
  240. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  241. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
  242. machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
  243. machineconfig/scripts/linux/fzf2g +0 -21
  244. machineconfig/scripts/linux/fzfag +0 -17
  245. machineconfig/scripts/linux/fzffg +0 -25
  246. machineconfig/scripts/linux/fzfrga +0 -21
  247. machineconfig/scripts/linux/mcfgs +0 -38
  248. machineconfig/scripts/linux/other/share_smb +0 -1
  249. machineconfig/scripts/linux/skrg +0 -4
  250. machineconfig/scripts/linux/warp-cli.sh +0 -122
  251. machineconfig/scripts/linux/z_ls +0 -104
  252. machineconfig/scripts/python/ai/command_runner/prompt.txt +0 -9
  253. machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_crush.py +0 -37
  254. machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_gemini.py +0 -44
  255. machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_qwen.py +0 -43
  256. machineconfig/scripts/python/helpers_fire/prompt.txt +0 -2
  257. machineconfig/scripts/python/helpers_fire/template.sh +0 -15
  258. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +0 -17
  259. machineconfig/scripts/python/helpers_repos/secure_repo.py +0 -15
  260. machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
  261. machineconfig/scripts/windows/fzfb.ps1 +0 -3
  262. machineconfig/scripts/windows/fzfg.ps1 +0 -2
  263. machineconfig/scripts/windows/fzfrga.bat +0 -20
  264. machineconfig/scripts/windows/mcfgs.ps1 +0 -17
  265. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  266. machineconfig/settings/lf/windows/fzf_edit.ps1 +0 -6
  267. machineconfig/settings/lf/windows/tst.ps1 +0 -1
  268. machineconfig/settings/yazi/yazi.toml +0 -4
  269. machineconfig/setup_linux/apps.sh +0 -66
  270. machineconfig/setup_linux/others/cli_installation.sh +0 -137
  271. machineconfig/setup_mac/apps.sh +0 -73
  272. machineconfig/setup_windows/apps.ps1 +0 -62
  273. machineconfig/utils/installer_utils/installer.py +0 -225
  274. machineconfig-6.82.dist-info/METADATA +0 -82
  275. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  276. /machineconfig/scripts/python/{helpers_fire → ai/utils}/__init__.py +0 -0
  277. /machineconfig/scripts/python/{helpers_fire/agentic_frameworks → helpers_agents}/__init__.py +0 -0
  278. /machineconfig/scripts/python/{nw → helpers_agents/agentic_frameworks}/__init__.py +0 -0
  279. /machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_help_search.py +0 -0
  280. /machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_load_balancer.py +0 -0
  281. /machineconfig/scripts/python/{helpers_fire → helpers_agents/templates}/template.ps1 +0 -0
  282. /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
  283. /machineconfig/{settings/yazi/keymap.toml → scripts/python/helpers_network/__init__.py} +0 -0
  284. /machineconfig/scripts/python/{nw → helpers_network}/devops_add_identity.py +0 -0
  285. /machineconfig/scripts/python/{nw → helpers_network}/mount_drive +0 -0
  286. /machineconfig/scripts/python/{nw → helpers_network}/mount_nw_drive +0 -0
  287. /machineconfig/scripts/python/{nw → helpers_network}/mount_nw_drive.py +0 -0
  288. /machineconfig/scripts/python/{nw → helpers_network}/mount_smb +0 -0
  289. /machineconfig/scripts/python/{nw → helpers_network}/onetimeshare.py +0 -0
  290. /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
  291. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
  292. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
  293. {machineconfig-6.82.dist-info → machineconfig-7.98.dist-info}/WHEEL +0 -0
  294. {machineconfig-6.82.dist-info → machineconfig-7.98.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,32 @@
1
- from typing import Callable, Optional, Any, Union, cast
1
+ from typing import Callable, Optional, Any, cast, Union, Literal
2
2
  import os
3
3
  from pathlib import Path
4
4
  import platform
5
- from machineconfig.scripts.python.helpers_devops.cli_utils import MachineSpecs
5
+ from machineconfig.scripts.python.helpers_utils.path import MachineSpecs
6
+ from machineconfig.utils.code import get_uv_run_command
6
7
  import rich.console
7
8
  from machineconfig.utils.terminal import Response
8
9
  from machineconfig.utils.accessories import pprint, randstr
9
10
  from machineconfig.utils.meta import lambda_to_python_script
10
- UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
11
- MACHINECONFIG_VERSION = "machineconfig>=6.81"
12
- DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
11
+ from machineconfig.utils.ssh_utils.abc import DEFAULT_PICKLE_SUBDIR
12
+
13
13
 
14
14
  class SSH:
15
15
  @staticmethod
16
16
  def from_config_file(host: str) -> "SSH":
17
17
  """Create SSH instance from SSH config file entry."""
18
18
  return SSH(host=host, username=None, hostname=None, ssh_key_path=None, password=None, port=22, enable_compression=False)
19
+
19
20
  def __init__(
20
- self, host: Optional[str], username: Optional[str], hostname: Optional[str], ssh_key_path: Optional[str], password: Optional[str], port: int, enable_compression: bool):
21
+ self,
22
+ host: Optional[str],
23
+ username: Optional[str],
24
+ hostname: Optional[str],
25
+ ssh_key_path: Optional[str],
26
+ password: Optional[str],
27
+ port: int,
28
+ enable_compression: bool,
29
+ ):
21
30
  self.password = password
22
31
  self.enable_compression = enable_compression
23
32
 
@@ -52,7 +61,9 @@ class SSH:
52
61
  else:
53
62
  ssh_key_path = wildcard_identity_file
54
63
  except (FileNotFoundError, KeyError):
55
- 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}"
64
+ assert "@" in host or ":" in host, (
65
+ f"Host must be in the form of `username@hostname:port` or `username@hostname` or `hostname:port`, but it is: {host}"
66
+ )
56
67
  if "@" in host:
57
68
  self.username, self.hostname = host.split("@")
58
69
  else:
@@ -72,7 +83,10 @@ class SSH:
72
83
  self.ssh = paramiko.SSHClient()
73
84
  self.ssh.load_system_host_keys()
74
85
  self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
75
- pprint(dict(host=self.host, hostname=self.hostname, username=self.username, password="***", port=self.port, key_filename=self.ssh_key_path), title="SSHing To")
86
+ pprint(
87
+ dict(host=self.host, hostname=self.hostname, username=self.username, password="***", port=self.port, key_filename=self.ssh_key_path),
88
+ title="SSHing To",
89
+ )
76
90
  sock = paramiko.ProxyCommand(self.proxycommand) if self.proxycommand is not None else None
77
91
  try:
78
92
  if password is None:
@@ -81,11 +95,31 @@ class SSH:
81
95
  else:
82
96
  allow_agent = False
83
97
  look_for_keys = False
84
- self.ssh.connect(hostname=self.hostname, username=self.username, password=self.password, port=self.port, key_filename=self.ssh_key_path, compress=self.enable_compression, sock=sock, allow_agent=allow_agent, look_for_keys=look_for_keys) # type: ignore
98
+ self.ssh.connect(
99
+ hostname=self.hostname,
100
+ username=self.username,
101
+ password=self.password,
102
+ port=self.port,
103
+ key_filename=self.ssh_key_path,
104
+ compress=self.enable_compression,
105
+ sock=sock,
106
+ allow_agent=allow_agent,
107
+ look_for_keys=look_for_keys,
108
+ ) # type: ignore
85
109
  except Exception as _err:
86
110
  rich.console.Console().print_exception()
87
111
  self.password = getpass.getpass(f"Enter password for {self.username}@{self.hostname}: ")
88
- self.ssh.connect(hostname=self.hostname, username=self.username, password=self.password, port=self.port, key_filename=self.ssh_key_path, compress=self.enable_compression, sock=sock, allow_agent=False, look_for_keys=False) # type: ignore
112
+ self.ssh.connect(
113
+ hostname=self.hostname,
114
+ username=self.username,
115
+ password=self.password,
116
+ port=self.port,
117
+ key_filename=self.ssh_key_path,
118
+ compress=self.enable_compression,
119
+ sock=sock,
120
+ allow_agent=False,
121
+ look_for_keys=False,
122
+ ) # type: ignore
89
123
  try:
90
124
  self.sftp: Optional[paramiko.SFTPClient] = self.ssh.open_sftp()
91
125
  except Exception as err:
@@ -100,7 +134,9 @@ class SSH:
100
134
  self.task: Optional[Any] = None
101
135
 
102
136
  def __enter__(self) -> "RichProgressWrapper":
103
- self.progress = Progress(SpinnerColumn(), TextColumn("[bold blue]{task.description}"), BarColumn(), FileSizeColumn(), TransferSpeedColumn())
137
+ self.progress = Progress(
138
+ SpinnerColumn(), TextColumn("[bold blue]{task.description}"), BarColumn(), FileSizeColumn(), TransferSpeedColumn()
139
+ )
104
140
  self.progress.start()
105
141
  self.task = self.progress.add_task("Transferring...", total=0)
106
142
  return self
@@ -113,88 +149,117 @@ class SSH:
113
149
  if self.progress and self.task is not None:
114
150
  self.progress.update(self.task, completed=transferred, total=total)
115
151
  self.tqdm_wrap = RichProgressWrapper
116
- from machineconfig.scripts.python.helpers_devops.cli_utils import get_machine_specs
152
+ from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
117
153
  self.local_specs: MachineSpecs = get_machine_specs()
118
- resp = self.run_shell(command=f"""~/.local/bin/utils get-machine-specs """, verbose_output=False, description="Getting remote machine specs", strict_stderr=False, strict_return_code=False)
154
+ resp = self.run_shell_cmd_on_remote(
155
+ command="""~/.local/bin/utils get-machine-specs """,
156
+ verbose_output=False,
157
+ description="Getting remote machine specs",
158
+ strict_stderr=False,
159
+ strict_return_code=False,
160
+ )
119
161
  json_str = resp.op
120
162
  import ast
121
163
  self.remote_specs: MachineSpecs = cast(MachineSpecs, ast.literal_eval(json_str))
122
164
  self.terminal_responses: list[Response] = []
123
-
165
+
124
166
  from rich import inspect
125
-
126
- local_info = dict(distro=self.local_specs.get("distro"), system=self.local_specs.get("system"), home_dir=self.local_specs.get("home_dir"))
127
- remote_info = dict(distro=self.remote_specs.get("distro"), system=self.remote_specs.get("system"), home_dir=self.remote_specs.get("home_dir"))
128
-
167
+
168
+ # local_info = dict(distro=self.local_specs.get("distro"), system=self.local_specs.get("system"), home_dir=self.local_specs.get("home_dir"))
169
+ # remote_info = dict(distro=self.remote_specs.get("distro"), system=self.remote_specs.get("system"), home_dir=self.remote_specs.get("home_dir"))
170
+
129
171
  console = rich.console.Console()
130
-
172
+
131
173
  from io import StringIO
174
+
132
175
  local_buffer = StringIO()
133
176
  remote_buffer = StringIO()
134
-
135
177
  local_console = rich.console.Console(file=local_buffer, width=40)
136
178
  remote_console = rich.console.Console(file=remote_buffer, width=40)
137
-
138
- inspect(type("LocalInfo", (object,), local_info)(), value=False, title="SSHing From", docs=False, dunder=False, sort=False, console=local_console)
139
- inspect(type("RemoteInfo", (object,), remote_info)(), value=False, title="SSHing To", docs=False, dunder=False, sort=False, console=remote_console)
140
-
179
+ inspect(
180
+ type("LocalInfo", (object,), dict(self.local_specs))(), value=False, title="SSHing From", docs=False, dunder=False, sort=False, console=local_console
181
+ )
182
+ inspect(
183
+ type("RemoteInfo", (object,), dict(self.remote_specs))(), value=False, title="SSHing To", docs=False, dunder=False, sort=False, console=remote_console
184
+ )
141
185
  local_lines = local_buffer.getvalue().split("\n")
142
186
  remote_lines = remote_buffer.getvalue().split("\n")
143
-
144
187
  max_lines = max(len(local_lines), len(remote_lines))
145
188
  for i in range(max_lines):
146
189
  left = local_lines[i] if i < len(local_lines) else ""
147
190
  right = remote_lines[i] if i < len(remote_lines) else ""
148
- console.print(f"{left:<42} {right}")
191
+ console.print(f"{left:<50} {right}")
149
192
 
150
193
  def __enter__(self) -> "SSH":
151
194
  return self
195
+
152
196
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
153
197
  self.close()
198
+
154
199
  def close(self) -> None:
155
200
  if self.sftp is not None:
156
201
  self.sftp.close()
157
202
  self.sftp = None
158
203
  self.ssh.close()
204
+
159
205
  def restart_computer(self) -> Response:
160
- return self.run_shell(command="Restart-Computer -Force" if self.remote_specs["system"] == "Windows" else "sudo reboot", verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
206
+ return self.run_shell_cmd_on_remote(
207
+ command="Restart-Computer -Force" if self.remote_specs["system"] == "Windows" else "sudo reboot",
208
+ verbose_output=True,
209
+ description="",
210
+ strict_stderr=False,
211
+ strict_return_code=False,
212
+ )
213
+
161
214
  def send_ssh_key(self) -> Response:
162
215
  self.copy_from_here(source_path="~/.ssh/id_rsa.pub", target_rel2home=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
163
216
  if self.remote_specs["system"] != "Windows":
164
217
  raise RuntimeError("send_ssh_key is only supported for Windows remote machines")
165
218
  code_url = "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/refs/heads/main/src/machineconfig/setup_windows/openssh-server_add-sshkey.ps1"
166
219
  import urllib.request
220
+
167
221
  with urllib.request.urlopen(code_url) as response:
168
222
  code = response.read().decode("utf-8")
169
- return self.run_shell(command=code, verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
223
+ return self.run_shell_cmd_on_remote(command=code, verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
170
224
 
171
225
  def get_remote_repr(self, add_machine: bool = False) -> str:
172
- return f"{self.username}@{self.hostname}:{self.port}" + (f" [{self.remote_specs['system']}][{self.remote_specs['distro']}]" if add_machine else "")
226
+ return f"{self.username}@{self.hostname}:{self.port}" + (
227
+ f" [{self.remote_specs['system']}][{self.remote_specs['distro']}]" if add_machine else ""
228
+ )
173
229
  def get_local_repr(self, add_machine: bool = False) -> str:
174
230
  import getpass
175
231
  return f"{getpass.getuser()}@{platform.node()}" + (f" [{platform.system()}][{self.local_specs['distro']}]" if add_machine else "")
232
+
176
233
  def get_ssh_conn_str(self, command: str) -> str:
177
- return "ssh " + (f" -i {self.ssh_key_path}" if self.ssh_key_path else "") + self.get_remote_repr(add_machine=False).replace(":", " -p ") + (f" -t {command} " if command != "" else " ")
234
+ return (
235
+ "ssh "
236
+ + (f" -i {self.ssh_key_path}" if self.ssh_key_path else "")
237
+ + self.get_remote_repr(add_machine=False).replace(":", " -p ")
238
+ + (f" -t {command} " if command != "" else " ")
239
+ )
240
+
178
241
  def __repr__(self) -> str:
179
242
  return f"local {self.get_local_repr(add_machine=True)} >>> SSH TO >>> remote {self.get_remote_repr(add_machine=True)}"
180
243
 
181
- def run_locally(self, command: str) -> Response:
182
- print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.local_specs['system']} Command: {command}""")
244
+ def run_shell_cmd_on_local(self, command: str) -> Response:
245
+ print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.local_specs["system"]} Command: {command}""")
183
246
  res = Response(cmd=command)
184
247
  res.output.returncode = os.system(command)
185
248
  return res
186
249
 
187
- def run_shell(self, command: str, verbose_output: bool, description: str, strict_stderr: bool, strict_return_code: bool) -> Response:
250
+ def run_shell_cmd_on_remote(self, command: str, verbose_output: bool, description: str, strict_stderr: bool, strict_return_code: bool) -> Response:
188
251
  raw = self.ssh.exec_command(command)
189
252
  res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=command, desc=description) # type: ignore
190
253
  if verbose_output:
191
254
  res.print()
192
255
  else:
193
- res.capture().print_if_unsuccessful(desc=description, strict_err=strict_stderr, strict_returncode=strict_return_code, assert_success=False)
256
+ res.capture().print_if_unsuccessful(
257
+ desc=description, strict_err=strict_stderr, strict_returncode=strict_return_code, assert_success=False
258
+ )
194
259
  # self.terminal_responses.append(res)
195
260
  return res
196
261
 
197
- def _run_py_prep(self, python_code: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str],) -> str:
262
+ def _run_py_prep(self, python_code: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str], on: Literal["local", "remote"]) -> str:
198
263
  py_path = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/runpy_{randstr()}.py")
199
264
  py_path.parent.mkdir(parents=True, exist_ok=True)
200
265
  py_path.write_text(python_code, encoding="utf-8")
@@ -207,407 +272,89 @@ class SSH:
207
272
  with_clause += f" --project {uv_project_dir}"
208
273
  else:
209
274
  with_clause += ""
210
- uv_cmd = f"""{UV_RUN_CMD} {with_clause} python {py_path.relative_to(Path.home())}"""
275
+ match on:
276
+ case "local":
277
+ uv_run_cmd = get_uv_run_command(platform=self.local_specs["system"])
278
+ case "remote":
279
+ uv_run_cmd = get_uv_run_command(platform=self.remote_specs["system"])
280
+ case _:
281
+ raise ValueError(f"Invalid value for 'on': {on}. Must be 'local' or 'remote'")
282
+ uv_cmd = f"""{uv_run_cmd} {with_clause} python {py_path.relative_to(Path.home())}"""
211
283
  return uv_cmd
212
284
 
213
- def run_py(self, python_code: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str],
214
- description: str, verbose_output: bool, strict_stderr: bool, strict_return_code: bool) -> Response:
215
- uv_cmd = self._run_py_prep(python_code=python_code, uv_with=uv_with, uv_project_dir=uv_project_dir)
216
- return self.run_shell(command=uv_cmd, verbose_output=verbose_output, description=description or f"run_py on {self.get_remote_repr(add_machine=False)}", strict_stderr=strict_stderr, strict_return_code=strict_return_code)
285
+ def run_py_remotely(
286
+ self,
287
+ python_code: str,
288
+ uv_with: Optional[list[str]],
289
+ uv_project_dir: Optional[str],
290
+ description: str,
291
+ verbose_output: bool,
292
+ strict_stderr: bool,
293
+ strict_return_code: bool,
294
+ ) -> Response:
295
+ uv_cmd = self._run_py_prep(python_code=python_code, uv_with=uv_with, uv_project_dir=uv_project_dir, on="remote")
296
+ return self.run_shell_cmd_on_remote(
297
+ command=uv_cmd,
298
+ verbose_output=verbose_output,
299
+ description=description or f"run_py on {self.get_remote_repr(add_machine=False)}",
300
+ strict_stderr=strict_stderr,
301
+ strict_return_code=strict_return_code,
302
+ )
217
303
 
218
304
  def run_lambda_function(self, func: Callable[..., Any], import_module: bool, uv_with: Optional[list[str]], uv_project_dir: Optional[str]):
219
- command = lambda_to_python_script(lmb=func, in_global=True, import_module=import_module)
305
+ command = lambda_to_python_script(func,
306
+ in_global=True, import_module=import_module)
220
307
  # turns ou that the code below for some reason runs but zellij doesn't start, looks like things are assigned to different user.
221
308
  # return self.run_py(python_code=command, uv_with=uv_with, uv_project_dir=uv_project_dir,
222
309
  # description=f"run_py_func {func.__name__} on {self.get_remote_repr(add_machine=False)}",
223
310
  # verbose_output=True, strict_stderr=True, strict_return_code=True)
224
- uv_cmd = self._run_py_prep(python_code=command, uv_with=uv_with, uv_project_dir=uv_project_dir)
311
+ uv_cmd = self._run_py_prep(python_code=command, uv_with=uv_with, uv_project_dir=uv_project_dir, on="remote")
225
312
  if self.remote_specs["system"] == "Linux":
226
313
  uv_cmd_modified = f'bash -l -c "{uv_cmd}"'
227
- else: uv_cmd_modified = uv_cmd
314
+ else:
315
+ uv_cmd_modified = uv_cmd
228
316
  # This works even withou the modified uv cmd:
229
317
  # from machineconfig.utils.code import run_shell_script
230
318
  # assert self.host is not None, "SSH host must be specified to run remote commands"
231
319
  # process = run_shell_script(f"ssh {self.host} -n '. ~/.profile; . ~/.bashrc; {uv_cmd}'")
232
320
  # return process
233
- return self.run_shell(command=uv_cmd_modified, verbose_output=True, description=f"run_py_func {func.__name__} on {self.get_remote_repr(add_machine=False)}", strict_stderr=True, strict_return_code=True)
321
+ return self.run_shell_cmd_on_remote(
322
+ command=uv_cmd_modified,
323
+ verbose_output=True,
324
+ description=f"run_py_func {func.__name__} on {self.get_remote_repr(add_machine=False)}",
325
+ strict_stderr=True,
326
+ strict_return_code=True,
327
+ )
234
328
 
235
- def _simple_sftp_get(self, remote_path: str, local_path: Path) -> None:
329
+ def simple_sftp_get(self, remote_path: str, local_path: Path) -> None:
236
330
  """Simple SFTP get without any recursion or path expansion - for internal use only."""
237
331
  if self.sftp is None:
238
332
  raise RuntimeError(f"SFTP connection not available for {self.hostname}")
239
333
  local_path.parent.mkdir(parents=True, exist_ok=True)
240
334
  self.sftp.get(remotepath=remote_path, localpath=str(local_path))
241
335
 
242
- def create_dir(self, path_rel2home: str, overwrite_existing: bool) -> None:
243
- """Helper to create a directory on remote machine and return its path."""
244
- def create_target_dir(target_rel2home: str, overwrite: bool):
245
- from pathlib import Path
246
- import shutil
247
- directory_path = Path(target_rel2home).expanduser()
248
- if overwrite and directory_path.exists():
249
- if directory_path.is_dir():
250
- shutil.rmtree(directory_path)
251
- else:
252
- directory_path.unlink()
253
- directory_path.parent.mkdir(parents=True, exist_ok=True)
254
- command = lambda_to_python_script(lmb=lambda: create_target_dir(target_rel2home=path_rel2home, overwrite=overwrite_existing), in_global=True, import_module=False)
255
- tmp_py_file = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/create_target_dir_{randstr()}.py")
256
- tmp_py_file.parent.mkdir(parents=True, exist_ok=True)
257
- tmp_py_file.write_text(command, encoding="utf-8")
258
- # self.copy_from_here(source_path=str(tmp_py_file), target_rel2home=".tmp_file.py", compress_with_zip=False, recursive=False, overwrite_existing=True)
259
- assert self.sftp is not None
260
- tmp_remote_path = ".tmp_pyfile.py"
261
- self.sftp.put(localpath=str(tmp_py_file), remotepath=str(Path(self.remote_specs["home_dir"]).joinpath(tmp_remote_path)))
262
- self.run_shell(command=f"""{UV_RUN_CMD} python {tmp_remote_path}""", verbose_output=False, description=f"Creating target dir {path_rel2home}", strict_stderr=True, strict_return_code=True)
336
+ def create_parent_dir_and_check_if_exists(self, path_rel2home: str, overwrite_existing: bool) -> None:
337
+ from machineconfig.utils.ssh_utils.utils import create_dir_and_check_if_exists
338
+ return create_dir_and_check_if_exists(self, path_rel2home=path_rel2home, overwrite_existing=overwrite_existing)
339
+
340
+ def check_remote_is_dir(self, source_path: Union[str, Path]) -> bool:
341
+ from machineconfig.utils.ssh_utils.utils import check_remote_is_dir
342
+ return check_remote_is_dir(self, source_path=source_path)
343
+
344
+ def expand_remote_path(self, source_path: Union[str, Path]) -> str:
345
+ from machineconfig.utils.ssh_utils.utils import expand_remote_path
346
+ return expand_remote_path(self, source_path=source_path)
263
347
 
264
348
  def copy_from_here(self, source_path: str, target_rel2home: Optional[str], compress_with_zip: bool, recursive: bool, overwrite_existing: bool) -> None:
265
- if self.sftp is None: raise RuntimeError(f"SFTP connection not available for {self.hostname}. Cannot transfer files.")
266
- source_obj = Path(source_path).expanduser().absolute()
267
- if not source_obj.exists(): raise RuntimeError(f"SSH Error: source `{source_obj}` does not exist!")
268
- if target_rel2home is None:
269
- try: target_rel2home = str(source_obj.relative_to(Path.home()))
270
- except ValueError:
271
- raise RuntimeError(f"If target is not specified, source must be relative to home directory, but got: {source_obj}")
272
- if not compress_with_zip and source_obj.is_dir():
273
- if not recursive:
274
- raise RuntimeError(f"SSH Error: source `{source_obj}` is a directory! Set `recursive=True` for recursive sending or `compress_with_zip=True` to zip it first.")
275
- file_paths_to_upload: list[Path] = [file_path for file_path in source_obj.rglob("*") if file_path.is_file()]
276
- self.create_dir(path_rel2home=target_rel2home, overwrite_existing=overwrite_existing)
277
- for idx, file_path in enumerate(file_paths_to_upload):
278
- print(f" {idx + 1:03d}. {file_path}")
279
- for file_path in file_paths_to_upload:
280
- remote_file_target = Path(target_rel2home).joinpath(file_path.relative_to(source_obj))
281
- self.copy_from_here(source_path=str(file_path), target_rel2home=str(remote_file_target), compress_with_zip=False, recursive=False, overwrite_existing=overwrite_existing)
282
- return None
283
- if compress_with_zip:
284
- print("🗜️ ZIPPING ...")
285
- import shutil
286
- zip_path = Path(str(source_obj) + "_archive")
287
- if source_obj.is_dir():
288
- shutil.make_archive(str(zip_path), "zip", source_obj)
289
- else:
290
- shutil.make_archive(str(zip_path), "zip", source_obj.parent, source_obj.name)
291
- source_obj = Path(str(zip_path) + ".zip")
292
- if not target_rel2home.endswith(".zip"): target_rel2home = target_rel2home + ".zip"
293
- self.create_dir(path_rel2home=str(Path(target_rel2home).parent), overwrite_existing=overwrite_existing)
294
- print(f"""📤 [SFTP UPLOAD] Sending file: {repr(source_obj)} ==> Remote Path: {target_rel2home}""")
295
- try:
296
- with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
297
- if self.sftp is None: # type: ignore[unreachable]
298
- raise RuntimeError(f"SFTP connection lost for {self.hostname}")
299
- self.sftp.put(localpath=str(source_obj), remotepath=str(Path(self.remote_specs["home_dir"]).joinpath(target_rel2home)), callback=pbar.view_bar)
300
- except Exception:
301
- if compress_with_zip and source_obj.exists() and str(source_obj).endswith("_archive.zip"):
302
- source_obj.unlink()
303
- raise
304
-
305
- if compress_with_zip:
306
- def unzip_archive(zip_file_path: str, overwrite_flag: bool) -> None:
307
- from pathlib import Path
308
- import shutil
309
- import zipfile
310
- archive_path = Path(zip_file_path).expanduser()
311
- extraction_directory = archive_path.parent / archive_path.stem
312
- if overwrite_flag and extraction_directory.exists():
313
- shutil.rmtree(extraction_directory)
314
- with zipfile.ZipFile(archive_path, "r") as archive_handle:
315
- archive_handle.extractall(extraction_directory)
316
- archive_path.unlink()
317
- command = lambda_to_python_script(lmb=lambda: unzip_archive(zip_file_path=str(Path(self.remote_specs["home_dir"]).joinpath(target_rel2home)), overwrite_flag=overwrite_existing), in_global=True, import_module=False)
318
- tmp_py_file = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/create_target_dir_{randstr()}.py")
319
- tmp_py_file.parent.mkdir(parents=True, exist_ok=True)
320
- tmp_py_file.write_text(command, encoding="utf-8")
321
- transferred_py_file = self.copy_from_here(source_path=str(tmp_py_file), target_rel2home=None, compress_with_zip=True, recursive=False, overwrite_existing=True)
322
- self.run_shell(command=f"""{UV_RUN_CMD} python {transferred_py_file}""", verbose_output=False, description=f"UNZIPPING {target_rel2home}", strict_stderr=True, strict_return_code=True)
323
- source_obj.unlink()
324
- return None
325
-
326
- def _check_remote_is_dir(self, source_path: Union[str, Path]) -> bool:
327
- """Helper to check if a remote path is a directory."""
328
- def check_is_dir(path_to_check: str, json_output_path: str) -> bool:
329
- from pathlib import Path
330
- import json
331
- is_directory = Path(path_to_check).expanduser().absolute().is_dir()
332
- json_result_path = Path(json_output_path)
333
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
334
- json_result_path.write_text(json.dumps(is_directory, indent=2), encoding="utf-8")
335
- print(json_result_path.as_posix())
336
- return is_directory
337
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
338
- command = lambda_to_python_script(lmb=lambda: check_is_dir(path_to_check=str(source_path), json_output_path=remote_json_output), in_global=True, import_module=False)
339
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Check if source `{source_path}` is a dir", verbose_output=False, strict_stderr=False, strict_return_code=False)
340
- remote_json_path = response.op.strip()
341
- if not remote_json_path:
342
- raise RuntimeError(f"Failed to check if {source_path} is directory - no response from remote")
343
-
344
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
345
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
346
- import json
347
- try:
348
- result = json.loads(local_json.read_text(encoding="utf-8"))
349
- except (json.JSONDecodeError, FileNotFoundError) as err:
350
- raise RuntimeError(f"Failed to check if {source_path} is directory - invalid JSON response: {err}") from err
351
- finally:
352
- if local_json.exists():
353
- local_json.unlink()
354
- assert isinstance(result, bool), f"Failed to check if {source_path} is directory"
355
- return result
356
-
357
- def _expand_remote_path(self, source_path: Union[str, Path]) -> str:
358
- """Helper to expand a path on the remote machine."""
359
- def expand_source(path_to_expand: str, json_output_path: str) -> str:
360
- from pathlib import Path
361
- import json
362
- expanded_path_posix = Path(path_to_expand).expanduser().absolute().as_posix()
363
- json_result_path = Path(json_output_path)
364
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
365
- json_result_path.write_text(json.dumps(expanded_path_posix, indent=2), encoding="utf-8")
366
- print(json_result_path.as_posix())
367
- return expanded_path_posix
368
-
369
-
370
-
371
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
372
- command = lambda_to_python_script(lmb=lambda: expand_source(path_to_expand=str(source_path), json_output_path=remote_json_output), in_global=True, import_module=False)
373
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Resolving source path by expanding user", verbose_output=False, strict_stderr=False, strict_return_code=False)
374
- remote_json_path = response.op.strip()
375
- if not remote_json_path:
376
- raise RuntimeError(f"Could not resolve source path {source_path} - no response from remote")
377
-
378
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
379
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
380
- import json
381
- try:
382
- result = json.loads(local_json.read_text(encoding="utf-8"))
383
- except (json.JSONDecodeError, FileNotFoundError) as err:
384
- raise RuntimeError(f"Could not resolve source path {source_path} - invalid JSON response: {err}") from err
385
- finally:
386
- if local_json.exists():
387
- local_json.unlink()
388
- assert isinstance(result, str), f"Could not resolve source path {source_path}"
389
- return result
390
-
391
- def copy_to_here(self, source: Union[str, Path], target: Optional[Union[str, Path]], compress_with_zip: bool = False, recursive: bool = False, internal_call: bool = False) -> Path:
392
- if self.sftp is None:
393
- raise RuntimeError(f"SFTP connection not available for {self.hostname}. Cannot transfer files.")
394
-
395
- if not internal_call:
396
- print(f"{'⬇️' * 5} SFTP DOWNLOADING FROM `{source}` TO `{target}`")
397
-
398
- source_obj = Path(source)
399
- expanded_source = self._expand_remote_path(source_path=source_obj)
400
-
401
- if not compress_with_zip:
402
- is_dir = self._check_remote_is_dir(source_path=expanded_source)
403
-
404
- if is_dir:
405
- if not recursive:
406
- raise RuntimeError(f"SSH Error: source `{source_obj}` is a directory! Set recursive=True for recursive transfer or compress_with_zip=True to zip it.")
407
-
408
- def search_files(directory_path: str, json_output_path: str) -> list[str]:
409
- from pathlib import Path
410
- import json
411
- file_paths_list = [file_path.as_posix() for file_path in Path(directory_path).expanduser().absolute().rglob("*") if file_path.is_file()]
412
- json_result_path = Path(json_output_path)
413
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
414
- json_result_path.write_text(json.dumps(file_paths_list, indent=2), encoding="utf-8")
415
- print(json_result_path.as_posix())
416
- return file_paths_list
417
-
418
-
419
-
420
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
421
- command = lambda_to_python_script(lmb=lambda: search_files(directory_path=expanded_source, json_output_path=remote_json_output), in_global=True, import_module=False)
422
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Searching for files in source", verbose_output=False, strict_stderr=False, strict_return_code=False)
423
- remote_json_path = response.op.strip()
424
- if not remote_json_path:
425
- raise RuntimeError(f"Could not resolve source path {source} - no response from remote")
426
-
427
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
428
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
429
- import json
430
- try:
431
- source_list_str = json.loads(local_json.read_text(encoding="utf-8"))
432
- except (json.JSONDecodeError, FileNotFoundError) as err:
433
- raise RuntimeError(f"Could not resolve source path {source} - invalid JSON response: {err}") from err
434
- finally:
435
- if local_json.exists():
436
- local_json.unlink()
437
- assert isinstance(source_list_str, list), f"Could not resolve source path {source}"
438
- file_paths_to_download = [Path(file_path_str) for file_path_str in source_list_str]
439
-
440
- if target is None:
441
- def collapse_to_home_dir(absolute_path: str, json_output_path: str) -> str:
442
- from pathlib import Path
443
- import json
444
- source_absolute_path = Path(absolute_path).expanduser().absolute()
445
- try:
446
- relative_to_home = source_absolute_path.relative_to(Path.home())
447
- collapsed_path_posix = (Path("~") / relative_to_home).as_posix()
448
- json_result_path = Path(json_output_path)
449
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
450
- json_result_path.write_text(json.dumps(collapsed_path_posix, indent=2), encoding="utf-8")
451
- print(json_result_path.as_posix())
452
- return collapsed_path_posix
453
- except ValueError:
454
- raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
455
-
456
-
457
-
458
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
459
- command = lambda_to_python_script(lmb=lambda: collapse_to_home_dir(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True, import_module=False)
460
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
461
- remote_json_path_dir = response.op.strip()
462
- if not remote_json_path_dir:
463
- raise RuntimeError("Could not resolve target path - no response from remote")
464
-
465
- local_json_dir = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
466
- self._simple_sftp_get(remote_path=remote_json_path_dir, local_path=local_json_dir)
467
- import json
468
- try:
469
- target_dir_str = json.loads(local_json_dir.read_text(encoding="utf-8"))
470
- except (json.JSONDecodeError, FileNotFoundError) as err:
471
- raise RuntimeError(f"Could not resolve target path - invalid JSON response: {err}") from err
472
- finally:
473
- if local_json_dir.exists():
474
- local_json_dir.unlink()
475
- assert isinstance(target_dir_str, str), "Could not resolve target path"
476
- target = Path(target_dir_str)
477
-
478
- target_dir = Path(target).expanduser().absolute()
479
-
480
- for idx, file_path in enumerate(file_paths_to_download):
481
- print(f" {idx + 1:03d}. {file_path}")
482
-
483
- for file_path in file_paths_to_download:
484
- local_file_target = target_dir.joinpath(Path(file_path).relative_to(expanded_source))
485
- self.copy_to_here(source=file_path, target=local_file_target, compress_with_zip=False, recursive=False, internal_call=True)
486
-
487
- return target_dir
488
-
489
- if compress_with_zip:
490
- print("🗜️ ZIPPING ...")
491
- def zip_source(path_to_zip: str, json_output_path: str) -> str:
492
- from pathlib import Path
493
- import shutil
494
- import json
495
- source_to_compress = Path(path_to_zip).expanduser().absolute()
496
- archive_base_path = source_to_compress.parent / (source_to_compress.name + "_archive")
497
- if source_to_compress.is_dir():
498
- shutil.make_archive(str(archive_base_path), "zip", source_to_compress)
499
- else:
500
- shutil.make_archive(str(archive_base_path), "zip", source_to_compress.parent, source_to_compress.name)
501
- zip_file_path = str(archive_base_path) + ".zip"
502
- json_result_path = Path(json_output_path)
503
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
504
- json_result_path.write_text(json.dumps(zip_file_path, indent=2), encoding="utf-8")
505
- print(json_result_path.as_posix())
506
- return zip_file_path
507
-
508
-
509
-
510
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
511
- command = lambda_to_python_script(lmb=lambda: zip_source(path_to_zip=expanded_source, json_output_path=remote_json_output), in_global=True, import_module=False)
512
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Zipping source file {source}", verbose_output=False, strict_stderr=False, strict_return_code=False)
513
- remote_json_path = response.op.strip()
514
- if not remote_json_path:
515
- raise RuntimeError(f"Could not zip {source} - no response from remote")
516
-
517
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
518
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
519
- import json
520
- try:
521
- zipped_path = json.loads(local_json.read_text(encoding="utf-8"))
522
- except (json.JSONDecodeError, FileNotFoundError) as err:
523
- raise RuntimeError(f"Could not zip {source} - invalid JSON response: {err}") from err
524
- finally:
525
- if local_json.exists():
526
- local_json.unlink()
527
- assert isinstance(zipped_path, str), f"Could not zip {source}"
528
- source_obj = Path(zipped_path)
529
- expanded_source = zipped_path
530
-
531
- if target is None:
532
- def collapse_to_home(absolute_path: str, json_output_path: str) -> str:
533
- from pathlib import Path
534
- import json
535
- source_absolute_path = Path(absolute_path).expanduser().absolute()
536
- try:
537
- relative_to_home = source_absolute_path.relative_to(Path.home())
538
- collapsed_path_posix = (Path("~") / relative_to_home).as_posix()
539
- json_result_path = Path(json_output_path)
540
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
541
- json_result_path.write_text(json.dumps(collapsed_path_posix, indent=2), encoding="utf-8")
542
- print(json_result_path.as_posix())
543
- return collapsed_path_posix
544
- except ValueError:
545
- raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
546
-
547
-
548
-
549
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
550
- command = lambda_to_python_script(lmb=lambda: collapse_to_home(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True, import_module=False)
551
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
552
- remote_json_path = response.op.strip()
553
- if not remote_json_path:
554
- raise RuntimeError("Could not resolve target path - no response from remote")
555
-
556
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
557
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
558
- import json
559
- try:
560
- target_str = json.loads(local_json.read_text(encoding="utf-8"))
561
- except (json.JSONDecodeError, FileNotFoundError) as err:
562
- raise RuntimeError(f"Could not resolve target path - invalid JSON response: {err}") from err
563
- finally:
564
- if local_json.exists():
565
- local_json.unlink()
566
- assert isinstance(target_str, str), "Could not resolve target path"
567
- target = Path(target_str)
568
- assert str(target).startswith("~"), f"If target is not specified, source must be relative to home.\n{target=}"
569
-
570
- target_obj = Path(target).expanduser().absolute()
571
- target_obj.parent.mkdir(parents=True, exist_ok=True)
572
-
573
- if compress_with_zip and target_obj.suffix != ".zip":
574
- target_obj = target_obj.with_suffix(target_obj.suffix + ".zip")
575
-
576
- print(f"""📥 [DOWNLOAD] Receiving: {expanded_source} ==> Local Path: {target_obj}""")
577
- try:
578
- with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
579
- if self.sftp is None: # type: ignore[unreachable]
580
- raise RuntimeError(f"SFTP connection lost for {self.hostname}")
581
- self.sftp.get(remotepath=expanded_source, localpath=str(target_obj), callback=pbar.view_bar) # type: ignore
582
- except Exception:
583
- if target_obj.exists():
584
- target_obj.unlink()
585
- raise
586
-
587
- if compress_with_zip:
588
- import zipfile
589
- extract_to = target_obj.parent / target_obj.stem
590
- with zipfile.ZipFile(target_obj, "r") as zip_ref:
591
- zip_ref.extractall(extract_to)
592
- target_obj.unlink()
593
- target_obj = extract_to
594
-
595
- def delete_temp_zip(path_to_delete: str) -> None:
596
- from pathlib import Path
597
- import shutil
598
- file_or_dir_path = Path(path_to_delete)
599
- if file_or_dir_path.exists():
600
- if file_or_dir_path.is_dir():
601
- shutil.rmtree(file_or_dir_path)
602
- else:
603
- file_or_dir_path.unlink()
604
-
605
-
606
- command = lambda_to_python_script(lmb=lambda: delete_temp_zip(path_to_delete=expanded_source), in_global=True, import_module=False)
607
- self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Cleaning temp zip files @ remote.", verbose_output=False, strict_stderr=True, strict_return_code=True)
608
-
609
- print("\n")
610
- return target_obj
349
+ from machineconfig.utils.ssh_utils.copy_from_here import copy_from_here
350
+ return copy_from_here(self, source_path=source_path, target_rel2home=target_rel2home, compress_with_zip=compress_with_zip, recursive=recursive, overwrite_existing=overwrite_existing)
351
+
352
+ def copy_to_here(self, source: Union[str, Path], target: Optional[Union[str, Path]], compress_with_zip: bool, recursive: bool, internal_call: bool = False) -> None:
353
+ from machineconfig.utils.ssh_utils.copy_to_here import copy_to_here
354
+ return copy_to_here(self, source=source, target=target, compress_with_zip=compress_with_zip, recursive=recursive, internal_call=internal_call)
355
+
356
+
357
+
611
358
 
612
359
  if __name__ == "__main__":
613
360
  ssh = SSH(host="p51s", username=None, hostname=None, ssh_key_path=None, password=None, port=22, enable_compression=False)