machineconfig 7.64__py3-none-any.whl → 7.83__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/sessions_managers/utils/maker.py +4 -2
- machineconfig/jobs/installer/custom/yazi.py +120 -0
- machineconfig/jobs/installer/custom_dev/nerdfont.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +26 -12
- machineconfig/jobs/installer/custom_dev/sysabc.py +26 -5
- machineconfig/jobs/installer/installer_data.json +232 -96
- machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
- machineconfig/profile/create_helper.py +0 -12
- machineconfig/profile/create_links_export.py +2 -2
- machineconfig/profile/mapper.toml +2 -2
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/python/agents.py +22 -17
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +1 -1
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +13 -13
- machineconfig/scripts/python/ftpx.py +36 -12
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
- machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
- machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
- machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
- machineconfig/scripts/python/helpers_devops/cli_config.py +10 -0
- machineconfig/scripts/python/helpers_devops/cli_nw.py +90 -10
- machineconfig/scripts/python/helpers_devops/cli_self.py +8 -7
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +7 -7
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +12 -11
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +8 -10
- machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -1
- machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +20 -9
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +2 -2
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +58 -1
- machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +5 -3
- machineconfig/scripts/python/helpers_repos/count_lines.py +40 -11
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_utils/download.py +4 -3
- machineconfig/scripts/python/helpers_utils/path.py +87 -34
- machineconfig/scripts/python/interactive.py +1 -1
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +55 -6
- machineconfig/scripts/python/nw/address.py +132 -0
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -11
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/nushell/config.nu +2 -2
- machineconfig/settings/shells/nushell/env.nu +45 -6
- machineconfig/settings/shells/nushell/init.nu +282 -95
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/yazi/init.lua +4 -0
- machineconfig/settings/yazi/keymap_linux.toml +11 -4
- machineconfig/settings/yazi/theme.toml +4 -0
- machineconfig/settings/yazi/yazi_linux.toml +84 -0
- machineconfig/settings/yazi/yazi_windows.toml +58 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +4 -2
- machineconfig/utils/installer_utils/github_release_bulk.py +104 -62
- machineconfig/utils/installer_utils/install_from_url.py +200 -0
- machineconfig/utils/installer_utils/installer_class.py +25 -74
- machineconfig/utils/installer_utils/installer_cli.py +40 -50
- machineconfig/utils/installer_utils/installer_helper.py +100 -0
- machineconfig/utils/installer_utils/installer_runner.py +5 -8
- machineconfig/utils/links.py +2 -2
- machineconfig/utils/meta.py +2 -2
- machineconfig/utils/options.py +3 -3
- machineconfig/utils/path_extended.py +1 -1
- machineconfig/utils/path_helper.py +0 -1
- machineconfig/utils/ssh.py +143 -409
- machineconfig/utils/ssh_utils/abc.py +8 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
- machineconfig/utils/ssh_utils/utils.py +141 -0
- machineconfig/utils/ssh_utils/wsl.py +210 -0
- machineconfig/utils/upgrade_packages.py +2 -1
- machineconfig/utils/ve.py +11 -4
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/METADATA +2 -2
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/RECORD +96 -89
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/entry_points.txt +2 -2
- machineconfig/scripts/python/explore.py +0 -49
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfag +0 -17
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfrga +0 -21
- machineconfig/scripts/python/helpers_msearch/scripts_linux/skrg +0 -4
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfb.ps1 +0 -3
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfrga.bat +0 -20
- machineconfig/settings/yazi/yazi.toml +0 -17
- machineconfig/setup_linux/others/cli_installation.sh +0 -137
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/WHEEL +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/top_level.txt +0 -0
|
@@ -1,31 +1,16 @@
|
|
|
1
|
+
from machineconfig.utils.installer_utils.installer_helper import install_deb_package
|
|
1
2
|
from machineconfig.utils.path_extended import PathExtended, DECOMPRESS_SUPPORTED_FORMATS
|
|
2
3
|
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
|
|
3
4
|
from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows, check_tool_exists
|
|
4
5
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData, get_os_name, get_normalized_arch
|
|
6
|
+
from machineconfig.utils.installer_utils.github_release_bulk import (
|
|
7
|
+
get_repo_name_from_url,
|
|
8
|
+
get_release_info,
|
|
9
|
+
)
|
|
5
10
|
|
|
6
11
|
import platform
|
|
7
12
|
import subprocess
|
|
8
|
-
import
|
|
9
|
-
from typing import Optional, Any
|
|
10
|
-
from urllib.parse import urlparse
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def install_deb_package(downloaded: PathExtended) -> None:
|
|
15
|
-
print(f"📦 Installing .deb package: {downloaded}")
|
|
16
|
-
assert platform.system() == "Linux"
|
|
17
|
-
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
18
|
-
success = result.returncode == 0 and result.stderr == ""
|
|
19
|
-
if not success:
|
|
20
|
-
desc = "Installing .deb"
|
|
21
|
-
print(f"❌ {desc} failed")
|
|
22
|
-
if result.stdout:
|
|
23
|
-
print(f"STDOUT: {result.stdout}")
|
|
24
|
-
if result.stderr:
|
|
25
|
-
print(f"STDERR: {result.stderr}")
|
|
26
|
-
print(f"Return code: {result.returncode}")
|
|
27
|
-
print("🗑️ Cleaning up .deb package...")
|
|
28
|
-
downloaded.delete(sure=True)
|
|
13
|
+
from typing import Optional
|
|
29
14
|
|
|
30
15
|
|
|
31
16
|
class Installer:
|
|
@@ -79,19 +64,23 @@ class Installer:
|
|
|
79
64
|
version_to_be_installed: str = "unknown" # Initialize to ensure it's always bound
|
|
80
65
|
if repo_url == "CMD":
|
|
81
66
|
if any(pm in installer_arch_os for pm in ["npm ", "pip ", "winget ", "brew ", "curl "]):
|
|
67
|
+
from rich import print as rprint
|
|
68
|
+
from rich.panel import Panel
|
|
69
|
+
from rich.console import Group
|
|
82
70
|
package_manager = installer_arch_os.split(" ", maxsplit=1)[0]
|
|
83
71
|
print(f"📦 Using package manager: {installer_arch_os}")
|
|
84
72
|
desc = package_manager + " installation"
|
|
85
73
|
version_to_be_installed = package_manager + "Latest"
|
|
86
|
-
result = subprocess.run(installer_arch_os, shell=True, capture_output=
|
|
87
|
-
success = result.returncode == 0 and result.stderr == ""
|
|
74
|
+
result = subprocess.run(installer_arch_os, shell=True, capture_output=False, text=True)
|
|
75
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
88
76
|
if not success:
|
|
89
|
-
|
|
77
|
+
sub_panels = []
|
|
90
78
|
if result.stdout:
|
|
91
|
-
|
|
79
|
+
sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
|
|
92
80
|
if result.stderr:
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
|
|
82
|
+
group_content = Group(f"❌ {desc} failed\nReturn code: {result.returncode}", *sub_panels)
|
|
83
|
+
rprint(Panel(group_content, title=desc, style="red"))
|
|
95
84
|
elif installer_arch_os.endswith((".sh", ".py", ".ps1")):
|
|
96
85
|
import machineconfig.jobs.installer as module
|
|
97
86
|
from pathlib import Path
|
|
@@ -117,7 +106,7 @@ class Installer:
|
|
|
117
106
|
import runpy
|
|
118
107
|
runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
|
|
119
108
|
version_to_be_installed = str(version)
|
|
120
|
-
elif installer_arch_os.startswith("https://")
|
|
109
|
+
elif installer_arch_os.startswith("https://") or installer_arch_os.startswith("http://"):
|
|
121
110
|
# downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
122
111
|
from machineconfig.scripts.python.helpers_utils.download import download
|
|
123
112
|
downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
|
|
@@ -213,44 +202,6 @@ class Installer:
|
|
|
213
202
|
if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
|
|
214
203
|
downloaded = only_file_in.decompress()
|
|
215
204
|
return downloaded, version_to_be_installed
|
|
216
|
-
@staticmethod
|
|
217
|
-
def _get_repo_name_from_url(repo_url: str) -> str:
|
|
218
|
-
"""Extract owner/repo from GitHub URL."""
|
|
219
|
-
try:
|
|
220
|
-
parsed = urlparse(repo_url)
|
|
221
|
-
path_parts = parsed.path.strip("/").split("/")
|
|
222
|
-
return f"{path_parts[0]}/{path_parts[1]}"
|
|
223
|
-
except (IndexError, AttributeError):
|
|
224
|
-
return ""
|
|
225
|
-
|
|
226
|
-
@staticmethod
|
|
227
|
-
def _fetch_github_release_data(repo_name: str, version: Optional[str] = None) -> Optional[dict[str, Any]]:
|
|
228
|
-
"""Fetch release data from GitHub API using requests."""
|
|
229
|
-
import requests
|
|
230
|
-
try:
|
|
231
|
-
if version and version.lower() != "latest": # Fetch specific version
|
|
232
|
-
url = f"https://api.github.com/repos/{repo_name}/releases/tags/{version}"
|
|
233
|
-
else: # Fetch latest release
|
|
234
|
-
url = f"https://api.github.com/repos/{repo_name}/releases/latest"
|
|
235
|
-
response = requests.get(url, timeout=30)
|
|
236
|
-
if response.status_code != 200:
|
|
237
|
-
print(f"❌ Failed to fetch data for {repo_name}: HTTP {response.status_code}")
|
|
238
|
-
return None
|
|
239
|
-
response_data = response.json()
|
|
240
|
-
# Check if API returned an error
|
|
241
|
-
if "message" in response_data:
|
|
242
|
-
if "API rate limit exceeded" in response_data.get("message", ""):
|
|
243
|
-
print(f"🚫 Rate limit exceeded for {repo_name}")
|
|
244
|
-
return None
|
|
245
|
-
elif "Not Found" in response_data.get("message", ""):
|
|
246
|
-
print(f"🔍 No releases found for {repo_name}")
|
|
247
|
-
return None
|
|
248
|
-
|
|
249
|
-
return response_data
|
|
250
|
-
|
|
251
|
-
except (requests.RequestException, requests.Timeout, json.JSONDecodeError) as e:
|
|
252
|
-
print(f"❌ Error fetching {repo_name}: {e}")
|
|
253
|
-
return None
|
|
254
205
|
|
|
255
206
|
def get_github_release(self, repo_url: str, version: Optional[str]) -> tuple[Optional[str], Optional[str]]:
|
|
256
207
|
"""
|
|
@@ -262,20 +213,20 @@ class Installer:
|
|
|
262
213
|
filename_pattern = self.installer_data["fileNamePattern"][arch][os_name]
|
|
263
214
|
if filename_pattern is None:
|
|
264
215
|
raise ValueError(f"No fileNamePattern for {self._get_exe_name()} on {os_name} {arch}")
|
|
265
|
-
|
|
266
|
-
if not
|
|
216
|
+
repo_info = get_repo_name_from_url(repo_url)
|
|
217
|
+
if not repo_info:
|
|
267
218
|
print(f"❌ Invalid repository URL: {repo_url}")
|
|
268
219
|
return None, None
|
|
269
|
-
|
|
270
|
-
|
|
220
|
+
username, repository = repo_info
|
|
221
|
+
release_info = get_release_info(username, repository, version)
|
|
222
|
+
if not release_info:
|
|
271
223
|
return None, None
|
|
272
|
-
|
|
273
|
-
actual_version = release_data.get("tag_name", "unknown")
|
|
224
|
+
actual_version = release_info.get("tag_name", "unknown") or "unknown"
|
|
274
225
|
filename = filename_pattern.format(version=actual_version)
|
|
275
226
|
|
|
276
227
|
available_filenames: list[str] = []
|
|
277
|
-
for asset in
|
|
278
|
-
an_dl = asset
|
|
228
|
+
for asset in release_info["assets"]:
|
|
229
|
+
an_dl = asset["browser_download_url"]
|
|
279
230
|
available_filenames.append(an_dl.split("/")[-1])
|
|
280
231
|
if filename not in available_filenames:
|
|
281
232
|
candidates = [
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
"""Devops Devapps Install
|
|
1
|
+
"""Devops Devapps Install
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
sudo apt update && sudo apt install -y \
|
|
5
|
+
git gcc g++ clang \
|
|
6
|
+
yasm nasm pkg-config \
|
|
7
|
+
meson ninja-build \
|
|
8
|
+
autoconf automake libtool \
|
|
9
|
+
libx11-dev libxext-dev libxrandr-dev libxrender-dev libxss-dev \
|
|
10
|
+
libvdpau-dev libgl1-mesa-dev libegl1-mesa-dev libxv-dev \
|
|
11
|
+
libasound2-dev libpulse-dev \
|
|
12
|
+
libfribidi-dev libfreetype-dev libfontconfig1-dev libharfbuzz-dev \
|
|
13
|
+
libjpeg-dev libssl-dev zlib1g-dev python3-pip
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from machineconfig.utils.installer_utils.installer_helper import get_group_name_to_repr
|
|
3
19
|
import typer
|
|
4
|
-
from typing import
|
|
5
|
-
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
20
|
+
from typing import Annotated, Optional
|
|
6
21
|
|
|
7
22
|
|
|
8
|
-
|
|
23
|
+
|
|
24
|
+
def main_installer_cli(
|
|
9
25
|
which: Annotated[Optional[str], typer.Argument(..., help="Comma-separated list of program/groups names to install (if --group flag is set).")] = None,
|
|
10
26
|
group: Annotated[bool, typer.Option(..., "--group", "-g", help="Treat 'which' as a group name. A group is bundle of apps.")] = False,
|
|
11
27
|
interactive: Annotated[bool, typer.Option(..., "--interactive", "-i", help="Interactive selection of programs to install.")] = False,
|
|
@@ -25,6 +41,7 @@ def main(
|
|
|
25
41
|
console = Console()
|
|
26
42
|
|
|
27
43
|
typer.echo("❌ You must provide a group name when using the --group/-g option.")
|
|
44
|
+
from machineconfig.utils.installer_utils.installer_helper import get_group_name_to_repr
|
|
28
45
|
res = get_group_name_to_repr()
|
|
29
46
|
console.print("[bold blue]Here are the available groups:[/bold blue]")
|
|
30
47
|
table = Table(show_header=True, header_style="bold magenta")
|
|
@@ -49,13 +66,7 @@ def main(
|
|
|
49
66
|
raise typer.Exit(1)
|
|
50
67
|
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
# Build category options and maintain a mapping from display text to actual category name
|
|
54
|
-
category_display_to_name: dict[str, str] = {}
|
|
55
|
-
for group_name, group_values in PACKAGE_GROUP2NAMES.items():
|
|
56
|
-
display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
|
|
57
|
-
category_display_to_name[display] = group_name
|
|
58
|
-
return category_display_to_name
|
|
69
|
+
|
|
59
70
|
|
|
60
71
|
|
|
61
72
|
def install_interactively():
|
|
@@ -95,6 +106,7 @@ def install_group(package_group: str):
|
|
|
95
106
|
from rich.console import Console
|
|
96
107
|
from rich.panel import Panel
|
|
97
108
|
# from rich.table import Table
|
|
109
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
98
110
|
if package_group in PACKAGE_GROUP2NAMES:
|
|
99
111
|
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))
|
|
100
112
|
console = Console()
|
|
@@ -104,55 +116,34 @@ def install_group(package_group: str):
|
|
|
104
116
|
return
|
|
105
117
|
console = Console()
|
|
106
118
|
console.print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
|
|
107
|
-
def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
|
|
108
|
-
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
109
|
-
from difflib import get_close_matches
|
|
110
|
-
from rich.console import Console
|
|
111
|
-
from rich.panel import Panel
|
|
112
|
-
from rich.table import Table
|
|
113
|
-
close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
114
|
-
console = Console()
|
|
115
|
-
|
|
116
|
-
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
117
|
-
if close_matches:
|
|
118
|
-
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
119
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
120
|
-
for i, match in enumerate(close_matches, 1):
|
|
121
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
|
|
122
|
-
console.print(table)
|
|
123
|
-
else:
|
|
124
|
-
console.print("📋 Here are some available options:", style="blue")
|
|
125
|
-
# Show first 10 installers as examples
|
|
126
|
-
if len(all_names) > 10:
|
|
127
|
-
sample_names = all_names[:10]
|
|
128
|
-
else:
|
|
129
|
-
sample_names = all_names
|
|
130
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
131
|
-
for i, name in enumerate(sample_names, 1):
|
|
132
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
|
|
133
|
-
console.print(table)
|
|
134
|
-
if len(all_names) > 10:
|
|
135
|
-
console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
|
|
136
119
|
|
|
137
|
-
panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
|
|
138
|
-
console.print(panel)
|
|
139
120
|
|
|
140
121
|
def install_clis(clis_names: list[str]):
|
|
141
122
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
142
123
|
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
143
124
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
144
125
|
from rich.console import Console
|
|
126
|
+
all_installers_data = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
145
127
|
total_messages: list[str] = []
|
|
146
|
-
for
|
|
147
|
-
|
|
128
|
+
for a_cli_name in clis_names:
|
|
129
|
+
if "github.com" in a_cli_name.lower():
|
|
130
|
+
from machineconfig.utils.installer_utils.install_from_url import install_from_github_url
|
|
131
|
+
install_from_github_url(github_url=a_cli_name)
|
|
132
|
+
continue
|
|
133
|
+
elif a_cli_name.startswith("https://") or a_cli_name.startswith("http://"):
|
|
134
|
+
print(f"⏳ Installing from binary URL: {a_cli_name} ...")
|
|
135
|
+
from machineconfig.utils.installer_utils.install_from_url import install_from_binary_url
|
|
136
|
+
install_from_binary_url(a_cli_name)
|
|
137
|
+
continue
|
|
148
138
|
selected_installer = None
|
|
149
|
-
for installer in
|
|
139
|
+
for installer in all_installers_data:
|
|
150
140
|
app_name = installer["appName"]
|
|
151
|
-
if app_name.lower() ==
|
|
141
|
+
if app_name.lower() == a_cli_name.lower():
|
|
152
142
|
selected_installer = installer
|
|
153
143
|
break
|
|
154
144
|
if selected_installer is None:
|
|
155
|
-
|
|
145
|
+
from machineconfig.utils.installer_utils.installer_helper import handle_installer_not_found
|
|
146
|
+
handle_installer_not_found(a_cli_name, all_installers_data)
|
|
156
147
|
return None
|
|
157
148
|
message = Installer(selected_installer).install_robust(version=None) # finish the task
|
|
158
149
|
total_messages.append(message)
|
|
@@ -169,13 +160,12 @@ def install_if_missing(which: str):
|
|
|
169
160
|
print(f"✅ {which} is already installed.")
|
|
170
161
|
return
|
|
171
162
|
print(f"⏳ {which} not found. Installing...")
|
|
172
|
-
from machineconfig.utils.installer_utils.installer_cli import
|
|
173
|
-
|
|
163
|
+
from machineconfig.utils.installer_utils.installer_cli import main_installer_cli
|
|
164
|
+
main_installer_cli(which=which, interactive=False)
|
|
174
165
|
|
|
175
166
|
|
|
176
167
|
if __name__ == "__main__":
|
|
177
168
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData
|
|
178
169
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
179
|
-
|
|
180
170
|
_ = InstallerData, Installer
|
|
181
171
|
pass
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
2
|
+
from machineconfig.utils.schemas.installer.installer_types import InstallerData
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_group_name_to_repr() -> dict[str, str]:
|
|
7
|
+
# Build category options and maintain a mapping from display text to actual category name
|
|
8
|
+
category_display_to_name: dict[str, str] = {}
|
|
9
|
+
for group_name, group_values in PACKAGE_GROUP2NAMES.items():
|
|
10
|
+
display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
|
|
11
|
+
category_display_to_name[display] = group_name
|
|
12
|
+
return category_display_to_name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_installer_not_found(search_term: str, app_apps: list[InstallerData]) -> None: # type: ignore
|
|
16
|
+
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
17
|
+
from difflib import get_close_matches
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
all_names = sorted([inst["appName"] for inst in app_apps])
|
|
22
|
+
name_to_doc = {inst["appName"]: inst["doc"] for inst in app_apps}
|
|
23
|
+
all_descriptions = {f"{inst['appName']}: {inst['doc']}": inst["appName"] for inst in app_apps}
|
|
24
|
+
|
|
25
|
+
close_name_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
26
|
+
close_description_matches = get_close_matches(search_term, list(all_descriptions.keys()), n=5, cutoff=0.4)
|
|
27
|
+
|
|
28
|
+
search_lower = search_term.lower()
|
|
29
|
+
substring_matches = [
|
|
30
|
+
inst["appName"]
|
|
31
|
+
for inst in app_apps
|
|
32
|
+
if search_lower in inst["appName"].lower() or search_lower in inst["doc"].lower()
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
ordered_matches: list[str] = list(
|
|
36
|
+
dict.fromkeys(
|
|
37
|
+
close_name_matches
|
|
38
|
+
+ [all_descriptions[desc] for desc in close_description_matches]
|
|
39
|
+
+ substring_matches
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
top_matches = ordered_matches[:10]
|
|
43
|
+
console = Console()
|
|
44
|
+
|
|
45
|
+
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
46
|
+
if top_matches:
|
|
47
|
+
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
48
|
+
table = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
|
|
49
|
+
table.add_column("#", justify="right", width=3)
|
|
50
|
+
table.add_column("Installer", style="green")
|
|
51
|
+
table.add_column("Description", style="dim", overflow="fold")
|
|
52
|
+
for i, match in enumerate(top_matches, 1):
|
|
53
|
+
table.add_row(f"[cyan]{i}[/cyan]", match, name_to_doc.get(match, ""))
|
|
54
|
+
console.print(table)
|
|
55
|
+
else:
|
|
56
|
+
console.print("📋 Here are some available options:", style="blue")
|
|
57
|
+
# Show first 10 installers as examples
|
|
58
|
+
if len(all_names) > 10:
|
|
59
|
+
sample_names = all_names[:10]
|
|
60
|
+
else:
|
|
61
|
+
sample_names = all_names
|
|
62
|
+
table = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
|
|
63
|
+
table.add_column("#", justify="right", width=3)
|
|
64
|
+
table.add_column("Installer", style="green")
|
|
65
|
+
table.add_column("Description", style="dim", overflow="fold")
|
|
66
|
+
for i, name in enumerate(sample_names, 1):
|
|
67
|
+
table.add_row(f"[cyan]{i}[/cyan]", name, name_to_doc.get(name, ""))
|
|
68
|
+
console.print(table)
|
|
69
|
+
if len(all_names) > 10:
|
|
70
|
+
console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
|
|
71
|
+
|
|
72
|
+
panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
|
|
73
|
+
console.print(panel)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def install_deb_package(downloaded: Path) -> None:
|
|
77
|
+
from rich import print as rprint
|
|
78
|
+
from rich.panel import Panel
|
|
79
|
+
print(f"📦 Installing .deb package: {downloaded}")
|
|
80
|
+
import platform
|
|
81
|
+
import subprocess
|
|
82
|
+
assert platform.system() == "Linux"
|
|
83
|
+
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
84
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
85
|
+
if not success:
|
|
86
|
+
from rich.console import Group
|
|
87
|
+
desc = "Installing .deb"
|
|
88
|
+
sub_panels = []
|
|
89
|
+
if result.stdout:
|
|
90
|
+
sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
|
|
91
|
+
if result.stderr:
|
|
92
|
+
sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
|
|
93
|
+
group_content = Group(f"❌ {desc} failed\nReturn code: {result.returncode}", *sub_panels)
|
|
94
|
+
rprint(Panel(group_content, title=desc, style="red"))
|
|
95
|
+
print("🗑️ Cleaning up .deb package...")
|
|
96
|
+
if downloaded.is_file():
|
|
97
|
+
downloaded.unlink(missing_ok=True)
|
|
98
|
+
elif downloaded.is_dir():
|
|
99
|
+
import shutil
|
|
100
|
+
shutil.rmtree(downloaded, ignore_errors=True)
|
|
@@ -92,7 +92,11 @@ def get_installed_cli_apps():
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: Optional[list[str]]) -> list[InstallerData]:
|
|
95
|
-
|
|
95
|
+
import machineconfig.jobs.installer as module
|
|
96
|
+
from pathlib import Path
|
|
97
|
+
res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
|
|
98
|
+
res_all: list[InstallerData] = res_raw["installers"]
|
|
99
|
+
|
|
96
100
|
acceptable_apps_names: list[str] | None = None
|
|
97
101
|
if which_cats is not None:
|
|
98
102
|
acceptable_apps_names = []
|
|
@@ -116,13 +120,6 @@ def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: O
|
|
|
116
120
|
return all_installers
|
|
117
121
|
|
|
118
122
|
|
|
119
|
-
def get_all_installer_data_files() -> list[InstallerData]:
|
|
120
|
-
import machineconfig.jobs.installer as module
|
|
121
|
-
from pathlib import Path
|
|
122
|
-
res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
|
|
123
|
-
res_final: list[InstallerData] = res_raw["installers"]
|
|
124
|
-
return res_final
|
|
125
|
-
|
|
126
123
|
|
|
127
124
|
def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs: int = 10, fresh: bool = False):
|
|
128
125
|
print("🚀 BULK INSTALLATION PROCESS 🚀")
|
machineconfig/utils/links.py
CHANGED
|
@@ -164,7 +164,7 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
164
164
|
else:
|
|
165
165
|
# Files are different, use on_conflict strategy
|
|
166
166
|
import subprocess
|
|
167
|
-
command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
|
|
167
|
+
command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
|
|
168
168
|
try:
|
|
169
169
|
console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
|
|
170
170
|
subprocess.run(command, shell=True, check=True)
|
|
@@ -293,7 +293,7 @@ def copy_map(config_file_default_path: PathExtended, self_managed_config_file_pa
|
|
|
293
293
|
else:
|
|
294
294
|
# Files are different, use on_conflict strategy
|
|
295
295
|
import subprocess
|
|
296
|
-
command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
|
|
296
|
+
command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
|
|
297
297
|
try:
|
|
298
298
|
console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
|
|
299
299
|
subprocess.run(command, shell=True, check=True)
|
machineconfig/utils/meta.py
CHANGED
|
@@ -29,7 +29,8 @@ except (ImportError, ModuleNotFoundError) as ex:
|
|
|
29
29
|
return txt
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def lambda_to_python_script(lmb: Callable[[], Any],
|
|
32
|
+
def lambda_to_python_script(lmb: Callable[[], Any],
|
|
33
|
+
in_global: bool, import_module: bool) -> str:
|
|
33
34
|
"""
|
|
34
35
|
caveats: always use keyword arguments in the lambda call for best results.
|
|
35
36
|
return statement not allowed in the wrapped function (otherwise it can be put in the global space)
|
|
@@ -250,7 +251,6 @@ if __name__ == "__main__":
|
|
|
250
251
|
import_code_robust = "<import_code_robust>"
|
|
251
252
|
res = lambda_to_python_script(
|
|
252
253
|
lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
|
|
253
|
-
# in_global=True, import_module=False
|
|
254
254
|
in_global=True, import_module=False
|
|
255
255
|
)
|
|
256
256
|
print(res)
|
machineconfig/utils/options.py
CHANGED
|
@@ -14,10 +14,10 @@ from typing import Optional, Union, Iterable, overload, Literal, cast
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@overload
|
|
17
|
-
def choose_from_options[T](
|
|
17
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
|
|
18
18
|
@overload
|
|
19
|
-
def choose_from_options[T](
|
|
20
|
-
def choose_from_options[T](
|
|
19
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> list[T]: ...
|
|
20
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> Union[T, list[T]]:
|
|
21
21
|
# TODO: replace with https://github.com/tmbo/questionary
|
|
22
22
|
# # also see https://github.com/charmbracelet/gum
|
|
23
23
|
options_strings: list[str] = [str(x) for x in options]
|
|
@@ -711,7 +711,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
711
711
|
:return: pathlib.Path pointing to the destination directory where contents were extracted
|
|
712
712
|
:raises: FileNotFoundError if archive does not exist; py7zr.Bad7zFile or other error if extraction fails
|
|
713
713
|
"""
|
|
714
|
-
import py7zr
|
|
714
|
+
import py7zr # type: ignore
|
|
715
715
|
import tempfile
|
|
716
716
|
from pathlib import Path
|
|
717
717
|
archive_path_obj = Path(archive_path)
|