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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
|
|
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.table import Table
|
|
8
|
+
from rich import box
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _run(cmd: list[str]) -> tuple[bool, str]:
|
|
18
|
+
try:
|
|
19
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
20
|
+
return result.returncode == 0, result.stdout.strip()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
return False, ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _check_sshd_installed() -> tuple[bool, str]:
|
|
26
|
+
sshd_paths = ["/usr/sbin/sshd", "/usr/bin/sshd", "/sbin/sshd"]
|
|
27
|
+
for path in sshd_paths:
|
|
28
|
+
if Path(path).exists():
|
|
29
|
+
return True, path
|
|
30
|
+
ok, which_out = _run(["which", "sshd"])
|
|
31
|
+
if ok and which_out:
|
|
32
|
+
return True, which_out
|
|
33
|
+
return False, ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _detect_package_manager() -> tuple[str, str]:
|
|
37
|
+
if Path("/usr/bin/apt").exists() or Path("/usr/bin/apt-get").exists():
|
|
38
|
+
return "apt", "sudo apt update && sudo apt install -y openssh-server"
|
|
39
|
+
if Path("/usr/bin/dnf").exists():
|
|
40
|
+
return "dnf", "sudo dnf install -y openssh-server"
|
|
41
|
+
if Path("/usr/bin/yum").exists():
|
|
42
|
+
return "yum", "sudo yum install -y openssh-server"
|
|
43
|
+
if Path("/usr/bin/pacman").exists():
|
|
44
|
+
return "pacman", "sudo pacman -S --noconfirm openssh"
|
|
45
|
+
if Path("/usr/bin/zypper").exists():
|
|
46
|
+
return "zypper", "sudo zypper install -y openssh"
|
|
47
|
+
return "unknown", "# Install openssh-server using your package manager"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ssh_debug_linux() -> dict[str, dict[str, str | bool]]:
|
|
51
|
+
if system() != "Linux":
|
|
52
|
+
raise NotImplementedError("ssh_debug_linux is only supported on Linux")
|
|
53
|
+
|
|
54
|
+
results: dict[str, dict[str, str | bool]] = {}
|
|
55
|
+
issues: list[tuple[str, str, str]] = []
|
|
56
|
+
current_user = os.environ.get("USER", os.environ.get("USERNAME", "unknown"))
|
|
57
|
+
ssh_port = "22"
|
|
58
|
+
ip_addresses: list[str] = []
|
|
59
|
+
|
|
60
|
+
ok, hostname = _run(["hostname"])
|
|
61
|
+
hostname = hostname if ok else "unknown"
|
|
62
|
+
|
|
63
|
+
install_info: list[str] = []
|
|
64
|
+
sshd_installed, sshd_path = _check_sshd_installed()
|
|
65
|
+
_pkg_manager, install_cmd = _detect_package_manager()
|
|
66
|
+
if not sshd_installed:
|
|
67
|
+
results["installation"] = {"status": "error", "message": "OpenSSH Server not installed"}
|
|
68
|
+
issues.append(("sshd not installed", "Cannot accept incoming SSH connections", install_cmd))
|
|
69
|
+
install_info.append("❌ OpenSSH Server: [red]NOT INSTALLED[/red]")
|
|
70
|
+
install_info.append(f" [dim]Install with: {install_cmd}[/dim]")
|
|
71
|
+
else:
|
|
72
|
+
results["installation"] = {"status": "ok", "message": f"sshd found at {sshd_path}"}
|
|
73
|
+
install_info.append(f"✅ OpenSSH Server: installed at [cyan]{sshd_path}[/cyan]")
|
|
74
|
+
console.print(Panel("\n".join(install_info), title="[bold]Installation[/bold]", border_style="blue"))
|
|
75
|
+
|
|
76
|
+
ssh_dir = Path.home().joinpath(".ssh")
|
|
77
|
+
authorized_keys = ssh_dir.joinpath("authorized_keys")
|
|
78
|
+
home_dir = Path.home()
|
|
79
|
+
|
|
80
|
+
perm_info: list[str] = []
|
|
81
|
+
home_stat = os.stat(home_dir)
|
|
82
|
+
home_perms = oct(home_stat.st_mode)[-3:]
|
|
83
|
+
if home_perms[2] in ["7", "6", "3", "2"]:
|
|
84
|
+
results["home_directory"] = {"status": "error", "message": f"Home world-writable: {home_perms}"}
|
|
85
|
+
issues.append((f"Home dir perms {home_perms}", "sshd refuses login if home is world-writable", f"chmod 755 {home_dir}"))
|
|
86
|
+
perm_info.append(f"❌ Home directory: [red]{home_perms}[/red] (world-writable)")
|
|
87
|
+
perm_info.append(" [dim]sshd will refuse key auth if home is writable by others[/dim]")
|
|
88
|
+
else:
|
|
89
|
+
perm_info.append(f"✅ Home directory: {home_perms}")
|
|
90
|
+
|
|
91
|
+
if not ssh_dir.exists():
|
|
92
|
+
results["ssh_directory"] = {"status": "error", "message": "~/.ssh missing"}
|
|
93
|
+
issues.append(("~/.ssh missing", "No place for authorized_keys", "mkdir -p ~/.ssh && chmod 700 ~/.ssh"))
|
|
94
|
+
perm_info.append("❌ ~/.ssh: [red]does not exist[/red]")
|
|
95
|
+
else:
|
|
96
|
+
ssh_perms = oct(os.stat(ssh_dir).st_mode)[-3:]
|
|
97
|
+
if ssh_perms != "700":
|
|
98
|
+
results["ssh_directory"] = {"status": "error", "message": f"~/.ssh perms {ssh_perms}"}
|
|
99
|
+
issues.append((f"~/.ssh perms {ssh_perms}", "sshd requires 700 on ~/.ssh", "chmod 700 ~/.ssh"))
|
|
100
|
+
perm_info.append(f"❌ ~/.ssh: [red]{ssh_perms}[/red] (must be 700)")
|
|
101
|
+
else:
|
|
102
|
+
perm_info.append(f"✅ ~/.ssh: {ssh_perms}")
|
|
103
|
+
|
|
104
|
+
if not authorized_keys.exists():
|
|
105
|
+
results["authorized_keys"] = {"status": "error", "message": "authorized_keys missing"}
|
|
106
|
+
issues.append(("authorized_keys missing", "No keys = no login", "Add public key: cat id_rsa.pub >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"))
|
|
107
|
+
perm_info.append("❌ authorized_keys: [red]does not exist[/red]")
|
|
108
|
+
perm_info.append(" [dim]No authorized keys = cannot login with SSH key[/dim]")
|
|
109
|
+
else:
|
|
110
|
+
ak_perms = oct(os.stat(authorized_keys).st_mode)[-3:]
|
|
111
|
+
try:
|
|
112
|
+
keys = [line for line in authorized_keys.read_text(encoding="utf-8").split("\n") if line.strip()]
|
|
113
|
+
key_count = len(keys)
|
|
114
|
+
except Exception:
|
|
115
|
+
key_count = 0
|
|
116
|
+
if ak_perms not in ["600", "644"]:
|
|
117
|
+
results["authorized_keys"] = {"status": "error", "message": f"authorized_keys perms {ak_perms}"}
|
|
118
|
+
issues.append((f"authorized_keys perms {ak_perms}", "sshd requires 600 or 644", "chmod 600 ~/.ssh/authorized_keys"))
|
|
119
|
+
perm_info.append(f"❌ authorized_keys: [red]{ak_perms}[/red] ({key_count} key(s)) - must be 600/644")
|
|
120
|
+
else:
|
|
121
|
+
results["authorized_keys"] = {"status": "ok", "message": f"{key_count} key(s)"}
|
|
122
|
+
perm_info.append(f"✅ authorized_keys: {ak_perms} ([green]{key_count} key(s)[/green])")
|
|
123
|
+
|
|
124
|
+
console.print(Panel("\n".join(perm_info), title="[bold]Permissions[/bold]", border_style="blue"))
|
|
125
|
+
|
|
126
|
+
svc_info: list[str] = []
|
|
127
|
+
ssh_ok, _ = _run(["systemctl", "is-active", "ssh"])
|
|
128
|
+
sshd_ok, _ = _run(["systemctl", "is-active", "sshd"])
|
|
129
|
+
if ssh_ok or sshd_ok:
|
|
130
|
+
svc_name = "ssh" if ssh_ok else "sshd"
|
|
131
|
+
results["ssh_service"] = {"status": "ok", "message": f"{svc_name} running"}
|
|
132
|
+
svc_info.append(f"✅ Service: [green]{svc_name} running[/green]")
|
|
133
|
+
else:
|
|
134
|
+
results["ssh_service"] = {"status": "error", "message": "sshd not running"}
|
|
135
|
+
issues.append(("sshd not running", "No SSH daemon = no connections", "sudo systemctl start ssh && sudo systemctl enable ssh"))
|
|
136
|
+
svc_info.append("❌ Service: [red]not running[/red]")
|
|
137
|
+
|
|
138
|
+
console.print(Panel("\n".join(svc_info), title="[bold]Service[/bold]", border_style="blue"))
|
|
139
|
+
|
|
140
|
+
net_info: list[str] = []
|
|
141
|
+
ok, ip_out = _run(["ip", "addr", "show"])
|
|
142
|
+
if ok:
|
|
143
|
+
ip_addresses = re.findall(r'inet\s+(\d+\.\d+\.\d+\.\d+)/\d+.*scope\s+global', ip_out)
|
|
144
|
+
if ip_addresses:
|
|
145
|
+
net_info.append(f"🌐 IP: [cyan]{', '.join(ip_addresses)}[/cyan]")
|
|
146
|
+
|
|
147
|
+
sshd_config_paths = [Path("/etc/ssh/sshd_config"), Path("/etc/sshd_config")]
|
|
148
|
+
sshd_config: Path | None = None
|
|
149
|
+
for p in sshd_config_paths:
|
|
150
|
+
if p.exists():
|
|
151
|
+
sshd_config = p
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if sshd_config:
|
|
155
|
+
try:
|
|
156
|
+
config_text = sshd_config.read_text(encoding="utf-8")
|
|
157
|
+
port_lines = [line for line in config_text.split("\n") if line.strip().startswith("Port") and not line.strip().startswith("#")]
|
|
158
|
+
if port_lines:
|
|
159
|
+
ssh_port = port_lines[0].split()[1]
|
|
160
|
+
net_info.append(f"🔌 Port: [cyan]{ssh_port}[/cyan]")
|
|
161
|
+
|
|
162
|
+
pubkey_lines = [line for line in config_text.split("\n") if "PubkeyAuthentication" in line and not line.strip().startswith("#")]
|
|
163
|
+
if pubkey_lines and "no" in pubkey_lines[-1].lower():
|
|
164
|
+
results["pubkey_auth"] = {"status": "error", "message": "PubkeyAuthentication disabled"}
|
|
165
|
+
issues.append(("PubkeyAuthentication disabled", "Key-based login won't work", f"Edit {sshd_config}: set PubkeyAuthentication yes, then sudo systemctl restart ssh"))
|
|
166
|
+
net_info.append("❌ PubkeyAuthentication: [red]disabled[/red]")
|
|
167
|
+
else:
|
|
168
|
+
net_info.append("✅ PubkeyAuthentication: enabled")
|
|
169
|
+
|
|
170
|
+
password_lines = [line for line in config_text.split("\n") if "PasswordAuthentication" in line and not line.strip().startswith("#")]
|
|
171
|
+
if password_lines:
|
|
172
|
+
password_enabled = "yes" in password_lines[-1].lower()
|
|
173
|
+
if password_enabled:
|
|
174
|
+
results["password_auth"] = {"status": "ok", "message": "PasswordAuthentication enabled"}
|
|
175
|
+
net_info.append("✅ PasswordAuthentication: [green]enabled[/green]")
|
|
176
|
+
else:
|
|
177
|
+
results["password_auth"] = {"status": "info", "message": "PasswordAuthentication disabled"}
|
|
178
|
+
net_info.append("ℹ️ PasswordAuthentication: [yellow]disabled[/yellow] (key-only)")
|
|
179
|
+
else:
|
|
180
|
+
results["password_auth"] = {"status": "ok", "message": "PasswordAuthentication enabled (default)"}
|
|
181
|
+
net_info.append("✅ PasswordAuthentication: [green]enabled[/green] (default)")
|
|
182
|
+
|
|
183
|
+
permit_root = [line for line in config_text.split("\n") if "PermitRootLogin" in line and not line.strip().startswith("#")]
|
|
184
|
+
if permit_root:
|
|
185
|
+
val = permit_root[-1].split()[-1].lower()
|
|
186
|
+
net_info.append(f"ℹ️ PermitRootLogin: {val}")
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
ok, ss_out = _run(["ss", "-tlnp"])
|
|
191
|
+
if ok:
|
|
192
|
+
listening = [line for line in ss_out.split("\n") if f":{ssh_port}" in line]
|
|
193
|
+
if not listening:
|
|
194
|
+
results["ssh_listening"] = {"status": "error", "message": f"Not listening on {ssh_port}"}
|
|
195
|
+
issues.append((f"Not listening on port {ssh_port}", "No connections possible", "sudo systemctl restart ssh"))
|
|
196
|
+
net_info.append(f"❌ Listening: [red]NOT on port {ssh_port}[/red]")
|
|
197
|
+
elif all("127.0.0.1" in line or "[::1]" in line for line in listening):
|
|
198
|
+
results["ssh_listening"] = {"status": "error", "message": "Localhost only"}
|
|
199
|
+
issues.append(("SSH bound to localhost", "Only local connections", f"Edit {sshd_config}: remove/comment ListenAddress 127.0.0.1"))
|
|
200
|
+
net_info.append("❌ Listening: [red]localhost only[/red]")
|
|
201
|
+
else:
|
|
202
|
+
results["ssh_listening"] = {"status": "ok", "message": f"Listening on {ssh_port}"}
|
|
203
|
+
net_info.append(f"✅ Listening: 0.0.0.0:{ssh_port}")
|
|
204
|
+
|
|
205
|
+
fw_checked = False
|
|
206
|
+
ok, ufw_out = _run(["ufw", "status"])
|
|
207
|
+
if ok and "Status: active" in ufw_out:
|
|
208
|
+
fw_checked = True
|
|
209
|
+
if f"{ssh_port}/tcp" in ufw_out.lower() or "ssh" in ufw_out.lower() or f" {ssh_port} " in ufw_out:
|
|
210
|
+
results["firewall"] = {"status": "ok", "message": "UFW allows SSH"}
|
|
211
|
+
net_info.append("✅ Firewall (UFW): allows SSH")
|
|
212
|
+
else:
|
|
213
|
+
results["firewall"] = {"status": "error", "message": "UFW blocking SSH"}
|
|
214
|
+
issues.append(("UFW blocking SSH", "Incoming connections dropped", f"sudo ufw allow {ssh_port}/tcp"))
|
|
215
|
+
net_info.append("❌ Firewall (UFW): [red]blocking SSH[/red]")
|
|
216
|
+
net_info.append(" [dim]Active firewall without SSH rule = blocked[/dim]")
|
|
217
|
+
|
|
218
|
+
if not fw_checked:
|
|
219
|
+
ok, fwd_out = _run(["firewall-cmd", "--state"])
|
|
220
|
+
if ok and "running" in fwd_out.lower():
|
|
221
|
+
fw_checked = True
|
|
222
|
+
ok2, svc_out = _run(["firewall-cmd", "--list-services"])
|
|
223
|
+
if ok2 and "ssh" in svc_out.lower():
|
|
224
|
+
results["firewall"] = {"status": "ok", "message": "firewalld allows SSH"}
|
|
225
|
+
net_info.append("✅ Firewall (firewalld): allows SSH")
|
|
226
|
+
else:
|
|
227
|
+
results["firewall"] = {"status": "error", "message": "firewalld blocking SSH"}
|
|
228
|
+
issues.append(("firewalld blocking SSH", "Incoming connections dropped", "sudo firewall-cmd --permanent --add-service=ssh && sudo firewall-cmd --reload"))
|
|
229
|
+
net_info.append("❌ Firewall (firewalld): [red]blocking SSH[/red]")
|
|
230
|
+
|
|
231
|
+
if not fw_checked:
|
|
232
|
+
ok, ipt_out = _run(["iptables", "-L", "INPUT", "-n"])
|
|
233
|
+
if ok and ipt_out:
|
|
234
|
+
has_drop_policy = "policy DROP" in ipt_out or "policy REJECT" in ipt_out
|
|
235
|
+
has_ssh_allow = f"dpt:{ssh_port}" in ipt_out or "dpt:ssh" in ipt_out
|
|
236
|
+
if has_drop_policy and not has_ssh_allow:
|
|
237
|
+
results["firewall"] = {"status": "error", "message": "iptables blocking SSH"}
|
|
238
|
+
issues.append(("iptables blocking SSH", "DROP/REJECT policy without SSH allow", f"sudo iptables -I INPUT -p tcp --dport {ssh_port} -j ACCEPT"))
|
|
239
|
+
net_info.append("❌ Firewall (iptables): [red]DROP policy, no SSH rule[/red]")
|
|
240
|
+
fw_checked = True
|
|
241
|
+
elif has_drop_policy and has_ssh_allow:
|
|
242
|
+
results["firewall"] = {"status": "ok", "message": "iptables allows SSH"}
|
|
243
|
+
net_info.append("✅ Firewall (iptables): allows SSH")
|
|
244
|
+
fw_checked = True
|
|
245
|
+
|
|
246
|
+
if not fw_checked:
|
|
247
|
+
net_info.append("ℹ️ Firewall: none detected / not active")
|
|
248
|
+
|
|
249
|
+
console.print(Panel("\n".join(net_info), title="[bold]Network & Firewall[/bold]", border_style="blue"))
|
|
250
|
+
|
|
251
|
+
other_info: list[str] = []
|
|
252
|
+
hosts_deny = Path("/etc/hosts.deny")
|
|
253
|
+
if hosts_deny.exists():
|
|
254
|
+
try:
|
|
255
|
+
content = hosts_deny.read_text(encoding="utf-8")
|
|
256
|
+
active = [line for line in content.splitlines() if line.strip() and not line.strip().startswith("#")]
|
|
257
|
+
joined = " ".join(active).lower()
|
|
258
|
+
if "sshd" in joined or "all" in joined:
|
|
259
|
+
results["hosts_deny"] = {"status": "error", "message": "hosts.deny blocking"}
|
|
260
|
+
issues.append(("hosts.deny blocking SSH", "TCP wrappers deny before firewall", "Edit /etc/hosts.deny to remove sshd/ALL entries"))
|
|
261
|
+
other_info.append("❌ /etc/hosts.deny: [red]may block SSH[/red]")
|
|
262
|
+
else:
|
|
263
|
+
other_info.append("✅ /etc/hosts.deny: OK")
|
|
264
|
+
except Exception:
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
ok, se_out = _run(["getenforce"])
|
|
268
|
+
if ok and se_out:
|
|
269
|
+
if se_out == "Enforcing":
|
|
270
|
+
other_info.append("ℹ️ SELinux: Enforcing (run [cyan]restorecon -Rv ~/.ssh[/cyan] if issues)")
|
|
271
|
+
else:
|
|
272
|
+
other_info.append(f"ℹ️ SELinux: {se_out}")
|
|
273
|
+
|
|
274
|
+
log_files = [Path("/var/log/auth.log"), Path("/var/log/secure")]
|
|
275
|
+
for lf in log_files:
|
|
276
|
+
if lf.exists():
|
|
277
|
+
ok, tail = _run(["tail", "-n", "20", str(lf)])
|
|
278
|
+
if ok:
|
|
279
|
+
errors = [line for line in tail.split("\n") if any(k in line.lower() for k in ["error", "failed", "refused", "denied"]) and "ssh" in line.lower()]
|
|
280
|
+
if errors:
|
|
281
|
+
other_info.append(f"⚠️ Recent SSH errors in {lf.name}: {len(errors)}")
|
|
282
|
+
else:
|
|
283
|
+
other_info.append(f"✅ {lf.name}: no recent SSH errors")
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
if other_info:
|
|
287
|
+
console.print(Panel("\n".join(other_info), title="[bold]Additional[/bold]", border_style="blue"))
|
|
288
|
+
|
|
289
|
+
if issues:
|
|
290
|
+
fix_table = Table(title="Issues & Fixes", box=box.ROUNDED, show_lines=True, title_style="bold red")
|
|
291
|
+
fix_table.add_column("Issue", style="yellow", width=25)
|
|
292
|
+
fix_table.add_column("Impact", style="white", width=35)
|
|
293
|
+
fix_table.add_column("Fix Command", style="green", width=55)
|
|
294
|
+
for issue, impact, fix in issues:
|
|
295
|
+
fix_table.add_row(issue, impact, fix)
|
|
296
|
+
console.print(fix_table)
|
|
297
|
+
|
|
298
|
+
fix_script_path = Path("/tmp/ssh_fix.sh")
|
|
299
|
+
script_lines = ["#!/bin/bash", "set -e", "", "# SSH Fix Script - Generated by ssh_debug_linux", f"# {len(issues)} issue(s) to fix", ""]
|
|
300
|
+
for issue, _impact, fix in issues:
|
|
301
|
+
script_lines.append(f"# Fix: {issue}")
|
|
302
|
+
script_lines.append(fix)
|
|
303
|
+
script_lines.append("")
|
|
304
|
+
script_lines.append("echo 'All fixes applied. Re-run ssh_debug_linux to verify.'")
|
|
305
|
+
fix_script_path.write_text("\n".join(script_lines), encoding="utf-8")
|
|
306
|
+
fix_script_path.chmod(0o755)
|
|
307
|
+
|
|
308
|
+
console.print(Panel(f"[bold yellow]⚠️ {len(issues)} issue(s) found[/bold yellow]\n\nFix script generated: [cyan]{fix_script_path}[/cyan]\nRun: [green]sudo bash {fix_script_path}[/green]", title="[bold]Summary[/bold]", border_style="yellow"))
|
|
309
|
+
else:
|
|
310
|
+
conn_info = f"👤 {current_user} 🖥️ {hostname} 🔌 :{ssh_port}"
|
|
311
|
+
if ip_addresses:
|
|
312
|
+
conn_info += f"\n\n[bold]Connect:[/bold] ssh {current_user}@{ip_addresses[0]}"
|
|
313
|
+
console.print(Panel(f"[bold green]✅ All checks passed[/bold green]\n\n{conn_info}", title="[bold]Ready[/bold]", border_style="green"))
|
|
314
|
+
|
|
315
|
+
return results
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
ssh_debug_linux()
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
|
|
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.table import Table
|
|
8
|
+
from rich import box
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _run_ps(cmd: str) -> tuple[bool, str]:
|
|
17
|
+
result = subprocess.run(["powershell", "-Command", cmd], capture_output=True, text=True, check=False)
|
|
18
|
+
return result.returncode == 0, result.stdout.strip()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _check_sshd_binary_exists() -> tuple[bool, str]:
|
|
22
|
+
sshd_locations = [
|
|
23
|
+
Path("C:/Windows/System32/OpenSSH/sshd.exe"),
|
|
24
|
+
Path("C:/Program Files/OpenSSH/sshd.exe"),
|
|
25
|
+
Path("C:/Program Files (x86)/OpenSSH/sshd.exe"),
|
|
26
|
+
]
|
|
27
|
+
for loc in sshd_locations:
|
|
28
|
+
if loc.exists():
|
|
29
|
+
return True, str(loc)
|
|
30
|
+
ok, which_out = _run_ps("Get-Command sshd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source")
|
|
31
|
+
if ok and which_out:
|
|
32
|
+
return True, which_out
|
|
33
|
+
return False, ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _detect_openssh() -> tuple[str, Path | None, Path | None]:
|
|
37
|
+
capability_sshd = Path("C:/Windows/System32/OpenSSH/sshd.exe")
|
|
38
|
+
winget_sshd = Path("C:/Program Files/OpenSSH/sshd.exe")
|
|
39
|
+
programdata_config = Path("C:/ProgramData/ssh")
|
|
40
|
+
capability_config = Path("C:/ProgramData/ssh")
|
|
41
|
+
if capability_sshd.exists():
|
|
42
|
+
return ("capability", capability_sshd, capability_config)
|
|
43
|
+
if winget_sshd.exists():
|
|
44
|
+
return ("winget", winget_sshd, programdata_config)
|
|
45
|
+
return ("not_found", None, None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ssh_debug_windows() -> dict[str, dict[str, str | bool]]:
|
|
49
|
+
if system() != "Windows":
|
|
50
|
+
raise NotImplementedError("ssh_debug_windows is only supported on Windows")
|
|
51
|
+
|
|
52
|
+
results: dict[str, dict[str, str | bool]] = {}
|
|
53
|
+
issues: list[tuple[str, str, str]] = []
|
|
54
|
+
current_user = os.environ.get("USERNAME", "unknown")
|
|
55
|
+
ssh_port = "22"
|
|
56
|
+
ip_addresses: list[str] = []
|
|
57
|
+
|
|
58
|
+
sshd_exists, sshd_path = _check_sshd_binary_exists()
|
|
59
|
+
install_type, _sshd_exe, config_dir = _detect_openssh()
|
|
60
|
+
ok, hostname = _run_ps("hostname")
|
|
61
|
+
hostname = hostname if ok else "unknown"
|
|
62
|
+
|
|
63
|
+
install_info: list[str] = []
|
|
64
|
+
if not sshd_exists:
|
|
65
|
+
results["installation"] = {"status": "error", "message": "sshd.exe not found on system"}
|
|
66
|
+
issues.append(("sshd.exe not found", "OpenSSH Server binary missing entirely", "Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0"))
|
|
67
|
+
install_info.append("❌ sshd.exe: [red]NOT FOUND[/red]")
|
|
68
|
+
install_info.append(" [dim]OpenSSH Server is not installed on this system[/dim]")
|
|
69
|
+
elif install_type == "not_found":
|
|
70
|
+
results["installation"] = {"status": "warning", "message": f"sshd found at {sshd_path} but not in standard location"}
|
|
71
|
+
install_info.append(f"⚠️ sshd.exe: found at [yellow]{sshd_path}[/yellow]")
|
|
72
|
+
install_info.append(" [dim]Non-standard location - may need manual configuration[/dim]")
|
|
73
|
+
else:
|
|
74
|
+
results["installation"] = {"status": "ok", "message": f"OpenSSH installed ({install_type})"}
|
|
75
|
+
install_info.append(f"✅ OpenSSH Server: installed via {'Windows Capability' if install_type == 'capability' else 'winget'}")
|
|
76
|
+
install_info.append(f" Binary: {sshd_path}")
|
|
77
|
+
install_info.append(f" Config: {config_dir}")
|
|
78
|
+
|
|
79
|
+
ok, status = _run_ps("Get-Service -Name sshd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Status")
|
|
80
|
+
if not ok or not status:
|
|
81
|
+
results["ssh_service"] = {"status": "error", "message": "sshd service not found"}
|
|
82
|
+
issues.append(("sshd service missing", "SSH daemon not installed", "Install OpenSSH Server first"))
|
|
83
|
+
install_info.append("❌ sshd service: [red]NOT FOUND[/red]")
|
|
84
|
+
elif status != "Running":
|
|
85
|
+
results["ssh_service"] = {"status": "error", "message": f"sshd is {status}"}
|
|
86
|
+
issues.append((f"sshd is {status}", "SSH connections will be refused", "Start-Service sshd ; Set-Service sshd -StartupType Automatic"))
|
|
87
|
+
install_info.append(f"❌ sshd service: [yellow]{status}[/yellow]")
|
|
88
|
+
else:
|
|
89
|
+
results["ssh_service"] = {"status": "ok", "message": "sshd running"}
|
|
90
|
+
ok, startup = _run_ps("Get-Service -Name sshd | Select-Object -ExpandProperty StartType")
|
|
91
|
+
startup_note = f" (startup: {startup})" if ok else ""
|
|
92
|
+
install_info.append(f"✅ sshd service: [green]Running[/green]{startup_note}")
|
|
93
|
+
|
|
94
|
+
console.print(Panel("\n".join(install_info), title="[bold]Installation & Service[/bold]", border_style="blue"))
|
|
95
|
+
|
|
96
|
+
ssh_dir = Path.home().joinpath(".ssh")
|
|
97
|
+
authorized_keys = ssh_dir.joinpath("authorized_keys")
|
|
98
|
+
admin_auth_keys = Path("C:/ProgramData/ssh/administrators_authorized_keys")
|
|
99
|
+
perm_info: list[str] = []
|
|
100
|
+
|
|
101
|
+
ok, is_admin_str = _run_ps("([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)")
|
|
102
|
+
is_admin = "True" in is_admin_str
|
|
103
|
+
|
|
104
|
+
if is_admin:
|
|
105
|
+
perm_info.append(f"👤 User [cyan]{current_user}[/cyan] is an [yellow]Administrator[/yellow]")
|
|
106
|
+
perm_info.append(" ➜ Keys must be in: [cyan]C:\\ProgramData\\ssh\\administrators_authorized_keys[/cyan]")
|
|
107
|
+
target_auth_keys = admin_auth_keys
|
|
108
|
+
else:
|
|
109
|
+
perm_info.append(f"👤 User [cyan]{current_user}[/cyan] is a standard user")
|
|
110
|
+
perm_info.append(f" ➜ Keys should be in: [cyan]{authorized_keys}[/cyan]")
|
|
111
|
+
target_auth_keys = authorized_keys
|
|
112
|
+
|
|
113
|
+
if not target_auth_keys.exists():
|
|
114
|
+
results["authorized_keys"] = {"status": "error", "message": f"{target_auth_keys.name} missing"}
|
|
115
|
+
issues.append((f"{target_auth_keys.name} missing", "No public keys authorized - SSH login will fail", f"Create file and add your public key to {target_auth_keys}"))
|
|
116
|
+
perm_info.append(f"\n❌ [red]{target_auth_keys.name} does not exist[/red]")
|
|
117
|
+
perm_info.append(" [dim]No keys = no login. Add your public key to this file.[/dim]")
|
|
118
|
+
else:
|
|
119
|
+
try:
|
|
120
|
+
keys = [line for line in target_auth_keys.read_text(encoding="utf-8").split("\n") if line.strip()]
|
|
121
|
+
results["authorized_keys"] = {"status": "ok", "message": f"{len(keys)} key(s)"}
|
|
122
|
+
perm_info.append(f"\n✅ {target_auth_keys.name}: [green]{len(keys)} key(s)[/green]")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
perm_info.append(f"\n⚠️ Could not read {target_auth_keys.name}: {e}")
|
|
125
|
+
|
|
126
|
+
if is_admin and admin_auth_keys.exists():
|
|
127
|
+
ok, icacls_out = _run_ps(f'icacls "{admin_auth_keys}"')
|
|
128
|
+
if ok:
|
|
129
|
+
needs_fix = "BUILTIN\\Users" in icacls_out or "Everyone" in icacls_out
|
|
130
|
+
if needs_fix:
|
|
131
|
+
results["admin_keys_perms"] = {"status": "error", "message": "Permissions too open"}
|
|
132
|
+
issues.append(("administrators_authorized_keys permissions wrong", "sshd ignores file if permissions allow other users", f'icacls "{admin_auth_keys}" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"'))
|
|
133
|
+
perm_info.append("❌ [red]Permissions too open[/red] - sshd will ignore this file!")
|
|
134
|
+
else:
|
|
135
|
+
perm_info.append("✅ Permissions: restricted to Administrators/SYSTEM")
|
|
136
|
+
|
|
137
|
+
console.print(Panel("\n".join(perm_info), title="[bold]Keys & Permissions[/bold]", border_style="blue"))
|
|
138
|
+
|
|
139
|
+
net_info: list[str] = []
|
|
140
|
+
ok, ip_out = _run_ps("Get-NetIPAddress -AddressFamily IPv4 -PrefixOrigin Dhcp,Manual | Where-Object {$_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*'} | Select-Object -ExpandProperty IPAddress")
|
|
141
|
+
if ok and ip_out:
|
|
142
|
+
ip_addresses = [ip.strip() for ip in ip_out.split("\n") if ip.strip()]
|
|
143
|
+
net_info.append(f"🌐 IP addresses: [cyan]{', '.join(ip_addresses)}[/cyan]")
|
|
144
|
+
|
|
145
|
+
sshd_config: Path | None = config_dir.joinpath("sshd_config") if config_dir else None
|
|
146
|
+
config_text: str | None = None
|
|
147
|
+
if sshd_config and sshd_config.exists():
|
|
148
|
+
try:
|
|
149
|
+
config_text = sshd_config.read_text(encoding="utf-8")
|
|
150
|
+
except PermissionError:
|
|
151
|
+
ok, config_text_ps = _run_ps(f'Get-Content "{sshd_config}" -Raw')
|
|
152
|
+
config_text = config_text_ps if ok and config_text_ps else None
|
|
153
|
+
except Exception:
|
|
154
|
+
config_text = None
|
|
155
|
+
if config_text:
|
|
156
|
+
port_lines = [line for line in config_text.split("\n") if line.strip().startswith("Port") and not line.strip().startswith("#")]
|
|
157
|
+
if port_lines:
|
|
158
|
+
ssh_port = port_lines[0].split()[1]
|
|
159
|
+
|
|
160
|
+
auth_info: list[str] = []
|
|
161
|
+
auth_info.append(f"📄 Config file: [cyan]{sshd_config}[/cyan]" if sshd_config else "📄 Config file: [red]not found[/red]")
|
|
162
|
+
if config_text:
|
|
163
|
+
pubkey_lines = [line for line in config_text.split("\n") if "PubkeyAuthentication" in line and not line.strip().startswith("#")]
|
|
164
|
+
if pubkey_lines and "no" in pubkey_lines[-1].lower():
|
|
165
|
+
results["pubkey_auth"] = {"status": "error", "message": "PubkeyAuthentication disabled"}
|
|
166
|
+
issues.append(("PubkeyAuthentication disabled", "Key-based login won't work", f'Edit {sshd_config} and set PubkeyAuthentication yes, then Restart-Service sshd'))
|
|
167
|
+
auth_info.append("❌ PubkeyAuthentication: [red]disabled[/red]")
|
|
168
|
+
else:
|
|
169
|
+
results["pubkey_auth"] = {"status": "ok", "message": "PubkeyAuthentication enabled (default)"}
|
|
170
|
+
auth_info.append("✅ PubkeyAuthentication: [green]enabled[/green] (default: yes)")
|
|
171
|
+
|
|
172
|
+
password_lines = [line for line in config_text.split("\n") if "PasswordAuthentication" in line and not line.strip().startswith("#")]
|
|
173
|
+
if password_lines:
|
|
174
|
+
password_enabled = "yes" in password_lines[-1].lower()
|
|
175
|
+
if password_enabled:
|
|
176
|
+
results["password_auth"] = {"status": "ok", "message": "PasswordAuthentication enabled"}
|
|
177
|
+
auth_info.append("✅ PasswordAuthentication: [green]enabled[/green]")
|
|
178
|
+
else:
|
|
179
|
+
results["password_auth"] = {"status": "info", "message": "PasswordAuthentication disabled"}
|
|
180
|
+
auth_info.append("🔐 PasswordAuthentication: [yellow]disabled[/yellow] (key-only)")
|
|
181
|
+
else:
|
|
182
|
+
results["password_auth"] = {"status": "ok", "message": "PasswordAuthentication enabled (default)"}
|
|
183
|
+
auth_info.append("✅ PasswordAuthentication: [green]enabled[/green] (default: yes)")
|
|
184
|
+
else:
|
|
185
|
+
auth_info.append("⚠️ Could not read sshd_config - auth settings unknown")
|
|
186
|
+
results["pubkey_auth"] = {"status": "unknown", "message": "Could not read config"}
|
|
187
|
+
results["password_auth"] = {"status": "unknown", "message": "Could not read config"}
|
|
188
|
+
|
|
189
|
+
console.print(Panel("\n".join(auth_info), title="[bold]Authentication Settings[/bold]", border_style="blue"))
|
|
190
|
+
|
|
191
|
+
net_info.append(f"🔌 SSH port: [cyan]{ssh_port}[/cyan]")
|
|
192
|
+
netstat = subprocess.run(["netstat", "-an"], capture_output=True, text=True, check=False)
|
|
193
|
+
if netstat.returncode == 0:
|
|
194
|
+
listening_lines = [line for line in netstat.stdout.split("\n") if f":{ssh_port}" in line and "LISTENING" in line]
|
|
195
|
+
if not listening_lines:
|
|
196
|
+
results["ssh_listening"] = {"status": "error", "message": f"Not listening on port {ssh_port}"}
|
|
197
|
+
issues.append((f"SSH not listening on port {ssh_port}", "No connections possible", "Restart-Service sshd"))
|
|
198
|
+
net_info.append(f"❌ Listening: [red]NOT listening on port {ssh_port}[/red]")
|
|
199
|
+
elif all("127.0.0.1" in line or "[::1]" in line for line in listening_lines):
|
|
200
|
+
results["ssh_listening"] = {"status": "error", "message": "Listening on localhost only"}
|
|
201
|
+
issues.append(("SSH bound to localhost only", "Only local connections work", f"Check ListenAddress in {sshd_config}"))
|
|
202
|
+
net_info.append("❌ Listening: [red]localhost only[/red] (remote connections blocked)")
|
|
203
|
+
else:
|
|
204
|
+
results["ssh_listening"] = {"status": "ok", "message": f"Listening on port {ssh_port}"}
|
|
205
|
+
net_info.append(f"✅ Listening: 0.0.0.0:{ssh_port}")
|
|
206
|
+
|
|
207
|
+
fw_cmd = f"""
|
|
208
|
+
$rules = Get-NetFirewallRule -ErrorAction SilentlyContinue | Where-Object {{
|
|
209
|
+
($_.DisplayName -like '*SSH*' -or $_.DisplayName -like '*OpenSSH*' -or $_.Name -like '*SSH*' -or $_.Name -like '*sshd*') -and
|
|
210
|
+
$_.Direction -eq 'Inbound'
|
|
211
|
+
}}
|
|
212
|
+
if (-not $rules) {{
|
|
213
|
+
$portFilter = Get-NetFirewallPortFilter -ErrorAction SilentlyContinue | Where-Object {{ $_.LocalPort -eq '{ssh_port}' -and $_.Protocol -eq 'TCP' }}
|
|
214
|
+
if ($portFilter) {{
|
|
215
|
+
$rules = $portFilter | ForEach-Object {{ Get-NetFirewallRule -AssociatedNetFirewallPortFilter $_ -ErrorAction SilentlyContinue }} | Where-Object {{ $_.Direction -eq 'Inbound' }}
|
|
216
|
+
}}
|
|
217
|
+
}}
|
|
218
|
+
if ($rules) {{
|
|
219
|
+
$rules | Select-Object Name, DisplayName, Enabled, Action | Format-List
|
|
220
|
+
}}
|
|
221
|
+
"""
|
|
222
|
+
ok, fw_out = _run_ps(fw_cmd)
|
|
223
|
+
if ok and fw_out.strip():
|
|
224
|
+
has_allow = "Enabled : True" in fw_out and "Action : Allow" in fw_out
|
|
225
|
+
if has_allow:
|
|
226
|
+
results["firewall"] = {"status": "ok", "message": "Firewall allows SSH"}
|
|
227
|
+
net_info.append("✅ Firewall: SSH rule exists and enabled")
|
|
228
|
+
else:
|
|
229
|
+
results["firewall"] = {"status": "warning", "message": "Firewall rule exists but may not be active"}
|
|
230
|
+
issues.append(("Firewall rule not active", "Incoming SSH may be blocked", f'New-NetFirewallRule -Name "SSH" -DisplayName "SSH" -Protocol TCP -LocalPort {ssh_port} -Action Allow -Enabled True'))
|
|
231
|
+
net_info.append("⚠️ Firewall: SSH rule exists but [yellow]not enabled[/yellow]")
|
|
232
|
+
else:
|
|
233
|
+
if not is_admin:
|
|
234
|
+
results["firewall"] = {"status": "warning", "message": "Cannot verify firewall (run as Admin)"}
|
|
235
|
+
net_info.append("⚠️ Firewall: [yellow]Cannot verify - run script as Administrator[/yellow]")
|
|
236
|
+
net_info.append(" [dim]Firewall rules may exist but require elevation to query.[/dim]")
|
|
237
|
+
else:
|
|
238
|
+
results["firewall"] = {"status": "error", "message": "No SSH firewall rule"}
|
|
239
|
+
issues.append(("No SSH firewall rule", "Windows Firewall blocks incoming SSH by default", f'New-NetFirewallRule -Name "SSH" -DisplayName "SSH" -Protocol TCP -LocalPort {ssh_port} -Action Allow -Enabled True'))
|
|
240
|
+
net_info.append("❌ Firewall: [red]No SSH rule found[/red]")
|
|
241
|
+
net_info.append(" [dim]Windows blocks all incoming by default. Must create allow rule.[/dim]")
|
|
242
|
+
|
|
243
|
+
console.print(Panel("\n".join(net_info), title="[bold]Network & Firewall[/bold]", border_style="blue"))
|
|
244
|
+
|
|
245
|
+
if issues:
|
|
246
|
+
fix_table = Table(title="Issues & Fixes", box=box.ROUNDED, show_lines=True, title_style="bold red")
|
|
247
|
+
fix_table.add_column("Issue", style="yellow", width=30)
|
|
248
|
+
fix_table.add_column("Impact", style="white", width=35)
|
|
249
|
+
fix_table.add_column("Fix Command", style="green", width=60)
|
|
250
|
+
for issue, impact, fix in issues:
|
|
251
|
+
fix_table.add_row(issue, impact, fix)
|
|
252
|
+
console.print(fix_table)
|
|
253
|
+
|
|
254
|
+
fix_script_path = Path(os.environ.get("TEMP", "C:/Temp")).joinpath("ssh_fix.ps1")
|
|
255
|
+
script_lines = ["# SSH Fix Script - Generated by ssh_debug_windows", f"# {len(issues)} issue(s) to fix", "# Run this script as Administrator", "", "$ErrorActionPreference = 'Stop'", ""]
|
|
256
|
+
for issue, _impact, fix in issues:
|
|
257
|
+
script_lines.append(f"# Fix: {issue}")
|
|
258
|
+
script_lines.append(f"Write-Host 'Fixing: {issue}' -ForegroundColor Yellow")
|
|
259
|
+
script_lines.append(fix)
|
|
260
|
+
script_lines.append("")
|
|
261
|
+
script_lines.append("Write-Host 'All fixes applied. Re-run ssh_debug_windows to verify.' -ForegroundColor Green")
|
|
262
|
+
fix_script_path.write_text("\n".join(script_lines), encoding="utf-8")
|
|
263
|
+
|
|
264
|
+
console.print(Panel(f"[bold yellow]⚠️ {len(issues)} issue(s) found[/bold yellow]\n\nFix script generated: [cyan]{fix_script_path}[/cyan]\nRun as Administrator: [green]powershell -ExecutionPolicy Bypass -File \"{fix_script_path}\"[/green]", title="[bold]Summary[/bold]", border_style="yellow"))
|
|
265
|
+
else:
|
|
266
|
+
conn_info = f"👤 {current_user} 🖥️ {hostname} 🔌 :{ssh_port}"
|
|
267
|
+
if ip_addresses:
|
|
268
|
+
conn_info += f"\n\n[bold]Connect:[/bold] ssh {current_user}@{ip_addresses[0]}"
|
|
269
|
+
console.print(Panel(f"[bold green]✅ All checks passed[/bold green]\n\n{conn_info}", title="[bold]Ready[/bold]", border_style="green"))
|
|
270
|
+
|
|
271
|
+
return results
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
ssh_debug_windows()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from machineconfig.scripts.python.helpers_repos.action_helper import GitAction, GitOperationResult, GitOperationSummary, print_git_operations_summary
|
|
1
|
+
from machineconfig.scripts.python.helpers.helpers_repos.action_helper import GitAction, GitOperationResult, GitOperationSummary, print_git_operations_summary
|
|
2
2
|
from machineconfig.utils.path_extended import PathExtended
|
|
3
3
|
from machineconfig.utils.accessories import randstr
|
|
4
|
-
from machineconfig.scripts.python.helpers_repos.update import update_repository
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers_repos.update import update_repository
|
|
5
5
|
|
|
6
6
|
from typing import Optional, Dict, Any, List, cast
|
|
7
7
|
import concurrent.futures
|
|
@@ -117,7 +117,7 @@ def perform_git_operations(repos_root: PathExtended, pull: bool, commit: bool, p
|
|
|
117
117
|
operations_performed.append("push")
|
|
118
118
|
|
|
119
119
|
# Collect all candidate paths first
|
|
120
|
-
paths = list(repos_root.
|
|
120
|
+
paths = list(repos_root.glob("*"))
|
|
121
121
|
|
|
122
122
|
def _process_path(a_path: PathExtended) -> Dict[str, Any]:
|
|
123
123
|
"""Worker that processes a single path and returns metadata and results."""
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from
|
|
2
|
+
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
@@ -12,7 +12,7 @@ from rich.table import Table
|
|
|
12
12
|
@dataclass
|
|
13
13
|
class GitOperationResult:
|
|
14
14
|
"""Result of a git operation on a single repository."""
|
|
15
|
-
repo_path:
|
|
15
|
+
repo_path: Path
|
|
16
16
|
action: str
|
|
17
17
|
success: bool
|
|
18
18
|
message: str
|
|
@@ -52,7 +52,7 @@ class GitOperationSummary:
|
|
|
52
52
|
|
|
53
53
|
def __post_init__(self):
|
|
54
54
|
self.failed_operations: list[GitOperationResult] = []
|
|
55
|
-
self.repos_without_remotes: list[
|
|
55
|
+
self.repos_without_remotes: list[Path] = []
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def print_git_operations_summary(summary: GitOperationSummary, operations_performed: list[str]) -> None:
|