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