machineconfig 6.23__py3-none-any.whl → 8.12__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.

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