machineconfig 7.58__py3-none-any.whl → 7.59__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/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +3 -3
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
- machineconfig/jobs/installer/custom_dev/sysabc.py +1 -20
- machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
- machineconfig/jobs/installer/installer_data.json +57 -23
- machineconfig/jobs/installer/package_groups.py +20 -13
- machineconfig/scripts/python/croshell.py +4 -4
- machineconfig/scripts/python/devops.py +1 -1
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +4 -5
- machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_utils.py +0 -72
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
- machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +3 -2
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
- machineconfig/scripts/python/helpers_repos/record.py +2 -1
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
- machineconfig/scripts/python/helpers_utils/download.py +151 -0
- machineconfig/scripts/python/helpers_utils/path.py +1 -1
- machineconfig/scripts/python/interactive.py +2 -2
- machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
- machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
- machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
- machineconfig/scripts/python/sessions.py +2 -3
- machineconfig/scripts/python/utils.py +2 -1
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/installer_class.py +38 -24
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +29 -15
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
- machineconfig/utils/options.py +1 -1
- machineconfig/utils/path_extended.py +2 -2
- machineconfig/utils/path_helper.py +34 -31
- machineconfig/utils/ssh.py +1 -1
- {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/METADATA +1 -1
- {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/RECORD +53 -52
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
- {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/WHEEL +0 -0
- {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/entry_points.txt +0 -0
- {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
2
|
-
from machineconfig.utils.installer_utils.
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended, DECOMPRESS_SUPPORTED_FORMATS
|
|
2
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows
|
|
3
3
|
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
|
|
4
|
-
from machineconfig.utils.installer_utils.
|
|
4
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
5
5
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData, get_os_name, get_normalized_arch
|
|
6
6
|
|
|
7
7
|
import platform
|
|
@@ -11,6 +11,24 @@ from typing import Optional, Any
|
|
|
11
11
|
from urllib.parse import urlparse
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
def install_deb_package(downloaded: PathExtended) -> None:
|
|
16
|
+
print(f"📦 Installing .deb package: {downloaded}")
|
|
17
|
+
assert platform.system() == "Linux"
|
|
18
|
+
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
19
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
20
|
+
if not success:
|
|
21
|
+
desc = "Installing .deb"
|
|
22
|
+
print(f"❌ {desc} failed")
|
|
23
|
+
if result.stdout:
|
|
24
|
+
print(f"STDOUT: {result.stdout}")
|
|
25
|
+
if result.stderr:
|
|
26
|
+
print(f"STDERR: {result.stderr}")
|
|
27
|
+
print(f"Return code: {result.returncode}")
|
|
28
|
+
print("🗑️ Cleaning up .deb package...")
|
|
29
|
+
downloaded.delete(sure=True)
|
|
30
|
+
|
|
31
|
+
|
|
14
32
|
class Installer:
|
|
15
33
|
def __init__(self, installer_data: InstallerData):
|
|
16
34
|
self.installer_data: InstallerData = installer_data
|
|
@@ -106,9 +124,14 @@ class Installer:
|
|
|
106
124
|
runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
|
|
107
125
|
version_to_be_installed = str(version)
|
|
108
126
|
elif installer_arch_os.startswith("https://"): # its a url to be downloaded
|
|
109
|
-
downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
127
|
+
# downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
128
|
+
from machineconfig.scripts.python.helpers_utils.download import download
|
|
129
|
+
downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
|
|
130
|
+
if downloaded_object is None:
|
|
131
|
+
raise ValueError(f"Failed to download from URL: {installer_arch_os}")
|
|
110
132
|
# object is either a zip containing a binary or a straight out binary.
|
|
111
|
-
|
|
133
|
+
downloaded_object = PathExtended(downloaded_object)
|
|
134
|
+
if downloaded_object.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
112
135
|
downloaded_object = downloaded_object.decompress()
|
|
113
136
|
if downloaded_object.suffix in [".exe", ""]: # likely an executable
|
|
114
137
|
if platform.system() == "Windows":
|
|
@@ -131,26 +154,17 @@ class Installer:
|
|
|
131
154
|
print(f"🔄 Renaming to correct name: {new_exe_name}")
|
|
132
155
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
133
156
|
version_to_be_installed = "downloaded_binary"
|
|
157
|
+
elif downloaded_object.suffix in [".deb"]:
|
|
158
|
+
install_deb_package(downloaded_object)
|
|
159
|
+
version_to_be_installed = "downloaded_deb"
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(f"Downloaded file is not an executable: {downloaded_object}")
|
|
134
162
|
else:
|
|
135
163
|
raise NotImplementedError(f"CMD installation method not implemented for: {installer_arch_os}")
|
|
136
164
|
else:
|
|
137
165
|
assert repo_url.startswith("https://github.com/"), f"repoURL must be a GitHub URL, got {repo_url}"
|
|
138
|
-
downloaded, version_to_be_installed = self.
|
|
139
|
-
if str(downloaded).endswith(".deb"):
|
|
140
|
-
print(f"📦 Installing .deb package: {downloaded}")
|
|
141
|
-
assert platform.system() == "Linux"
|
|
142
|
-
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
143
|
-
success = result.returncode == 0 and result.stderr == ""
|
|
144
|
-
if not success:
|
|
145
|
-
desc = "Installing .deb"
|
|
146
|
-
print(f"❌ {desc} failed")
|
|
147
|
-
if result.stdout:
|
|
148
|
-
print(f"STDOUT: {result.stdout}")
|
|
149
|
-
if result.stderr:
|
|
150
|
-
print(f"STDERR: {result.stderr}")
|
|
151
|
-
print(f"Return code: {result.returncode}")
|
|
152
|
-
print("🗑️ Cleaning up .deb package...")
|
|
153
|
-
downloaded.delete(sure=True)
|
|
166
|
+
downloaded, version_to_be_installed = self.binary_download(version=version)
|
|
167
|
+
if str(downloaded).endswith(".deb"): install_deb_package(downloaded)
|
|
154
168
|
else:
|
|
155
169
|
if platform.system() == "Windows":
|
|
156
170
|
exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
|
|
@@ -173,13 +187,13 @@ class Installer:
|
|
|
173
187
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
174
188
|
INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
|
|
175
189
|
INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed or "unknown", encoding="utf-8")
|
|
176
|
-
def
|
|
190
|
+
def binary_download(self, version: Optional[str]) -> tuple[PathExtended, str]:
|
|
177
191
|
exe_name = self._get_exe_name()
|
|
178
192
|
repo_url = self.installer_data["repoURL"]
|
|
179
193
|
# app_name = self.installer_data["appName"]
|
|
180
194
|
download_link: Optional[str] = None
|
|
181
195
|
version_to_be_installed: Optional[str] = None
|
|
182
|
-
if "github" not in repo_url or
|
|
196
|
+
if "github" not in repo_url or (any(ext in repo_url for ext in DECOMPRESS_SUPPORTED_FORMATS)):
|
|
183
197
|
# Direct download URL
|
|
184
198
|
download_link = repo_url
|
|
185
199
|
version_to_be_installed = "predefined_url"
|
|
@@ -202,7 +216,7 @@ class Installer:
|
|
|
202
216
|
downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
|
|
203
217
|
if downloaded.is_dir() and len(downloaded.search("*", r=True)) == 1:
|
|
204
218
|
only_file_in = next(downloaded.glob("*"))
|
|
205
|
-
if only_file_in.is_file() and only_file_in.suffix in
|
|
219
|
+
if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
|
|
206
220
|
downloaded = only_file_in.decompress()
|
|
207
221
|
return downloaded, version_to_be_installed
|
|
208
222
|
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
"""Devops Devapps Install"""
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
from rich.panel import Panel
|
|
6
|
-
from rich.table import Table
|
|
7
4
|
from typing import Optional, Annotated
|
|
8
5
|
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
9
6
|
|
|
10
|
-
console = Console()
|
|
11
7
|
|
|
12
8
|
|
|
13
9
|
def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
|
|
14
10
|
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
15
11
|
from difflib import get_close_matches
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
16
15
|
close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
17
18
|
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
18
19
|
if close_matches:
|
|
19
20
|
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
@@ -61,6 +62,10 @@ def main(
|
|
|
61
62
|
return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
|
|
62
63
|
else:
|
|
63
64
|
if group:
|
|
65
|
+
from rich.console import Console
|
|
66
|
+
from rich.table import Table
|
|
67
|
+
console = Console()
|
|
68
|
+
|
|
64
69
|
typer.echo("❌ You must provide a group name when using the --group/-g option.")
|
|
65
70
|
res = get_group_name_to_repr()
|
|
66
71
|
console.print("[bold blue]Here are the available groups:[/bold blue]")
|
|
@@ -98,16 +103,16 @@ def get_group_name_to_repr() -> dict[str, str]:
|
|
|
98
103
|
def install_interactively():
|
|
99
104
|
from machineconfig.utils.options import choose_from_options
|
|
100
105
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
101
|
-
from machineconfig.utils.
|
|
106
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
102
107
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
108
|
+
from rich.console import Console
|
|
109
|
+
from rich.panel import Panel
|
|
110
|
+
# from rich.table import Table
|
|
103
111
|
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
104
|
-
installer_options = []
|
|
105
|
-
for x in installers:
|
|
106
|
-
installer_options.append(Installer(installer_data=x).get_description())
|
|
107
|
-
|
|
112
|
+
installer_options = [Installer(installer_data=x).get_description() for x in installers]
|
|
108
113
|
category_display_to_name = get_group_name_to_repr()
|
|
109
114
|
options = list(category_display_to_name.keys()) + ["─" * 50] + installer_options
|
|
110
|
-
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY",
|
|
115
|
+
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", fzf=True)
|
|
111
116
|
installation_messages: list[str] = []
|
|
112
117
|
for _an_idx, a_program_name in enumerate(program_names):
|
|
113
118
|
if a_program_name.startswith("─"): # 50 dashes separator
|
|
@@ -122,24 +127,32 @@ def install_interactively():
|
|
|
122
127
|
status_message = Installer(an_installer_data).install_robust(version=None) # finish the task - this returns a status message, not a command
|
|
123
128
|
installation_messages.append(status_message)
|
|
124
129
|
if installation_messages:
|
|
130
|
+
console = Console()
|
|
131
|
+
|
|
125
132
|
panel = Panel("\n".join([f"[blue]• {message}[/blue]" for message in installation_messages]), title="[bold green]📊 Installation Summary[/bold green]", border_style="green", padding=(1, 2))
|
|
126
133
|
console.print(panel)
|
|
127
134
|
|
|
128
135
|
|
|
129
136
|
def install_group(package_group: str):
|
|
130
|
-
from machineconfig.utils.
|
|
137
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers, install_bulk
|
|
131
138
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
139
|
+
from rich.console import Console
|
|
140
|
+
from rich.panel import Panel
|
|
141
|
+
# from rich.table import Table
|
|
132
142
|
if package_group in PACKAGE_GROUP2NAMES:
|
|
133
143
|
panel = Panel(f"[bold yellow]Installing programs from category: [green]{package_group}[/green][/bold yellow]", title="[bold blue]📦 Category Installation[/bold blue]", border_style="blue", padding=(1, 2))
|
|
144
|
+
console = Console()
|
|
134
145
|
console.print(panel)
|
|
135
146
|
installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
|
|
136
147
|
install_bulk(installers_data=installers_)
|
|
137
148
|
return
|
|
138
|
-
|
|
149
|
+
console = Console()
|
|
150
|
+
console.print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
|
|
139
151
|
def install_clis(clis_names: list[str]):
|
|
140
152
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
141
|
-
from machineconfig.utils.
|
|
153
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
142
154
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
155
|
+
from rich.console import Console
|
|
143
156
|
total_messages: list[str] = []
|
|
144
157
|
for a_which in clis_names:
|
|
145
158
|
all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
@@ -155,6 +168,7 @@ def install_clis(clis_names: list[str]):
|
|
|
155
168
|
message = Installer(selected_installer).install_robust(version=None) # finish the task
|
|
156
169
|
total_messages.append(message)
|
|
157
170
|
if total_messages:
|
|
171
|
+
console = Console()
|
|
158
172
|
console.print("\n[bold green]📊 Installation Results:[/bold green]")
|
|
159
173
|
for a_message in total_messages:
|
|
160
174
|
console.print(f"[blue]• {a_message}[/blue]")
|
|
@@ -162,13 +176,13 @@ def install_clis(clis_names: list[str]):
|
|
|
162
176
|
|
|
163
177
|
|
|
164
178
|
def install_if_missing(which: str):
|
|
165
|
-
from machineconfig.utils.installer_utils.
|
|
179
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
166
180
|
exists = check_tool_exists(which)
|
|
167
181
|
if exists:
|
|
168
182
|
print(f"✅ {which} is already installed.")
|
|
169
183
|
return
|
|
170
184
|
print(f"⏳ {which} not found. Installing...")
|
|
171
|
-
from machineconfig.utils.installer_utils.
|
|
185
|
+
from machineconfig.utils.installer_utils.installer_cli import main
|
|
172
186
|
main(which=which, interactive=False)
|
|
173
187
|
|
|
174
188
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""package manager"""
|
|
2
2
|
|
|
3
|
-
from machineconfig.utils.installer_utils.
|
|
3
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_if_installed_already
|
|
4
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
5
5
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
|
|
6
6
|
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
@@ -132,30 +132,6 @@ def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs:
|
|
|
132
132
|
print("✅ Version cache cleared")
|
|
133
133
|
if safe:
|
|
134
134
|
pass
|
|
135
|
-
# print("⚠️ Safe installation mode activated...")
|
|
136
|
-
# from machineconfig.jobs.python.check_installations import APP_SUMMARY_PATH
|
|
137
|
-
# if platform.system().lower() == "windows":
|
|
138
|
-
# print("🪟 Moving applications to Windows Apps folder...")
|
|
139
|
-
# # PathExtended.get_env().WindowsPaths().WindowsApps)
|
|
140
|
-
# folder = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps")
|
|
141
|
-
# apps_dir.search("*").apply(lambda app: app.move(folder=folder))
|
|
142
|
-
# elif platform.system().lower() in ["linux", "darwin"]:
|
|
143
|
-
# system_name = "Linux" if platform.system().lower() == "linux" else "macOS"
|
|
144
|
-
# print(f"🐧 Moving applications to {system_name} bin folder...")
|
|
145
|
-
# if platform.system().lower() == "linux":
|
|
146
|
-
# install_path = LINUX_INSTALL_PATH
|
|
147
|
-
# else: # Darwin/macOS
|
|
148
|
-
# install_path = "/usr/local/bin"
|
|
149
|
-
# Terminal().run(f"sudo mv {apps_dir.as_posix()}/* {install_path}/").capture().print_if_unsuccessful(desc=f"MOVING executable to {install_path}", strict_err=True, strict_returncode=True)
|
|
150
|
-
# else:
|
|
151
|
-
# error_msg = f"❌ ERROR: System {platform.system()} not supported"
|
|
152
|
-
# print(error_msg)
|
|
153
|
-
# raise NotImplementedError(error_msg)
|
|
154
|
-
|
|
155
|
-
# apps_dir.delete(sure=True)
|
|
156
|
-
# print(f"✅ Safe installation completed\n{'='*80}")
|
|
157
|
-
# return None
|
|
158
|
-
|
|
159
135
|
print(f"🚀 Starting installation of {len(installers_data)} packages...")
|
|
160
136
|
print("📦 INSTALLING FIRST PACKAGE 📦")
|
|
161
137
|
Installer(installers_data[0]).install(version=None)
|
machineconfig/utils/options.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from machineconfig.utils.installer_utils.
|
|
2
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
3
3
|
from rich.text import Text
|
|
4
4
|
from rich.panel import Panel
|
|
5
5
|
from rich.console import Console
|
|
@@ -16,6 +16,7 @@ OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
|
|
|
16
16
|
PLike: TypeAlias = Union[str, "PathExtended", Path]
|
|
17
17
|
FILE_MODE: TypeAlias = Literal["r", "w", "x", "a"]
|
|
18
18
|
SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
|
|
19
|
+
DECOMPRESS_SUPPORTED_FORMATS = [".tar.gz", ".tgz", ".tar", ".gz", ".tar.bz", ".tbz", ".tar.xz", ".zip", ".7z"]
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _is_user_admin() -> bool:
|
|
@@ -152,7 +153,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
152
153
|
# ======================================= File Editing / Reading ===================================
|
|
153
154
|
def download(self, folder: OPLike = None, name: Optional[str] = None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> "PathExtended":
|
|
154
155
|
import requests
|
|
155
|
-
|
|
156
156
|
response = requests.get(self.as_url_str(), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
|
|
157
157
|
assert response.status_code == 200, f"Download failed with status code {response.status_code}\n{response.text}"
|
|
158
158
|
if name is not None:
|
|
@@ -809,7 +809,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
809
809
|
path = self
|
|
810
810
|
else:
|
|
811
811
|
try:
|
|
812
|
-
path = self.
|
|
812
|
+
path = PathExtended(self.expanduser().absolute().relative_to(Path.home()))
|
|
813
813
|
except ValueError as ve:
|
|
814
814
|
if strict:
|
|
815
815
|
raise ve
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from machineconfig.utils.path_extended import
|
|
1
|
+
# from machineconfig.utils.path_extended import Path
|
|
2
2
|
from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from rich.panel import Panel
|
|
@@ -10,8 +10,8 @@ from typing import Optional
|
|
|
10
10
|
console = Console()
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def sanitize_path(a_path: str) ->
|
|
14
|
-
path =
|
|
13
|
+
def sanitize_path(a_path: str) -> Path:
|
|
14
|
+
path = Path(a_path)
|
|
15
15
|
if Path.cwd() == Path.home() and not path.exists():
|
|
16
16
|
result = input("Current working directory is home, and passed path is not full path, are you sure you want to continue, [y]/n? ") or "y"
|
|
17
17
|
if result == "y":
|
|
@@ -22,13 +22,13 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
22
22
|
if platform.system() == "Windows": # path copied from Linux/Mac to Windows
|
|
23
23
|
# For Linux: /home/username, for Mac: /Users/username
|
|
24
24
|
skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
|
|
25
|
-
path =
|
|
25
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
26
26
|
assert path.exists(), f"File not found: {path}"
|
|
27
27
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
28
28
|
console.print(Panel(f"🔗 PATH MAPPING | {source_os} → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
29
|
-
elif platform.system() in ["Linux", "Darwin"] and
|
|
29
|
+
elif platform.system() in ["Linux", "Darwin"] and Path.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
|
|
30
30
|
skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
|
|
31
|
-
path =
|
|
31
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
32
32
|
assert path.exists(), f"File not found: {path}"
|
|
33
33
|
current_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
34
34
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
@@ -36,12 +36,12 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
36
36
|
elif path.as_posix().startswith("C:"):
|
|
37
37
|
if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
|
|
38
38
|
xx = str(a_path).replace("\\\\", "/")
|
|
39
|
-
path =
|
|
39
|
+
path = Path.home().joinpath(*Path(xx).parts[3:]) # exclude C:\\Users\\username
|
|
40
40
|
assert path.exists(), f"File not found: {path}"
|
|
41
41
|
target_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
42
42
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → {target_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
43
|
-
elif platform.system() == "Windows" and
|
|
44
|
-
path =
|
|
43
|
+
elif platform.system() == "Windows" and Path.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
|
|
44
|
+
path = Path.home().joinpath(*path.parts[2:])
|
|
45
45
|
assert path.exists(), f"File not found: {path}"
|
|
46
46
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
47
47
|
return path.expanduser().absolute()
|
|
@@ -66,12 +66,12 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
|
|
|
66
66
|
return filename_matches, partial_path_matches
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def match_file_name(sub_string: str, search_root:
|
|
69
|
+
def match_file_name(sub_string: str, search_root: Path, suffixes: set[str]) -> Path:
|
|
70
70
|
search_root_obj = search_root.absolute()
|
|
71
71
|
# assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
|
|
72
72
|
filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
|
|
73
73
|
if len(filename_matches) == 1:
|
|
74
|
-
return
|
|
74
|
+
return Path(filename_matches[0])
|
|
75
75
|
console.print(Panel(f"Partial filename {search_root_obj} match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
|
|
76
76
|
if len(filename_matches) < 20:
|
|
77
77
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
|
|
@@ -80,23 +80,23 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
80
80
|
# let's see if avoiding .lower() helps narrowing down to one result
|
|
81
81
|
reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
|
|
82
82
|
if len(reduced_scripts) == 1:
|
|
83
|
-
return
|
|
83
|
+
return Path(reduced_scripts[0])
|
|
84
84
|
elif len(reduced_scripts) > 1:
|
|
85
85
|
from machineconfig.utils.options import choose_from_options
|
|
86
86
|
choice = choose_from_options(multi=False, msg="Multiple matches found", options=reduced_scripts, fzf=True)
|
|
87
|
-
return
|
|
87
|
+
return Path(choice)
|
|
88
88
|
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
89
89
|
if len(reduced_scripts) < 10:
|
|
90
90
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in reduced_scripts]))
|
|
91
91
|
|
|
92
92
|
console.print(Panel(f"Partial path match with case-insensitivity failed. This generated #{len(partial_path_matches)} results.", title="Search", expand=False))
|
|
93
93
|
if len(partial_path_matches) == 1:
|
|
94
|
-
return
|
|
94
|
+
return Path(partial_path_matches[0])
|
|
95
95
|
elif len(partial_path_matches) > 1:
|
|
96
96
|
print("Try to narrow down partial_path_matches search by case-sensitivity.")
|
|
97
97
|
reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
|
|
98
98
|
if len(reduced_scripts) == 1:
|
|
99
|
-
return
|
|
99
|
+
return Path(reduced_scripts[0])
|
|
100
100
|
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
101
101
|
|
|
102
102
|
try:
|
|
@@ -131,21 +131,24 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
131
131
|
return search_root_obj.joinpath(res)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def search_for_files_of_interest(path_obj:
|
|
135
|
-
if path_obj.joinpath(".venv").exists():
|
|
136
|
-
path_objects = path_obj.search("*", not_in=[".venv"])
|
|
137
|
-
files: list[PathExtended] = []
|
|
138
|
-
for a_path_obj in path_objects:
|
|
139
|
-
files += search_for_files_of_interest(path_obj=a_path_obj, suffixes=suffixes)
|
|
140
|
-
return files
|
|
134
|
+
def search_for_files_of_interest(path_obj: Path, suffixes: set[str]) -> list[Path]:
|
|
141
135
|
if path_obj.is_file():
|
|
142
136
|
return [path_obj]
|
|
143
|
-
files: list[
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
files: list[Path] = []
|
|
138
|
+
directories_to_visit: list[Path] = [path_obj]
|
|
139
|
+
while directories_to_visit:
|
|
140
|
+
current_dir = directories_to_visit.pop()
|
|
141
|
+
for entry in current_dir.iterdir():
|
|
142
|
+
if entry.is_dir():
|
|
143
|
+
if entry.name == ".venv":
|
|
144
|
+
continue
|
|
145
|
+
directories_to_visit.append(entry)
|
|
146
|
+
continue
|
|
147
|
+
if entry.suffix not in suffixes:
|
|
148
|
+
continue
|
|
149
|
+
if entry.suffix == ".py" and entry.name == "__init__.py":
|
|
150
|
+
continue
|
|
151
|
+
files.append(entry)
|
|
149
152
|
return files
|
|
150
153
|
|
|
151
154
|
|
|
@@ -160,15 +163,15 @@ def get_choice_file(path: str, suffixes: Optional[set[str]]):
|
|
|
160
163
|
else:
|
|
161
164
|
suffixes = {".py"}
|
|
162
165
|
if not path_obj.exists():
|
|
163
|
-
print(f"🔍 Searching for file matching `{path}` under `{
|
|
164
|
-
choice_file = match_file_name(sub_string=path, search_root=
|
|
166
|
+
print(f"🔍 Searching for file matching `{path}` under `{Path.cwd()}`, but only if suffix matches {suffixes}")
|
|
167
|
+
choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
|
|
165
168
|
elif path_obj.is_dir():
|
|
166
169
|
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
167
170
|
files = search_for_files_of_interest(path_obj, suffixes=suffixes)
|
|
168
171
|
print(f"🔍 Got #{len(files)} results.")
|
|
169
172
|
from machineconfig.utils.options import choose_from_options
|
|
170
173
|
choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
|
|
171
|
-
choice_file =
|
|
174
|
+
choice_file = Path(choice_file)
|
|
172
175
|
else:
|
|
173
176
|
choice_file = path_obj
|
|
174
177
|
return choice_file
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -8,7 +8,7 @@ from machineconfig.utils.terminal import Response
|
|
|
8
8
|
from machineconfig.utils.accessories import pprint, randstr
|
|
9
9
|
from machineconfig.utils.meta import lambda_to_python_script
|
|
10
10
|
UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
|
|
11
|
-
MACHINECONFIG_VERSION = "machineconfig>=7.
|
|
11
|
+
MACHINECONFIG_VERSION = "machineconfig>=7.59"
|
|
12
12
|
DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
|
|
13
13
|
|
|
14
14
|
class SSH:
|