machineconfig 6.82__py3-none-any.whl → 8.51__py3-none-any.whl

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