machineconfig 8.14__py3-none-any.whl → 8.50__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/remote/run_cluster.py +1 -1
- machineconfig/cluster/remote/run_remote.py +1 -1
- machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
- machineconfig/cluster/sessions_managers/wt_local.py +1 -1
- machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
- 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 +225 -140
- machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
- machineconfig/jobs/installer/package_groups.py +10 -9
- machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
- machineconfig/jobs/installer/python_scripts/code.py +10 -8
- machineconfig/jobs/installer/python_scripts/hx.py +30 -13
- machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
- machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
- machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
- machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
- machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
- machineconfig/jobs/scripts_dynamic/a.py +413 -10
- machineconfig/profile/create_links.py +77 -20
- machineconfig/profile/create_links_export.py +63 -58
- machineconfig/profile/mapper_data.toml +30 -0
- machineconfig/profile/mapper_dotfiles.toml +253 -0
- machineconfig/scripts/python/agents.py +70 -172
- machineconfig/scripts/python/ai/initai.py +3 -1
- machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
- 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/github_copilot.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -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/{solutions → utils}/generic.py +2 -15
- machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
- machineconfig/scripts/python/cloud.py +58 -11
- machineconfig/scripts/python/croshell.py +4 -156
- machineconfig/scripts/python/devops.py +57 -40
- machineconfig/scripts/python/devops_navigator.py +17 -3
- machineconfig/scripts/python/fire_jobs.py +8 -207
- machineconfig/scripts/python/ftpx.py +5 -225
- 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/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
- machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
- 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 +7 -6
- machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -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 +76 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
- machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +274 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +40 -23
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
- machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
- 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 +3 -3
- 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 +3 -3
- machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
- 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/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
- machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
- 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/{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}/cloud_repo_sync.py +117 -33
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
- machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -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 +1 -1
- 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_utils → helpers/helpers_utils}/download.py +1 -1
- machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +47 -26
- machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
- machineconfig/scripts/python/mcfg_entry.py +133 -48
- machineconfig/scripts/python/msearch.py +15 -61
- machineconfig/scripts/python/sessions.py +59 -194
- machineconfig/scripts/python/terminal.py +18 -96
- machineconfig/scripts/python/utils.py +101 -20
- machineconfig/settings/atuin/config.toml +294 -0
- machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
- machineconfig/settings/linters/.ruff.toml +1 -0
- machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
- machineconfig/settings/shells/bash/init.sh +6 -3
- machineconfig/settings/shells/pwsh/init.ps1 +69 -1
- machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
- machineconfig/settings/shells/wt/settings.json +20 -7
- machineconfig/settings/shells/zsh/init.sh +25 -4
- machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
- machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
- 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/yazi/yazi_linux.toml +18 -8
- machineconfig/settings/zellij/layouts/st.kdl +2 -2
- machineconfig/settings/zellij/layouts/st2.kdl +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
- machineconfig/setup_mac/__init__.py +0 -2
- machineconfig/setup_windows/__init__.py +0 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
- machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
- 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 +24 -0
- machineconfig/utils/code.py +41 -13
- machineconfig/utils/files/ascii_art.py +10 -14
- machineconfig/utils/files/headers.py +3 -5
- machineconfig/utils/files/read.py +8 -1
- machineconfig/utils/installer_utils/github_release_bulk.py +11 -91
- machineconfig/utils/installer_utils/github_release_scraper.py +99 -0
- machineconfig/utils/installer_utils/install_from_url.py +1 -1
- machineconfig/utils/installer_utils/installer_class.py +12 -4
- machineconfig/utils/installer_utils/installer_cli.py +1 -15
- machineconfig/utils/installer_utils/installer_helper.py +2 -2
- machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
- machineconfig/utils/installer_utils/installer_runner.py +4 -4
- machineconfig/utils/io.py +25 -8
- machineconfig/utils/meta.py +6 -4
- machineconfig/utils/options.py +49 -19
- 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 +6 -6
- machineconfig/utils/scheduler.py +8 -2
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/source_of_truth.py +6 -1
- machineconfig/utils/ssh.py +69 -18
- machineconfig/utils/ssh_utils/abc.py +1 -1
- machineconfig/utils/ssh_utils/copy_from_here.py +17 -12
- machineconfig/utils/ssh_utils/utils.py +21 -5
- machineconfig/utils/ssh_utils/wsl.py +107 -170
- machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
- machineconfig/utils/upgrade_packages.py +4 -8
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/METADATA +29 -22
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/RECORD +251 -211
- machineconfig/jobs/installer/check_installations.py +0 -248
- machineconfig/profile/backup.toml +0 -49
- machineconfig/profile/mapper.toml +0 -263
- machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
- machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
- machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
- 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_navigator/__init__.py +0 -20
- machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
- machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
- machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
- machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
- machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
- machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
- machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
- machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
- machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
- machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
- machineconfig/utils/options_tv.py +0 -119
- machineconfig/utils/tst.py +0 -20
- /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
- /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
- /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → 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_croshell → helpers/helpers_agents}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.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/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_sessions → 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 → helpers/helpers_fire_command}/f.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
- /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
- /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
- /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
- /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
- /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/WHEEL +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/entry_points.txt +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
2
1
|
from typing import Optional, TypedDict, cast
|
|
3
2
|
|
|
3
|
+
|
|
4
4
|
class CountryFlag(TypedDict, total=False):
|
|
5
5
|
emoji: str
|
|
6
6
|
unicode: str
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
class CountryCurrency(TypedDict, total=False):
|
|
9
10
|
code: str
|
|
10
11
|
symbol: str
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
class Continent(TypedDict, total=False):
|
|
13
15
|
code: str
|
|
14
16
|
name: str
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
class PublicIpInfo(TypedDict, total=True):
|
|
17
20
|
ip: str
|
|
18
21
|
hostname: str
|
|
@@ -33,15 +36,11 @@ class PublicIpInfo(TypedDict, total=True):
|
|
|
33
36
|
def get_public_ip_address() -> PublicIpInfo:
|
|
34
37
|
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
35
38
|
import subprocess
|
|
39
|
+
|
|
36
40
|
install_if_missing("ipinfo")
|
|
37
|
-
result = subprocess.run(
|
|
38
|
-
["ipinfo", "myip", "--json"],
|
|
39
|
-
check=True,
|
|
40
|
-
capture_output=True,
|
|
41
|
-
text=True,
|
|
42
|
-
encoding="utf-8",
|
|
43
|
-
)
|
|
41
|
+
result = subprocess.run(["ipinfo", "myip", "--json"], check=True, capture_output=True, text=True, encoding="utf-8")
|
|
44
42
|
import json
|
|
43
|
+
|
|
45
44
|
loaded_json: PublicIpInfo = json.loads(result.stdout)
|
|
46
45
|
return loaded_json
|
|
47
46
|
|
|
@@ -49,6 +48,7 @@ def get_public_ip_address() -> PublicIpInfo:
|
|
|
49
48
|
def get_all_ipv4_addresses() -> list[tuple[str, str]]:
|
|
50
49
|
import psutil
|
|
51
50
|
import socket
|
|
51
|
+
|
|
52
52
|
result: list[tuple[str, str]] = []
|
|
53
53
|
for iface, addrs in psutil.net_if_addrs().items():
|
|
54
54
|
for addr in addrs:
|
|
@@ -82,19 +82,16 @@ def select_lan_ipv4(prefer_vpn: bool) -> Optional[str]:
|
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
# Light preference for names that look like real NICs
|
|
85
|
-
PHYSICAL_IFACE_PAT = re.compile(
|
|
86
|
-
r"^(?:eth\d*|en\d*|enp.*|ens.*|eno.*|wlan\d*|wl.*|.*wifi.*|.*ethernet.*)$",
|
|
87
|
-
re.IGNORECASE,
|
|
88
|
-
)
|
|
85
|
+
PHYSICAL_IFACE_PAT = re.compile(r"^(?:eth\d*|en\d*|enp.*|ens.*|eno.*|wlan\d*|wl.*|.*wifi.*|.*ethernet.*)$", re.IGNORECASE)
|
|
89
86
|
|
|
90
87
|
# Known noisy CIDRs to avoid
|
|
91
88
|
NOISY_NETS: list[ipaddress.IPv4Network] = [
|
|
92
|
-
ipaddress.IPv4Network("100.64.0.0/10"),
|
|
93
|
-
ipaddress.IPv4Network("172.17.0.0/16"),
|
|
89
|
+
ipaddress.IPv4Network("100.64.0.0/10"), # CGNAT (Tailscale/others)
|
|
90
|
+
ipaddress.IPv4Network("172.17.0.0/16"), # docker0 default
|
|
94
91
|
ipaddress.IPv4Network("172.18.0.0/16"),
|
|
95
92
|
ipaddress.IPv4Network("172.19.0.0/16"),
|
|
96
|
-
ipaddress.IPv4Network("192.168.49.0/24"),
|
|
97
|
-
ipaddress.IPv4Network("10.0.2.0/24"),
|
|
93
|
+
ipaddress.IPv4Network("192.168.49.0/24"), # minikube default
|
|
94
|
+
ipaddress.IPv4Network("10.0.2.0/24"), # VirtualBox NAT
|
|
98
95
|
]
|
|
99
96
|
|
|
100
97
|
def _in_any(ip: ipaddress.IPv4Address, nets: Sequence[ipaddress.IPv4Network]) -> bool:
|
|
@@ -102,8 +99,9 @@ def select_lan_ipv4(prefer_vpn: bool) -> Optional[str]:
|
|
|
102
99
|
|
|
103
100
|
stats = psutil.net_if_stats()
|
|
104
101
|
best = None
|
|
105
|
-
best_score = -10**9
|
|
102
|
+
best_score = -(10**9)
|
|
106
103
|
import socket
|
|
104
|
+
|
|
107
105
|
for iface, addrs in psutil.net_if_addrs().items():
|
|
108
106
|
st = stats.get(iface)
|
|
109
107
|
if not st or not st.isup:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import time
|
|
3
3
|
|
|
4
|
-
from machineconfig.scripts.python.helpers_network.address import get_public_ip_address
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers_network.address import get_public_ip_address
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def switch_public_ip_address(max_trials: int = 10, wait_seconds: float = 4.0) -> None:
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Pure Python implementation for ftpx command - no typer dependencies."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ftpx(source: str, target: str, recursive: bool, zipFirst: bool, cloud: bool, overwrite_existing: bool) -> None:
|
|
8
|
+
"""File transfer utility through SSH."""
|
|
9
|
+
if target == "wsl" or source == "wsl":
|
|
10
|
+
_handle_wsl_transfer(source=source, target=target, overwrite_existing=overwrite_existing)
|
|
11
|
+
return
|
|
12
|
+
elif source == "win" or target == "win":
|
|
13
|
+
_handle_win_transfer(source=source, target=target, overwrite_existing=overwrite_existing, windows_username=None)
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
console.print(
|
|
22
|
+
Panel(
|
|
23
|
+
"\n".join(["🚀 FTP File Transfer", "📋 Starting transfer process..."]),
|
|
24
|
+
title="Transfer Initialisation",
|
|
25
|
+
border_style="blue",
|
|
26
|
+
padding=(1, 2),
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
resolved_source, resolved_target, machine, source_is_remote = _resolve_paths(source=source, target=target)
|
|
31
|
+
|
|
32
|
+
from machineconfig.utils.accessories import pprint
|
|
33
|
+
|
|
34
|
+
pprint({"source": str(resolved_source), "target": str(resolved_target), "machine": machine}, "CLI Resolution")
|
|
35
|
+
|
|
36
|
+
ssh = _create_ssh_connection(machine=machine, console=console)
|
|
37
|
+
|
|
38
|
+
if cloud:
|
|
39
|
+
received_file = _handle_cloud_transfer(ssh=ssh, resolved_source=resolved_source, resolved_target=resolved_target, console=console)
|
|
40
|
+
else:
|
|
41
|
+
received_file = _handle_direct_transfer(
|
|
42
|
+
ssh=ssh,
|
|
43
|
+
resolved_source=resolved_source,
|
|
44
|
+
resolved_target=resolved_target,
|
|
45
|
+
source_is_remote=source_is_remote,
|
|
46
|
+
zipFirst=zipFirst,
|
|
47
|
+
recursive=recursive,
|
|
48
|
+
overwrite_existing=overwrite_existing,
|
|
49
|
+
console=console,
|
|
50
|
+
)
|
|
51
|
+
if source_is_remote and received_file is not None:
|
|
52
|
+
console.print(
|
|
53
|
+
Panel(
|
|
54
|
+
"\n".join(["📁 File Received", f"Parent: [cyan]{repr(received_file.parent)}[/cyan]", f"File: [cyan]{repr(received_file)}[/cyan]"]),
|
|
55
|
+
title="Transfer Result",
|
|
56
|
+
border_style="green",
|
|
57
|
+
padding=(1, 2),
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
console.print(Panel("File transfer process finished successfully", title="✅ Transfer Complete", border_style="green", padding=(1, 2)))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _handle_wsl_transfer(source: str, target: str, overwrite_existing: bool) -> None:
|
|
64
|
+
"""Handle WSL transfer when inside Windows."""
|
|
65
|
+
from machineconfig.utils.ssh_utils.wsl import copy_when_inside_windows
|
|
66
|
+
|
|
67
|
+
if target == "wsl":
|
|
68
|
+
target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
|
|
69
|
+
source_obj = target_obj
|
|
70
|
+
else:
|
|
71
|
+
source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
|
|
72
|
+
target_obj = source_obj
|
|
73
|
+
copy_when_inside_windows(source_obj, target_obj, overwrite_existing)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _handle_win_transfer(source: str, target: str, overwrite_existing: bool, windows_username: str | None) -> None:
|
|
77
|
+
"""Handle Windows transfer when inside WSL."""
|
|
78
|
+
from machineconfig.utils.ssh_utils.wsl import copy_when_inside_wsl
|
|
79
|
+
|
|
80
|
+
if source == "win":
|
|
81
|
+
source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
|
|
82
|
+
target_obj = source_obj
|
|
83
|
+
else:
|
|
84
|
+
target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
|
|
85
|
+
source_obj = target_obj
|
|
86
|
+
copy_when_inside_wsl(source_obj, target_obj, overwrite_existing, windows_username=windows_username)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _resolve_paths(source: str, target: str) -> tuple[Optional[str], Optional[str], str, bool]:
|
|
90
|
+
"""Resolve source and target paths, determine machine and direction."""
|
|
91
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
92
|
+
from machineconfig.scripts.python.helpers.helpers_cloud.helpers2 import ES
|
|
93
|
+
|
|
94
|
+
resolved_source: Optional[str] = None
|
|
95
|
+
resolved_target: Optional[str] = None
|
|
96
|
+
machine: str = ""
|
|
97
|
+
source_is_remote: bool = False
|
|
98
|
+
|
|
99
|
+
if ":" in source and (source[1] != ":" if len(source) > 1 else True):
|
|
100
|
+
source_is_remote = True
|
|
101
|
+
source_parts = source.split(":")
|
|
102
|
+
machine = source_parts[0]
|
|
103
|
+
if len(source_parts) > 1 and source_parts[1] == ES:
|
|
104
|
+
if target == ES:
|
|
105
|
+
raise ValueError(f"""
|
|
106
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
107
|
+
┃ ❌ Configuration Error
|
|
108
|
+
┃ Cannot use expand symbol `{ES}` in both source and target
|
|
109
|
+
┃ This creates a cyclical inference dependency
|
|
110
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
|
|
111
|
+
else:
|
|
112
|
+
target_path_obj = PathExtended(target).expanduser().absolute()
|
|
113
|
+
resolved_source = target_path_obj.collapseuser().as_posix()
|
|
114
|
+
resolved_target = target
|
|
115
|
+
else:
|
|
116
|
+
resolved_source = ":".join(source.split(":")[1:])
|
|
117
|
+
if target == ES:
|
|
118
|
+
resolved_target = None
|
|
119
|
+
else:
|
|
120
|
+
resolved_target = PathExtended(target).expanduser().absolute().as_posix()
|
|
121
|
+
|
|
122
|
+
elif ":" in target and (target[1] != ":" if len(target) > 1 else True):
|
|
123
|
+
source_is_remote = False
|
|
124
|
+
target_parts = target.split(":")
|
|
125
|
+
machine = target_parts[0]
|
|
126
|
+
if len(target_parts) > 1 and target_parts[1] == ES:
|
|
127
|
+
if source == ES:
|
|
128
|
+
raise ValueError(f"""
|
|
129
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
130
|
+
┃ ❌ Configuration Error
|
|
131
|
+
┃ Cannot use expand symbol `{ES}` in both source and target
|
|
132
|
+
┃ This creates a cyclical inference dependency
|
|
133
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
|
|
134
|
+
else:
|
|
135
|
+
resolved_source = source
|
|
136
|
+
resolved_target = None
|
|
137
|
+
else:
|
|
138
|
+
resolved_target = ":".join(target.split(":")[1:])
|
|
139
|
+
if source == ES:
|
|
140
|
+
resolved_source = None
|
|
141
|
+
else:
|
|
142
|
+
resolved_source = PathExtended(source).expanduser().absolute().as_posix()
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError("""
|
|
145
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
146
|
+
┃ ❌ Path Error
|
|
147
|
+
┃ Either source or target must be a remote path
|
|
148
|
+
┃ Format should be: machine:path
|
|
149
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
|
|
150
|
+
|
|
151
|
+
return resolved_source, resolved_target, machine, source_is_remote
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _create_ssh_connection(machine: str, console: "Console") -> "SSH": # type: ignore[name-defined]
|
|
155
|
+
"""Create SSH connection, handling authentication."""
|
|
156
|
+
from machineconfig.utils.ssh import SSH
|
|
157
|
+
from paramiko.ssh_exception import AuthenticationException # type: ignore
|
|
158
|
+
from rich.panel import Panel
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
ssh = SSH(host=rf"{machine}", username=None, hostname=None, ssh_key_path=None, password=None, port=22, enable_compression=True)
|
|
162
|
+
except AuthenticationException:
|
|
163
|
+
console.print(
|
|
164
|
+
Panel(
|
|
165
|
+
"\n".join(
|
|
166
|
+
[
|
|
167
|
+
"🔑 Authentication failed. Trying manual authentication...",
|
|
168
|
+
"⚠️ Ensure that the username is provided correctly; only password prompts are handled here.",
|
|
169
|
+
]
|
|
170
|
+
),
|
|
171
|
+
title="Authentication Required",
|
|
172
|
+
border_style="yellow",
|
|
173
|
+
padding=(1, 2),
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
import getpass
|
|
177
|
+
|
|
178
|
+
pwd = getpass.getpass()
|
|
179
|
+
ssh = SSH(host=rf"{machine}", username=None, hostname=None, ssh_key_path=None, password=pwd, port=22, enable_compression=True)
|
|
180
|
+
|
|
181
|
+
return ssh
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _handle_cloud_transfer(
|
|
185
|
+
ssh: "SSH", resolved_source: Optional[str], resolved_target: Optional[str], console: "Console"
|
|
186
|
+
) -> Optional["PathExtended"]: # type: ignore[name-defined]
|
|
187
|
+
"""Handle cloud transfer mode."""
|
|
188
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
189
|
+
from rich.panel import Panel
|
|
190
|
+
|
|
191
|
+
console.print(Panel.fit("☁️ Cloud transfer mode — uploading from remote to cloud...", title="Cloud Upload", border_style="cyan"))
|
|
192
|
+
ssh.run_shell_cmd_on_remote(
|
|
193
|
+
command=f"cloud copy {resolved_source} :^",
|
|
194
|
+
verbose_output=True,
|
|
195
|
+
description="Uploading from remote to the cloud.",
|
|
196
|
+
strict_stderr=False,
|
|
197
|
+
strict_return_code=False,
|
|
198
|
+
)
|
|
199
|
+
console.print(Panel.fit("⬇️ Cloud transfer mode — downloading from cloud to local...", title="Cloud Download", border_style="cyan"))
|
|
200
|
+
ssh.run_shell_cmd_on_local(command=f"cloud copy :^ {resolved_target}")
|
|
201
|
+
return PathExtended(resolved_target) # type: ignore
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _handle_direct_transfer(
|
|
205
|
+
ssh: "SSH",
|
|
206
|
+
resolved_source: Optional[str],
|
|
207
|
+
resolved_target: Optional[str],
|
|
208
|
+
source_is_remote: bool,
|
|
209
|
+
zipFirst: bool,
|
|
210
|
+
recursive: bool,
|
|
211
|
+
overwrite_existing: bool,
|
|
212
|
+
console: "Console",
|
|
213
|
+
) -> Optional["PathExtended"]: # type: ignore[name-defined]
|
|
214
|
+
"""Handle direct SSH transfer."""
|
|
215
|
+
from rich.panel import Panel
|
|
216
|
+
|
|
217
|
+
if source_is_remote:
|
|
218
|
+
if resolved_source is None:
|
|
219
|
+
print("❌ Path Error: Source must be a remote path (machine:path)")
|
|
220
|
+
return None
|
|
221
|
+
target_display = resolved_target or "<auto>"
|
|
222
|
+
console.print(
|
|
223
|
+
Panel(
|
|
224
|
+
"\n".join(
|
|
225
|
+
[
|
|
226
|
+
"📥 Transfer Mode: Remote → Local",
|
|
227
|
+
f"Source: [cyan]{resolved_source}[/cyan]",
|
|
228
|
+
f"Target: [cyan]{target_display}[/cyan]",
|
|
229
|
+
f"Options: {'ZIP compression' if zipFirst else 'No compression'}, {'Recursive' if recursive else 'Non-recursive'}",
|
|
230
|
+
]
|
|
231
|
+
),
|
|
232
|
+
title="Transfer Details",
|
|
233
|
+
border_style="cyan",
|
|
234
|
+
padding=(1, 2),
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
received_file = ssh.copy_to_here(source=resolved_source, target=resolved_target, compress_with_zip=zipFirst, recursive=recursive)
|
|
238
|
+
else:
|
|
239
|
+
assert resolved_source is not None, "❌ Path Error: Target must be a remote path (machine:path)"
|
|
240
|
+
target_display = resolved_target or "<auto>"
|
|
241
|
+
console.print(
|
|
242
|
+
Panel(
|
|
243
|
+
"\n".join(
|
|
244
|
+
[
|
|
245
|
+
"📤 Transfer Mode: Local → Remote",
|
|
246
|
+
f"Source: [cyan]{resolved_source}[/cyan]",
|
|
247
|
+
f"Target: [cyan]{target_display}[/cyan]",
|
|
248
|
+
f"Options: {'ZIP compression' if zipFirst else 'No compression'}, {'Recursive' if recursive else 'Non-recursive'}",
|
|
249
|
+
]
|
|
250
|
+
),
|
|
251
|
+
title="Transfer Details",
|
|
252
|
+
border_style="cyan",
|
|
253
|
+
padding=(1, 2),
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
received_file = ssh.copy_from_here(
|
|
257
|
+
source_path=resolved_source,
|
|
258
|
+
target_rel2home=resolved_target,
|
|
259
|
+
compress_with_zip=zipFirst,
|
|
260
|
+
recursive=recursive,
|
|
261
|
+
overwrite_existing=overwrite_existing,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return received_file
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if __name__ == "__main__":
|
|
268
|
+
from machineconfig.utils.ssh import SSH
|
|
269
|
+
|
|
270
|
+
_ = SSH
|
|
271
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
272
|
+
|
|
273
|
+
_ = PathExtended
|
|
274
|
+
from rich.console import Console
|
|
275
|
+
|
|
276
|
+
_ = Console
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from platform import system
|
|
4
4
|
import subprocess
|
|
5
5
|
from machineconfig.utils.ssh import SSH
|
|
6
|
-
from
|
|
6
|
+
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from machineconfig.utils.options import choose_ssh_host
|
|
9
9
|
|
|
@@ -35,7 +35,7 @@ def main():
|
|
|
35
35
|
|
|
36
36
|
mount_point = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_ssh/{ssh.hostname}]: ")
|
|
37
37
|
if mount_point == "":
|
|
38
|
-
mount_point =
|
|
38
|
+
mount_point = Path.home().joinpath(rf"data/mount_ssh/{ssh.hostname}")
|
|
39
39
|
|
|
40
40
|
print(f"\n📁 Mount Point: {mount_point}")
|
|
41
41
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
r"""ID
|
|
2
|
+
|
|
3
|
+
On windows:
|
|
4
|
+
|
|
5
|
+
$sshfile = "$env:USERPROFILE/.ssh/id_rsa"
|
|
6
|
+
Set-Service ssh-agent -StartupType Manual # allow the service to be started manually
|
|
7
|
+
ssh-agent # start the service
|
|
8
|
+
ssh-add.exe $sshfile # add the key to the agent
|
|
9
|
+
|
|
10
|
+
# copy ssh key:
|
|
11
|
+
# This is the Windows equivalent of copy-ssh-id on Linux.
|
|
12
|
+
# Just like the original function, it is a convenient way of doing two things in one go:
|
|
13
|
+
# 1- copy a certain public key to the remote machine.
|
|
14
|
+
# scp ~/.ssh/id_rsa.pub $remote_user@$remote_host:~/.ssh/authorized_keys
|
|
15
|
+
# 2- Store the value on the remote in a file called .ssh/authorized_keys
|
|
16
|
+
# ssh $remote_user@$remote_host "echo $public_key >> ~/.ssh/authorized_keys"
|
|
17
|
+
# Idea from: https://www.chrisjhart.com/Windows-10-ssh-copy-id/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
24
|
+
from machineconfig.utils.options import choose_from_options
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.panel import Panel
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> None:
|
|
33
|
+
private_keys = [PathExtended(x).with_name(x.stem) for x in PathExtended.home().joinpath(".ssh").glob("*.pub")]
|
|
34
|
+
key_status = f"Found {len(private_keys)} key(s)" if private_keys else "No keys found"
|
|
35
|
+
console.print(Panel(f"🔑 SSH Identity Management\n🔍 {key_status} in ~/.ssh", title="[bold blue]Setup[/bold blue]", expand=False))
|
|
36
|
+
|
|
37
|
+
choice = choose_from_options(msg="Path to private key to be used when ssh'ing: ", options=[str(x) for x in private_keys] + ["I have the path to the key file", "I want to paste the key itself"], multi=False)
|
|
38
|
+
|
|
39
|
+
if choice == "I have the path to the key file":
|
|
40
|
+
path_to_key = PathExtended(input("📋 Enter path to private key: ")).expanduser().absolute()
|
|
41
|
+
elif choice == "I want to paste the key itself":
|
|
42
|
+
key_filename = input("📝 File name (default: my_pasted_key): ") or "my_pasted_key"
|
|
43
|
+
path_to_key = PathExtended.home().joinpath(f".ssh/{key_filename}")
|
|
44
|
+
path_to_key.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
path_to_key.write_text(input("🔑 Paste the private key: "), encoding="utf-8")
|
|
46
|
+
else:
|
|
47
|
+
path_to_key = PathExtended(choice)
|
|
48
|
+
|
|
49
|
+
txt = f"IdentityFile {path_to_key.collapseuser().as_posix()}"
|
|
50
|
+
config_path = PathExtended.home().joinpath(".ssh/config")
|
|
51
|
+
|
|
52
|
+
if config_path.exists():
|
|
53
|
+
current = config_path.read_text(encoding="utf-8")
|
|
54
|
+
config_action = "updated"
|
|
55
|
+
else:
|
|
56
|
+
current = txt
|
|
57
|
+
config_action = "created"
|
|
58
|
+
lines = current.split("\n")
|
|
59
|
+
found = False
|
|
60
|
+
for i, line in enumerate(lines):
|
|
61
|
+
if txt in line:
|
|
62
|
+
lines[i] = txt
|
|
63
|
+
found = True
|
|
64
|
+
if not found:
|
|
65
|
+
lines.insert(0, txt)
|
|
66
|
+
config_path.write_text("\n".join(lines), encoding="utf-8")
|
|
67
|
+
|
|
68
|
+
console.print(Panel(f"✅ Identity: {path_to_key.name}\n📄 Config {config_action}: {config_path}", title="[bold green]Complete[/bold green]", expand=False, border_style="green"))
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
pass
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""SSH"""
|
|
2
|
+
|
|
3
|
+
from platform import system
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich import box
|
|
8
|
+
from typing import Optional, Annotated
|
|
9
|
+
import typer
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _add_ssh_key_windows(path_to_key: Path) -> None:
|
|
17
|
+
"""Add SSH key on Windows using Python with proper UTF-8 encoding.
|
|
18
|
+
This replaces the PowerShell script that was writing UTF-16LE encoded files which openssh server cannot read.
|
|
19
|
+
"""
|
|
20
|
+
sshd_dir = Path("C:/ProgramData/ssh")
|
|
21
|
+
admin_auth_keys = sshd_dir / "administrators_authorized_keys"
|
|
22
|
+
sshd_config = sshd_dir / "sshd_config"
|
|
23
|
+
key_content = path_to_key.read_text(encoding="utf-8").strip()
|
|
24
|
+
if admin_auth_keys.exists():
|
|
25
|
+
existing = admin_auth_keys.read_text(encoding="utf-8")
|
|
26
|
+
if not existing.endswith("\n"):
|
|
27
|
+
existing += "\n"
|
|
28
|
+
admin_auth_keys.write_text(existing + key_content + "\n", encoding="utf-8")
|
|
29
|
+
else:
|
|
30
|
+
admin_auth_keys.write_text(key_content + "\n", encoding="utf-8")
|
|
31
|
+
icacls_cmd = f'icacls "{admin_auth_keys}" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"'
|
|
32
|
+
subprocess.run(icacls_cmd, shell=True, check=True)
|
|
33
|
+
if sshd_config.exists():
|
|
34
|
+
config_text = sshd_config.read_text(encoding="utf-8")
|
|
35
|
+
config_text = config_text.replace("#PubkeyAuthentication", "PubkeyAuthentication")
|
|
36
|
+
sshd_config.write_text(config_text, encoding="utf-8")
|
|
37
|
+
subprocess.run("Restart-Service sshd -Force", shell=True, check=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_add_ssh_key_script(path_to_key: Path, verbose: bool = True) -> tuple[str, str]:
|
|
41
|
+
"""Returns (program_script, status_message) tuple. For Windows, program_script is empty because we handle it in Python."""
|
|
42
|
+
os_name = system()
|
|
43
|
+
if os_name == "Linux" or os_name == "Darwin":
|
|
44
|
+
authorized_keys = Path.home().joinpath(".ssh/authorized_keys")
|
|
45
|
+
os_icon, os_label = "🐧", "Linux/macOS"
|
|
46
|
+
elif os_name == "Windows":
|
|
47
|
+
authorized_keys = Path("C:/ProgramData/ssh/administrators_authorized_keys")
|
|
48
|
+
os_icon, os_label = "🪟", "Windows"
|
|
49
|
+
else:
|
|
50
|
+
raise NotImplementedError("Only Linux, macOS and Windows are supported")
|
|
51
|
+
|
|
52
|
+
status_lines: list[str] = [f"{os_icon} {os_label} │ Auth file: {authorized_keys}"]
|
|
53
|
+
program = ""
|
|
54
|
+
|
|
55
|
+
if authorized_keys.exists():
|
|
56
|
+
keys_text = authorized_keys.read_text(encoding="utf-8").split("\n")
|
|
57
|
+
key_count = len([k for k in keys_text if k.strip()])
|
|
58
|
+
status_lines.append(f"🔑 Existing keys: {key_count}")
|
|
59
|
+
if path_to_key.read_text(encoding="utf-8") in authorized_keys.read_text(encoding="utf-8"):
|
|
60
|
+
status_lines.append(f"⚠️ Key [yellow]{path_to_key.name}[/yellow] already authorized, skipping")
|
|
61
|
+
else:
|
|
62
|
+
status_lines.append(f"➕ Adding: [green]{path_to_key.name}[/green]")
|
|
63
|
+
if os_name == "Linux" or os_name == "Darwin":
|
|
64
|
+
program = f"cat {path_to_key} >> ~/.ssh/authorized_keys"
|
|
65
|
+
elif os_name == "Windows":
|
|
66
|
+
_add_ssh_key_windows(path_to_key)
|
|
67
|
+
else:
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
else:
|
|
70
|
+
status_lines.append(f"📝 Creating auth file with: [green]{path_to_key.name}[/green]")
|
|
71
|
+
if os_name == "Linux" or os_name == "Darwin":
|
|
72
|
+
program = f"cat {path_to_key} > ~/.ssh/authorized_keys"
|
|
73
|
+
else:
|
|
74
|
+
_add_ssh_key_windows(path_to_key)
|
|
75
|
+
|
|
76
|
+
if os_name == "Linux" or os_name == "Darwin":
|
|
77
|
+
program += """
|
|
78
|
+
sudo chmod 700 ~/.ssh
|
|
79
|
+
sudo chmod 644 ~/.ssh/authorized_keys
|
|
80
|
+
sudo chmod 644 ~/.ssh/*.pub
|
|
81
|
+
sudo service ssh --full-restart
|
|
82
|
+
# from superuser.com/questions/215504/permissions-on-private-key-in-ssh-folder
|
|
83
|
+
"""
|
|
84
|
+
return program, "\n".join(status_lines)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
Common pitfalls:
|
|
89
|
+
🚫 Wrong line endings (LF/CRLF) in config files
|
|
90
|
+
🌐 Network port conflicts (try 2222 -> 2223) between WSL and Windows
|
|
91
|
+
sudo service ssh restart
|
|
92
|
+
sudo service ssh status
|
|
93
|
+
sudo nano /etc/ssh/sshd_config
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main(pub_path: Annotated[Optional[str], typer.Argument(help="Path to the public key file")] = None,
|
|
98
|
+
pub_choose: Annotated[bool, typer.Option("--choose", "-c", help="Choose from available public keys in ~/.ssh")] = False,
|
|
99
|
+
pub_val: Annotated[bool, typer.Option("--paste", "-p", help="Paste the public key content manually")] = False,
|
|
100
|
+
from_github: Annotated[Optional[str], typer.Option("--from-github", "-g", help="Fetch public keys from a GitHub username")] = None
|
|
101
|
+
) -> None:
|
|
102
|
+
info_lines: list[str] = []
|
|
103
|
+
program = ""
|
|
104
|
+
status_msg = ""
|
|
105
|
+
|
|
106
|
+
if pub_path:
|
|
107
|
+
key_path = Path(pub_path).expanduser().absolute()
|
|
108
|
+
key_path.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
if not key_path.exists():
|
|
110
|
+
console.print(Panel(f"❌ Key path does not exist: {key_path}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
111
|
+
raise typer.Exit(code=1)
|
|
112
|
+
info_lines.append(f"📄 Source: Local file │ {key_path}")
|
|
113
|
+
program, status_msg = get_add_ssh_key_script(key_path)
|
|
114
|
+
|
|
115
|
+
elif pub_choose:
|
|
116
|
+
pub_keys = list(Path.home().joinpath(".ssh").glob("*.pub"))
|
|
117
|
+
if not pub_keys:
|
|
118
|
+
console.print(Panel("⚠️ No public keys found in ~/.ssh", title="[bold yellow]Warning[/bold yellow]", border_style="yellow"))
|
|
119
|
+
return
|
|
120
|
+
info_lines.append(f"📄 Source: Local ~/.ssh │ Found {len(pub_keys)} key(s)")
|
|
121
|
+
programs: list[str] = []
|
|
122
|
+
statuses: list[str] = []
|
|
123
|
+
for key in pub_keys:
|
|
124
|
+
p, s = get_add_ssh_key_script(key)
|
|
125
|
+
programs.append(p)
|
|
126
|
+
statuses.append(s)
|
|
127
|
+
program = "\n\n\n".join(programs)
|
|
128
|
+
status_msg = "\n".join(statuses)
|
|
129
|
+
|
|
130
|
+
elif pub_val:
|
|
131
|
+
key_filename = input("📝 File name (default: my_pasted_key.pub): ") or "my_pasted_key.pub"
|
|
132
|
+
key_path = Path.home().joinpath(f".ssh/{key_filename}")
|
|
133
|
+
key_path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
key_path.write_text(input("🔑 Paste the public key here: "), encoding="utf-8")
|
|
135
|
+
info_lines.append(f"📄 Source: Pasted │ Saved to {key_path}")
|
|
136
|
+
program, status_msg = get_add_ssh_key_script(key_path)
|
|
137
|
+
|
|
138
|
+
elif from_github:
|
|
139
|
+
import requests
|
|
140
|
+
response = requests.get(f"https://api.github.com/users/{from_github}/keys")
|
|
141
|
+
if response.status_code != 200:
|
|
142
|
+
console.print(Panel(f"❌ GitHub API error for user '{from_github}' │ Status: {response.status_code}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
keys = response.json()
|
|
145
|
+
if not keys:
|
|
146
|
+
console.print(Panel(f"⚠️ No public keys found for GitHub user: {from_github}", title="[bold yellow]Warning[/bold yellow]", border_style="yellow"))
|
|
147
|
+
return
|
|
148
|
+
key_path = Path.home().joinpath(f".ssh/{from_github}_github_keys.pub")
|
|
149
|
+
key_path.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
key_path.write_text("\n".join([key["key"] for key in keys]), encoding="utf-8")
|
|
151
|
+
info_lines.append(f"📄 Source: GitHub @{from_github} │ {len(keys)} key(s) → {key_path}")
|
|
152
|
+
program, status_msg = get_add_ssh_key_script(key_path)
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
console.print(Panel("❌ No key source specified. Use --help for options.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
156
|
+
raise typer.Exit(code=1)
|
|
157
|
+
|
|
158
|
+
combined_info = "\n".join(info_lines + [""] + status_msg.split("\n"))
|
|
159
|
+
console.print(Panel(combined_info, title="[bold blue]🔑 SSH Key Authorization[/bold blue]", border_style="blue"))
|
|
160
|
+
|
|
161
|
+
if program.strip():
|
|
162
|
+
from machineconfig.utils.code import run_shell_script
|
|
163
|
+
run_shell_script(script=program)
|
|
164
|
+
|
|
165
|
+
import machineconfig.scripts.python.helpers.helpers_network.address as helper
|
|
166
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
167
|
+
if res is None:
|
|
168
|
+
console.print(Panel("❌ Could not determine local LAN IPv4 address", title="[bold red]Error[/bold red]", border_style="red"))
|
|
169
|
+
raise typer.Exit(code=1)
|
|
170
|
+
|
|
171
|
+
console.print(Panel(f"✅ Complete │ This machine accessible at: [green]{res}[/green]", title="[bold green]SSH Key Authorization[/bold green]", border_style="green", box=box.DOUBLE_EDGE))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
pass
|