machineconfig 8.51__py3-none-any.whl → 8.61__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- machineconfig/jobs/installer/python_scripts/sysabc.py +13 -34
- machineconfig/profile/mapper_dotfiles.toml +3 -3
- machineconfig/scripts/python/devops.py +1 -1
- machineconfig/scripts/python/devops_navigator.py +1 -1
- machineconfig/scripts/python/helper_env/path_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helper_env/env_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +8 -4
- machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +33 -1
- machineconfig/scripts/python/helpers/helpers_devops/cli_config_mount.py +77 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +4 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_nw.py +90 -6
- machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +3 -3
- machineconfig/scripts/python/helpers/helpers_devops/cli_self.py +41 -15
- machineconfig/scripts/python/helpers/helpers_devops/cli_share_temp.py +69 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +4 -4
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/commands.py +25 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/device_entry.py +17 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/devices.py +17 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/linux.py +103 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/macos.py +100 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/selection.py +47 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/utils.py +28 -0
- machineconfig/scripts/python/helpers/helpers_devops/mount_helpers/windows.py +91 -0
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_windows/fzfg.ps1 +1 -6
- machineconfig/scripts/python/helpers/helpers_network/ssh/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh/ssh_add_key_windows.py +23 -0
- machineconfig/scripts/python/helpers/helpers_network/{ssh_add_ssh_key.py → ssh/ssh_add_ssh_key.py} +21 -27
- machineconfig/scripts/python/helpers/helpers_network/ssh/ssh_cloud_init.py +33 -0
- machineconfig/scripts/python/helpers/helpers_network/{ssh_debug_linux.py → ssh/ssh_debug_linux.py} +70 -51
- machineconfig/scripts/python/helpers/helpers_network/ssh/ssh_debug_linux_utils.py +35 -0
- machineconfig/scripts/python/helpers/helpers_network/{ssh_debug_windows.py → ssh/ssh_debug_windows.py} +12 -42
- machineconfig/scripts/python/helpers/helpers_network/ssh/ssh_debug_windows_utils.py +34 -0
- machineconfig/scripts/python/helpers/helpers_repos/cloud_repo_sync.py +2 -3
- machineconfig/scripts/python/helpers/{helpers_terminal/terminal_impl.py → helpers_sessions/attach_impl.py} +16 -25
- machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +57 -129
- machineconfig/scripts/python/helpers/helpers_sessions/utils.py +69 -0
- machineconfig/scripts/python/mcfg_entry.py +0 -7
- machineconfig/scripts/python/sessions.py +95 -14
- machineconfig/scripts/python/utils.py +3 -2
- machineconfig/settings/shells/bash/init.sh +0 -7
- machineconfig/settings/shells/pwsh/init.ps1 +2 -4
- machineconfig/settings/shells/wezterm/wezterm.lua +1 -0
- machineconfig/settings/shells/wt/settings.json +13 -19
- machineconfig/settings/shells/zsh/init.sh +0 -1
- machineconfig/settings/zellij/__init__.py +0 -0
- machineconfig/settings/zellij/config.kdl +0 -295
- machineconfig/settings/zellij/layouts/__init__.py +0 -0
- machineconfig/settings/zellij/layouts/st.kdl +0 -1
- machineconfig/settings/zellij/layouts/st2.kdl +6 -2
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/utils/installer_utils/installer_cli.py +6 -2
- machineconfig/utils/installer_utils/installer_helper.py +50 -34
- machineconfig/utils/installer_utils/installer_locator_utils.py +3 -13
- machineconfig/utils/options_utils/tv_options.py +1 -1
- machineconfig/utils/procs.py +35 -27
- machineconfig/utils/schemas/layouts/layout_types.py +10 -0
- machineconfig/utils/source_of_truth.py +1 -0
- machineconfig/utils/ssh_utils/abc.py +1 -1
- {machineconfig-8.51.dist-info → machineconfig-8.61.dist-info}/METADATA +2 -3
- {machineconfig-8.51.dist-info → machineconfig-8.61.dist-info}/RECORD +68 -72
- {machineconfig-8.51.dist-info → machineconfig-8.61.dist-info}/entry_points.txt +0 -1
- machineconfig/jobs/scripts/bash_scripts/android.sh +0 -2
- machineconfig/jobs/scripts/bash_scripts/mount_drive +0 -128
- machineconfig/jobs/scripts/bash_scripts/mount_nfs +0 -49
- machineconfig/jobs/scripts/bash_scripts/mount_nw_drive +0 -61
- machineconfig/jobs/scripts/bash_scripts/mount_smb +0 -3
- machineconfig/jobs/scripts/bash_scripts/share_cloud.sh +0 -64
- machineconfig/jobs/scripts/bash_scripts/share_nfs +0 -49
- machineconfig/jobs/scripts/bash_scripts/start_docker +0 -23
- machineconfig/jobs/scripts/powershell_scripts/Restore-ThunderbirdProfile.ps1 +0 -92
- machineconfig/jobs/scripts/powershell_scripts/docker.ps1 +0 -7
- machineconfig/jobs/scripts/powershell_scripts/mount_nfs.ps1 +0 -42
- machineconfig/jobs/scripts/powershell_scripts/mount_nw.ps1 +0 -9
- machineconfig/jobs/scripts/powershell_scripts/mount_smb.ps1 +0 -2
- machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +0 -13
- machineconfig/jobs/scripts/powershell_scripts/obs.ps1 +0 -4
- machineconfig/jobs/scripts/powershell_scripts/power_options.ps1 +0 -7
- machineconfig/jobs/scripts/powershell_scripts/share_cloud.cmd +0 -34
- machineconfig/jobs/scripts/powershell_scripts/share_smb.ps1 +0 -16
- machineconfig/scripts/python/helpers/helpers_network/mount_nfs.py +0 -85
- machineconfig/scripts/python/helpers/helpers_network/mount_nw_drive.py +0 -48
- machineconfig/scripts/python/helpers/helpers_network/mount_ssh.py +0 -64
- machineconfig/scripts/python/terminal.py +0 -58
- machineconfig/settings/zellij/config.orig.kdl +0 -295
- /machineconfig/{scripts/python/helpers/helpers_terminal → cluster/sessions_managers/wt_utils/examples}/__init__.py +0 -0
- /machineconfig/scripts/python/helpers/helpers_network/{ssh_add_identity.py → ssh/ssh_add_identity.py} +0 -0
- {machineconfig-8.51.dist-info → machineconfig-8.61.dist-info}/WHEEL +0 -0
- {machineconfig-8.51.dist-info → machineconfig-8.61.dist-info}/top_level.txt +0 -0
|
@@ -128,13 +128,13 @@ def add_ssh_key(
|
|
|
128
128
|
github: Annotated[Optional[str], typer.Option(..., "--github", "-g", help="Fetch public keys from a GitHub username")] = None,
|
|
129
129
|
) -> None:
|
|
130
130
|
"""🔑 Add SSH public key to this machine so its accessible by owner of corresponding private key."""
|
|
131
|
-
import machineconfig.scripts.python.helpers.helpers_network.ssh_add_ssh_key as helper
|
|
131
|
+
import machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_add_ssh_key as helper
|
|
132
132
|
helper.main(pub_path=path, pub_choose=choose, pub_val=value, from_github=github)
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def add_ssh_identity() -> None:
|
|
136
136
|
"""🗝️ Add SSH identity (private key) to this machine"""
|
|
137
|
-
import machineconfig.scripts.python.helpers.helpers_network.ssh_add_identity as helper
|
|
137
|
+
import machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_add_identity as helper
|
|
138
138
|
helper.main()
|
|
139
139
|
|
|
140
140
|
|
|
@@ -142,10 +142,10 @@ def debug_ssh() -> None:
|
|
|
142
142
|
"""🐛 Debug SSH connection"""
|
|
143
143
|
from platform import system
|
|
144
144
|
if system() == "Linux" or system() == "Darwin":
|
|
145
|
-
import machineconfig.scripts.python.helpers.helpers_network.ssh_debug_linux as helper
|
|
145
|
+
import machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_debug_linux as helper
|
|
146
146
|
helper.ssh_debug_linux()
|
|
147
147
|
elif system() == "Windows":
|
|
148
|
-
import machineconfig.scripts.python.helpers.helpers_network.ssh_debug_windows as helper
|
|
148
|
+
import machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_debug_windows as helper
|
|
149
149
|
helper.ssh_debug_windows()
|
|
150
150
|
else:
|
|
151
151
|
print(f"❌ Error: Platform {system()} is not supported.")
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def run_command(command: list[str]) -> subprocess.CompletedProcess[str]:
|
|
5
|
+
result = subprocess.run(command, capture_output=True, text=True, check=False)
|
|
6
|
+
return result
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_command_sudo(command: list[str]) -> subprocess.CompletedProcess[str]:
|
|
10
|
+
result = subprocess.run(["sudo", *command], capture_output=True, text=True, check=False)
|
|
11
|
+
return result
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_powershell(command: str) -> subprocess.CompletedProcess[str]:
|
|
15
|
+
result = subprocess.run(["powershell", "-NoProfile", "-Command", command], capture_output=True, text=True, check=False)
|
|
16
|
+
return result
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def ensure_ok(result: subprocess.CompletedProcess[str], context: str) -> str:
|
|
20
|
+
if result.returncode != 0:
|
|
21
|
+
stderr_value = result.stderr.strip()
|
|
22
|
+
stdout_value = result.stdout.strip()
|
|
23
|
+
error_text = stderr_value if stderr_value != "" else stdout_value
|
|
24
|
+
raise RuntimeError(f"{context} failed: {error_text}")
|
|
25
|
+
return result.stdout
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True)
|
|
5
|
+
class DeviceEntry:
|
|
6
|
+
platform_name: str
|
|
7
|
+
key: str
|
|
8
|
+
device_path: str
|
|
9
|
+
device_type: str | None
|
|
10
|
+
label: str | None
|
|
11
|
+
mount_point: str | None
|
|
12
|
+
fs_type: str | None
|
|
13
|
+
size: str | None
|
|
14
|
+
extra: str | None
|
|
15
|
+
disk_number: int | None
|
|
16
|
+
partition_number: int | None
|
|
17
|
+
drive_letter: str | None
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.linux import list_linux_devices
|
|
5
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.macos import list_macos_devices
|
|
6
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.windows import list_windows_devices
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_devices() -> list[DeviceEntry]:
|
|
10
|
+
platform_name = platform.system()
|
|
11
|
+
if platform_name == "Linux":
|
|
12
|
+
return list_linux_devices()
|
|
13
|
+
if platform_name == "Darwin":
|
|
14
|
+
return list_macos_devices()
|
|
15
|
+
if platform_name == "Windows":
|
|
16
|
+
return list_windows_devices()
|
|
17
|
+
return []
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.commands import ensure_ok, run_command, run_command_sudo
|
|
6
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
7
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.selection import pick_device
|
|
8
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.utils import as_str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _flatten_lsblk_devices(devices: list[dict[str, object]]) -> list[dict[str, object]]:
|
|
12
|
+
result: list[dict[str, object]] = []
|
|
13
|
+
stack = list(devices)
|
|
14
|
+
while stack:
|
|
15
|
+
item = stack.pop(0)
|
|
16
|
+
result.append(item)
|
|
17
|
+
children = item.get("children")
|
|
18
|
+
if isinstance(children, list):
|
|
19
|
+
for child in children:
|
|
20
|
+
if isinstance(child, dict):
|
|
21
|
+
stack.append(child)
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def list_linux_devices() -> list[DeviceEntry]:
|
|
26
|
+
result = run_command(["lsblk", "-J", "-o", "NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINT,UUID,MODEL"])
|
|
27
|
+
text = ensure_ok(result, "lsblk")
|
|
28
|
+
data = json.loads(text)
|
|
29
|
+
raw_devices = data.get("blockdevices")
|
|
30
|
+
entries: list[DeviceEntry] = []
|
|
31
|
+
if isinstance(raw_devices, list):
|
|
32
|
+
for item in _flatten_lsblk_devices(raw_devices):
|
|
33
|
+
name_value = item.get("name")
|
|
34
|
+
if not isinstance(name_value, str):
|
|
35
|
+
continue
|
|
36
|
+
device_type = item.get("type")
|
|
37
|
+
if not isinstance(device_type, str):
|
|
38
|
+
continue
|
|
39
|
+
if device_type not in {"disk", "part"}:
|
|
40
|
+
continue
|
|
41
|
+
device_path = f"/dev/{name_value}"
|
|
42
|
+
label = as_str(item.get("label"))
|
|
43
|
+
mount_point = as_str(item.get("mountpoint"))
|
|
44
|
+
fs_type = as_str(item.get("fstype"))
|
|
45
|
+
size = as_str(item.get("size"))
|
|
46
|
+
model = as_str(item.get("model"))
|
|
47
|
+
entries.append(
|
|
48
|
+
DeviceEntry(
|
|
49
|
+
platform_name="Linux",
|
|
50
|
+
key=name_value,
|
|
51
|
+
device_path=device_path,
|
|
52
|
+
device_type=device_type,
|
|
53
|
+
label=label,
|
|
54
|
+
mount_point=mount_point,
|
|
55
|
+
fs_type=fs_type,
|
|
56
|
+
size=size,
|
|
57
|
+
extra=model,
|
|
58
|
+
disk_number=None,
|
|
59
|
+
partition_number=None,
|
|
60
|
+
drive_letter=None,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
return entries
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def mount_linux(entry: DeviceEntry, mount_point: str) -> None:
|
|
67
|
+
mount_path = Path(mount_point)
|
|
68
|
+
try:
|
|
69
|
+
mount_path.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
except PermissionError:
|
|
71
|
+
mkdir_result = run_command_sudo(["mkdir", "-p", str(mount_path)])
|
|
72
|
+
ensure_ok(mkdir_result, "mkdir")
|
|
73
|
+
if os.geteuid() == 0:
|
|
74
|
+
result = run_command(["mount", entry.device_path, str(mount_path)])
|
|
75
|
+
else:
|
|
76
|
+
result = run_command_sudo(["mount", entry.device_path, str(mount_path)])
|
|
77
|
+
ensure_ok(result, "mount")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_partition_of_disk(partition: DeviceEntry, disk: DeviceEntry) -> bool:
|
|
81
|
+
if partition.device_type != "part" or disk.device_type != "disk":
|
|
82
|
+
return False
|
|
83
|
+
if partition.key == disk.key:
|
|
84
|
+
return False
|
|
85
|
+
if not partition.key.startswith(disk.key):
|
|
86
|
+
return False
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def select_linux_partition(entries: list[DeviceEntry], entry: DeviceEntry) -> DeviceEntry:
|
|
91
|
+
if entry.device_type != "disk":
|
|
92
|
+
return entry
|
|
93
|
+
candidates = [device for device in entries if _is_partition_of_disk(device, entry)]
|
|
94
|
+
with_fs = [device for device in candidates if device.fs_type is not None and device.fs_type != ""]
|
|
95
|
+
if len(with_fs) == 1:
|
|
96
|
+
return with_fs[0]
|
|
97
|
+
if len(with_fs) > 1:
|
|
98
|
+
return pick_device(with_fs, header="Select partition to mount")
|
|
99
|
+
if len(candidates) == 1:
|
|
100
|
+
return candidates[0]
|
|
101
|
+
if len(candidates) > 1:
|
|
102
|
+
return pick_device(candidates, header="Select partition to mount")
|
|
103
|
+
raise RuntimeError("No mountable partitions found for selected disk")
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import plistlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.commands import ensure_ok, run_command
|
|
5
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
6
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.utils import as_str, format_size_bytes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _diskutil_info(identifier: str) -> dict[str, object]:
|
|
10
|
+
result = run_command(["diskutil", "info", "-plist", identifier])
|
|
11
|
+
text = ensure_ok(result, "diskutil info")
|
|
12
|
+
return plistlib.loads(text.encode("utf-8"))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def list_macos_devices() -> list[DeviceEntry]:
|
|
16
|
+
result = run_command(["diskutil", "list", "-plist"])
|
|
17
|
+
text = ensure_ok(result, "diskutil list")
|
|
18
|
+
data = plistlib.loads(text.encode("utf-8"))
|
|
19
|
+
entries: list[DeviceEntry] = []
|
|
20
|
+
all_disks = data.get("AllDisksAndPartitions")
|
|
21
|
+
if isinstance(all_disks, list):
|
|
22
|
+
for disk in all_disks:
|
|
23
|
+
if not isinstance(disk, dict):
|
|
24
|
+
continue
|
|
25
|
+
partitions = disk.get("Partitions")
|
|
26
|
+
if isinstance(partitions, list) and partitions:
|
|
27
|
+
for partition in partitions:
|
|
28
|
+
if not isinstance(partition, dict):
|
|
29
|
+
continue
|
|
30
|
+
identifier = partition.get("DeviceIdentifier")
|
|
31
|
+
if not isinstance(identifier, str):
|
|
32
|
+
continue
|
|
33
|
+
info = _diskutil_info(identifier)
|
|
34
|
+
mount_point = as_str(info.get("MountPoint"))
|
|
35
|
+
label = as_str(info.get("VolumeName"))
|
|
36
|
+
fs_type = as_str(info.get("FilesystemType"))
|
|
37
|
+
if fs_type is None:
|
|
38
|
+
fs_type = as_str(info.get("FileSystemType"))
|
|
39
|
+
size_value = info.get("TotalSize")
|
|
40
|
+
size = format_size_bytes(size_value) if isinstance(size_value, int) else None
|
|
41
|
+
media_name = as_str(info.get("MediaName"))
|
|
42
|
+
device_path = f"/dev/{identifier}"
|
|
43
|
+
entries.append(
|
|
44
|
+
DeviceEntry(
|
|
45
|
+
platform_name="Darwin",
|
|
46
|
+
key=identifier,
|
|
47
|
+
device_path=device_path,
|
|
48
|
+
device_type="part",
|
|
49
|
+
label=label,
|
|
50
|
+
mount_point=mount_point,
|
|
51
|
+
fs_type=fs_type,
|
|
52
|
+
size=size,
|
|
53
|
+
extra=media_name,
|
|
54
|
+
disk_number=None,
|
|
55
|
+
partition_number=None,
|
|
56
|
+
drive_letter=None,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
identifier = disk.get("DeviceIdentifier")
|
|
61
|
+
if not isinstance(identifier, str):
|
|
62
|
+
continue
|
|
63
|
+
info = _diskutil_info(identifier)
|
|
64
|
+
mount_point = as_str(info.get("MountPoint"))
|
|
65
|
+
label = as_str(info.get("VolumeName"))
|
|
66
|
+
fs_type = as_str(info.get("FilesystemType"))
|
|
67
|
+
if fs_type is None:
|
|
68
|
+
fs_type = as_str(info.get("FileSystemType"))
|
|
69
|
+
size_value = info.get("TotalSize")
|
|
70
|
+
size = format_size_bytes(size_value) if isinstance(size_value, int) else None
|
|
71
|
+
media_name = as_str(info.get("MediaName"))
|
|
72
|
+
device_path = f"/dev/{identifier}"
|
|
73
|
+
entries.append(
|
|
74
|
+
DeviceEntry(
|
|
75
|
+
platform_name="Darwin",
|
|
76
|
+
key=identifier,
|
|
77
|
+
device_path=device_path,
|
|
78
|
+
device_type="disk",
|
|
79
|
+
label=label,
|
|
80
|
+
mount_point=mount_point,
|
|
81
|
+
fs_type=fs_type,
|
|
82
|
+
size=size,
|
|
83
|
+
extra=media_name,
|
|
84
|
+
disk_number=None,
|
|
85
|
+
partition_number=None,
|
|
86
|
+
drive_letter=None,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
return entries
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def mount_macos(entry: DeviceEntry, mount_point: str) -> None:
|
|
93
|
+
if mount_point == "-":
|
|
94
|
+
result = run_command(["diskutil", "mount", entry.key])
|
|
95
|
+
ensure_ok(result, "diskutil mount")
|
|
96
|
+
return
|
|
97
|
+
mount_path = Path(mount_point)
|
|
98
|
+
mount_path.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
result = run_command(["diskutil", "mount", "-mountPoint", str(mount_path), entry.key])
|
|
100
|
+
ensure_ok(result, "diskutil mount")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
2
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.utils import format_device
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def pick_device(entries: list[DeviceEntry], header: str) -> DeviceEntry:
|
|
6
|
+
from machineconfig.utils.options import choose_from_options
|
|
7
|
+
|
|
8
|
+
options: list[str] = []
|
|
9
|
+
map_option: dict[str, DeviceEntry] = {}
|
|
10
|
+
for idx, entry in enumerate(entries):
|
|
11
|
+
option = f"{idx:02d} {format_device(entry)}"
|
|
12
|
+
options.append(option)
|
|
13
|
+
map_option[option] = entry
|
|
14
|
+
choice = choose_from_options(options=options, msg="Select a device", multi=False, header=header, tv=True)
|
|
15
|
+
selected = map_option.get(choice)
|
|
16
|
+
if selected is None:
|
|
17
|
+
raise RuntimeError("Selection not found")
|
|
18
|
+
return selected
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_device(entries: list[DeviceEntry], query: str) -> DeviceEntry:
|
|
22
|
+
query_value = query.strip()
|
|
23
|
+
if query_value in {"?", ""}:
|
|
24
|
+
return pick_device(entries, header="Available devices")
|
|
25
|
+
exact_matches = [
|
|
26
|
+
entry
|
|
27
|
+
for entry in entries
|
|
28
|
+
if entry.device_path.lower() == query_value.lower()
|
|
29
|
+
or entry.key.lower() == query_value.lower()
|
|
30
|
+
or (entry.label is not None and entry.label.lower() == query_value.lower())
|
|
31
|
+
]
|
|
32
|
+
if len(exact_matches) == 1:
|
|
33
|
+
return exact_matches[0]
|
|
34
|
+
if len(exact_matches) > 1:
|
|
35
|
+
return pick_device(exact_matches, header="Multiple matches")
|
|
36
|
+
partial_matches = [
|
|
37
|
+
entry
|
|
38
|
+
for entry in entries
|
|
39
|
+
if query_value.lower() in entry.device_path.lower()
|
|
40
|
+
or query_value.lower() in entry.key.lower()
|
|
41
|
+
or (entry.label is not None and query_value.lower() in entry.label.lower())
|
|
42
|
+
]
|
|
43
|
+
if len(partial_matches) == 1:
|
|
44
|
+
return partial_matches[0]
|
|
45
|
+
if len(partial_matches) > 1:
|
|
46
|
+
return pick_device(partial_matches, header="Multiple matches")
|
|
47
|
+
return pick_device(entries, header="Available devices")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def as_str(value: object) -> str | None:
|
|
5
|
+
if isinstance(value, str) and value != "":
|
|
6
|
+
return value
|
|
7
|
+
return None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_size_bytes(size_bytes: int) -> str:
|
|
11
|
+
units = ["B", "KB", "MB", "GB", "TB", "PB"]
|
|
12
|
+
value = float(size_bytes)
|
|
13
|
+
unit_index = 0
|
|
14
|
+
while value >= 1024 and unit_index < len(units) - 1:
|
|
15
|
+
value = value / 1024
|
|
16
|
+
unit_index += 1
|
|
17
|
+
if unit_index == 0:
|
|
18
|
+
return f"{int(value)} {units[unit_index]}"
|
|
19
|
+
return f"{value:.1f} {units[unit_index]}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_device(entry: DeviceEntry) -> str:
|
|
23
|
+
label = entry.label if entry.label is not None and entry.label != "" else "-"
|
|
24
|
+
fs_type = entry.fs_type if entry.fs_type is not None and entry.fs_type != "" else "-"
|
|
25
|
+
size = entry.size if entry.size is not None and entry.size != "" else "-"
|
|
26
|
+
mount_point = entry.mount_point if entry.mount_point is not None and entry.mount_point != "" else "-"
|
|
27
|
+
extra = entry.extra if entry.extra is not None and entry.extra != "" else "-"
|
|
28
|
+
return f"{entry.key} | {entry.device_path} | {fs_type} | {size} | {mount_point} | {label} | {extra}"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.commands import ensure_ok, run_powershell
|
|
5
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.device_entry import DeviceEntry
|
|
6
|
+
from machineconfig.scripts.python.helpers.helpers_devops.mount_helpers.utils import as_str, format_size_bytes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _normalize_ps_json(raw_text: str) -> list[dict[str, object]]:
|
|
10
|
+
if raw_text.strip() == "":
|
|
11
|
+
return []
|
|
12
|
+
data = json.loads(raw_text)
|
|
13
|
+
if isinstance(data, list):
|
|
14
|
+
return [x for x in data if isinstance(x, dict)]
|
|
15
|
+
if isinstance(data, dict):
|
|
16
|
+
return [data]
|
|
17
|
+
return []
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def list_windows_devices() -> list[DeviceEntry]:
|
|
21
|
+
partitions_result = run_powershell(
|
|
22
|
+
"Get-Partition | Select-Object DiskNumber,PartitionNumber,DriveLetter,Size,Type,Guid | ConvertTo-Json"
|
|
23
|
+
)
|
|
24
|
+
volumes_result = run_powershell(
|
|
25
|
+
"Get-Volume | Select-Object DriveLetter,FileSystemLabel,FileSystem,Size,SizeRemaining,DriveType,Path | ConvertTo-Json"
|
|
26
|
+
)
|
|
27
|
+
partitions_text = ensure_ok(partitions_result, "Get-Partition")
|
|
28
|
+
volumes_text = ensure_ok(volumes_result, "Get-Volume")
|
|
29
|
+
partitions = _normalize_ps_json(partitions_text)
|
|
30
|
+
volumes = _normalize_ps_json(volumes_text)
|
|
31
|
+
volume_map: dict[str, dict[str, object]] = {}
|
|
32
|
+
for volume in volumes:
|
|
33
|
+
drive_letter = as_str(volume.get("DriveLetter"))
|
|
34
|
+
if isinstance(drive_letter, str):
|
|
35
|
+
volume_map[drive_letter.upper()] = volume
|
|
36
|
+
entries: list[DeviceEntry] = []
|
|
37
|
+
for partition in partitions:
|
|
38
|
+
disk_number = partition.get("DiskNumber")
|
|
39
|
+
partition_number = partition.get("PartitionNumber")
|
|
40
|
+
drive_letter = as_str(partition.get("DriveLetter"))
|
|
41
|
+
volume = volume_map.get(drive_letter.upper()) if isinstance(drive_letter, str) else None
|
|
42
|
+
label = None
|
|
43
|
+
fs_type = None
|
|
44
|
+
mount_point = None
|
|
45
|
+
extra = as_str(partition.get("Type"))
|
|
46
|
+
if isinstance(volume, dict):
|
|
47
|
+
label = as_str(volume.get("FileSystemLabel"))
|
|
48
|
+
fs_type = as_str(volume.get("FileSystem"))
|
|
49
|
+
mount_point = as_str(volume.get("Path"))
|
|
50
|
+
if mount_point is None and isinstance(drive_letter, str) and drive_letter != "":
|
|
51
|
+
mount_point = f"{drive_letter}:\\"
|
|
52
|
+
size_value = partition.get("Size")
|
|
53
|
+
size = format_size_bytes(size_value) if isinstance(size_value, int) else None
|
|
54
|
+
key = f"Disk {disk_number} Part {partition_number}"
|
|
55
|
+
device_path = as_str(partition.get("Guid")) or key
|
|
56
|
+
entries.append(
|
|
57
|
+
DeviceEntry(
|
|
58
|
+
platform_name="Windows",
|
|
59
|
+
key=key,
|
|
60
|
+
device_path=device_path,
|
|
61
|
+
device_type="part",
|
|
62
|
+
label=label,
|
|
63
|
+
mount_point=mount_point,
|
|
64
|
+
fs_type=fs_type,
|
|
65
|
+
size=size,
|
|
66
|
+
extra=extra,
|
|
67
|
+
disk_number=disk_number if isinstance(disk_number, int) else None,
|
|
68
|
+
partition_number=partition_number if isinstance(partition_number, int) else None,
|
|
69
|
+
drive_letter=drive_letter.upper() if isinstance(drive_letter, str) and drive_letter != "" else None,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return entries
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _normalize_drive_letter(value: str) -> str:
|
|
76
|
+
match = re.search(r"([A-Za-z])", value)
|
|
77
|
+
if match is None:
|
|
78
|
+
raise RuntimeError("Invalid drive letter")
|
|
79
|
+
return match.group(1).upper()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def mount_windows(entry: DeviceEntry, mount_point: str) -> None:
|
|
83
|
+
if entry.disk_number is None or entry.partition_number is None:
|
|
84
|
+
raise RuntimeError("Partition details not available")
|
|
85
|
+
letter = _normalize_drive_letter(mount_point)
|
|
86
|
+
command = (
|
|
87
|
+
f"Get-Partition -DiskNumber {entry.disk_number} -PartitionNumber {entry.partition_number} | "
|
|
88
|
+
f"Set-Partition -NewDriveLetter {letter}"
|
|
89
|
+
)
|
|
90
|
+
result = run_powershell(command)
|
|
91
|
+
ensure_ok(result, "Set-Partition")
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def add_ssh_key_windows(path_to_key: Path) -> None:
|
|
6
|
+
sshd_dir = Path("C:/ProgramData/ssh")
|
|
7
|
+
admin_auth_keys = sshd_dir / "administrators_authorized_keys"
|
|
8
|
+
sshd_config = sshd_dir / "sshd_config"
|
|
9
|
+
key_content = path_to_key.read_text(encoding="utf-8").strip()
|
|
10
|
+
if admin_auth_keys.exists():
|
|
11
|
+
existing = admin_auth_keys.read_text(encoding="utf-8")
|
|
12
|
+
if not existing.endswith("\n"):
|
|
13
|
+
existing += "\n"
|
|
14
|
+
admin_auth_keys.write_text(existing + key_content + "\n", encoding="utf-8")
|
|
15
|
+
else:
|
|
16
|
+
admin_auth_keys.write_text(key_content + "\n", encoding="utf-8")
|
|
17
|
+
icacls_cmd = f'icacls "{admin_auth_keys}" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"'
|
|
18
|
+
subprocess.run(icacls_cmd, shell=True, check=True)
|
|
19
|
+
if sshd_config.exists():
|
|
20
|
+
config_text = sshd_config.read_text(encoding="utf-8")
|
|
21
|
+
config_text = config_text.replace("#PubkeyAuthentication", "PubkeyAuthentication")
|
|
22
|
+
sshd_config.write_text(config_text, encoding="utf-8")
|
|
23
|
+
subprocess.run("Restart-Service sshd -Force", shell=True, check=True)
|
machineconfig/scripts/python/helpers/helpers_network/{ssh_add_ssh_key.py → ssh/ssh_add_ssh_key.py}
RENAMED
|
@@ -7,34 +7,12 @@ from rich.panel import Panel
|
|
|
7
7
|
from rich import box
|
|
8
8
|
from typing import Optional, Annotated
|
|
9
9
|
import typer
|
|
10
|
-
import subprocess
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_add_key_windows import add_ssh_key_windows
|
|
12
|
+
from machineconfig.scripts.python.helpers.helpers_network.ssh.ssh_cloud_init import check_cloud_init_overrides, generate_cloud_init_fix_script
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
|
|
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)
|
|
15
|
+
console = Console()
|
|
38
16
|
|
|
39
17
|
|
|
40
18
|
def get_add_ssh_key_script(path_to_key: Path, verbose: bool = True) -> tuple[str, str]:
|
|
@@ -63,7 +41,7 @@ def get_add_ssh_key_script(path_to_key: Path, verbose: bool = True) -> tuple[str
|
|
|
63
41
|
if os_name == "Linux" or os_name == "Darwin":
|
|
64
42
|
program = f"cat {path_to_key} >> ~/.ssh/authorized_keys"
|
|
65
43
|
elif os_name == "Windows":
|
|
66
|
-
|
|
44
|
+
add_ssh_key_windows(path_to_key)
|
|
67
45
|
else:
|
|
68
46
|
raise NotImplementedError
|
|
69
47
|
else:
|
|
@@ -71,9 +49,25 @@ def get_add_ssh_key_script(path_to_key: Path, verbose: bool = True) -> tuple[str
|
|
|
71
49
|
if os_name == "Linux" or os_name == "Darwin":
|
|
72
50
|
program = f"cat {path_to_key} > ~/.ssh/authorized_keys"
|
|
73
51
|
else:
|
|
74
|
-
|
|
52
|
+
add_ssh_key_windows(path_to_key)
|
|
75
53
|
|
|
76
54
|
if os_name == "Linux" or os_name == "Darwin":
|
|
55
|
+
override_files, auth_overrides = check_cloud_init_overrides()
|
|
56
|
+
if override_files:
|
|
57
|
+
status_lines.append(f"\n⚠️ [yellow]Cloud-init override files detected:[/yellow]")
|
|
58
|
+
for of in override_files:
|
|
59
|
+
status_lines.append(f" • {of.name}")
|
|
60
|
+
blocking_overrides: list[str] = []
|
|
61
|
+
for key, (file_path, value) in auth_overrides.items():
|
|
62
|
+
if key == "PubkeyAuthentication" and value == "no":
|
|
63
|
+
blocking_overrides.append(f" ❌ {key}={value} in {file_path.name} - [red]blocks key auth![/red]")
|
|
64
|
+
elif key == "PasswordAuthentication" and value == "no":
|
|
65
|
+
blocking_overrides.append(f" ⚠️ {key}={value} in {file_path.name}")
|
|
66
|
+
if blocking_overrides:
|
|
67
|
+
status_lines.extend(blocking_overrides)
|
|
68
|
+
cloud_init_fix = generate_cloud_init_fix_script(auth_overrides)
|
|
69
|
+
if cloud_init_fix:
|
|
70
|
+
program += f"\n# === Fix cloud-init SSH overrides ===\n{cloud_init_fix}\n"
|
|
77
71
|
program += """
|
|
78
72
|
sudo chmod 700 ~/.ssh
|
|
79
73
|
sudo chmod 644 ~/.ssh/authorized_keys
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def check_cloud_init_overrides() -> tuple[list[Path], dict[str, tuple[Path, str]]]:
|
|
5
|
+
sshd_config_d = Path("/etc/ssh/sshd_config.d")
|
|
6
|
+
override_files: list[Path] = []
|
|
7
|
+
auth_overrides: dict[str, tuple[Path, str]] = {}
|
|
8
|
+
if not sshd_config_d.exists():
|
|
9
|
+
return override_files, auth_overrides
|
|
10
|
+
for conf_file in sorted(sshd_config_d.glob("*.conf")):
|
|
11
|
+
override_files.append(conf_file)
|
|
12
|
+
try:
|
|
13
|
+
conf_text = conf_file.read_text(encoding="utf-8")
|
|
14
|
+
for line in conf_text.split("\n"):
|
|
15
|
+
line_stripped = line.strip()
|
|
16
|
+
if line_stripped and not line_stripped.startswith("#"):
|
|
17
|
+
parts = line_stripped.split(None, 1)
|
|
18
|
+
if len(parts) >= 2:
|
|
19
|
+
key, value = parts[0], parts[1]
|
|
20
|
+
if key in ("PasswordAuthentication", "PubkeyAuthentication", "PermitRootLogin", "ChallengeResponseAuthentication", "KbdInteractiveAuthentication"):
|
|
21
|
+
auth_overrides[key] = (conf_file, value.lower())
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
return override_files, auth_overrides
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_cloud_init_fix_script(auth_overrides: dict[str, tuple[Path, str]]) -> str:
|
|
28
|
+
fix_commands: list[str] = []
|
|
29
|
+
for key, (file_path, value) in auth_overrides.items():
|
|
30
|
+
if key in ("PasswordAuthentication", "PubkeyAuthentication") and value == "no":
|
|
31
|
+
fix_commands.append(f"# Fix {key} in {file_path.name}")
|
|
32
|
+
fix_commands.append(f"sudo sed -i 's/^{key}.*no/{key} yes/' {file_path}")
|
|
33
|
+
return "\n".join(fix_commands)
|