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.
- machineconfig/cluster/remote/cloud_manager.py +1 -1
- machineconfig/cluster/remote/run_cluster.py +1 -1
- machineconfig/cluster/remote/run_remote.py +1 -1
- machineconfig/cluster/sessions_managers/utils/maker.py +29 -15
- machineconfig/cluster/sessions_managers/wt_local.py +17 -222
- machineconfig/cluster/sessions_managers/wt_local_manager.py +56 -194
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +42 -198
- machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
- machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
- machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
- machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
- machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +4 -2
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +3 -2
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +2 -2
- machineconfig/jobs/installer/checks/check_installations.py +133 -0
- machineconfig/jobs/installer/checks/install_utils.py +132 -0
- machineconfig/jobs/installer/checks/report_utils.py +39 -0
- machineconfig/jobs/installer/checks/vt_utils.py +89 -0
- machineconfig/jobs/installer/installer_data.json +1500 -310
- machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
- machineconfig/jobs/installer/linux_scripts/q.sh +10 -7
- machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
- machineconfig/jobs/installer/package_groups.py +62 -91
- machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
- machineconfig/jobs/installer/{custom → python_scripts}/boxes.py +2 -3
- machineconfig/jobs/installer/{custom_dev → python_scripts}/brave.py +5 -3
- machineconfig/jobs/installer/python_scripts/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/code.py +14 -9
- machineconfig/jobs/installer/{custom_dev → python_scripts}/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/python_scripts/hx.py +214 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/nerdfont.py +2 -2
- machineconfig/jobs/installer/{custom_dev → python_scripts}/nerfont_windows_helper.py +32 -26
- machineconfig/jobs/installer/python_scripts/sysabc.py +145 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/wezterm.py +2 -19
- machineconfig/jobs/installer/{custom_dev → python_scripts}/winget.py +10 -14
- machineconfig/jobs/installer/python_scripts/yazi.py +139 -0
- machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_nfs +0 -1
- machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
- machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +13 -0
- machineconfig/jobs/scripts/powershell_scripts/obs.ps1 +4 -0
- machineconfig/jobs/scripts_dynamic/a.py +428 -0
- machineconfig/logger.py +1 -2
- machineconfig/profile/create_helper.py +56 -18
- machineconfig/profile/create_links.py +79 -21
- machineconfig/profile/create_links_export.py +87 -36
- machineconfig/profile/create_shell_profile.py +92 -127
- machineconfig/profile/mapper_data.toml +45 -0
- machineconfig/profile/mapper_dotfiles.toml +249 -0
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/linux/wrap_mcfg +46 -0
- machineconfig/scripts/nu/wrap_mcfg.nu +69 -0
- machineconfig/scripts/python/agents.py +85 -165
- machineconfig/scripts/python/ai/initai.py +4 -2
- machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
- machineconfig/scripts/python/ai/scripts/command_runner.ps1 +33 -0
- machineconfig/scripts/python/ai/{command_runner → scripts}/command_runner.sh +1 -1
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +8 -6
- machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
- machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Thinking-Beast-Mode.chatmode.md → agents/Thinking-Beast-Mode.agent.md} +0 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md → agents/Ultimate-Transparent-Thinking-Beast-Mode.agent.md} +0 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/deepResearch.chatmode.md → agents/deepResearch.agent.md} +2 -2
- machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +6 -6
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +33 -0
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/watch_exec.prompt.md +20 -0
- machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
- machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
- machineconfig/scripts/python/ai/{generate_files.py → utils/generate_files.py} +2 -2
- machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
- machineconfig/scripts/python/ai/{vscode_tasks.py → utils/vscode_tasks.py} +13 -5
- machineconfig/scripts/python/cloud.py +58 -11
- machineconfig/scripts/python/croshell.py +10 -162
- machineconfig/scripts/python/devops.py +73 -36
- machineconfig/scripts/python/devops_navigator.py +16 -6
- machineconfig/scripts/python/fire_jobs.py +8 -222
- machineconfig/scripts/python/ftpx.py +7 -200
- machineconfig/scripts/python/graph/cli_graph.json +8743 -0
- machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
- machineconfig/scripts/python/helpers/helper_env/env_manager_tui.py +204 -0
- machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +1 -1
- machineconfig/scripts/python/helpers/helpers_agents/agentic_frameworks/fire_crush.py +39 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +3 -4
- machineconfig/scripts/python/helpers/helpers_agents/agentic_frameworks/fire_gemini.py +55 -0
- machineconfig/scripts/python/helpers/helpers_agents/agentic_frameworks/fire_qwen.py +30 -0
- machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/fire_agents_help_launch.py +38 -16
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/fire_agents_helper_types.py +11 -14
- machineconfig/scripts/python/helpers/helpers_agents/privacy/configs/aichat/config.yaml +5 -0
- machineconfig/scripts/python/helpers/helpers_agents/privacy/configs/aider/.aider.conf.yml +2 -0
- machineconfig/scripts/python/helpers/helpers_agents/privacy/configs/copilot/config.yml +1 -0
- machineconfig/scripts/python/helpers/helpers_agents/privacy/configs/crush/crush.json +10 -0
- machineconfig/scripts/python/helpers/helpers_agents/privacy/configs/gemini/settings.json +12 -0
- machineconfig/scripts/python/helpers/helpers_agents/privacy/privacy.py +109 -0
- machineconfig/scripts/python/helpers/helpers_agents/templates/prompt.txt +10 -0
- machineconfig/scripts/python/helpers/helpers_agents/templates/template.sh +34 -0
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +32 -25
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +29 -22
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +9 -8
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +3 -3
- machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +12 -12
- machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +262 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +67 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_nw.py +201 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +274 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_self.py +197 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_share_file.py +151 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_share_server.py +125 -0
- machineconfig/scripts/python/{helpers_devops/cli_terminal.py → helpers/helpers_devops/cli_share_terminal.py} +26 -22
- machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +17 -23
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +78 -71
- machineconfig/scripts/python/helpers/helpers_devops/run_script.py +197 -0
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +4 -4
- machineconfig/scripts/python/{helpers_fire/helpers4.py → helpers/helpers_fire_command/file_wrangler.py} +57 -20
- machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +1 -0
- machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
- machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +26 -16
- machineconfig/scripts/python/helpers/helpers_msearch/__init__.py +5 -0
- machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
- machineconfig/scripts/{linux → python/helpers/helpers_msearch/scripts_linux}/fzfg +6 -5
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_windows/fzfg.ps1 +59 -0
- machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
- machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
- machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
- machineconfig/scripts/python/helpers/helpers_network/address.py +174 -0
- machineconfig/scripts/python/helpers/helpers_network/address_switch.py +78 -0
- machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
- machineconfig/scripts/python/{nw → helpers/helpers_network}/mount_nfs.py +2 -2
- machineconfig/scripts/python/{nw → helpers/helpers_network}/mount_ssh.py +3 -3
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
- machineconfig/scripts/python/{nw → helpers/helpers_network}/wifi_conn.py +1 -53
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -1
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +159 -48
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +4 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -12
- machineconfig/scripts/python/helpers/helpers_repos/repo_analyzer_1.py +160 -0
- machineconfig/scripts/python/{helpers_repos/count_lines.py → helpers/helpers_repos/repo_analyzer_2.py} +156 -191
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
- machineconfig/scripts/python/helpers/helpers_search/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/helpers_search/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/helpers_search/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
- machineconfig/scripts/python/helpers/helpers_search/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +186 -0
- machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +20 -14
- machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
- machineconfig/scripts/python/helpers/helpers_utils/download.py +150 -0
- machineconfig/scripts/python/helpers/helpers_utils/pdf.py +96 -0
- machineconfig/scripts/python/helpers/helpers_utils/python.py +210 -0
- machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
- machineconfig/scripts/python/mcfg_entry.py +143 -0
- machineconfig/scripts/python/msearch.py +26 -0
- machineconfig/scripts/python/sessions.py +69 -135
- machineconfig/scripts/python/terminal.py +58 -0
- machineconfig/scripts/python/utils.py +115 -38
- machineconfig/scripts/windows/wrap_mcfg.ps1 +63 -0
- machineconfig/settings/atuin/config.toml +294 -0
- machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
- machineconfig/settings/broot/conf.toml +1 -1
- machineconfig/settings/helix/config.toml +16 -0
- machineconfig/settings/helix/languages.toml +13 -4
- machineconfig/settings/helix/yazi-picker.sh +12 -0
- machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
- machineconfig/settings/lf/linux/exe/previewer.sh +3 -2
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/lf/windows/lfrc +14 -16
- machineconfig/settings/linters/.ruff.toml +2 -1
- machineconfig/settings/marimo/marimo.toml +1 -1
- machineconfig/settings/marimo/snippets/globalize.py +34 -0
- machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
- machineconfig/settings/shells/bash/init.sh +47 -12
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +1 -1
- machineconfig/settings/shells/nushell/config.nu +25 -33
- machineconfig/settings/shells/nushell/env.nu +21 -8
- machineconfig/settings/shells/nushell/init.nu +138 -0
- machineconfig/settings/shells/pwsh/init.ps1 +111 -17
- machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
- machineconfig/settings/shells/starship/starship.toml +16 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +6 -1
- machineconfig/settings/shells/wt/settings.json +27 -18
- machineconfig/settings/shells/zsh/init.sh +42 -23
- machineconfig/settings/television/cable_unix/alias.toml +8 -0
- machineconfig/settings/television/cable_unix/aws-buckets.toml +14 -0
- machineconfig/settings/television/cable_unix/aws-instances.toml +13 -0
- machineconfig/settings/television/cable_unix/bash-history.toml +8 -0
- machineconfig/settings/television/cable_unix/channels.toml +19 -0
- machineconfig/settings/television/cable_unix/dirs.toml +13 -0
- machineconfig/settings/television/cable_unix/distrobox-list.toml +42 -0
- machineconfig/settings/television/cable_unix/docker-images.toml +13 -0
- machineconfig/settings/television/cable_unix/dotfiles.toml +11 -0
- machineconfig/settings/television/cable_unix/env.toml +17 -0
- machineconfig/settings/television/cable_unix/files.toml +11 -0
- machineconfig/settings/television/cable_unix/fish-history.toml +8 -0
- machineconfig/settings/television/cable_unix/git-branch.toml +11 -0
- machineconfig/settings/television/cable_unix/git-diff.toml +10 -0
- machineconfig/settings/television/cable_unix/git-log.toml +12 -0
- machineconfig/settings/television/cable_unix/git-reflog.toml +12 -0
- machineconfig/settings/television/cable_unix/git-repos.toml +16 -0
- machineconfig/settings/television/cable_unix/guix.toml +20 -0
- machineconfig/settings/television/cable_unix/just-recipes.toml +18 -0
- machineconfig/settings/television/cable_unix/k8s-deployments.toml +36 -0
- machineconfig/settings/television/cable_unix/k8s-pods.toml +50 -0
- machineconfig/settings/television/cable_unix/k8s-services.toml +36 -0
- machineconfig/settings/television/cable_unix/man-pages.toml +24 -0
- machineconfig/settings/television/cable_unix/nu-history.toml +7 -0
- machineconfig/settings/television/cable_unix/procs.toml +20 -0
- machineconfig/settings/television/cable_unix/text.toml +17 -0
- machineconfig/settings/television/cable_unix/tldr.toml +18 -0
- machineconfig/settings/television/cable_unix/zsh-history.toml +9 -0
- machineconfig/settings/television/cable_windows/alias.toml +7 -0
- machineconfig/settings/television/cable_windows/dirs.toml +13 -0
- machineconfig/settings/television/cable_windows/docker-images.toml +13 -0
- machineconfig/settings/television/cable_windows/dotfiles.toml +11 -0
- machineconfig/settings/television/cable_windows/env.toml +17 -0
- machineconfig/settings/television/cable_windows/files.toml +14 -0
- machineconfig/settings/television/cable_windows/git-branch.toml +11 -0
- machineconfig/settings/television/cable_windows/git-diff.toml +10 -0
- machineconfig/settings/television/cable_windows/git-log.toml +11 -0
- machineconfig/settings/television/cable_windows/git-reflog.toml +11 -0
- machineconfig/settings/television/cable_windows/git-repos.toml +15 -0
- machineconfig/settings/television/cable_windows/nu-history.toml +7 -0
- machineconfig/settings/television/cable_windows/pwsh-history.toml +6 -0
- machineconfig/settings/television/cable_windows/text.toml +17 -0
- machineconfig/settings/tv/config.toml +234 -0
- machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
- machineconfig/settings/wsl/.wslconfig +5 -30
- machineconfig/settings/wt/__init__.py +0 -0
- machineconfig/settings/yazi/init.lua +61 -0
- machineconfig/settings/yazi/keymap_linux.toml +94 -0
- machineconfig/settings/yazi/keymap_windows.toml +78 -0
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +33 -0
- machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
- machineconfig/settings/yazi/theme.toml +4 -0
- machineconfig/settings/yazi/yazi_linux.toml +94 -0
- machineconfig/settings/yazi/yazi_windows.toml +58 -0
- machineconfig/settings/zellij/layouts/st.kdl +40 -9
- machineconfig/settings/zellij/layouts/st2.kdl +1 -1
- machineconfig/setup_linux/__init__.py +2 -2
- machineconfig/setup_linux/apps_desktop.sh +8 -27
- machineconfig/setup_linux/web_shortcuts/interactive.sh +27 -12
- machineconfig/setup_linux/web_shortcuts/live_from_github.sh +34 -0
- machineconfig/setup_mac/__init__.py +1 -4
- machineconfig/setup_mac/apps_gui.sh +248 -0
- machineconfig/setup_windows/__init__.py +2 -5
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +28 -12
- machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +31 -0
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +17 -0
- machineconfig/type_hinting/sql/__init__.py +1 -0
- machineconfig/type_hinting/sql/base.py +216 -0
- machineconfig/type_hinting/sql/core_schema.py +64 -0
- machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
- machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
- machineconfig/type_hinting/typedict/__init__.py +1 -0
- machineconfig/type_hinting/typedict/ast_utils.py +130 -0
- machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
- machineconfig/type_hinting/typedict/generators.py +231 -0
- machineconfig/type_hinting/typedict/polars_schema.py +24 -0
- machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
- machineconfig/utils/accessories.py +31 -4
- machineconfig/utils/code.py +163 -51
- machineconfig/utils/files/ascii_art.py +11 -15
- machineconfig/utils/files/headers.py +6 -7
- machineconfig/utils/files/read.py +8 -1
- machineconfig/utils/installer_utils/github_release_bulk.py +95 -138
- machineconfig/utils/installer_utils/github_release_scraper.py +99 -0
- machineconfig/utils/installer_utils/install_from_url.py +183 -0
- machineconfig/utils/installer_utils/installer_class.py +53 -102
- machineconfig/utils/installer_utils/installer_cli.py +161 -0
- machineconfig/utils/installer_utils/installer_helper.py +129 -0
- machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +42 -91
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +20 -65
- machineconfig/utils/io.py +94 -9
- machineconfig/utils/links.py +56 -38
- machineconfig/utils/meta.py +38 -21
- machineconfig/utils/options.py +81 -23
- machineconfig/utils/options_utils/__init__.py +0 -0
- machineconfig/utils/options_utils/options_tv_linux.py +211 -0
- machineconfig/utils/options_utils/options_tv_windows.py +88 -0
- machineconfig/utils/options_utils/tv_options.py +37 -0
- machineconfig/utils/path_extended.py +52 -102
- machineconfig/utils/path_helper.py +76 -23
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/scheduler.py +26 -53
- machineconfig/utils/scheduling.py +0 -2
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/source_of_truth.py +6 -1
- machineconfig/utils/ssh.py +216 -419
- machineconfig/utils/ssh_utils/abc.py +5 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +116 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +303 -0
- machineconfig/utils/ssh_utils/utils.py +158 -0
- machineconfig/utils/ssh_utils/wsl.py +147 -0
- machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
- machineconfig/utils/terminal.py +1 -0
- machineconfig/utils/upgrade_packages.py +107 -35
- machineconfig/utils/ve.py +12 -4
- machineconfig-8.51.dist-info/METADATA +140 -0
- machineconfig-8.51.dist-info/RECORD +543 -0
- {machineconfig-6.82.dist-info → machineconfig-8.51.dist-info}/entry_points.txt +4 -1
- machineconfig/jobs/installer/check_installations.py +0 -248
- machineconfig/jobs/installer/custom/hx.py +0 -140
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
- machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
- machineconfig/jobs/installer/powershell_scripts/openssh-server_add_key.ps1 +0 -7
- machineconfig/jobs/installer/powershell_scripts/openssh-server_copy-ssh-id.ps1 +0 -14
- machineconfig/profile/backup.toml +0 -49
- machineconfig/profile/mapper.toml +0 -256
- machineconfig/scripts/linux/fzf2g +0 -21
- machineconfig/scripts/linux/fzfag +0 -17
- machineconfig/scripts/linux/fzffg +0 -25
- machineconfig/scripts/linux/fzfrga +0 -21
- machineconfig/scripts/linux/mcfgs +0 -38
- machineconfig/scripts/linux/other/share_smb +0 -1
- machineconfig/scripts/linux/other/switch_ip +0 -20
- machineconfig/scripts/linux/skrg +0 -4
- machineconfig/scripts/linux/warp-cli.sh +0 -122
- machineconfig/scripts/linux/z_ls +0 -104
- machineconfig/scripts/python/ai/command_runner/prompt.txt +0 -9
- machineconfig/scripts/python/helpers_devops/cli_config.py +0 -120
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -77
- machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
- machineconfig/scripts/python/helpers_devops/cli_nw.py +0 -73
- machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -181
- machineconfig/scripts/python/helpers_devops/cli_self.py +0 -122
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +0 -104
- machineconfig/scripts/python/helpers_devops/cli_utils.py +0 -221
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
- machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
- machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_crush.py +0 -37
- machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_gemini.py +0 -44
- machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_qwen.py +0 -43
- machineconfig/scripts/python/helpers_fire/prompt.txt +0 -2
- machineconfig/scripts/python/helpers_fire/template.sh +0 -15
- machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
- machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
- machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -588
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +0 -17
- machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -76
- machineconfig/scripts/python/helpers_repos/secure_repo.py +0 -15
- machineconfig/scripts/python/mcfg.py +0 -48
- machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
- machineconfig/scripts/python/nw/devops_add_identity.py +0 -82
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +0 -134
- machineconfig/scripts/python/nw/ssh_debug_linux.py +0 -391
- machineconfig/scripts/python/nw/ssh_debug_windows.py +0 -338
- machineconfig/scripts/python/nw/wsl_windows_transfer.py +0 -66
- machineconfig/scripts/windows/fzfb.ps1 +0 -3
- machineconfig/scripts/windows/fzfg.ps1 +0 -2
- machineconfig/scripts/windows/fzfrga.bat +0 -20
- machineconfig/scripts/windows/mcfgs.ps1 +0 -17
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +0 -13
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig/settings/lf/windows/fzf_edit.ps1 +0 -6
- machineconfig/settings/lf/windows/tst.ps1 +0 -1
- machineconfig/settings/yazi/yazi.toml +0 -4
- machineconfig/setup_linux/apps.sh +0 -66
- machineconfig/setup_linux/others/cli_installation.sh +0 -137
- machineconfig/setup_linux/others/mint_keyboard_shortcuts.sh +0 -30
- machineconfig/setup_linux/ssh/openssh_all.sh +0 -25
- machineconfig/setup_linux/ssh/openssh_wsl.sh +0 -38
- machineconfig/setup_mac/apps.sh +0 -73
- machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
- machineconfig/setup_windows/apps.ps1 +0 -62
- machineconfig/setup_windows/others/obs.ps1 +0 -4
- machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
- machineconfig/setup_windows/ssh/add_identity.ps1 +0 -11
- machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
- machineconfig/utils/installer_utils/installer.py +0 -225
- machineconfig/utils/tst.py +0 -20
- machineconfig-6.82.dist-info/METADATA +0 -82
- machineconfig-6.82.dist-info/RECORD +0 -441
- machineconfig/jobs/installer/{custom_dev → checks}/__init__.py +0 -0
- machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- machineconfig/{scripts/python/helpers_cloud → jobs/installer/python_scripts}/__init__.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/alacritty.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/bypass_paywall.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/cursor.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/espanso.py +0 -0
- machineconfig/jobs/installer/{custom → python_scripts}/gh.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/goes.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/lvim.py +0 -0
- machineconfig/jobs/installer/{custom_dev → python_scripts}/redis.py +0 -0
- machineconfig/{setup_linux/others → jobs/scripts/bash_scripts}/android.sh +0 -0
- machineconfig/jobs/{installer/linux_scripts → scripts/bash_scripts}/lid.sh +0 -0
- machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_drive +0 -0
- machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_nw_drive +0 -0
- machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_smb +0 -0
- machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/share_cloud.sh +0 -0
- machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/share_nfs +0 -0
- machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/start_docker +0 -0
- machineconfig/{scripts → jobs/scripts/powershell_scripts}/Restore-ThunderbirdProfile.ps1 +0 -0
- machineconfig/{setup_windows/others → jobs/scripts/powershell_scripts}/docker.ps1 +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_nfs.ps1 +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_nw.ps1 +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_smb.ps1 +0 -0
- machineconfig/{setup_windows/others → jobs/scripts/powershell_scripts}/power_options.ps1 +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/share_cloud.cmd +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/share_smb.ps1 +0 -0
- machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/unlock_bitlocker.ps1 +0 -0
- machineconfig/scripts/python/{helpers_croshell → ai/utils}/__init__.py +0 -0
- machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
- machineconfig/scripts/python/{helpers_devops → graph}/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_devops/themes → helpers}/__init__.py +0 -0
- machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
- machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/agentic_frameworks/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
- machineconfig/scripts/python/{helpers_fire → helpers/helpers_agents/templates}/template.ps1 +0 -0
- machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_cloud}/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +1 -1
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
- /machineconfig/scripts/python/{nw → helpers/helpers_devops}/__init__.py +0 -0
- /machineconfig/{setup_windows/wt_and_pwsh → scripts/python/helpers/helpers_devops/themes}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command/fire_jobs_streamlit_helper.py → helpers/helpers_fire_command/f.py} +0 -0
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers/helpers_fire_command/fire_jobs_streamlit_helper.py} +0 -0
- /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
- /machineconfig/{settings/yazi/keymap.toml → scripts/python/helpers/helpers_network/__init__.py} +0 -0
- /machineconfig/scripts/python/{nw → helpers/helpers_network}/mount_nw_drive.py +0 -0
- /machineconfig/scripts/python/{nw → helpers/helpers_network}/onetimeshare.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
- /machineconfig/{setup_windows/wt_and_pwsh → settings/wt}/set_wt_settings.py +0 -0
- {machineconfig-6.82.dist-info → machineconfig-8.51.dist-info}/WHEEL +0 -0
- {machineconfig-6.82.dist-info → machineconfig-8.51.dist-info}/top_level.txt +0 -0
machineconfig/utils/ssh.py
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
from typing import Callable, Optional, Any, Union,
|
|
1
|
+
from typing import Callable, Optional, Any, cast, Union, Literal
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import platform
|
|
5
|
-
from machineconfig.scripts.python.
|
|
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
|
-
|
|
11
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
139
|
-
|
|
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:<
|
|
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.
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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}" + (
|
|
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
|
|
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
|
|
182
|
-
print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.local_specs[
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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(
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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)
|