machineconfig 7.49__py3-none-any.whl → 7.64__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 +21 -11
- machineconfig/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +16 -12
- machineconfig/jobs/installer/custom_dev/brave.py +1 -1
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/code.py +4 -1
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -10
- machineconfig/jobs/installer/custom_dev/sysabc.py +119 -0
- machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
- machineconfig/jobs/installer/installer_data.json +739 -25
- machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
- machineconfig/jobs/installer/package_groups.py +49 -83
- machineconfig/logger.py +0 -1
- machineconfig/profile/create_links_export.py +21 -7
- machineconfig/profile/mapper.toml +1 -4
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/croshell.py +20 -43
- machineconfig/scripts/python/devops.py +3 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +53 -39
- machineconfig/scripts/python/ftpx.py +4 -2
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +0 -12
- machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_config.py +3 -19
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
- machineconfig/scripts/python/helpers_devops/cli_self.py +12 -6
- 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 +1 -152
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
- machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -20
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +1 -1
- machineconfig/scripts/python/helpers_repos/clone.py +0 -1
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +9 -3
- 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 +7 -7
- machineconfig/scripts/python/helpers_utils/download.py +151 -0
- machineconfig/scripts/python/helpers_utils/path.py +106 -0
- machineconfig/scripts/python/interactive.py +17 -26
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +21 -5
- 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 +37 -22
- machineconfig/scripts/python/utils.py +8 -3
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/shells/nushell/init.nu +2 -2
- machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
- machineconfig/settings/shells/zsh/init.sh +1 -8
- machineconfig/settings/yazi/init.lua +45 -24
- machineconfig/settings/yazi/keymap_windows.toml +1 -2
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
- machineconfig/setup_linux/__init__.py +0 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -10
- machineconfig/setup_mac/__init__.py +2 -3
- machineconfig/setup_windows/__init__.py +0 -3
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +16 -0
- machineconfig/utils/code.py +2 -2
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/installer_class.py +42 -40
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +61 -101
- machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -68
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +11 -51
- machineconfig/utils/io.py +0 -1
- machineconfig/utils/meta.py +29 -15
- machineconfig/utils/options.py +1 -1
- machineconfig/utils/path_extended.py +40 -19
- machineconfig/utils/path_helper.py +75 -21
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/ssh.py +3 -3
- machineconfig-7.64.dist-info/METADATA +124 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/RECORD +84 -87
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
- machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
- machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig/setup_linux/apps.sh +0 -66
- machineconfig/setup_mac/apps.sh +0 -73
- machineconfig/setup_windows/apps.ps1 +0 -62
- machineconfig-7.49.dist-info/METADATA +0 -92
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
- /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/WHEEL +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/entry_points.txt +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/top_level.txt +0 -0
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
|
|
4
4
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
|
|
5
5
|
|
|
6
|
-
function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
7
|
-
function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
8
|
-
function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
9
|
-
function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
10
|
-
function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
11
|
-
function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
12
|
-
function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
13
|
-
function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
14
|
-
function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
15
|
-
function msearch
|
|
6
|
+
function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" devops $args }
|
|
7
|
+
function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" cloud $args }
|
|
8
|
+
function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" agents $args }
|
|
9
|
+
function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" sessions $args }
|
|
10
|
+
function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" ftpx $args }
|
|
11
|
+
function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" fire $args }
|
|
12
|
+
function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" croshell $args }
|
|
13
|
+
function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" utils $args }
|
|
14
|
+
function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" terminal $args }
|
|
15
|
+
function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" msearch $args }
|
|
16
16
|
|
|
17
17
|
function d { wrap_in_shell_script devops @args }
|
|
18
18
|
function c { wrap_in_shell_script cloud @args }
|
|
@@ -26,3 +26,5 @@ function t { wrap_in_shell_script terminal @args }
|
|
|
26
26
|
function ms { wrap_in_shell_script msearch @args }
|
|
27
27
|
|
|
28
28
|
Write-Host "mcfg command aliases are now defined in this PowerShell session."
|
|
29
|
+
|
|
30
|
+
devops self interactive
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
|
|
3
|
+
# iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
|
|
4
|
+
uv tool install --upgrade --python 3.14 machineconfig both
|
|
5
|
+
|
|
6
|
+
devops install --group sysabc
|
|
7
|
+
|
|
8
|
+
# configs
|
|
9
|
+
devops config copy-assets both
|
|
10
|
+
devops config public --method copy --on-conflict overwrite-default-path --which all
|
|
11
|
+
devops config shell
|
|
12
|
+
devops config shell --which nushell
|
|
13
|
+
|
|
14
|
+
wt # start Windows Terminal to pick up config changes
|
|
15
|
+
devops install --group termabc
|
|
16
|
+
devops install --group gui
|
machineconfig/utils/code.py
CHANGED
|
@@ -132,7 +132,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
|
|
|
132
132
|
op_program_path = Path.home().joinpath("tmp_results", "tmp_scripts", "manual_run", f"manual_script_{randstr()}{suffix}")
|
|
133
133
|
op_program_path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
134
|
op_program_path.write_text(script, encoding="utf-8")
|
|
135
|
-
print_code(script, lexer=lexer, desc="script to run manually")
|
|
135
|
+
print_code(code=script, lexer=lexer, desc="script to run manually")
|
|
136
136
|
console.print("[bold yellow]⚠️ STRICT MODE:[/bold yellow] [cyan]Please run the script manually via your shell by executing:[/cyan]")
|
|
137
137
|
console.print(f"[green]{str(op_program_path)}[/green]")
|
|
138
138
|
console.print("[red]❌ OP_PROGRAM_PATH environment variable is not set in strict mode.[/red]")
|
|
@@ -145,7 +145,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
|
|
|
145
145
|
op_program_path.write_text(script, encoding="utf-8")
|
|
146
146
|
console.print("[cyan]🚀 Handing over to shell script runner via OP_PROGRAM_PATH:[/cyan]")
|
|
147
147
|
console.print(f"[bold green]{str(op_program_path)}[/bold green]")
|
|
148
|
-
print_code(script, lexer="shell", desc="script to run via OP_PROGRAM_PATH")
|
|
148
|
+
print_code(code=script, lexer="shell", desc="script to run via OP_PROGRAM_PATH")
|
|
149
149
|
else:
|
|
150
150
|
if op_program_path is not None and exists:
|
|
151
151
|
console.print(f"[yellow]⚠️ OP_PROGRAM_PATH @ {str(op_program_path)} already exists.[/yellow] [cyan]Falling back to direct execution.[/cyan]")
|
|
@@ -25,7 +25,7 @@ def print_header():
|
|
|
25
25
|
table.add_row("Virtual Environment", os.getenv('VIRTUAL_ENV', 'None'))
|
|
26
26
|
table.add_row("Running @", str(Path.cwd()))
|
|
27
27
|
|
|
28
|
-
from machineconfig.utils.
|
|
28
|
+
from machineconfig.utils.installer_utils.installer_runner import get_machineconfig_version
|
|
29
29
|
|
|
30
30
|
console.print(Panel(table, title=f"[bold blue]✨ 🐊 Machineconfig Shell {get_machineconfig_version()} ✨ Made with 🐍 | Built with ❤️[/bold blue]", border_style="blue"))
|
|
31
31
|
def print_logo(logo: str):
|
|
@@ -44,7 +44,7 @@ def print_logo(logo: str):
|
|
|
44
44
|
_default_art = Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*")))))
|
|
45
45
|
print(_default_art.read_text())
|
|
46
46
|
elif platform.system() in ["Linux", "Darwin"]: # Explicitly handle both Linux and macOS
|
|
47
|
-
from machineconfig.utils.installer_utils.
|
|
47
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import is_executable_in_path
|
|
48
48
|
avail_cowsay = is_executable_in_path("cowsay")
|
|
49
49
|
avail_lolcat = is_executable_in_path("lolcat")
|
|
50
50
|
avail_boxes = is_executable_in_path("boxes")
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
2
|
-
from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended, DECOMPRESS_SUPPORTED_FORMATS
|
|
3
2
|
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
|
|
4
|
-
from machineconfig.utils.installer_utils.
|
|
3
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows, check_tool_exists
|
|
5
4
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData, get_os_name, get_normalized_arch
|
|
6
5
|
|
|
7
6
|
import platform
|
|
@@ -11,6 +10,24 @@ from typing import Optional, Any
|
|
|
11
10
|
from urllib.parse import urlparse
|
|
12
11
|
|
|
13
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)
|
|
29
|
+
|
|
30
|
+
|
|
14
31
|
class Installer:
|
|
15
32
|
def __init__(self, installer_data: InstallerData):
|
|
16
33
|
self.installer_data: InstallerData = installer_data
|
|
@@ -42,10 +59,8 @@ class Installer:
|
|
|
42
59
|
result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
|
|
43
60
|
new_version_cli = result_new.stdout.strip()
|
|
44
61
|
if old_version_cli == new_version_cli:
|
|
45
|
-
# print(f"ℹ️ Same version detected: {old_version_cli}")
|
|
46
62
|
return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
|
|
47
63
|
else:
|
|
48
|
-
# print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
|
|
49
64
|
return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
|
|
50
65
|
except Exception as ex:
|
|
51
66
|
exe_name = self._get_exe_name()
|
|
@@ -69,8 +84,6 @@ class Installer:
|
|
|
69
84
|
desc = package_manager + " installation"
|
|
70
85
|
version_to_be_installed = package_manager + "Latest"
|
|
71
86
|
result = subprocess.run(installer_arch_os, shell=True, capture_output=True, text=False)
|
|
72
|
-
# from machineconfig.utils.code import run_shell_script
|
|
73
|
-
# result = run_shell_script(installer_arch_os)
|
|
74
87
|
success = result.returncode == 0 and result.stderr == "".encode()
|
|
75
88
|
if not success:
|
|
76
89
|
print(f"❌ {desc} failed")
|
|
@@ -80,7 +93,6 @@ class Installer:
|
|
|
80
93
|
print(f"STDERR: {result.stderr}")
|
|
81
94
|
print(f"Return code: {result.returncode}")
|
|
82
95
|
elif installer_arch_os.endswith((".sh", ".py", ".ps1")):
|
|
83
|
-
# search for the script, see which path ends with the script name
|
|
84
96
|
import machineconfig.jobs.installer as module
|
|
85
97
|
from pathlib import Path
|
|
86
98
|
search_root = Path(module.__file__).parent
|
|
@@ -106,9 +118,14 @@ class Installer:
|
|
|
106
118
|
runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
|
|
107
119
|
version_to_be_installed = str(version)
|
|
108
120
|
elif installer_arch_os.startswith("https://"): # its a url to be downloaded
|
|
109
|
-
downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
121
|
+
# downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
122
|
+
from machineconfig.scripts.python.helpers_utils.download import download
|
|
123
|
+
downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
|
|
124
|
+
if downloaded_object is None:
|
|
125
|
+
raise ValueError(f"Failed to download from URL: {installer_arch_os}")
|
|
110
126
|
# object is either a zip containing a binary or a straight out binary.
|
|
111
|
-
|
|
127
|
+
downloaded_object = PathExtended(downloaded_object)
|
|
128
|
+
if downloaded_object.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
112
129
|
downloaded_object = downloaded_object.decompress()
|
|
113
130
|
if downloaded_object.suffix in [".exe", ""]: # likely an executable
|
|
114
131
|
if platform.system() == "Windows":
|
|
@@ -131,26 +148,17 @@ class Installer:
|
|
|
131
148
|
print(f"🔄 Renaming to correct name: {new_exe_name}")
|
|
132
149
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
133
150
|
version_to_be_installed = "downloaded_binary"
|
|
151
|
+
elif downloaded_object.suffix in [".deb"]:
|
|
152
|
+
install_deb_package(downloaded_object)
|
|
153
|
+
version_to_be_installed = "downloaded_deb"
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError(f"Downloaded file is not an executable: {downloaded_object}")
|
|
134
156
|
else:
|
|
135
157
|
raise NotImplementedError(f"CMD installation method not implemented for: {installer_arch_os}")
|
|
136
158
|
else:
|
|
137
159
|
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)
|
|
160
|
+
downloaded, version_to_be_installed = self.binary_download(version=version)
|
|
161
|
+
if str(downloaded).endswith(".deb"): install_deb_package(downloaded)
|
|
154
162
|
else:
|
|
155
163
|
if platform.system() == "Windows":
|
|
156
164
|
exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
|
|
@@ -173,13 +181,13 @@ class Installer:
|
|
|
173
181
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
174
182
|
INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
|
|
175
183
|
INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed or "unknown", encoding="utf-8")
|
|
176
|
-
def
|
|
184
|
+
def binary_download(self, version: Optional[str]) -> tuple[PathExtended, str]:
|
|
177
185
|
exe_name = self._get_exe_name()
|
|
178
186
|
repo_url = self.installer_data["repoURL"]
|
|
179
187
|
# app_name = self.installer_data["appName"]
|
|
180
188
|
download_link: Optional[str] = None
|
|
181
189
|
version_to_be_installed: Optional[str] = None
|
|
182
|
-
if "github" not in repo_url or
|
|
190
|
+
if "github" not in repo_url or (any(ext in repo_url for ext in DECOMPRESS_SUPPORTED_FORMATS)):
|
|
183
191
|
# Direct download URL
|
|
184
192
|
download_link = repo_url
|
|
185
193
|
version_to_be_installed = "predefined_url"
|
|
@@ -200,10 +208,11 @@ class Installer:
|
|
|
200
208
|
assert download_link is not None, "download_link must be set"
|
|
201
209
|
assert version_to_be_installed is not None, "version_to_be_installed must be set"
|
|
202
210
|
downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
|
|
211
|
+
if downloaded.is_dir() and len(downloaded.search("*", r=True)) == 1:
|
|
212
|
+
only_file_in = next(downloaded.glob("*"))
|
|
213
|
+
if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
|
|
214
|
+
downloaded = only_file_in.decompress()
|
|
203
215
|
return downloaded, version_to_be_installed
|
|
204
|
-
|
|
205
|
-
# --------------------------- Arch / template helpers ---------------------------
|
|
206
|
-
|
|
207
216
|
@staticmethod
|
|
208
217
|
def _get_repo_name_from_url(repo_url: str) -> str:
|
|
209
218
|
"""Extract owner/repo from GitHub URL."""
|
|
@@ -218,23 +227,16 @@ class Installer:
|
|
|
218
227
|
def _fetch_github_release_data(repo_name: str, version: Optional[str] = None) -> Optional[dict[str, Any]]:
|
|
219
228
|
"""Fetch release data from GitHub API using requests."""
|
|
220
229
|
import requests
|
|
221
|
-
|
|
222
230
|
try:
|
|
223
|
-
if version and version.lower() != "latest":
|
|
224
|
-
# Fetch specific version
|
|
231
|
+
if version and version.lower() != "latest": # Fetch specific version
|
|
225
232
|
url = f"https://api.github.com/repos/{repo_name}/releases/tags/{version}"
|
|
226
|
-
else:
|
|
227
|
-
# Fetch latest release
|
|
233
|
+
else: # Fetch latest release
|
|
228
234
|
url = f"https://api.github.com/repos/{repo_name}/releases/latest"
|
|
229
|
-
|
|
230
235
|
response = requests.get(url, timeout=30)
|
|
231
|
-
|
|
232
236
|
if response.status_code != 200:
|
|
233
237
|
print(f"❌ Failed to fetch data for {repo_name}: HTTP {response.status_code}")
|
|
234
238
|
return None
|
|
235
|
-
|
|
236
239
|
response_data = response.json()
|
|
237
|
-
|
|
238
240
|
# Check if API returned an error
|
|
239
241
|
if "message" in response_data:
|
|
240
242
|
if "API rate limit exceeded" in response_data.get("message", ""):
|
|
@@ -1,56 +1,14 @@
|
|
|
1
1
|
"""Devops Devapps Install"""
|
|
2
2
|
|
|
3
|
-
from machineconfig.utils.installer import dynamically_extract_installers_system_groups_from_scripts
|
|
4
3
|
import typer
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from rich.table import Table
|
|
8
|
-
from typing import Optional, cast, get_args, Annotated
|
|
9
|
-
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
|
|
10
|
-
|
|
11
|
-
console = Console()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
|
|
15
|
-
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
16
|
-
from difflib import get_close_matches
|
|
17
|
-
close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
18
|
-
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
19
|
-
if close_matches:
|
|
20
|
-
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
21
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
22
|
-
for i, match in enumerate(close_matches, 1):
|
|
23
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
|
|
24
|
-
console.print(table)
|
|
25
|
-
else:
|
|
26
|
-
console.print("📋 Here are some available options:", style="blue")
|
|
27
|
-
# Show first 10 installers as examples
|
|
28
|
-
if len(all_names) > 10:
|
|
29
|
-
sample_names = all_names[:10]
|
|
30
|
-
else:
|
|
31
|
-
sample_names = all_names
|
|
32
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
33
|
-
for i, name in enumerate(sample_names, 1):
|
|
34
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
|
|
35
|
-
console.print(table)
|
|
36
|
-
if len(all_names) > 10:
|
|
37
|
-
console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
|
|
38
|
-
|
|
39
|
-
panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(get_args(PACKAGE_GROUPS))}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
|
|
40
|
-
console.print(panel)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def main_with_parser():
|
|
44
|
-
import typer
|
|
45
|
-
app = typer.Typer()
|
|
46
|
-
app.command()(main)
|
|
47
|
-
app()
|
|
4
|
+
from typing import Optional, Annotated
|
|
5
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
48
6
|
|
|
49
7
|
|
|
50
8
|
def main(
|
|
51
9
|
which: Annotated[Optional[str], typer.Argument(..., help="Comma-separated list of program/groups names to install (if --group flag is set).")] = None,
|
|
52
10
|
group: Annotated[bool, typer.Option(..., "--group", "-g", help="Treat 'which' as a group name. A group is bundle of apps.")] = False,
|
|
53
|
-
interactive: Annotated[bool, typer.Option(..., "--interactive", "-
|
|
11
|
+
interactive: Annotated[bool, typer.Option(..., "--interactive", "-i", help="Interactive selection of programs to install.")] = False,
|
|
54
12
|
) -> None:
|
|
55
13
|
if interactive:
|
|
56
14
|
return install_interactively()
|
|
@@ -62,8 +20,12 @@ def main(
|
|
|
62
20
|
return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
|
|
63
21
|
else:
|
|
64
22
|
if group:
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
65
27
|
typer.echo("❌ You must provide a group name when using the --group/-g option.")
|
|
66
|
-
res =
|
|
28
|
+
res = get_group_name_to_repr()
|
|
67
29
|
console.print("[bold blue]Here are the available groups:[/bold blue]")
|
|
68
30
|
table = Table(show_header=True, header_style="bold magenta")
|
|
69
31
|
table.add_column("Group", style="cyan", no_wrap=True)
|
|
@@ -87,100 +49,99 @@ def main(
|
|
|
87
49
|
raise typer.Exit(1)
|
|
88
50
|
|
|
89
51
|
|
|
90
|
-
def
|
|
52
|
+
def get_group_name_to_repr() -> dict[str, str]:
|
|
91
53
|
# Build category options and maintain a mapping from display text to actual category name
|
|
92
54
|
category_display_to_name: dict[str, str] = {}
|
|
93
55
|
for group_name, group_values in PACKAGE_GROUP2NAMES.items():
|
|
94
56
|
display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
|
|
95
57
|
category_display_to_name[display] = group_name
|
|
96
|
-
options_system = dynamically_extract_installers_system_groups_from_scripts()
|
|
97
|
-
for item in options_system:
|
|
98
|
-
display = f"📦 {item['appName']:<20} -- {item['doc']:<60}"
|
|
99
|
-
category_display_to_name[display] = item['appName']
|
|
100
58
|
return category_display_to_name
|
|
101
59
|
|
|
102
60
|
|
|
103
61
|
def install_interactively():
|
|
104
62
|
from machineconfig.utils.options import choose_from_options
|
|
105
63
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
106
|
-
from machineconfig.utils.
|
|
64
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
107
65
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
66
|
+
from rich.console import Console
|
|
67
|
+
from rich.panel import Panel
|
|
68
|
+
# from rich.table import Table
|
|
108
69
|
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
109
|
-
installer_options = []
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
category_display_to_name = get_static_groups_combined_with_dynamic_groups_extracted()
|
|
114
|
-
options = list(category_display_to_name.keys()) + ["─" * 50] + installer_options
|
|
115
|
-
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", default="📦 essentials", fzf=True)
|
|
70
|
+
installer_options = [Installer(installer_data=x).get_description() for x in installers]
|
|
71
|
+
category_display_to_name = get_group_name_to_repr()
|
|
72
|
+
options = list(category_display_to_name.keys()) + installer_options
|
|
73
|
+
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", fzf=True)
|
|
116
74
|
installation_messages: list[str] = []
|
|
117
75
|
for _an_idx, a_program_name in enumerate(program_names):
|
|
118
|
-
if a_program_name.startswith("─"): # 50 dashes separator
|
|
119
|
-
continue
|
|
120
76
|
if a_program_name.startswith("📦 "):
|
|
121
77
|
category_name = category_display_to_name.get(a_program_name)
|
|
122
78
|
if category_name:
|
|
123
|
-
install_group(package_group=
|
|
79
|
+
install_group(package_group=category_name)
|
|
124
80
|
else:
|
|
125
81
|
installer_idx = installer_options.index(a_program_name)
|
|
126
82
|
an_installer_data = installers[installer_idx]
|
|
127
83
|
status_message = Installer(an_installer_data).install_robust(version=None) # finish the task - this returns a status message, not a command
|
|
128
84
|
installation_messages.append(status_message)
|
|
129
85
|
if installation_messages:
|
|
86
|
+
console = Console()
|
|
87
|
+
|
|
130
88
|
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))
|
|
131
89
|
console.print(panel)
|
|
132
90
|
|
|
133
91
|
|
|
134
92
|
def install_group(package_group: str):
|
|
135
|
-
from machineconfig.utils.
|
|
93
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers, install_bulk
|
|
136
94
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
95
|
+
from rich.console import Console
|
|
96
|
+
from rich.panel import Panel
|
|
97
|
+
# from rich.table import Table
|
|
137
98
|
if package_group in PACKAGE_GROUP2NAMES:
|
|
138
99
|
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
|
+
console = Console()
|
|
139
101
|
console.print(panel)
|
|
140
102
|
installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
|
|
141
103
|
install_bulk(installers_data=installers_)
|
|
142
104
|
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
break
|
|
154
|
-
else:
|
|
155
|
-
console.print(f"❌ [red]Group '{package_group}' not found.[/red]", style="bold")
|
|
156
|
-
_handle_installer_not_found(package_group, all_names=list(PACKAGE_GROUP2NAMES.keys()) + [item['appName'] for item in options_system])
|
|
157
|
-
|
|
105
|
+
console = Console()
|
|
106
|
+
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()
|
|
158
115
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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]
|
|
165
128
|
else:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
_, sub_program = options_system[group_name] # Extract content from tuple
|
|
174
|
-
if sub_program.startswith("#winget"):
|
|
175
|
-
sub_program = sub_program[1:]
|
|
176
|
-
program += "\n" + sub_program
|
|
177
|
-
return program
|
|
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]")
|
|
178
136
|
|
|
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)
|
|
179
139
|
|
|
180
140
|
def install_clis(clis_names: list[str]):
|
|
181
141
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
182
|
-
from machineconfig.utils.
|
|
142
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
183
143
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
144
|
+
from rich.console import Console
|
|
184
145
|
total_messages: list[str] = []
|
|
185
146
|
for a_which in clis_names:
|
|
186
147
|
all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
@@ -196,20 +157,19 @@ def install_clis(clis_names: list[str]):
|
|
|
196
157
|
message = Installer(selected_installer).install_robust(version=None) # finish the task
|
|
197
158
|
total_messages.append(message)
|
|
198
159
|
if total_messages:
|
|
160
|
+
console = Console()
|
|
199
161
|
console.print("\n[bold green]📊 Installation Results:[/bold green]")
|
|
200
162
|
for a_message in total_messages:
|
|
201
163
|
console.print(f"[blue]• {a_message}[/blue]")
|
|
202
164
|
return None
|
|
203
|
-
|
|
204
|
-
|
|
205
165
|
def install_if_missing(which: str):
|
|
206
|
-
from machineconfig.utils.installer_utils.
|
|
166
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
207
167
|
exists = check_tool_exists(which)
|
|
208
168
|
if exists:
|
|
209
169
|
print(f"✅ {which} is already installed.")
|
|
210
170
|
return
|
|
211
171
|
print(f"⏳ {which} not found. Installing...")
|
|
212
|
-
from machineconfig.utils.installer_utils.
|
|
172
|
+
from machineconfig.utils.installer_utils.installer_cli import main
|
|
213
173
|
main(which=which, interactive=False)
|
|
214
174
|
|
|
215
175
|
|
|
@@ -191,71 +191,3 @@ def check_if_installed_already(exe_name: str, version: Optional[str], use_cache:
|
|
|
191
191
|
return ("⚠️ NotInstalled", "None", version or "unknown")
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
def parse_apps_installer_linux(txt: str) -> dict[str, tuple[str, str]]:
|
|
195
|
-
"""Parse Linux shell installation scripts into logical chunks.
|
|
196
|
-
|
|
197
|
-
Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
|
|
198
|
-
mapping block names to (description, shell script content) tuples.
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
|
|
202
|
-
"""
|
|
203
|
-
chunks = txt.split('# --GROUP:')
|
|
204
|
-
res: dict[str, tuple[str, str]] = {}
|
|
205
|
-
|
|
206
|
-
for chunk in chunks[1:]: # Skip first empty chunk before first group
|
|
207
|
-
lines = chunk.split('\n')
|
|
208
|
-
# First line contains the group name and description in format "NAME:DESCRIPTION"
|
|
209
|
-
group_line = lines[0].strip()
|
|
210
|
-
|
|
211
|
-
# Extract group name and description
|
|
212
|
-
if ':' in group_line:
|
|
213
|
-
parts = group_line.split(':', 1) # Split only on first colon
|
|
214
|
-
group_name = parts[0].strip()
|
|
215
|
-
group_description = parts[1].strip() if len(parts) > 1 else ""
|
|
216
|
-
else:
|
|
217
|
-
group_name = group_line
|
|
218
|
-
group_description = ""
|
|
219
|
-
|
|
220
|
-
# Rest is the content
|
|
221
|
-
content = '\n'.join(lines[1:]).strip()
|
|
222
|
-
|
|
223
|
-
if group_name and content:
|
|
224
|
-
res[group_name] = (group_description, content)
|
|
225
|
-
|
|
226
|
-
return res
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def parse_apps_installer_windows(txt: str) -> dict[str, tuple[str, str]]:
|
|
230
|
-
"""Parse Windows PowerShell installation scripts into logical chunks.
|
|
231
|
-
|
|
232
|
-
Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
|
|
233
|
-
mapping block names to (description, PowerShell script content) tuples.
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
|
|
237
|
-
"""
|
|
238
|
-
chunks = txt.split('# --GROUP:')
|
|
239
|
-
res: dict[str, tuple[str, str]] = {}
|
|
240
|
-
|
|
241
|
-
for chunk in chunks[1:]: # Skip first chunk before first group
|
|
242
|
-
lines = chunk.split('\n')
|
|
243
|
-
# First line contains the group name and description in format "NAME:DESCRIPTION"
|
|
244
|
-
group_line = lines[0].strip()
|
|
245
|
-
|
|
246
|
-
# Extract group name and description
|
|
247
|
-
if ':' in group_line:
|
|
248
|
-
parts = group_line.split(':', 1) # Split only on first colon
|
|
249
|
-
group_name = parts[0].strip()
|
|
250
|
-
group_description = parts[1].strip() if len(parts) > 1 else ""
|
|
251
|
-
else:
|
|
252
|
-
group_name = group_line
|
|
253
|
-
group_description = ""
|
|
254
|
-
|
|
255
|
-
# Rest is the content
|
|
256
|
-
content = '\n'.join(lines[1:]).strip()
|
|
257
|
-
|
|
258
|
-
if group_name and content:
|
|
259
|
-
res[group_name] = (group_description, content)
|
|
260
|
-
|
|
261
|
-
return res
|