machineconfig 3.2__py3-none-any.whl → 3.3__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/zellij_local.py +2 -2
- machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py +17 -7
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +7 -6
- machineconfig/jobs/python_custom_installers/dev/aider.py +9 -1
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +2 -1
- machineconfig/jobs/python_custom_installers/dev/brave.py +10 -1
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +9 -1
- machineconfig/jobs/python_custom_installers/dev/code.py +10 -3
- machineconfig/jobs/python_custom_installers/dev/cursor.py +2 -1
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +7 -6
- machineconfig/jobs/python_custom_installers/dev/espanso.py +13 -5
- machineconfig/jobs/python_custom_installers/dev/goes.py +10 -1
- machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -1
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +9 -1
- machineconfig/jobs/python_custom_installers/dev/redis.py +2 -2
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -1
- machineconfig/jobs/python_custom_installers/dev/winget.py +2 -1
- machineconfig/jobs/python_custom_installers/docker.py +9 -1
- machineconfig/jobs/python_custom_installers/gh.py +11 -2
- machineconfig/jobs/python_custom_installers/hx.py +9 -8
- machineconfig/jobs/python_custom_installers/warp-cli.py +9 -1
- machineconfig/jobs/python_generic_installers/config.json +612 -412
- machineconfig/jobs/python_generic_installers/config.json.bak +414 -0
- machineconfig/jobs/python_generic_installers/dev/config.json +822 -562
- machineconfig/jobs/python_generic_installers/dev/config.json.bak +565 -0
- machineconfig/jobs/python_linux_installers/archive/config.json +16 -8
- machineconfig/jobs/python_linux_installers/archive/config.json.bak +10 -0
- machineconfig/jobs/python_linux_installers/config.json +134 -99
- machineconfig/jobs/python_linux_installers/config.json.bak +110 -0
- machineconfig/jobs/python_linux_installers/dev/config.json +273 -203
- machineconfig/jobs/python_linux_installers/dev/config.json.bak +206 -0
- machineconfig/jobs/python_windows_installers/config.json +74 -48
- machineconfig/jobs/python_windows_installers/config.json.bak +56 -0
- machineconfig/jobs/python_windows_installers/dev/config.json +3 -2
- machineconfig/jobs/python_windows_installers/dev/config.json.bak +3 -0
- machineconfig/scripts/python/devops.py +1 -0
- machineconfig/scripts/python/devops_devapps_install.py +39 -17
- machineconfig/scripts/python/fire_agents.py +4 -0
- machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +8 -7
- machineconfig/utils/installer.py +45 -31
- machineconfig/utils/installer_utils/installer_abc.py +1 -4
- machineconfig/utils/installer_utils/installer_class.py +108 -102
- machineconfig/utils/path_reduced.py +1 -1
- machineconfig/utils/procs.py +1 -0
- machineconfig/utils/schemas/installer/installer_types.py +20 -0
- machineconfig/utils/utils2.py +2 -0
- {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/METADATA +1 -1
- {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/RECORD +51 -43
- {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/WHEEL +0 -0
- {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/entry_points.txt +0 -0
- {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/top_level.txt +0 -0
machineconfig/utils/installer.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""package manager"""
|
|
2
2
|
|
|
3
|
-
from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH
|
|
3
|
+
from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH
|
|
4
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
5
|
+
from machineconfig.utils.schemas.installer.installer_types import APP_INSTALLER_CATEGORY, InstallerData, InstallerDataFiles
|
|
5
6
|
from rich.console import Console
|
|
6
7
|
from rich.panel import Panel # Added import
|
|
7
8
|
|
|
@@ -9,7 +10,6 @@ from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
|
9
10
|
from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT
|
|
10
11
|
from machineconfig.utils.utils2 import read_json
|
|
11
12
|
|
|
12
|
-
# from dataclasses import dataclass
|
|
13
13
|
from typing import Any
|
|
14
14
|
import platform
|
|
15
15
|
from joblib import Parallel, delayed
|
|
@@ -22,21 +22,25 @@ def check_latest():
|
|
|
22
22
|
# installers += get_installers(system=platform.system(), dev=True)
|
|
23
23
|
installers_github = []
|
|
24
24
|
for inst__ in installers:
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
app_name = inst__.installer_data.get("appName", "unknown")
|
|
26
|
+
repo_url = inst__.installer_data.get("repoURL", "")
|
|
27
|
+
if "ntop" in app_name:
|
|
28
|
+
print(f"⏭️ Skipping {app_name} (ntop)")
|
|
27
29
|
continue
|
|
28
|
-
if "github" not in
|
|
29
|
-
print(f"⏭️ Skipping {
|
|
30
|
+
if "github" not in repo_url:
|
|
31
|
+
print(f"⏭️ Skipping {app_name} (not a GitHub release)")
|
|
30
32
|
continue
|
|
31
33
|
installers_github.append(inst__)
|
|
32
34
|
|
|
33
35
|
print(f"\n🔍 Checking {len(installers_github)} GitHub-based installers...\n")
|
|
34
36
|
|
|
35
37
|
def func(inst: Installer):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
exe_name = inst.installer_data.get("exeName", "unknown")
|
|
39
|
+
repo_url = inst.installer_data.get("repoURL", "")
|
|
40
|
+
print(f"🔎 Checking {exe_name}...")
|
|
41
|
+
_release_url, version_to_be_installed = inst.get_github_release(repo_url=repo_url, version=None)
|
|
42
|
+
verdict, current_ver, new_ver = inst.check_if_installed_already(exe_name=exe_name, version=version_to_be_installed, use_cache=False)
|
|
43
|
+
return exe_name, verdict, current_ver, new_ver
|
|
40
44
|
|
|
41
45
|
print("\n⏳ Processing installers...\n")
|
|
42
46
|
res = [func(inst) for inst in installers_github]
|
|
@@ -90,20 +94,23 @@ def get_installed_cli_apps():
|
|
|
90
94
|
|
|
91
95
|
def get_installers(system: str, dev: bool) -> list[Installer]:
|
|
92
96
|
print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
|
|
93
|
-
res_all =
|
|
97
|
+
res_all = get_all_installer_data_files(system=system)
|
|
94
98
|
if not dev:
|
|
95
99
|
print("ℹ️ Excluding development installers...")
|
|
96
100
|
del res_all["CUSTOM_DEV"]
|
|
97
101
|
del res_all["OS_SPECIFIC_DEV"]
|
|
98
102
|
del res_all["OS_GENERIC_DEV"]
|
|
99
|
-
res_final = {}
|
|
100
|
-
for _k, v in res_all.items():
|
|
101
|
-
res_final.update(v)
|
|
102
|
-
print(f"✅ Loaded {len(res_final)} installer configurations\n{'=' * 80}")
|
|
103
|
-
return [Installer.from_dict(d=vd, name=k) for k, vd in res_final.items()]
|
|
104
103
|
|
|
104
|
+
# Flatten the installer data from all categories
|
|
105
|
+
all_installers: list[InstallerData] = []
|
|
106
|
+
for _category, installer_data_files in res_all.items():
|
|
107
|
+
all_installers.extend(installer_data_files["installers"])
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
|
|
110
|
+
return [Installer(installer_data=installer_data) for installer_data in all_installers]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_all_installer_data_files(system: str) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
|
|
107
114
|
print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
|
|
108
115
|
|
|
109
116
|
print(f"🔍 Importing OS-specific installers for {system}...")
|
|
@@ -122,18 +129,23 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
|
|
|
122
129
|
path_os_generic_dev = path_os_generic.joinpath("dev")
|
|
123
130
|
|
|
124
131
|
print("📂 Loading configuration files...")
|
|
125
|
-
res_final: dict[
|
|
132
|
+
res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {}
|
|
133
|
+
|
|
126
134
|
print(f"""📄 Loading OS-specific config from: {path_os_specific.joinpath("config.json")}""")
|
|
127
|
-
|
|
135
|
+
os_specific_data = read_json(path=path_os_specific.joinpath("config.json"))
|
|
136
|
+
res_final["OS_SPECIFIC"] = InstallerDataFiles(os_specific_data)
|
|
128
137
|
|
|
129
138
|
print(f"""📄 Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
|
|
130
|
-
|
|
139
|
+
os_generic_data = read_json(path=path_os_generic.joinpath("config.json"))
|
|
140
|
+
res_final["OS_GENERIC"] = InstallerDataFiles(os_generic_data)
|
|
131
141
|
|
|
132
142
|
print(f"""📄 Loading OS-specific dev config from: {path_os_specific_dev.joinpath("config.json")}""")
|
|
133
|
-
|
|
143
|
+
os_specific_dev_data = read_json(path=path_os_specific_dev.joinpath("config.json"))
|
|
144
|
+
res_final["OS_SPECIFIC_DEV"] = InstallerDataFiles(os_specific_dev_data)
|
|
134
145
|
|
|
135
146
|
print(f"""📄 Loading OS-generic dev config from: {path_os_generic_dev.joinpath("config.json")}""")
|
|
136
|
-
|
|
147
|
+
os_generic_dev_data = read_json(path=path_os_generic_dev.joinpath("config.json"))
|
|
148
|
+
res_final["OS_GENERIC_DEV"] = InstallerDataFiles(os_generic_dev_data)
|
|
137
149
|
|
|
138
150
|
path_custom_installer = path_os_generic.with_name("python_custom_installers")
|
|
139
151
|
path_custom_installer_dev = path_custom_installer.joinpath("dev")
|
|
@@ -141,29 +153,31 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
|
|
|
141
153
|
print(f"🔍 Loading custom installers from: {path_custom_installer}")
|
|
142
154
|
import runpy
|
|
143
155
|
|
|
144
|
-
|
|
156
|
+
res_custom_installers: list[InstallerData] = []
|
|
145
157
|
for item in path_custom_installer.search("*.py", r=False, not_in=["__init__"]):
|
|
146
158
|
try:
|
|
147
159
|
print(f"📄 Loading custom installer: {item.name}")
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
|
|
161
|
+
res_custom_installers.append(installer_data)
|
|
150
162
|
except Exception as ex:
|
|
151
163
|
print(f"❌ Failed to load {item}: {ex}")
|
|
152
164
|
|
|
153
165
|
print(f"🔍 Loading custom dev installers from: {path_custom_installer_dev}")
|
|
154
|
-
|
|
166
|
+
res_custom_dev_installers: list[InstallerData] = []
|
|
155
167
|
for item in path_custom_installer_dev.search("*.py", r=False, not_in=["__init__"]):
|
|
156
168
|
try:
|
|
157
169
|
print(f"📄 Loading custom dev installer: {item.name}")
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
|
|
171
|
+
res_custom_dev_installers.append(installer_data)
|
|
160
172
|
except Exception as ex:
|
|
161
173
|
print(f"❌ Failed to load {item}: {ex}")
|
|
162
174
|
|
|
163
|
-
res_final["CUSTOM"] =
|
|
164
|
-
res_final["CUSTOM_DEV"] =
|
|
175
|
+
res_final["CUSTOM"] = InstallerDataFiles({"version": "1", "installers": res_custom_installers})
|
|
176
|
+
res_final["CUSTOM_DEV"] = InstallerDataFiles({"version": "1", "installers": res_custom_dev_installers})
|
|
165
177
|
|
|
166
|
-
print(
|
|
178
|
+
print(
|
|
179
|
+
f"✅ Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC']['installers'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC']['installers'])} items\n - CUSTOM: {len(res_final['CUSTOM']['installers'])} items\n{'=' * 80}"
|
|
180
|
+
)
|
|
167
181
|
return res_final
|
|
168
182
|
|
|
169
183
|
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
2
2
|
from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional
|
|
4
4
|
import subprocess
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
|
|
8
|
-
|
|
9
|
-
|
|
10
7
|
def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool = True, rename_to: Optional[str] = None):
|
|
11
8
|
print(f"\n{'=' * 80}\n🔍 PROCESSING WINDOWS EXECUTABLE 🔍\n{'=' * 80}")
|
|
12
9
|
if exe_name is not None and ".exe" in exe_name:
|
|
@@ -2,62 +2,37 @@ from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
|
2
2
|
from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
|
|
3
3
|
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT
|
|
4
4
|
from machineconfig.utils.options import check_tool_exists
|
|
5
|
-
from machineconfig.utils.utils2 import
|
|
5
|
+
from machineconfig.utils.utils2 import read_json
|
|
6
|
+
from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles
|
|
6
7
|
|
|
7
8
|
import platform
|
|
8
9
|
import subprocess
|
|
9
|
-
from typing import
|
|
10
|
+
from typing import Optional
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Installer:
|
|
14
|
-
def __init__(
|
|
15
|
-
self
|
|
16
|
-
repo_url: str,
|
|
17
|
-
name: str,
|
|
18
|
-
doc: str,
|
|
19
|
-
strip_v: bool,
|
|
20
|
-
exe_name: str,
|
|
21
|
-
filename_template_windows_amd_64: str,
|
|
22
|
-
filename_template_linux_amd_64: str,
|
|
23
|
-
filename_template_windows_arm_64: Optional[str] = None,
|
|
24
|
-
filename_template_linux_arm_64: Optional[str] = None,
|
|
25
|
-
filename_template_macos_amd_64: Optional[str] = None,
|
|
26
|
-
filename_template_macos_arm_64: Optional[str] = None,
|
|
27
|
-
):
|
|
28
|
-
self.repo_url: str = repo_url
|
|
29
|
-
self.name: str = name
|
|
30
|
-
self.doc: str = doc
|
|
31
|
-
self.filename_template_windows_amd_64: str = filename_template_windows_amd_64
|
|
32
|
-
self.filename_template_windows_arm_64: Optional[str] = filename_template_windows_arm_64
|
|
33
|
-
self.filename_template_linux_arm_64: Optional[str] = filename_template_linux_arm_64
|
|
34
|
-
self.filename_template_linux_amd_64: str = filename_template_linux_amd_64
|
|
35
|
-
self.filename_template_macos_amd_64: Optional[str] = filename_template_macos_amd_64
|
|
36
|
-
self.filename_template_macos_arm_64: Optional[str] = filename_template_macos_arm_64
|
|
37
|
-
self.strip_v: bool = strip_v
|
|
38
|
-
self.exe_name: str = exe_name
|
|
15
|
+
def __init__(self, installer_data: InstallerData):
|
|
16
|
+
self.installer_data: InstallerData = installer_data
|
|
39
17
|
|
|
40
18
|
def __repr__(self) -> str:
|
|
41
|
-
|
|
19
|
+
exe_name = self.installer_data.get("exeName", "unknown")
|
|
20
|
+
app_name = self.installer_data.get("appName", "unknown")
|
|
21
|
+
repo_url = self.installer_data.get("repoURL", "unknown")
|
|
22
|
+
return f"Installer of {exe_name} {app_name} @ {repo_url}"
|
|
42
23
|
|
|
43
24
|
def get_description(self):
|
|
44
25
|
# old_version_cli = Terminal().run(f"{self.exe_name} --version").op.replace("\n", "")
|
|
45
26
|
# old_version_cli = os.system(f"{self.exe_name} --version").replace("\n", "")
|
|
46
|
-
|
|
27
|
+
exe_name = self.installer_data.get("exeName", "")
|
|
28
|
+
if not exe_name:
|
|
29
|
+
return "Invalid installer: missing exeName"
|
|
30
|
+
|
|
31
|
+
old_version_cli: bool = check_tool_exists(tool_name=exe_name)
|
|
47
32
|
old_version_cli_str = "✅" if old_version_cli else "❌"
|
|
48
33
|
# name_version = f"{self.exe_name} {old_version_cli_str}"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def to_dict(self):
|
|
52
|
-
return self.__dict__
|
|
53
|
-
|
|
54
|
-
@staticmethod
|
|
55
|
-
def from_dict(d: dict[str, Any], name: str):
|
|
56
|
-
try:
|
|
57
|
-
return Installer(name=name, **d)
|
|
58
|
-
except Exception as ex:
|
|
59
|
-
pprint(d, "Installer Creation Error")
|
|
60
|
-
raise ex
|
|
34
|
+
doc = self.installer_data.get("doc", "No description")
|
|
35
|
+
return f"{exe_name:<12} {old_version_cli_str} {doc}"
|
|
61
36
|
|
|
62
37
|
@staticmethod
|
|
63
38
|
def choose_app_and_install():
|
|
@@ -69,49 +44,69 @@ class Installer:
|
|
|
69
44
|
config_paths = [Path(p) for p in jobs_dir.rglob("config.json")]
|
|
70
45
|
path = choose_one_option(options=config_paths)
|
|
71
46
|
print(f"📄 Loading configuration from: {path}")
|
|
72
|
-
|
|
47
|
+
config_data = read_json(path)
|
|
48
|
+
installer_data_files = InstallerDataFiles(config_data)
|
|
49
|
+
|
|
50
|
+
# Extract app names from the installers
|
|
51
|
+
app_names = [installer["appName"] for installer in installer_data_files["installers"]]
|
|
73
52
|
print("🔍 Select an application to install:")
|
|
74
|
-
app_name = choose_one_option(options=
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
53
|
+
app_name = choose_one_option(options=app_names, fzf=True)
|
|
54
|
+
|
|
55
|
+
# Find the selected installer data
|
|
56
|
+
selected_installer_data = None
|
|
57
|
+
for installer_data in installer_data_files["installers"]:
|
|
58
|
+
if installer_data["appName"] == app_name:
|
|
59
|
+
selected_installer_data = installer_data
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
if selected_installer_data is None:
|
|
63
|
+
raise ValueError(f"Could not find installer data for {app_name}")
|
|
64
|
+
|
|
65
|
+
installer = Installer(installer_data=selected_installer_data)
|
|
66
|
+
print(f"📦 Selected application: {selected_installer_data.get('exeName', 'unknown')}")
|
|
67
|
+
version = input(f"📝 Enter version to install for {selected_installer_data.get('exeName', 'unknown')} [latest]: ") or None
|
|
68
|
+
print(f"\n{'=' * 80}\n🚀 INSTALLING {selected_installer_data.get('exeName', 'UNKNOWN').upper()} 🚀\n{'=' * 80}")
|
|
80
69
|
installer.install(version=version)
|
|
81
70
|
|
|
82
71
|
def install_robust(self, version: Optional[str]):
|
|
83
72
|
try:
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
exe_name = self.installer_data.get("exeName", "unknown")
|
|
74
|
+
print(f"\n{'=' * 80}\n🚀 INSTALLING {exe_name.upper()} 🚀\n{'=' * 80}")
|
|
75
|
+
result_old = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
|
|
86
76
|
old_version_cli = result_old.stdout.strip()
|
|
87
77
|
print(f"📊 Current version: {old_version_cli or 'Not installed'}")
|
|
88
78
|
|
|
89
79
|
self.install(version=version)
|
|
90
80
|
|
|
91
|
-
result_new = subprocess.run(f"{
|
|
81
|
+
result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
|
|
92
82
|
new_version_cli = result_new.stdout.strip()
|
|
93
83
|
print(f"📊 New version: {new_version_cli}")
|
|
94
84
|
|
|
95
85
|
if old_version_cli == new_version_cli:
|
|
96
86
|
print(f"ℹ️ Same version detected: {old_version_cli}")
|
|
97
|
-
return f"""📦️ 😑 {
|
|
87
|
+
return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
|
|
98
88
|
else:
|
|
99
89
|
print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
|
|
100
|
-
return f"""📦️ 🤩 {
|
|
90
|
+
return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
|
|
101
91
|
|
|
102
92
|
except Exception as ex:
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
exe_name = self.installer_data.get("exeName", "unknown")
|
|
94
|
+
app_name = self.installer_data.get("appName", "unknown")
|
|
95
|
+
print(f"❌ ERROR: Installation failed for {exe_name}: {ex}")
|
|
96
|
+
return f"""📦️ ❌ Failed to install `{app_name}` with error: {ex}"""
|
|
105
97
|
|
|
106
98
|
def install(self, version: Optional[str]):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
exe_name = self.installer_data.get("exeName", "unknown")
|
|
100
|
+
repo_url = self.installer_data.get("repoURL", "")
|
|
101
|
+
|
|
102
|
+
print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {exe_name} 🔧\n{'=' * 80}")
|
|
103
|
+
if repo_url == "CUSTOM":
|
|
104
|
+
print(f"🧩 Using custom installer for {exe_name}")
|
|
110
105
|
import machineconfig.jobs.python_custom_installers as python_custom_installers
|
|
111
106
|
|
|
112
|
-
installer_path = Path(python_custom_installers.__file__).parent.joinpath(
|
|
107
|
+
installer_path = Path(python_custom_installers.__file__).parent.joinpath(exe_name + ".py")
|
|
113
108
|
if not installer_path.exists():
|
|
114
|
-
installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev",
|
|
109
|
+
installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", exe_name + ".py")
|
|
115
110
|
print(f"🔍 Looking for installer in dev folder: {installer_path}")
|
|
116
111
|
else:
|
|
117
112
|
print(f"🔍 Found installer at: {installer_path}")
|
|
@@ -138,13 +133,13 @@ class Installer:
|
|
|
138
133
|
version_to_be_installed = str(version)
|
|
139
134
|
print(f"✅ Custom installation completed\n{'=' * 80}")
|
|
140
135
|
|
|
141
|
-
elif "npm " in
|
|
142
|
-
package_manager =
|
|
136
|
+
elif "npm " in repo_url or "pip " in repo_url or "winget " in repo_url:
|
|
137
|
+
package_manager = repo_url.split(" ", maxsplit=1)[0]
|
|
143
138
|
print(f"📦 Using package manager: {package_manager}")
|
|
144
139
|
desc = package_manager + " installation"
|
|
145
140
|
version_to_be_installed = package_manager + "Latest"
|
|
146
|
-
print(f"🚀 Running: {
|
|
147
|
-
result = subprocess.run(
|
|
141
|
+
print(f"🚀 Running: {repo_url}")
|
|
142
|
+
result = subprocess.run(repo_url, shell=True, capture_output=True, text=True)
|
|
148
143
|
success = result.returncode == 0 and result.stderr == ""
|
|
149
144
|
if not success:
|
|
150
145
|
print(f"❌ {desc} failed")
|
|
@@ -177,38 +172,43 @@ class Installer:
|
|
|
177
172
|
else:
|
|
178
173
|
if platform.system() == "Windows":
|
|
179
174
|
print("🪟 Installing on Windows...")
|
|
180
|
-
exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=
|
|
175
|
+
exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
|
|
181
176
|
elif platform.system() in ["Linux", "Darwin"]:
|
|
182
177
|
system_name = "Linux" if platform.system() == "Linux" else "macOS"
|
|
183
178
|
print(f"🐧 Installing on {system_name}...")
|
|
184
|
-
exe = find_move_delete_linux(downloaded=downloaded, tool_name=
|
|
179
|
+
exe = find_move_delete_linux(downloaded=downloaded, tool_name=exe_name, delete=True, rename_to=exe_name)
|
|
185
180
|
else:
|
|
186
181
|
error_msg = f"❌ ERROR: System {platform.system()} not supported"
|
|
187
182
|
print(error_msg)
|
|
188
183
|
raise NotImplementedError(error_msg)
|
|
189
184
|
|
|
190
185
|
_ = exe
|
|
191
|
-
if exe.name.replace(".exe", "") !=
|
|
186
|
+
if exe.name.replace(".exe", "") != exe_name.replace(".exe", ""):
|
|
192
187
|
from rich import print as pprint
|
|
193
188
|
from rich.panel import Panel
|
|
194
189
|
|
|
195
190
|
print("⚠️ Warning: Executable name mismatch")
|
|
196
|
-
pprint(Panel(f"Expected exe name: [red]{
|
|
197
|
-
new_exe_name =
|
|
191
|
+
pprint(Panel(f"Expected exe name: [red]{exe_name}[/red] \nAttained name: [red]{exe.name.replace('.exe', '')}[/red]", title="exe name mismatch", subtitle=repo_url))
|
|
192
|
+
new_exe_name = exe_name + ".exe" if platform.system() == "Windows" else exe_name
|
|
198
193
|
print(f"🔄 Renaming to correct name: {new_exe_name}")
|
|
199
194
|
exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
|
|
200
195
|
|
|
201
|
-
print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(
|
|
202
|
-
INSTALL_VERSION_ROOT.joinpath(
|
|
203
|
-
INSTALL_VERSION_ROOT.joinpath(
|
|
196
|
+
print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(exe_name)}")
|
|
197
|
+
INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
|
|
198
|
+
INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed, encoding="utf-8")
|
|
204
199
|
print(f"✅ Installation completed successfully!\n{'=' * 80}")
|
|
205
200
|
|
|
206
201
|
def download(self, version: Optional[str]):
|
|
207
|
-
|
|
202
|
+
exe_name = self.installer_data.get("exeName", "unknown")
|
|
203
|
+
repo_url = self.installer_data.get("repoURL", "")
|
|
204
|
+
app_name = self.installer_data.get("appName", "unknown")
|
|
205
|
+
strip_v = self.installer_data.get("stripVersion", False)
|
|
206
|
+
|
|
207
|
+
print(f"\n{'=' * 80}\n📥 DOWNLOADING: {exe_name} 📥\n{'=' * 80}")
|
|
208
208
|
download_link: Optional[Path] = None
|
|
209
209
|
version_to_be_installed: Optional[str] = None
|
|
210
|
-
if "github" not in
|
|
211
|
-
download_link = Path(
|
|
210
|
+
if "github" not in repo_url or ".zip" in repo_url or ".tar.gz" in repo_url:
|
|
211
|
+
download_link = Path(repo_url)
|
|
212
212
|
version_to_be_installed = "predefined_url"
|
|
213
213
|
print(f"🔗 Using direct download URL: {download_link}")
|
|
214
214
|
print(f"📦 Version to be installed: {version_to_be_installed}")
|
|
@@ -228,11 +228,11 @@ class Installer:
|
|
|
228
228
|
|
|
229
229
|
else:
|
|
230
230
|
print("🌐 Retrieving release information from GitHub...")
|
|
231
|
-
release_url, version_to_be_installed = Installer.get_github_release(repo_url=
|
|
231
|
+
release_url, version_to_be_installed = Installer.get_github_release(repo_url=repo_url, version=version)
|
|
232
232
|
print(f"📦 Version to be installed: {version_to_be_installed}")
|
|
233
233
|
print(f"📦 Release URL: {release_url}")
|
|
234
234
|
|
|
235
|
-
version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if
|
|
235
|
+
version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if strip_v else version_to_be_installed
|
|
236
236
|
version_to_be_installed_stripped = version_to_be_installed_stripped.replace("ipinfo-", "")
|
|
237
237
|
|
|
238
238
|
template, arch = self._select_template()
|
|
@@ -247,7 +247,7 @@ class Installer:
|
|
|
247
247
|
|
|
248
248
|
assert download_link is not None, "download_link must be set"
|
|
249
249
|
assert version_to_be_installed is not None, "version_to_be_installed must be set"
|
|
250
|
-
print(f"📥 Downloading {
|
|
250
|
+
print(f"📥 Downloading {app_name} from: {download_link}")
|
|
251
251
|
downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
|
|
252
252
|
print(f"✅ Download and extraction completed to: {downloaded}\n{'=' * 80}")
|
|
253
253
|
return downloaded, version_to_be_installed
|
|
@@ -268,47 +268,53 @@ class Installer:
|
|
|
268
268
|
return sys_
|
|
269
269
|
|
|
270
270
|
def _any_direct_http_template(self) -> bool:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
]
|
|
279
|
-
return any(t for t in templates if t is not None and t.startswith("http"))
|
|
271
|
+
filename_templates = self.installer_data.get("filenameTemplate", {})
|
|
272
|
+
templates: list[str] = []
|
|
273
|
+
|
|
274
|
+
for arch_templates in filename_templates.values():
|
|
275
|
+
templates.extend([t for t in arch_templates.values() if t])
|
|
276
|
+
|
|
277
|
+
return any(t for t in templates if t.startswith("http"))
|
|
280
278
|
|
|
281
279
|
def _select_template(self) -> tuple[str, str]:
|
|
282
280
|
sys_name = platform.system()
|
|
283
281
|
arch = self._normalized_arch()
|
|
282
|
+
|
|
283
|
+
filename_templates = self.installer_data.get("filenameTemplate", {})
|
|
284
|
+
|
|
285
|
+
# Get templates for each architecture
|
|
286
|
+
amd64_templates = filename_templates.get("amd64", {})
|
|
287
|
+
arm64_templates = filename_templates.get("arm64", {})
|
|
288
|
+
|
|
284
289
|
# mapping logic
|
|
285
290
|
candidates: list[str] = []
|
|
286
291
|
template: Optional[str] = None
|
|
292
|
+
|
|
287
293
|
if sys_name == "Windows":
|
|
288
|
-
if arch == "arm64" and
|
|
289
|
-
template =
|
|
294
|
+
if arch == "arm64" and arm64_templates.get("windows"):
|
|
295
|
+
template = arm64_templates["windows"]
|
|
290
296
|
else:
|
|
291
|
-
template =
|
|
292
|
-
candidates = ["
|
|
297
|
+
template = amd64_templates.get("windows", "")
|
|
298
|
+
candidates = ["arm64.windows", "amd64.windows"]
|
|
293
299
|
elif sys_name == "Linux":
|
|
294
|
-
if arch == "arm64" and
|
|
295
|
-
template =
|
|
300
|
+
if arch == "arm64" and arm64_templates.get("linux"):
|
|
301
|
+
template = arm64_templates["linux"]
|
|
296
302
|
else:
|
|
297
|
-
template =
|
|
298
|
-
candidates = ["
|
|
303
|
+
template = amd64_templates.get("linux", "")
|
|
304
|
+
candidates = ["arm64.linux", "amd64.linux"]
|
|
299
305
|
elif sys_name == "Darwin":
|
|
300
|
-
if arch == "arm64" and
|
|
301
|
-
template =
|
|
302
|
-
elif arch == "amd64" and
|
|
303
|
-
template =
|
|
306
|
+
if arch == "arm64" and arm64_templates.get("macos"):
|
|
307
|
+
template = arm64_templates["macos"]
|
|
308
|
+
elif arch == "amd64" and amd64_templates.get("macos"):
|
|
309
|
+
template = amd64_templates["macos"]
|
|
304
310
|
else:
|
|
305
311
|
# fallback between available mac templates
|
|
306
|
-
template =
|
|
307
|
-
candidates = ["
|
|
312
|
+
template = arm64_templates.get("macos") or amd64_templates.get("macos") or ""
|
|
313
|
+
candidates = ["arm64.macos", "amd64.macos"]
|
|
308
314
|
else:
|
|
309
315
|
raise NotImplementedError(f"System {sys_name} not supported")
|
|
310
316
|
|
|
311
|
-
if template
|
|
317
|
+
if not template:
|
|
312
318
|
raise ValueError(f"No filename template available for system={sys_name} arch={arch}. Checked {candidates}")
|
|
313
319
|
|
|
314
320
|
return template, arch
|
|
@@ -395,7 +395,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
395
395
|
from machineconfig.utils.terminal import Terminal
|
|
396
396
|
|
|
397
397
|
if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
|
|
398
|
-
import win32com.shell.shell
|
|
398
|
+
import win32com.shell.shell # type: ignore # pylint: disable=E0401
|
|
399
399
|
|
|
400
400
|
_proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
|
|
401
401
|
# TODO update PATH for this to take effect immediately.
|
machineconfig/utils/procs.py
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import TypedDict, Literal, TypeAlias
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
APP_INSTALLER_CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
|
|
5
|
+
CPU_ARCHITECTURES: TypeAlias = Literal["amd64", "arm64"]
|
|
6
|
+
OPERATING_SYSTEMS: TypeAlias = Literal["windows", "linux", "macos"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InstallerData(TypedDict):
|
|
10
|
+
appName: str
|
|
11
|
+
repoURL: str
|
|
12
|
+
doc: str
|
|
13
|
+
filenameTemplate: dict[CPU_ARCHITECTURES, dict[OPERATING_SYSTEMS, str]]
|
|
14
|
+
stripVersion: bool
|
|
15
|
+
exeName: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InstallerDataFiles(TypedDict):
|
|
19
|
+
version: str
|
|
20
|
+
installers: list[InstallerData]
|
machineconfig/utils/utils2.py
CHANGED
|
@@ -44,10 +44,12 @@ def read_ini(path: "Path", encoding: Optional[str] = None):
|
|
|
44
44
|
|
|
45
45
|
def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
|
|
46
46
|
import json
|
|
47
|
+
|
|
47
48
|
try:
|
|
48
49
|
mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
|
|
49
50
|
except Exception:
|
|
50
51
|
import pyjson5
|
|
52
|
+
|
|
51
53
|
mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
|
|
52
54
|
_ = r
|
|
53
55
|
return mydict
|