machineconfig 7.53__py3-none-any.whl → 7.69__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 -9
- machineconfig/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +15 -12
- 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 +39 -34
- machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
- machineconfig/jobs/installer/installer_data.json +103 -35
- machineconfig/jobs/installer/package_groups.py +28 -13
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +3 -4
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +17 -15
- machineconfig/scripts/python/ftpx.py +13 -11
- machineconfig/scripts/python/helpers/ast_search.py +74 -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_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
- machineconfig/scripts/python/helpers_devops/cli_self.py +7 -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 +2 -73
- 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_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +13 -5
- 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 +152 -0
- machineconfig/scripts/python/helpers_utils/path.py +4 -2
- machineconfig/scripts/python/interactive.py +11 -14
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +21 -2
- 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 +35 -20
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -10
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
- machineconfig/settings/shells/zsh/init.sh +0 -7
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
- machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -11
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -11
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +4 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +6 -4
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/install_from_url.py +180 -0
- machineconfig/utils/installer_utils/installer_class.py +56 -46
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +71 -65
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
- machineconfig/utils/meta.py +28 -15
- machineconfig/utils/options.py +4 -4
- machineconfig/utils/path_extended.py +40 -19
- machineconfig/utils/path_helper.py +33 -31
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/ssh.py +330 -99
- machineconfig/utils/ve.py +11 -4
- machineconfig-7.69.dist-info/METADATA +124 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/RECORD +85 -83
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/entry_points.txt +2 -2
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/scripts/python/explore.py +0 -49
- machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig-7.53.dist-info/METADATA +0 -94
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/WHEEL +0 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/top_level.txt +0 -0
|
@@ -7,4 +7,11 @@ if (-not (Test-Path -Path "$HOME\.local\bin\uv.exe")) {
|
|
|
7
7
|
Write-Output "uv binary found, updating..."
|
|
8
8
|
& "$HOME\.local\bin\uv.exe" self update
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
# `C:\Users\aalsaf01\.local\bin` is not on your PATH. To use installed Python executables, run `$env:PATH = "C:\Users\aalsaf01\.local\bin;$env:PATH"` or `uv python update-shell`.
|
|
12
|
+
& "$env:USERPROFILE\.local\bin\uv.exe" python update-shell
|
|
13
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
|
|
14
|
+
|
|
15
|
+
# & "$HOME\.local\bin\uv.exe" python install 3.14
|
|
16
|
+
uv python install 3.14
|
|
17
|
+
|
|
@@ -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 { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
6
|
+
function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" devops $args }
|
|
7
|
+
function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" cloud $args }
|
|
8
|
+
function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" agents $args }
|
|
9
|
+
function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" sessions $args }
|
|
10
|
+
function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" ftpx $args }
|
|
11
|
+
function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" fire $args }
|
|
12
|
+
function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" croshell $args }
|
|
13
|
+
function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" utils $args }
|
|
14
|
+
function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" terminal $args }
|
|
15
|
+
function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.69" msearch $args }
|
|
16
16
|
|
|
17
17
|
function d { wrap_in_shell_script devops @args }
|
|
18
18
|
function c { wrap_in_shell_script cloud @args }
|
|
@@ -25,5 +25,6 @@ function u { wrap_in_shell_script utils @args }
|
|
|
25
25
|
function t { wrap_in_shell_script terminal @args }
|
|
26
26
|
function ms { wrap_in_shell_script msearch @args }
|
|
27
27
|
|
|
28
|
-
devops self
|
|
29
28
|
Write-Host "mcfg command aliases are now defined in this PowerShell session."
|
|
29
|
+
|
|
30
|
+
devops self interactive
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
|
|
3
3
|
# iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
uv tool install --upgrade --python 3.14 machineconfig
|
|
5
6
|
|
|
6
7
|
devops install --group sysabc
|
|
7
8
|
|
|
@@ -12,4 +13,5 @@ devops config shell
|
|
|
12
13
|
devops config shell --which nushell
|
|
13
14
|
|
|
14
15
|
wt # start Windows Terminal to pick up config changes
|
|
15
|
-
devops install --group termabc
|
|
16
|
+
devops install --group termabc
|
|
17
|
+
devops install --group gui
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
1
|
from typing import Optional, Any
|
|
3
2
|
|
|
4
3
|
from datetime import datetime, timezone, timedelta
|
|
@@ -96,14 +95,18 @@ def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
|
|
|
96
95
|
return result
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
def get_repo_root(path: Path) -> Optional[Path]:
|
|
98
|
+
def get_repo_root(path: "Path") -> Optional["Path"]:
|
|
100
99
|
from git import Repo, InvalidGitRepositoryError
|
|
101
|
-
|
|
102
100
|
try:
|
|
103
|
-
repo = Repo(
|
|
101
|
+
repo = Repo(path, search_parent_directories=True)
|
|
104
102
|
root = repo.working_tree_dir
|
|
105
103
|
if root is not None:
|
|
104
|
+
from pathlib import Path
|
|
106
105
|
return Path(root)
|
|
107
106
|
except InvalidGitRepositoryError:
|
|
108
107
|
pass
|
|
109
108
|
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
from pathlib import Path
|
machineconfig/utils/code.py
CHANGED
|
@@ -38,14 +38,16 @@ def get_uv_command_executing_python_script(python_script: str, uv_with: Optional
|
|
|
38
38
|
uv_project_dir_arg = "--project" + f' "{uv_project_dir}"'
|
|
39
39
|
else:
|
|
40
40
|
uv_project_dir_arg = ""
|
|
41
|
-
print_code_string = lambda_to_python_script(lambda: print_code(code=python_script, lexer="python", desc="Temporary Python Script", subtitle="Executing via shell script"),
|
|
41
|
+
print_code_string = lambda_to_python_script(lambda: print_code(code=python_script, lexer="python", desc="Temporary Python Script", subtitle="Executing via shell script"),
|
|
42
|
+
in_global=True, import_module=False)
|
|
42
43
|
python_file.write_text(print_code_string + "\n" + python_script, encoding="utf-8")
|
|
43
44
|
shell_script = f"""uv run {uv_with_arg} {uv_project_dir_arg} {str(python_file)} """
|
|
44
45
|
return shell_script, python_file
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def run_lambda_function(lmb: Callable[[], Any], uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> None:
|
|
48
|
-
code = lambda_to_python_script(lmb,
|
|
49
|
+
code = lambda_to_python_script(lmb,
|
|
50
|
+
in_global=True, import_module=False)
|
|
49
51
|
uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=uv_with, uv_project_dir=uv_project_dir)
|
|
50
52
|
run_shell_script(uv_command)
|
|
51
53
|
def run_python_script_in_marimo(py_script: str, uv_project_with: Optional[str]):
|
|
@@ -132,7 +134,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
|
|
|
132
134
|
op_program_path = Path.home().joinpath("tmp_results", "tmp_scripts", "manual_run", f"manual_script_{randstr()}{suffix}")
|
|
133
135
|
op_program_path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
136
|
op_program_path.write_text(script, encoding="utf-8")
|
|
135
|
-
print_code(script, lexer=lexer, desc="script to run manually")
|
|
137
|
+
print_code(code=script, lexer=lexer, desc="script to run manually")
|
|
136
138
|
console.print("[bold yellow]⚠️ STRICT MODE:[/bold yellow] [cyan]Please run the script manually via your shell by executing:[/cyan]")
|
|
137
139
|
console.print(f"[green]{str(op_program_path)}[/green]")
|
|
138
140
|
console.print("[red]❌ OP_PROGRAM_PATH environment variable is not set in strict mode.[/red]")
|
|
@@ -145,7 +147,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
|
|
|
145
147
|
op_program_path.write_text(script, encoding="utf-8")
|
|
146
148
|
console.print("[cyan]🚀 Handing over to shell script runner via OP_PROGRAM_PATH:[/cyan]")
|
|
147
149
|
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")
|
|
150
|
+
print_code(code=script, lexer="shell", desc="script to run via OP_PROGRAM_PATH")
|
|
149
151
|
else:
|
|
150
152
|
if op_program_path is not None and exists:
|
|
151
153
|
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")
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from typing import Optional, TypeAlias, cast
|
|
8
|
+
|
|
9
|
+
from machineconfig.utils.installer_utils.installer_class import install_deb_package
|
|
10
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows
|
|
11
|
+
from machineconfig.utils.path_extended import DECOMPRESS_SUPPORTED_FORMATS, PathExtended
|
|
12
|
+
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
|
|
13
|
+
|
|
14
|
+
SUPPORTED_GITHUB_HOSTS = {"github.com", "www.github.com"}
|
|
15
|
+
|
|
16
|
+
GitHubAsset: TypeAlias = dict[str, object]
|
|
17
|
+
GitHubRelease: TypeAlias = dict[str, object]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _extract_repo_name(github_url: str) -> str:
|
|
21
|
+
parsed = urlparse(github_url)
|
|
22
|
+
parts = [part for part in parsed.path.strip("/").split("/") if part]
|
|
23
|
+
if len(parts) < 2:
|
|
24
|
+
return ""
|
|
25
|
+
owner, repo = parts[0], parts[1]
|
|
26
|
+
if repo == "":
|
|
27
|
+
return ""
|
|
28
|
+
return f"{owner}/{repo}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _fetch_latest_release(repo_name: str) -> Optional[GitHubRelease]:
|
|
32
|
+
import json
|
|
33
|
+
import requests
|
|
34
|
+
try:
|
|
35
|
+
response = requests.get(f"https://api.github.com/repos/{repo_name}/releases/latest", timeout=30)
|
|
36
|
+
except requests.RequestException:
|
|
37
|
+
return None
|
|
38
|
+
if response.status_code != 200:
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
data = response.json()
|
|
42
|
+
except json.JSONDecodeError:
|
|
43
|
+
return None
|
|
44
|
+
if not isinstance(data, dict):
|
|
45
|
+
return None
|
|
46
|
+
return cast(GitHubRelease, data)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _format_size(size_bytes: int) -> str:
|
|
50
|
+
if size_bytes <= 0:
|
|
51
|
+
return "0 B"
|
|
52
|
+
units = ("B", "KiB", "MiB", "GiB", "TiB")
|
|
53
|
+
value = float(size_bytes)
|
|
54
|
+
index = 0
|
|
55
|
+
while value >= 1024 and index < len(units) - 1:
|
|
56
|
+
value /= 1024
|
|
57
|
+
index += 1
|
|
58
|
+
return f"{value:.1f} {units[index]}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _derive_tool_name(repo_name: str, asset_name: str) -> str:
|
|
62
|
+
repo_segment = repo_name.split("/", maxsplit=1)[-1]
|
|
63
|
+
repo_clean = repo_segment.replace(".git", "").lower()
|
|
64
|
+
repo_filtered = "".join(char for char in repo_clean if char.isalnum())
|
|
65
|
+
if repo_filtered:
|
|
66
|
+
return repo_filtered
|
|
67
|
+
asset_clean = asset_name.lower()
|
|
68
|
+
asset_filtered = "".join(char for char in asset_clean if char.isalnum())
|
|
69
|
+
if asset_filtered:
|
|
70
|
+
return asset_filtered
|
|
71
|
+
return "githubapp"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def install_from_github_url(github_url: str) -> None:
|
|
75
|
+
from machineconfig.utils.options import choose_from_options
|
|
76
|
+
from rich.console import Console
|
|
77
|
+
from rich.panel import Panel
|
|
78
|
+
|
|
79
|
+
console = Console()
|
|
80
|
+
repo_name = _extract_repo_name(github_url)
|
|
81
|
+
if repo_name == "":
|
|
82
|
+
console.print(Panel(f"Invalid GitHub URL: {github_url}", title="❌ Error", border_style="red"))
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
console.print(Panel(f"Fetching latest release for [green]{repo_name}[/green]", title="🌐 GitHub", border_style="blue"))
|
|
85
|
+
release_raw = _fetch_latest_release(repo_name)
|
|
86
|
+
if not release_raw:
|
|
87
|
+
console.print(Panel("No releases available for this repository.", title="❌ Error", border_style="red"))
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
release = release_raw
|
|
90
|
+
assets_value = release.get("assets", [])
|
|
91
|
+
assets: list[GitHubAsset] = []
|
|
92
|
+
if isinstance(assets_value, list):
|
|
93
|
+
for asset in assets_value:
|
|
94
|
+
if isinstance(asset, dict):
|
|
95
|
+
typed_asset: GitHubAsset = {}
|
|
96
|
+
name_value = asset.get("name")
|
|
97
|
+
url_value = asset.get("browser_download_url")
|
|
98
|
+
size_value = asset.get("size")
|
|
99
|
+
content_value = asset.get("content_type")
|
|
100
|
+
if isinstance(name_value, str):
|
|
101
|
+
typed_asset["name"] = name_value
|
|
102
|
+
if isinstance(url_value, str):
|
|
103
|
+
typed_asset["browser_download_url"] = url_value
|
|
104
|
+
if isinstance(size_value, int):
|
|
105
|
+
typed_asset["size"] = size_value
|
|
106
|
+
if isinstance(content_value, str):
|
|
107
|
+
typed_asset["content_type"] = content_value
|
|
108
|
+
assets.append(typed_asset)
|
|
109
|
+
if not assets:
|
|
110
|
+
console.print(Panel("No downloadable assets found in the latest release.", title="❌ Error", border_style="red"))
|
|
111
|
+
raise typer.Exit(1)
|
|
112
|
+
binary_assets = assets
|
|
113
|
+
selection_pool = binary_assets if binary_assets else assets
|
|
114
|
+
if not selection_pool:
|
|
115
|
+
console.print(Panel("No assets available for installation.", title="❌ Error", border_style="red"))
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
options_map: dict[str, GitHubAsset] = {}
|
|
118
|
+
for asset in selection_pool:
|
|
119
|
+
name = asset.get("name")
|
|
120
|
+
download_url = asset.get("browser_download_url")
|
|
121
|
+
if not isinstance(name, str) or not isinstance(download_url, str) or name == "" or download_url == "":
|
|
122
|
+
continue
|
|
123
|
+
size_value = asset.get("size")
|
|
124
|
+
size = size_value if isinstance(size_value, int) else 0
|
|
125
|
+
label = f"{name} [{_format_size(size)}]"
|
|
126
|
+
options_map[label] = asset
|
|
127
|
+
if not options_map:
|
|
128
|
+
console.print(Panel("Release assets lack download URLs.", title="❌ Error", border_style="red"))
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
selection_label = choose_from_options(options=list(options_map.keys()), msg="Select a release asset", multi=False, header="📦 GitHub Release Assets", fzf=True)
|
|
131
|
+
selected_asset = options_map[selection_label]
|
|
132
|
+
download_url_value = selected_asset.get("browser_download_url")
|
|
133
|
+
asset_name_value = selected_asset.get("name")
|
|
134
|
+
if not isinstance(download_url_value, str) or download_url_value == "":
|
|
135
|
+
console.print(Panel("Selected asset lacks a download URL.", title="❌ Error", border_style="red"))
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
asset_name = asset_name_value if isinstance(asset_name_value, str) else "github_binary"
|
|
138
|
+
version_value = release.get("tag_name")
|
|
139
|
+
version = version_value if isinstance(version_value, str) and version_value != "" else "latest"
|
|
140
|
+
console.print(Panel(f"Downloading [cyan]{asset_name}[/cyan]", title="⬇️ Download", border_style="magenta"))
|
|
141
|
+
archive_path = PathExtended(download_url_value).download(folder=INSTALL_TMP_DIR)
|
|
142
|
+
extracted_path = archive_path
|
|
143
|
+
if extracted_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
144
|
+
extracted_path = archive_path.decompress()
|
|
145
|
+
archive_path.delete(sure=True)
|
|
146
|
+
if extracted_path.is_dir():
|
|
147
|
+
nested_items = list(extracted_path.glob("*"))
|
|
148
|
+
if len(nested_items) == 1:
|
|
149
|
+
nested_path = PathExtended(nested_items[0])
|
|
150
|
+
if nested_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
151
|
+
extracted_path = nested_path.decompress()
|
|
152
|
+
nested_path.delete(sure=True)
|
|
153
|
+
if extracted_path.suffix == ".deb":
|
|
154
|
+
install_deb_package(extracted_path)
|
|
155
|
+
tool_name_deb = _derive_tool_name(repo_name, asset_name)
|
|
156
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name_deb).parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name_deb).write_text(version, encoding="utf-8")
|
|
158
|
+
console.print(Panel(f"Installed Debian package for [green]{tool_name_deb}[/green]", title="✅ Complete", border_style="green"))
|
|
159
|
+
return
|
|
160
|
+
system_name = platform.system()
|
|
161
|
+
tool_name = _derive_tool_name(repo_name, asset_name)
|
|
162
|
+
rename_target = f"{tool_name}.exe" if system_name == "Windows" else tool_name
|
|
163
|
+
try:
|
|
164
|
+
if system_name == "Windows":
|
|
165
|
+
installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=tool_name, delete=True, rename_to=rename_target)
|
|
166
|
+
elif system_name in {"Linux", "Darwin"}:
|
|
167
|
+
installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name=tool_name, delete=True, rename_to=rename_target)
|
|
168
|
+
else:
|
|
169
|
+
console.print(Panel(f"Unsupported operating system: {system_name}", title="❌ Error", border_style="red"))
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
except IndexError:
|
|
172
|
+
if system_name == "Windows":
|
|
173
|
+
installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=None, delete=True, rename_to=rename_target)
|
|
174
|
+
elif system_name in {"Linux", "Darwin"}:
|
|
175
|
+
installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name="", delete=True, rename_to=rename_target)
|
|
176
|
+
else:
|
|
177
|
+
raise
|
|
178
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name).parent.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name).write_text(version, encoding="utf-8")
|
|
180
|
+
console.print(Panel(f"Installed [green]{tool_name}[/green] to {installed_path}\nVersion: {version}", title="✅ Complete", border_style="green"))
|
|
@@ -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,28 @@ 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
|
+
from rich import print as rprint
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
print(f"📦 Installing .deb package: {downloaded}")
|
|
18
|
+
assert platform.system() == "Linux"
|
|
19
|
+
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
20
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
21
|
+
if not success:
|
|
22
|
+
from rich.console import Group
|
|
23
|
+
desc = "Installing .deb"
|
|
24
|
+
sub_panels = []
|
|
25
|
+
if result.stdout:
|
|
26
|
+
sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
|
|
27
|
+
if result.stderr:
|
|
28
|
+
sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
|
|
29
|
+
group_content = Group(f"❌ {desc} failed\nReturn code: {result.returncode}", *sub_panels)
|
|
30
|
+
rprint(Panel(group_content, title=desc, style="red"))
|
|
31
|
+
print("🗑️ Cleaning up .deb package...")
|
|
32
|
+
downloaded.delete(sure=True)
|
|
33
|
+
|
|
34
|
+
|
|
14
35
|
class Installer:
|
|
15
36
|
def __init__(self, installer_data: InstallerData):
|
|
16
37
|
self.installer_data: InstallerData = installer_data
|
|
@@ -42,10 +63,8 @@ class Installer:
|
|
|
42
63
|
result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
|
|
43
64
|
new_version_cli = result_new.stdout.strip()
|
|
44
65
|
if old_version_cli == new_version_cli:
|
|
45
|
-
# print(f"ℹ️ Same version detected: {old_version_cli}")
|
|
46
66
|
return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
|
|
47
67
|
else:
|
|
48
|
-
# print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
|
|
49
68
|
return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
|
|
50
69
|
except Exception as ex:
|
|
51
70
|
exe_name = self._get_exe_name()
|
|
@@ -64,23 +83,24 @@ class Installer:
|
|
|
64
83
|
version_to_be_installed: str = "unknown" # Initialize to ensure it's always bound
|
|
65
84
|
if repo_url == "CMD":
|
|
66
85
|
if any(pm in installer_arch_os for pm in ["npm ", "pip ", "winget ", "brew ", "curl "]):
|
|
86
|
+
from rich import print as rprint
|
|
87
|
+
from rich.panel import Panel
|
|
88
|
+
from rich.console import Group
|
|
67
89
|
package_manager = installer_arch_os.split(" ", maxsplit=1)[0]
|
|
68
90
|
print(f"📦 Using package manager: {installer_arch_os}")
|
|
69
91
|
desc = package_manager + " installation"
|
|
70
92
|
version_to_be_installed = package_manager + "Latest"
|
|
71
|
-
result = subprocess.run(installer_arch_os, shell=True, capture_output=
|
|
72
|
-
|
|
73
|
-
# result = run_shell_script(installer_arch_os)
|
|
74
|
-
success = result.returncode == 0 and result.stderr == "".encode()
|
|
93
|
+
result = subprocess.run(installer_arch_os, shell=True, capture_output=False, text=True)
|
|
94
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
75
95
|
if not success:
|
|
76
|
-
|
|
96
|
+
sub_panels = []
|
|
77
97
|
if result.stdout:
|
|
78
|
-
|
|
98
|
+
sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
|
|
79
99
|
if result.stderr:
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
|
|
101
|
+
group_content = Group(f"❌ {desc} failed\nReturn code: {result.returncode}", *sub_panels)
|
|
102
|
+
rprint(Panel(group_content, title=desc, style="red"))
|
|
82
103
|
elif installer_arch_os.endswith((".sh", ".py", ".ps1")):
|
|
83
|
-
# search for the script, see which path ends with the script name
|
|
84
104
|
import machineconfig.jobs.installer as module
|
|
85
105
|
from pathlib import Path
|
|
86
106
|
search_root = Path(module.__file__).parent
|
|
@@ -106,9 +126,14 @@ class Installer:
|
|
|
106
126
|
runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
|
|
107
127
|
version_to_be_installed = str(version)
|
|
108
128
|
elif installer_arch_os.startswith("https://"): # its a url to be downloaded
|
|
109
|
-
downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
129
|
+
# downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
|
|
130
|
+
from machineconfig.scripts.python.helpers_utils.download import download
|
|
131
|
+
downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
|
|
132
|
+
if downloaded_object is None:
|
|
133
|
+
raise ValueError(f"Failed to download from URL: {installer_arch_os}")
|
|
110
134
|
# object is either a zip containing a binary or a straight out binary.
|
|
111
|
-
|
|
135
|
+
downloaded_object = PathExtended(downloaded_object)
|
|
136
|
+
if downloaded_object.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
112
137
|
downloaded_object = downloaded_object.decompress()
|
|
113
138
|
if downloaded_object.suffix in [".exe", ""]: # likely an executable
|
|
114
139
|
if platform.system() == "Windows":
|
|
@@ -131,26 +156,17 @@ class Installer:
|
|
|
131
156
|
print(f"🔄 Renaming to correct name: {new_exe_name}")
|
|
132
157
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
133
158
|
version_to_be_installed = "downloaded_binary"
|
|
159
|
+
elif downloaded_object.suffix in [".deb"]:
|
|
160
|
+
install_deb_package(downloaded_object)
|
|
161
|
+
version_to_be_installed = "downloaded_deb"
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError(f"Downloaded file is not an executable: {downloaded_object}")
|
|
134
164
|
else:
|
|
135
165
|
raise NotImplementedError(f"CMD installation method not implemented for: {installer_arch_os}")
|
|
136
166
|
else:
|
|
137
167
|
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)
|
|
168
|
+
downloaded, version_to_be_installed = self.binary_download(version=version)
|
|
169
|
+
if str(downloaded).endswith(".deb"): install_deb_package(downloaded)
|
|
154
170
|
else:
|
|
155
171
|
if platform.system() == "Windows":
|
|
156
172
|
exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
|
|
@@ -173,13 +189,13 @@ class Installer:
|
|
|
173
189
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
174
190
|
INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
|
|
175
191
|
INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed or "unknown", encoding="utf-8")
|
|
176
|
-
def
|
|
192
|
+
def binary_download(self, version: Optional[str]) -> tuple[PathExtended, str]:
|
|
177
193
|
exe_name = self._get_exe_name()
|
|
178
194
|
repo_url = self.installer_data["repoURL"]
|
|
179
195
|
# app_name = self.installer_data["appName"]
|
|
180
196
|
download_link: Optional[str] = None
|
|
181
197
|
version_to_be_installed: Optional[str] = None
|
|
182
|
-
if "github" not in repo_url or
|
|
198
|
+
if "github" not in repo_url or (any(ext in repo_url for ext in DECOMPRESS_SUPPORTED_FORMATS)):
|
|
183
199
|
# Direct download URL
|
|
184
200
|
download_link = repo_url
|
|
185
201
|
version_to_be_installed = "predefined_url"
|
|
@@ -200,10 +216,11 @@ class Installer:
|
|
|
200
216
|
assert download_link is not None, "download_link must be set"
|
|
201
217
|
assert version_to_be_installed is not None, "version_to_be_installed must be set"
|
|
202
218
|
downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
|
|
219
|
+
if downloaded.is_dir() and len(downloaded.search("*", r=True)) == 1:
|
|
220
|
+
only_file_in = next(downloaded.glob("*"))
|
|
221
|
+
if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
|
|
222
|
+
downloaded = only_file_in.decompress()
|
|
203
223
|
return downloaded, version_to_be_installed
|
|
204
|
-
|
|
205
|
-
# --------------------------- Arch / template helpers ---------------------------
|
|
206
|
-
|
|
207
224
|
@staticmethod
|
|
208
225
|
def _get_repo_name_from_url(repo_url: str) -> str:
|
|
209
226
|
"""Extract owner/repo from GitHub URL."""
|
|
@@ -218,23 +235,16 @@ class Installer:
|
|
|
218
235
|
def _fetch_github_release_data(repo_name: str, version: Optional[str] = None) -> Optional[dict[str, Any]]:
|
|
219
236
|
"""Fetch release data from GitHub API using requests."""
|
|
220
237
|
import requests
|
|
221
|
-
|
|
222
238
|
try:
|
|
223
|
-
if version and version.lower() != "latest":
|
|
224
|
-
# Fetch specific version
|
|
239
|
+
if version and version.lower() != "latest": # Fetch specific version
|
|
225
240
|
url = f"https://api.github.com/repos/{repo_name}/releases/tags/{version}"
|
|
226
|
-
else:
|
|
227
|
-
# Fetch latest release
|
|
241
|
+
else: # Fetch latest release
|
|
228
242
|
url = f"https://api.github.com/repos/{repo_name}/releases/latest"
|
|
229
|
-
|
|
230
243
|
response = requests.get(url, timeout=30)
|
|
231
|
-
|
|
232
244
|
if response.status_code != 200:
|
|
233
245
|
print(f"❌ Failed to fetch data for {repo_name}: HTTP {response.status_code}")
|
|
234
246
|
return None
|
|
235
|
-
|
|
236
247
|
response_data = response.json()
|
|
237
|
-
|
|
238
248
|
# Check if API returned an error
|
|
239
249
|
if "message" in response_data:
|
|
240
250
|
if "API rate limit exceeded" in response_data.get("message", ""):
|