machineconfig 3.2__py3-none-any.whl → 3.5__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/wt_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local.py +2 -2
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py +17 -7
- machineconfig/cluster/templates/utils.py +1 -1
- machineconfig/jobs/linux/msc/cli_agents.sh +18 -2
- machineconfig/jobs/python/python_ve_symlink.py +1 -1
- machineconfig/jobs/python/vscode/api.py +1 -1
- machineconfig/jobs/python/vscode/select_interpreter.py +2 -2
- machineconfig/jobs/python/vscode/sync_code.py +1 -1
- 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 +12 -4
- 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 +14 -6
- 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 +10 -9
- machineconfig/jobs/python_custom_installers/warp-cli.py +9 -1
- machineconfig/jobs/python_generic_installers/config.json +601 -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/profile/create.py +3 -3
- machineconfig/profile/shell.py +1 -1
- machineconfig/scripts/python/ai/mcinit.py +23 -67
- machineconfig/scripts/python/ai/solutions/__init__.py +0 -0
- machineconfig/scripts/python/ai/solutions/_shared.py +5 -0
- machineconfig/scripts/python/ai/solutions/claude/claude.py +8 -0
- machineconfig/scripts/python/ai/solutions/cline/cline.py +10 -0
- machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +35 -0
- machineconfig/scripts/python/ai/solutions/copilot/privacy.md +4 -0
- machineconfig/scripts/python/ai/solutions/crush/crush.json +216 -0
- machineconfig/scripts/python/ai/solutions/crush/crush.py +25 -0
- machineconfig/scripts/python/ai/solutions/crush/privacy.md +2 -0
- machineconfig/scripts/python/ai/solutions/cursor/cursors.py +10 -0
- machineconfig/scripts/python/ai/solutions/gemini/gemini.py +14 -0
- machineconfig/scripts/python/ai/solutions/generic.py +41 -0
- machineconfig/scripts/python/ai/solutions/kilocode/privacy.md +3 -0
- machineconfig/scripts/python/ai/solutions/opencode/opencode.json +4 -0
- machineconfig/scripts/python/ai/solutions/opencode/opencode.py +1 -0
- machineconfig/scripts/python/choose_wezterm_theme.py +1 -1
- machineconfig/scripts/python/cloud_copy.py +2 -2
- machineconfig/scripts/python/cloud_mount.py +2 -2
- machineconfig/scripts/python/cloud_repo_sync.py +3 -2
- machineconfig/scripts/python/croshell.py +12 -7
- machineconfig/scripts/python/devops.py +1 -0
- machineconfig/scripts/python/devops_add_identity.py +1 -1
- machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
- machineconfig/scripts/python/devops_backup_retrieve.py +4 -3
- machineconfig/scripts/python/devops_devapps_install.py +39 -17
- machineconfig/scripts/python/devops_update_repos.py +2 -2
- machineconfig/scripts/python/dotfile.py +1 -1
- machineconfig/scripts/python/fire_agents.py +7 -3
- machineconfig/scripts/python/fire_agents_help_launch.py +2 -2
- machineconfig/scripts/python/fire_jobs.py +8 -8
- machineconfig/scripts/python/fire_jobs_layout_helper.py +2 -2
- machineconfig/scripts/python/ftpx.py +2 -2
- machineconfig/scripts/python/helpers/cloud_helpers.py +2 -1
- machineconfig/scripts/python/helpers/helpers2.py +4 -3
- machineconfig/scripts/python/helpers/helpers4.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
- machineconfig/scripts/python/mount_nfs.py +1 -1
- machineconfig/scripts/python/mount_ssh.py +1 -1
- machineconfig/scripts/python/repos.py +6 -3
- machineconfig/scripts/python/repos_helper_clone.py +121 -0
- machineconfig/scripts/python/repos_helper_record.py +2 -2
- machineconfig/scripts/python/start_slidev.py +1 -1
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +9 -8
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -3
- machineconfig/utils/{utils2.py → accessories.py} +13 -27
- machineconfig/utils/code.py +2 -2
- machineconfig/utils/installer.py +47 -33
- machineconfig/utils/installer_utils/installer_abc.py +2 -5
- machineconfig/utils/installer_utils/installer_class.py +109 -103
- machineconfig/utils/io.py +94 -0
- machineconfig/utils/links.py +2 -2
- machineconfig/utils/notifications.py +0 -9
- machineconfig/utils/{path_reduced.py → path_extended.py} +2 -2
- machineconfig/utils/{path.py → path_helper.py} +1 -1
- machineconfig/utils/procs.py +2 -1
- machineconfig/utils/{utils5.py → scheduler.py} +3 -8
- machineconfig/utils/schemas/installer/installer_types.py +20 -0
- machineconfig/utils/ssh.py +2 -2
- machineconfig/utils/terminal.py +12 -2
- machineconfig/utils/ve.py +2 -16
- {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/METADATA +1 -4
- {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/RECORD +121 -97
- machineconfig/utils/io_save.py +0 -95
- /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Thinking-Beast-Mode.chatmode.md +0 -0
- /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +0 -0
- /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/deepResearch.chatmode.md +0 -0
- /machineconfig/scripts/python/ai/{instructions → solutions/copilot/instructions}/python/dev.instructions.md +0 -0
- /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/allLintersAndTypeCheckers.prompt.md +0 -0
- /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/research-report-skeleton.prompt.md +0 -0
- /machineconfig/scripts/python/ai/{configs/.gemini → solutions/gemini}/settings.json +0 -0
- {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/WHEEL +0 -0
- {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/entry_points.txt +0 -0
- {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/top_level.txt +0 -0
|
@@ -1,63 +1,38 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.path_extended 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.
|
|
5
|
+
from machineconfig.utils.io 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
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Union, Optional, Mapping
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import json
|
|
6
|
+
import pickle
|
|
7
|
+
import configparser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PathLike = Union[str, Path]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ensure_parent(path: PathLike) -> Path:
|
|
14
|
+
path_obj = Path(path)
|
|
15
|
+
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
return path_obj
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def save_pickle(obj: Any, path: PathLike, verbose: bool = False) -> Path:
|
|
20
|
+
path_obj = _ensure_parent(path)
|
|
21
|
+
with open(path_obj, "wb") as fh:
|
|
22
|
+
pickle.dump(obj, fh, protocol=pickle.HIGHEST_PROTOCOL)
|
|
23
|
+
if verbose:
|
|
24
|
+
print(f"Saved pickle -> {path_obj}")
|
|
25
|
+
return Path(path_obj)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def save_json(obj: Any, path: PathLike, indent: Optional[int] = None, verbose: bool = False) -> Path:
|
|
29
|
+
path_obj = _ensure_parent(path)
|
|
30
|
+
with open(path_obj, "w", encoding="utf-8") as fh:
|
|
31
|
+
json.dump(obj, fh, indent=indent, ensure_ascii=False)
|
|
32
|
+
fh.write("\n")
|
|
33
|
+
if verbose:
|
|
34
|
+
print(f"Saved json -> {path_obj}")
|
|
35
|
+
return Path(path_obj)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# def save_toml(obj: Mapping[str, Any], path: PathLike, verbose: bool = False) -> Path:
|
|
39
|
+
# path_obj = _ensure_parent(path)
|
|
40
|
+
# with open(path_obj, "w", encoding="utf-8") as fh:
|
|
41
|
+
# toml.dump(obj, fh)
|
|
42
|
+
# if verbose:
|
|
43
|
+
# print(f"Saved toml -> {path_obj}")
|
|
44
|
+
# return Path(path_obj)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# def save_yaml(obj: Any, path: PathLike, verbose: bool = False) -> Path:
|
|
48
|
+
# path_obj = _ensure_parent(path)
|
|
49
|
+
# with open(path_obj, "w", encoding="utf-8") as fh:
|
|
50
|
+
# yaml.safe_dump(obj, fh, sort_keys=False)
|
|
51
|
+
# if verbose:
|
|
52
|
+
# print(f"Saved yaml -> {path_obj}")
|
|
53
|
+
# return Path(path_obj)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def save_ini(path: PathLike, obj: Mapping[str, Mapping[str, Any]], verbose: bool = False) -> Path:
|
|
57
|
+
cp = configparser.ConfigParser()
|
|
58
|
+
for section, values in obj.items():
|
|
59
|
+
cp[section] = {str(k): str(v) for k, v in values.items()}
|
|
60
|
+
path_obj = _ensure_parent(path)
|
|
61
|
+
with open(path_obj, "w", encoding="utf-8") as fh:
|
|
62
|
+
cp.write(fh)
|
|
63
|
+
if verbose:
|
|
64
|
+
print(f"Saved ini -> {path_obj}")
|
|
65
|
+
return Path(path_obj)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def read_ini(path: "Path", encoding: Optional[str] = None):
|
|
69
|
+
if not Path(path).exists() or Path(path).is_dir():
|
|
70
|
+
raise FileNotFoundError(f"File not found or is a directory: {path}")
|
|
71
|
+
import configparser
|
|
72
|
+
|
|
73
|
+
res = configparser.ConfigParser()
|
|
74
|
+
res.read(filenames=[str(path)], encoding=encoding)
|
|
75
|
+
return res
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
|
|
83
|
+
except Exception:
|
|
84
|
+
import pyjson5
|
|
85
|
+
|
|
86
|
+
mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
|
|
87
|
+
_ = r
|
|
88
|
+
return mydict
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def from_pickle(path: Path) -> Any:
|
|
92
|
+
import pickle
|
|
93
|
+
|
|
94
|
+
return pickle.loads(path.read_bytes())
|
machineconfig/utils/links.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
2
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended as PathExtended, PLike
|
|
2
|
+
from machineconfig.utils.accessories import randstr
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from rich.panel import Panel
|
|
5
5
|
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
"""Notifications Module"""
|
|
2
2
|
|
|
3
|
-
# from crocodile.core import install_n_import
|
|
4
|
-
# from crocodile.file_management import P, Read
|
|
5
3
|
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
# from crocodile.meta import RepeatUntilNoException
|
|
8
4
|
import smtplib
|
|
9
5
|
import imaplib
|
|
10
|
-
|
|
11
|
-
# from email import message
|
|
12
|
-
# from email import encoders
|
|
13
|
-
# from email.mime.base import MIMEBase
|
|
14
6
|
from email.mime.text import MIMEText
|
|
15
7
|
from email.mime.multipart import MIMEMultipart
|
|
16
8
|
from typing import Optional, Any, Union
|
|
@@ -192,7 +184,6 @@ encryption = ssl
|
|
|
192
184
|
# else:
|
|
193
185
|
# body_file_path = None
|
|
194
186
|
# assert body is not None, "You must pass either body or body_file."
|
|
195
|
-
# from crocodile.meta import Terminal
|
|
196
187
|
|
|
197
188
|
# to_str = ",".join(to)
|
|
198
189
|
# attachments_str = " ".join([f"--attachment {str(p)}" for p in attachments]) if attachments is not None else ""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.accessories import randstr
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
import time
|
|
@@ -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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
2
2
|
from machineconfig.utils.options import choose_one_option
|
|
3
3
|
from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
|
|
4
4
|
from rich.console import Console
|
machineconfig/utils/procs.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Optional, Any
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
|
-
from machineconfig.utils.
|
|
11
|
+
from machineconfig.utils.accessories import pprint
|
|
12
12
|
|
|
13
13
|
console = Console()
|
|
14
14
|
|
|
@@ -241,6 +241,7 @@ def get_age(create_time: Any) -> str:
|
|
|
241
241
|
|
|
242
242
|
def main():
|
|
243
243
|
from machineconfig.utils.procs import ProcessManager
|
|
244
|
+
|
|
244
245
|
ProcessManager().choose_and_kill()
|
|
245
246
|
|
|
246
247
|
|
|
@@ -3,7 +3,8 @@ from typing import Callable, Optional, Union, Any, NoReturn, TypeVar, Protocol,
|
|
|
3
3
|
import logging
|
|
4
4
|
import time
|
|
5
5
|
from datetime import datetime, timezone, timedelta
|
|
6
|
-
from machineconfig.utils.
|
|
6
|
+
from machineconfig.utils.io import from_pickle
|
|
7
|
+
from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class LoggerTemplate(Protocol):
|
|
@@ -108,7 +109,7 @@ class Scheduler:
|
|
|
108
109
|
# "logfile": self.logger.file_path
|
|
109
110
|
}
|
|
110
111
|
summ.update(sess_stats)
|
|
111
|
-
from machineconfig.utils.
|
|
112
|
+
from machineconfig.utils.accessories import get_repr
|
|
112
113
|
|
|
113
114
|
tmp = get_repr(summ)
|
|
114
115
|
self.logger.critical("\n--> Scheduler has finished running a session. \n" + tmp + "\n" + "-" * 100)
|
|
@@ -157,12 +158,6 @@ def to_pickle(obj: Any, path: Path) -> None:
|
|
|
157
158
|
path.write_bytes(pickle.dumps(obj))
|
|
158
159
|
|
|
159
160
|
|
|
160
|
-
def from_pickle(path: Path) -> Any:
|
|
161
|
-
import pickle
|
|
162
|
-
|
|
163
|
-
return pickle.loads(path.read_bytes())
|
|
164
|
-
|
|
165
|
-
|
|
166
161
|
class Cache(Generic[T]): # This class helps to accelrate access to latest data coming from expensive function. The class has two flavours, memory-based and disk-based variants."""
|
|
167
162
|
# source_func: Callable[[], T]
|
|
168
163
|
def __init__(
|
|
@@ -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/ssh.py
CHANGED
|
@@ -3,8 +3,8 @@ import os
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
import rich.console
|
|
5
5
|
from machineconfig.utils.terminal import Terminal, Response, MACHINE
|
|
6
|
-
from machineconfig.utils.
|
|
7
|
-
from machineconfig.utils.
|
|
6
|
+
from machineconfig.utils.path_extended import PathExtended, PLike, OPLike
|
|
7
|
+
from machineconfig.utils.accessories import pprint
|
|
8
8
|
# from machineconfig.utils.ve import get_ve_activate_line
|
|
9
9
|
|
|
10
10
|
|
machineconfig/utils/terminal.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended, OPLike
|
|
2
2
|
import subprocess
|
|
3
3
|
from typing import Any, BinaryIO, Optional, Union
|
|
4
4
|
import platform
|
|
@@ -219,9 +219,19 @@ class Terminal:
|
|
|
219
219
|
|
|
220
220
|
@staticmethod
|
|
221
221
|
def get_header(wdir: OPLike, toolbox: bool):
|
|
222
|
+
if toolbox:
|
|
223
|
+
toobox_code = """
|
|
224
|
+
try:
|
|
225
|
+
from crocodile.toolbox import *
|
|
226
|
+
except ImportError:
|
|
227
|
+
print("Crocodile not found, skipping import.")
|
|
228
|
+
pass
|
|
229
|
+
"""
|
|
230
|
+
else:
|
|
231
|
+
toobox_code = "# No toolbox import."
|
|
222
232
|
return f"""
|
|
223
233
|
# >> Code prepended
|
|
224
|
-
{
|
|
234
|
+
{toobox_code}
|
|
225
235
|
{'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
|
|
226
236
|
# >> End of header, start of script passed
|
|
227
237
|
"""
|
machineconfig/utils/ve.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from machineconfig.utils.
|
|
2
|
-
from machineconfig.utils.
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
2
|
+
from machineconfig.utils.io import read_ini
|
|
3
3
|
import platform
|
|
4
4
|
from typing import Optional
|
|
5
|
-
from pathlib import Path
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[str], Optional[str]]:
|
|
@@ -39,19 +38,6 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
|
|
|
39
38
|
return ve_path, ipy_profile
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
def get_repo_root(path: Path) -> Optional[Path]:
|
|
43
|
-
from git import Repo, InvalidGitRepositoryError
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
repo = Repo(str(path), search_parent_directories=True)
|
|
47
|
-
root = repo.working_tree_dir
|
|
48
|
-
if root is not None:
|
|
49
|
-
return Path(root)
|
|
50
|
-
except InvalidGitRepositoryError:
|
|
51
|
-
pass
|
|
52
|
-
return None
|
|
53
|
-
|
|
54
|
-
|
|
55
41
|
def get_ve_activate_line(ve_root: str):
|
|
56
42
|
if platform.system() == "Windows":
|
|
57
43
|
activate_ve_line = f". {ve_root}/Scripts/activate.ps1"
|