machineconfig 4.7__py3-none-any.whl → 4.9__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 +15 -6
- machineconfig/jobs/installer/custom_dev/winget.py +1 -0
- machineconfig/jobs/installer/installer_data.json +2403 -0
- machineconfig/jobs/installer/package_groups.py +154 -0
- machineconfig/scripts/python/devops.py +33 -42
- machineconfig/scripts/python/devops_devapps_install.py +87 -34
- machineconfig/scripts/python/fire_agents.py +49 -70
- machineconfig/scripts/python/fire_agents_help_launch.py +1 -8
- machineconfig/scripts/python/fire_agents_helper_types.py +12 -0
- machineconfig/scripts/python/fire_jobs.py +0 -6
- machineconfig/scripts/python/fire_jobs_args_helper.py +0 -1
- machineconfig/scripts/python/fire_jobs_layout_helper.py +100 -36
- machineconfig/scripts/python/interactive.py +10 -31
- machineconfig/scripts/python/repos.py +7 -6
- machineconfig/scripts/python/share_terminal.py +6 -4
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/installer.py +15 -14
- machineconfig/utils/installer_utils/github_release_bulk.py +2 -12
- machineconfig/utils/installer_utils/installer_abc.py +56 -60
- machineconfig/utils/procs.py +0 -4
- machineconfig/utils/schemas/installer/installer_types.py +0 -1
- {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/METADATA +1 -1
- {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/RECORD +27 -30
- {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/entry_points.txt +0 -1
- machineconfig/jobs/installer/packages_custom_dev.json +0 -380
- machineconfig/jobs/installer/packages_custom_essential.json +0 -39
- machineconfig/jobs/installer/packages_github_dev.json +0 -1127
- machineconfig/jobs/installer/packages_github_essential.json +0 -787
- machineconfig/scripts/linux/fire_agents +0 -2
- machineconfig/scripts/linux/programs +0 -21
- {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/WHEEL +0 -0
- {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/top_level.txt +0 -0
|
@@ -2,14 +2,7 @@
|
|
|
2
2
|
import random
|
|
3
3
|
import shlex
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
AGENTS: TypeAlias = Literal[
|
|
9
|
-
"cursor-agent", "gemini", "crush", "q"
|
|
10
|
-
# warp terminal
|
|
11
|
-
]
|
|
12
|
-
AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
|
|
5
|
+
from machineconfig.scripts.python.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER
|
|
13
6
|
|
|
14
7
|
|
|
15
8
|
def get_gemini_api_keys() -> list[str]:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Literal, TypeAlias
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
AGENTS: TypeAlias = Literal[
|
|
6
|
+
"cursor-agent", "gemini", "crush", "q"
|
|
7
|
+
# warp terminal
|
|
8
|
+
]
|
|
9
|
+
AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
|
|
10
|
+
|
|
11
|
+
SEARCH_STRATEGIES: TypeAlias = Literal["file_path", "keyword_search", "filename_pattern"]
|
|
12
|
+
|
|
@@ -26,10 +26,6 @@ import typer
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def route(args: FireJobArgs, fire_args: str = "") -> None:
|
|
29
|
-
if args.layout:
|
|
30
|
-
from machineconfig.scripts.python.fire_jobs_layout_helper import handle_layout_args
|
|
31
|
-
return handle_layout_args(args.path, args.function)
|
|
32
|
-
|
|
33
29
|
path_obj = sanitize_path(args.path)
|
|
34
30
|
if not path_obj.exists():
|
|
35
31
|
suffixes = {".py", ".sh", ".ps1"}
|
|
@@ -348,7 +344,6 @@ def main(
|
|
|
348
344
|
Nprocess: Annotated[int, typer.Option("--Nprocess", "-p", help="Number of processes to use")] = 1,
|
|
349
345
|
zellij_tab: Annotated[Optional[str], typer.Option("--zellij_tab", "-z", help="Open in a new zellij tab")] = None,
|
|
350
346
|
watch: Annotated[bool, typer.Option("--watch", "-w", help="Watch the file for changes")] = False,
|
|
351
|
-
layout: Annotated[bool, typer.Option("--layout", "-L", help="Use layout configuration (Zellij Or WindowsTerminal)")] = False,
|
|
352
347
|
) -> None:
|
|
353
348
|
"""Main function to process fire jobs arguments."""
|
|
354
349
|
|
|
@@ -377,7 +372,6 @@ def main(
|
|
|
377
372
|
Nprocess=Nprocess,
|
|
378
373
|
zellij_tab=zellij_tab,
|
|
379
374
|
watch=watch,
|
|
380
|
-
layout=layout,
|
|
381
375
|
)
|
|
382
376
|
try:
|
|
383
377
|
route(args, fire_args)
|
|
@@ -1,30 +1,45 @@
|
|
|
1
|
+
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
from typing import Optional, Literal
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_balance(layout_path: Path = typer.Argument(..., help="Path to the layout.json file"),
|
|
8
|
+
max_thresh: int = typer.Option(..., help="Maximum tabs per layout"),
|
|
9
|
+
thresh_type: Literal['number', 'weight'] = typer.Option(..., help="Threshold type"),
|
|
10
|
+
breaking_method: Literal['moreLayouts', 'combineTabs'] = typer.Option(..., help="Breaking method"),
|
|
11
|
+
output_path: Optional[Path] = typer.Option(None, help="Path to write the adjusted layout.json file")):
|
|
12
|
+
"""Adjust layout file to limit max tabs per layout, etc."""
|
|
13
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutsFile
|
|
14
|
+
import json
|
|
15
|
+
layoutfile: LayoutsFile = json.loads(layout_path.read_text())
|
|
16
|
+
layout_configs = layoutfile["layouts"]
|
|
17
|
+
from machineconfig.cluster.sessions_managers.utils.load_balancer import limit_tab_num
|
|
18
|
+
new_layouts = limit_tab_num(layout_configs=layout_configs, max_thresh=max_thresh, threshold_type=thresh_type, breaking_method=breaking_method)
|
|
19
|
+
layoutfile["layouts"] = new_layouts
|
|
20
|
+
target_file = output_path if output_path is not None else layout_path.parent / f'{layout_path.stem}_adjusted_{max_thresh}_{thresh_type}_{breaking_method}.json'
|
|
21
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
target_file.write_text(data=json.dumps(layoutfile, indent=4), encoding="utf-8")
|
|
23
|
+
typer.echo(f"Adjusted layout saved to {target_file}")
|
|
8
24
|
|
|
9
25
|
|
|
10
|
-
def select_layout(layouts_json_file: Path,
|
|
26
|
+
def select_layout(layouts_json_file: Path, selected_layouts_names: Optional[list[str]], select_interactively: bool) -> list["LayoutConfig"]:
|
|
11
27
|
import json
|
|
28
|
+
from machineconfig.utils.options import choose_from_options
|
|
29
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, LayoutsFile
|
|
12
30
|
layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
|
|
13
31
|
if len(layout_file["layouts"]) == 0:
|
|
14
32
|
raise ValueError(f"No layouts found in {layouts_json_file}")
|
|
15
|
-
if
|
|
33
|
+
if selected_layouts_names is None: # choose all, or interactively
|
|
34
|
+
if not select_interactively:
|
|
35
|
+
return layout_file["layouts"]
|
|
16
36
|
options = [layout["layoutName"] for layout in layout_file["layouts"]]
|
|
17
37
|
from machineconfig.utils.options import choose_from_options
|
|
18
|
-
|
|
19
|
-
print(f"Selected layout(s): {
|
|
20
|
-
#
|
|
21
|
-
# if layout_chosen is None:
|
|
22
|
-
# layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layouts_name.lower()), None)
|
|
23
|
-
# if layout_chosen is None:
|
|
24
|
-
# available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
|
|
25
|
-
# raise ValueError(f"Layout '{layouts_name}' not found. Available layouts: {available_layouts}")
|
|
38
|
+
selected_layouts_names = choose_from_options(multi=True, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
|
|
39
|
+
print(f"Selected layout(s): {selected_layouts_names}")
|
|
40
|
+
# Extract the configs from the names:
|
|
26
41
|
layouts_chosen: list[LayoutConfig] = []
|
|
27
|
-
for name in
|
|
42
|
+
for name in selected_layouts_names:
|
|
28
43
|
layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == name), None)
|
|
29
44
|
if layout_chosen is None:
|
|
30
45
|
layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == name.lower()), None)
|
|
@@ -35,23 +50,11 @@ def select_layout(layouts_json_file: Path, layouts_name: Optional[list[str]]) ->
|
|
|
35
50
|
return layouts_chosen
|
|
36
51
|
|
|
37
52
|
|
|
38
|
-
def
|
|
39
|
-
import
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
run_zellij_layout(layout_config=layout_config)
|
|
44
|
-
elif platform.system() == "Windows":
|
|
45
|
-
print("🧑💻 Launching layout using Windows Terminal...")
|
|
46
|
-
from machineconfig.cluster.sessions_managers.wt_local import run_wt_layout
|
|
47
|
-
|
|
48
|
-
run_wt_layout(layout_config=layout_config)
|
|
49
|
-
else:
|
|
50
|
-
print(f"❌ Unsupported platform: {platform.system()}")
|
|
51
|
-
return None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def handle_layout_args(layout_path: str, layouts: Optional[str]) -> None:
|
|
53
|
+
def handle_layout_args(layout_path: str, select_layouts: Optional[str], select_interactively: bool) -> list["LayoutConfig"]:
|
|
54
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
55
|
+
from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
|
|
56
|
+
from machineconfig.utils.options import choose_from_options
|
|
57
|
+
from machineconfig.utils.path_helper import match_file_name, sanitize_path
|
|
55
58
|
path_obj = sanitize_path(layout_path)
|
|
56
59
|
if not path_obj.exists():
|
|
57
60
|
choice_file = match_file_name(sub_string=layout_path, search_root=PathExtended.cwd(), suffixes={".json"})
|
|
@@ -63,5 +66,66 @@ def handle_layout_args(layout_path: str, layouts: Optional[str]) -> None:
|
|
|
63
66
|
choice_file = PathExtended(choice_file)
|
|
64
67
|
else:
|
|
65
68
|
choice_file = path_obj
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
selected_layouts = select_layout(layouts_json_file=choice_file, selected_layouts_names=select_layouts.split(",") if select_layouts else None, select_interactively=select_interactively)
|
|
70
|
+
return selected_layouts
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def launch(layout_path: str = typer.Argument(..., help="Path to the layout.json file"),
|
|
74
|
+
max_tabs: int = typer.Option(10, help="A Sanity checker that throws an error if any layout exceeds the maximum number of tabs to launch."),
|
|
75
|
+
max_layouts: int = typer.Option(10, help="A Sanity checker that throws an error if the total number of layouts exceeds this number."),
|
|
76
|
+
sleep_inbetween: float = typer.Option(1.0, help="Sleep time in seconds between launching layouts"),
|
|
77
|
+
monitor: bool = typer.Option(False, help="Monitor the layout sessions for completion"),
|
|
78
|
+
parallel: bool = typer.Option(False, help="Launch multiple layouts in parallel"),
|
|
79
|
+
kill_upon_completion: bool = typer.Option(False, help="Kill session(s) upon completion (only relevant if monitor flag is set)"),
|
|
80
|
+
select: Optional[str] = typer.Option(None, help="Comma separated names of layouts to be selected from the layout file passed"),
|
|
81
|
+
select_interactively: bool = typer.Option(False, help="Select layouts interactively")
|
|
82
|
+
):
|
|
83
|
+
"""
|
|
84
|
+
"""
|
|
85
|
+
layouts_selected = handle_layout_args(layout_path=layout_path, select_layouts=select, select_interactively=select_interactively)
|
|
86
|
+
|
|
87
|
+
# ============= Basic sanity checks =============
|
|
88
|
+
if len(layouts_selected) > max_layouts:
|
|
89
|
+
raise ValueError(f"Number of layouts {len(layouts_selected)} exceeds the maximum allowed {max_layouts}. Please adjust your layout file.")
|
|
90
|
+
for a_layout in layouts_selected:
|
|
91
|
+
if len(a_layout["layoutTabs"]) > max_tabs:
|
|
92
|
+
typer.echo(f"Layout '{a_layout.get('layoutName', 'Unnamed')}' has {len(a_layout['layoutTabs'])} tabs which exceeds the max of {max_tabs}.")
|
|
93
|
+
confirm = typer.confirm("Do you want to proceed with launching this layout?", default=False)
|
|
94
|
+
if not confirm:
|
|
95
|
+
typer.echo("Aborting launch.")
|
|
96
|
+
raise typer.Exit(0)
|
|
97
|
+
|
|
98
|
+
import time
|
|
99
|
+
import platform
|
|
100
|
+
if platform.system() == "Linux" or platform.system() == "Darwin":
|
|
101
|
+
from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
|
|
102
|
+
if not parallel: iterable = [[item] for item in layouts_selected]
|
|
103
|
+
else: iterable = [layouts_selected]
|
|
104
|
+
for i, a_layouts in enumerate(iterable):
|
|
105
|
+
manager = ZellijLocalManager(session_layouts=a_layouts)
|
|
106
|
+
manager.start_all_sessions(poll_interval=2, poll_seconds=2)
|
|
107
|
+
if monitor:
|
|
108
|
+
manager.run_monitoring_routine(wait_ms=2000)
|
|
109
|
+
if kill_upon_completion:
|
|
110
|
+
manager.kill_all_sessions()
|
|
111
|
+
if i < len(layouts_selected) - 1: # Don't sleep after the last layout
|
|
112
|
+
time.sleep(sleep_inbetween)
|
|
113
|
+
elif platform.system() == "Windows":
|
|
114
|
+
from machineconfig.cluster.sessions_managers.wt_local_manager import WTLocalManager
|
|
115
|
+
if not parallel: iterable = [[item] for item in layouts_selected]
|
|
116
|
+
else: iterable = [layouts_selected]
|
|
117
|
+
for i, a_layouts in enumerate(iterable):
|
|
118
|
+
manager = WTLocalManager(session_layouts=a_layouts)
|
|
119
|
+
manager.start_all_sessions()
|
|
120
|
+
if monitor:
|
|
121
|
+
manager.run_monitoring_routine(wait_ms=2000)
|
|
122
|
+
if kill_upon_completion:
|
|
123
|
+
manager.kill_all_sessions()
|
|
124
|
+
if i < len(layouts_selected) - 1: # Don't sleep after the last layout
|
|
125
|
+
time.sleep(sleep_inbetween)
|
|
126
|
+
else:
|
|
127
|
+
print(f"❌ Unsupported platform: {platform.system()}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
@@ -81,24 +81,7 @@ def install_windows_desktop_apps() -> bool:
|
|
|
81
81
|
"""Install Windows desktop applications using winget."""
|
|
82
82
|
if system() != "Windows":
|
|
83
83
|
console.print("❌ This function is only available on Windows systems.", style="bold red")
|
|
84
|
-
return False
|
|
85
|
-
|
|
86
|
-
console.print(Panel("💻 [bold cyan]WINDOWS DESKTOP APPS[/bold cyan]\n[italic]Installing Brave, Windows Terminal, PowerShell, and VSCode[/italic]", border_style="cyan"))
|
|
87
|
-
|
|
88
|
-
# Install winget applications
|
|
89
|
-
winget_commands = [
|
|
90
|
-
('winget install --no-upgrade --name "Windows Terminal" --Id "Microsoft.WindowsTerminal" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Windows Terminal"),
|
|
91
|
-
('winget install --no-upgrade --name "Powershell" --Id "Microsoft.PowerShell" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing PowerShell"),
|
|
92
|
-
('winget install --no-upgrade --name "Brave" --Id "Brave.Brave" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Brave Browser"),
|
|
93
|
-
('winget install --no-upgrade --name "Microsoft Visual Studio Code" --Id "Microsoft.VisualStudioCode" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Visual Studio Code"),
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
success = True
|
|
97
|
-
for command, description in winget_commands:
|
|
98
|
-
if not run_command(command, description):
|
|
99
|
-
success = False
|
|
100
|
-
|
|
101
|
-
# Install Nerd Fonts via Python
|
|
84
|
+
return False
|
|
102
85
|
console.print("🔧 Installing Nerd Fonts", style="bold cyan")
|
|
103
86
|
try:
|
|
104
87
|
from machineconfig.jobs.installer.custom_dev.nerfont_windows_helper import install_nerd_fonts
|
|
@@ -106,19 +89,14 @@ def install_windows_desktop_apps() -> bool:
|
|
|
106
89
|
console.print("✅ Nerd Fonts installed successfully", style="bold green")
|
|
107
90
|
except Exception as e:
|
|
108
91
|
console.print(f"❌ Error installing Nerd Fonts: {e}", style="bold red")
|
|
109
|
-
success = False
|
|
110
|
-
|
|
111
|
-
# Set Windows Terminal settings via Python
|
|
112
92
|
console.print("🔧 Setting Windows Terminal settings", style="bold cyan")
|
|
113
93
|
try:
|
|
114
94
|
from machineconfig.setup_windows.wt_and_pwsh.set_wt_settings import main as set_wt_settings_main
|
|
115
95
|
set_wt_settings_main()
|
|
116
96
|
console.print("✅ Windows Terminal settings configured successfully", style="bold green")
|
|
117
97
|
except Exception as e:
|
|
118
|
-
console.print(f"❌ Error setting Windows Terminal settings: {e}", style="bold red")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return success
|
|
98
|
+
console.print(f"❌ Error setting Windows Terminal settings: {e}", style="bold red")
|
|
99
|
+
return True
|
|
122
100
|
|
|
123
101
|
|
|
124
102
|
def get_installation_choices() -> list[str]:
|
|
@@ -162,9 +140,13 @@ def execute_installations(selected_options: list[str]) -> None:
|
|
|
162
140
|
run_command(f"bash {script}", "Installing Linux base system applications")
|
|
163
141
|
|
|
164
142
|
if "upgrade_system" in selected_options:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
if system() == "Windows":
|
|
144
|
+
console.print("❌ System upgrade is not applicable on Windows via this script.", style="bold red")
|
|
145
|
+
elif system() == "Linux":
|
|
146
|
+
console.print(Panel("🔄 [bold magenta]SYSTEM UPDATE[/bold magenta]\n[italic]Package management[/italic]", border_style="magenta"))
|
|
147
|
+
run_command("sudo nala upgrade -y", "Upgrading system packages")
|
|
148
|
+
else:
|
|
149
|
+
console.print(f"❌ System upgrade not supported on {system()}.", style="bold red")
|
|
168
150
|
if "install_uv_repos" in selected_options:
|
|
169
151
|
console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Virtual environment setup[/italic]", border_style="green"))
|
|
170
152
|
from machineconfig import setup_linux as module
|
|
@@ -212,9 +194,6 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
|
|
|
212
194
|
|
|
213
195
|
if "install_dev_tools" in selected_options:
|
|
214
196
|
console.print(Panel("🛠️ [bold bright_blue]DEVELOPMENT TOOLS[/bold bright_blue]\n[italic]Software development packages[/italic]", border_style="bright_blue"))
|
|
215
|
-
run_command("(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) || true", "Installing Rust toolchain")
|
|
216
|
-
run_command("sudo nala install libssl-dev -y", "Installing libssl-dev")
|
|
217
|
-
run_command("sudo nala install ffmpeg -y", "Installing ffmpeg")
|
|
218
197
|
console.print("🔧 Installing development applications", style="bold cyan")
|
|
219
198
|
try:
|
|
220
199
|
from machineconfig.scripts.python.devops_devapps_install import main as devops_devapps_install_main
|
|
@@ -5,12 +5,6 @@ in the event that username@github.com is not mentioned in the remote url.
|
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from machineconfig.utils.io import read_ini
|
|
9
|
-
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
10
|
-
from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
11
|
-
from machineconfig.scripts.python.repos_helper_record import main as record_repos
|
|
12
|
-
from machineconfig.scripts.python.repos_helper_clone import clone_repos
|
|
13
|
-
from machineconfig.scripts.python.repos_helper_action import perform_git_operations
|
|
14
8
|
|
|
15
9
|
import typer
|
|
16
10
|
from typing import Annotated, Optional
|
|
@@ -34,6 +28,13 @@ def main(
|
|
|
34
28
|
print("📂 Welcome to the Repository Manager")
|
|
35
29
|
print("=" * 50 + "\n")
|
|
36
30
|
|
|
31
|
+
from machineconfig.utils.io import read_ini
|
|
32
|
+
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
33
|
+
from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
34
|
+
from machineconfig.scripts.python.repos_helper_record import main as record_repos
|
|
35
|
+
from machineconfig.scripts.python.repos_helper_clone import clone_repos
|
|
36
|
+
from machineconfig.scripts.python.repos_helper_action import perform_git_operations
|
|
37
|
+
|
|
37
38
|
if directory == "":
|
|
38
39
|
repos_root = PathExtended.home().joinpath("code") # it is a positional argument, can never be empty.
|
|
39
40
|
else:
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, Annotated
|
|
5
5
|
import typer
|
|
6
|
-
|
|
7
|
-
from rich.panel import Panel
|
|
8
|
-
from rich.text import Text
|
|
9
|
-
from rich.align import Align
|
|
6
|
+
|
|
10
7
|
|
|
11
8
|
|
|
12
9
|
"""
|
|
@@ -21,6 +18,11 @@ reference:
|
|
|
21
18
|
|
|
22
19
|
def display_terminal_url(local_ip_v4: str, port: int, protocol: str = "http") -> None:
|
|
23
20
|
"""Display a flashy, unmissable terminal URL announcement."""
|
|
21
|
+
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
from rich.text import Text
|
|
25
|
+
from rich.align import Align
|
|
24
26
|
console = Console()
|
|
25
27
|
|
|
26
28
|
# Create the main message with styling
|
|
@@ -6,4 +6,4 @@ echo """
|
|
|
6
6
|
======================================================================="""
|
|
7
7
|
|
|
8
8
|
curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/ve.sh | bash
|
|
9
|
-
$HOME/.local/bin/uv run --python 3.13 --with machineconfig devops
|
|
9
|
+
$HOME/.local/bin/uv run --python 3.13 --with machineconfig devops interactive
|
machineconfig/utils/installer.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from machineconfig.utils.installer_utils.installer_abc import check_if_installed_already
|
|
4
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
5
|
-
from machineconfig.utils.schemas.installer.installer_types import
|
|
5
|
+
from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
|
|
6
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.panel import Panel
|
|
8
9
|
|
|
@@ -18,7 +19,7 @@ from joblib import Parallel, delayed
|
|
|
18
19
|
def check_latest():
|
|
19
20
|
console = Console() # Added console initialization
|
|
20
21
|
console.print(Panel("🔍 CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
|
|
21
|
-
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["
|
|
22
|
+
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["ESSENTIAL"])
|
|
22
23
|
installers_github = []
|
|
23
24
|
for inst__ in installers:
|
|
24
25
|
app_name = inst__["appName"]
|
|
@@ -91,30 +92,30 @@ def get_installed_cli_apps():
|
|
|
91
92
|
return apps
|
|
92
93
|
|
|
93
94
|
|
|
94
|
-
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: list[
|
|
95
|
+
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: list[PACKAGE_GROUPS]) -> list[InstallerData]:
|
|
95
96
|
print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
|
|
96
|
-
res_all = get_all_installer_data_files(
|
|
97
|
+
res_all = get_all_installer_data_files()
|
|
98
|
+
acceptable_apps_names: list[str] = []
|
|
99
|
+
for cat in which_cats:
|
|
100
|
+
acceptable_apps_names += PACKAGE_GROUP2NAMES[cat]
|
|
97
101
|
all_installers: list[InstallerData] = []
|
|
98
|
-
for
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if an_installer["fileNamePattern"][arch][os] is None:
|
|
102
|
+
for installer_data in res_all:
|
|
103
|
+
if installer_data["appName"] in acceptable_apps_names:
|
|
104
|
+
if installer_data["fileNamePattern"][arch][os] is None:
|
|
102
105
|
continue
|
|
103
|
-
|
|
104
|
-
all_installers.extend(suitable_installers)
|
|
106
|
+
all_installers.append(installer_data)
|
|
105
107
|
print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
|
|
106
108
|
return all_installers
|
|
107
109
|
|
|
108
110
|
|
|
109
|
-
def get_all_installer_data_files(
|
|
111
|
+
def get_all_installer_data_files() -> list[InstallerData]:
|
|
110
112
|
print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
|
|
111
113
|
import machineconfig.jobs.installer as module
|
|
112
114
|
from pathlib import Path
|
|
113
115
|
print("📂 Loading configuration files...")
|
|
114
|
-
|
|
116
|
+
res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
|
|
117
|
+
res_final: list[InstallerData] = res_raw["installers"]
|
|
115
118
|
print(f"Loaded: {len(res_final)} installer categories")
|
|
116
|
-
for k, v in res_final.items():
|
|
117
|
-
print(f" - {k}: {len(v['installers'])} items")
|
|
118
119
|
return res_final
|
|
119
120
|
|
|
120
121
|
|
|
@@ -109,8 +109,7 @@ def main() -> None:
|
|
|
109
109
|
current_dir = Path(__file__).parent
|
|
110
110
|
installer_dir = current_dir.parent.parent / "jobs" / "installer"
|
|
111
111
|
|
|
112
|
-
standard_json = installer_dir / "
|
|
113
|
-
dev_json = installer_dir / "packages_dev.json"
|
|
112
|
+
standard_json = installer_dir / "installer_data.json"
|
|
114
113
|
output_json = current_dir / "github_releases.json"
|
|
115
114
|
|
|
116
115
|
print("🔍 Starting GitHub release data extraction...")
|
|
@@ -125,16 +124,7 @@ def main() -> None:
|
|
|
125
124
|
all_github_repos.update(repos)
|
|
126
125
|
print(f" Found {len(repos)} GitHub repos")
|
|
127
126
|
else:
|
|
128
|
-
print(f"⚠️ File not found: {standard_json}")
|
|
129
|
-
|
|
130
|
-
if dev_json.exists():
|
|
131
|
-
print(f"📄 Reading {dev_json.name}...")
|
|
132
|
-
repos = extract_github_repos_from_json(dev_json)
|
|
133
|
-
all_github_repos.update(repos)
|
|
134
|
-
print(f" Found {len(repos)} GitHub repos")
|
|
135
|
-
else:
|
|
136
|
-
print(f"⚠️ File not found: {dev_json}")
|
|
137
|
-
|
|
127
|
+
print(f"⚠️ File not found: {standard_json}")
|
|
138
128
|
print(f"🎯 Total unique GitHub repositories found: {len(all_github_repos)}")
|
|
139
129
|
|
|
140
130
|
if not all_github_repos:
|
|
@@ -3,7 +3,7 @@ from machineconfig.utils.path_extended import PathExtended as PathExtended
|
|
|
3
3
|
from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH, INSTALL_VERSION_ROOT
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Optional
|
|
7
7
|
import subprocess
|
|
8
8
|
import platform
|
|
9
9
|
|
|
@@ -166,75 +166,71 @@ def check_if_installed_already(exe_name: str, version: Optional[str], use_cache:
|
|
|
166
166
|
return ("⚠️ NotInstalled", "None", version or "unknown")
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
def parse_apps_installer_linux(txt: str) -> dict[str,
|
|
169
|
+
def parse_apps_installer_linux(txt: str) -> dict[str, tuple[str, str]]:
|
|
170
170
|
"""Parse Linux shell installation scripts into logical chunks.
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
2. New format with # --BLOCK:<name>-- comment signatures
|
|
172
|
+
Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
|
|
173
|
+
mapping block names to (description, shell script content) tuples.
|
|
175
174
|
|
|
176
175
|
Returns:
|
|
177
|
-
dict[str, str]: Dictionary mapping block/section names to
|
|
176
|
+
dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
|
|
178
177
|
"""
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
chunks = txt.split('# --GROUP:')
|
|
179
|
+
res: dict[str, tuple[str, str]] = {}
|
|
180
|
+
|
|
181
|
+
for chunk in chunks[1:]: # Skip first empty chunk before first group
|
|
182
|
+
lines = chunk.split('\n')
|
|
183
|
+
# First line contains the group name and description in format "NAME:DESCRIPTION"
|
|
184
|
+
group_line = lines[0].strip()
|
|
185
|
+
|
|
186
|
+
# Extract group name and description
|
|
187
|
+
if ':' in group_line:
|
|
188
|
+
parts = group_line.split(':', 1) # Split only on first colon
|
|
189
|
+
group_name = parts[0].strip()
|
|
190
|
+
group_description = parts[1].strip() if len(parts) > 1 else ""
|
|
191
|
+
else:
|
|
192
|
+
group_name = group_line
|
|
193
|
+
group_description = ""
|
|
185
194
|
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
if i + 1 < len(blocks):
|
|
189
|
-
block_name = blocks[i].strip()
|
|
190
|
-
block_content = blocks[i + 1].strip()
|
|
191
|
-
if block_content:
|
|
192
|
-
res[block_name] = block_content
|
|
195
|
+
# Rest is the content
|
|
196
|
+
content = '\n'.join(lines[1:]).strip()
|
|
193
197
|
|
|
194
|
-
|
|
198
|
+
if group_name and content:
|
|
199
|
+
res[group_name] = (group_description, content)
|
|
195
200
|
|
|
196
|
-
# Legacy format fallback
|
|
197
|
-
txts = txt.split("""yes '' | sed 3q; echo "----------------------------- installing """)
|
|
198
|
-
res = {}
|
|
199
|
-
for chunk in txts[1:]:
|
|
200
|
-
try:
|
|
201
|
-
k = chunk.split("----")[0].rstrip().lstrip()
|
|
202
|
-
v = "\n".join(chunk.split("\n")[1:])
|
|
203
|
-
res[k] = v
|
|
204
|
-
except IndexError as e:
|
|
205
|
-
print(f"""
|
|
206
|
-
❌ Error parsing chunk:
|
|
207
|
-
{"-" * 50}
|
|
208
|
-
{chunk}
|
|
209
|
-
{"-" * 50}""")
|
|
210
|
-
raise e
|
|
211
201
|
return res
|
|
212
202
|
|
|
213
203
|
|
|
214
|
-
def parse_apps_installer_windows(txt: str) -> dict[str,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
204
|
+
def parse_apps_installer_windows(txt: str) -> dict[str, tuple[str, str]]:
|
|
205
|
+
"""Parse Windows PowerShell installation scripts into logical chunks.
|
|
206
|
+
|
|
207
|
+
Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
|
|
208
|
+
mapping block names to (description, PowerShell script content) tuples.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
|
|
212
|
+
"""
|
|
213
|
+
chunks = txt.split('# --GROUP:')
|
|
214
|
+
res: dict[str, tuple[str, str]] = {}
|
|
215
|
+
|
|
216
|
+
for chunk in chunks[1:]: # Skip first chunk before first group
|
|
217
|
+
lines = chunk.split('\n')
|
|
218
|
+
# First line contains the group name and description in format "NAME:DESCRIPTION"
|
|
219
|
+
group_line = lines[0].strip()
|
|
220
|
+
|
|
221
|
+
# Extract group name and description
|
|
222
|
+
if ':' in group_line:
|
|
223
|
+
parts = group_line.split(':', 1) # Split only on first colon
|
|
224
|
+
group_name = parts[0].strip()
|
|
225
|
+
group_description = parts[1].strip() if len(parts) > 1 else ""
|
|
221
226
|
else:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
name = name.split(" --Id ", maxsplit=1)[0].strip('"').strip('"')
|
|
232
|
-
res[name] = a_chunk
|
|
233
|
-
except IndexError as e:
|
|
234
|
-
print(f"""
|
|
235
|
-
❌ Error parsing chunk:
|
|
236
|
-
{"-" * 50}
|
|
237
|
-
{a_chunk}
|
|
238
|
-
{"-" * 50}""")
|
|
239
|
-
raise e
|
|
227
|
+
group_name = group_line
|
|
228
|
+
group_description = ""
|
|
229
|
+
|
|
230
|
+
# Rest is the content
|
|
231
|
+
content = '\n'.join(lines[1:]).strip()
|
|
232
|
+
|
|
233
|
+
if group_name and content:
|
|
234
|
+
res[group_name] = (group_description, content)
|
|
235
|
+
|
|
240
236
|
return res
|
machineconfig/utils/procs.py
CHANGED
|
@@ -58,10 +58,8 @@ class ProcessManager:
|
|
|
58
58
|
title = "📊 INITIALIZING PROCESS MANAGER"
|
|
59
59
|
console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
60
60
|
process_info = []
|
|
61
|
-
|
|
62
61
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
|
|
63
62
|
progress.add_task("🔍 Reading system processes...", total=None)
|
|
64
|
-
|
|
65
63
|
for proc in psutil.process_iter():
|
|
66
64
|
try:
|
|
67
65
|
mem_usage_mb = proc.memory_info().rss / (1024 * 1024)
|
|
@@ -162,7 +160,6 @@ class ProcessManager:
|
|
|
162
160
|
console.print(Panel("", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
163
161
|
|
|
164
162
|
def kill(self, names: Optional[list[str]] = None, pids: Optional[list[int]] = None, commands: Optional[list[str]] = None):
|
|
165
|
-
# header for process termination
|
|
166
163
|
title = "💀 PROCESS TERMINATION"
|
|
167
164
|
console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
168
165
|
if names is None and pids is None and commands is None:
|
|
@@ -241,7 +238,6 @@ def get_age(create_time: Any) -> str:
|
|
|
241
238
|
|
|
242
239
|
def main():
|
|
243
240
|
from machineconfig.utils.procs import ProcessManager
|
|
244
|
-
|
|
245
241
|
ProcessManager().choose_and_kill()
|
|
246
242
|
|
|
247
243
|
|
|
@@ -2,7 +2,6 @@ from typing import TypedDict, Literal, TypeAlias, Optional
|
|
|
2
2
|
import platform
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
APP_INSTALLER_CATEGORY: TypeAlias = Literal["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL", "GITHUB_DEV", "CUSTOM_DEV"]
|
|
6
5
|
CPU_ARCHITECTURES: TypeAlias = Literal["amd64", "arm64"]
|
|
7
6
|
OPERATING_SYSTEMS: TypeAlias = Literal["windows", "linux", "macos"]
|
|
8
7
|
|