machineconfig 1.96__py3-none-any.whl â 2.0__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/cloud_manager.py +22 -26
- machineconfig/cluster/data_transfer.py +2 -2
- machineconfig/cluster/distribute.py +0 -2
- machineconfig/cluster/file_manager.py +4 -4
- machineconfig/cluster/job_params.py +1 -1
- machineconfig/cluster/loader_runner.py +8 -8
- machineconfig/cluster/remote_machine.py +4 -4
- machineconfig/cluster/script_execution.py +2 -2
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +1 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +23 -23
- machineconfig/cluster/sessions_managers/wt_local.py +78 -76
- machineconfig/cluster/sessions_managers/wt_local_manager.py +91 -91
- machineconfig/cluster/sessions_managers/wt_remote.py +39 -39
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +94 -91
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +56 -54
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +18 -18
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +42 -42
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +36 -36
- machineconfig/cluster/sessions_managers/zellij_local.py +43 -46
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +139 -120
- machineconfig/cluster/sessions_managers/zellij_remote.py +35 -35
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +33 -33
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +25 -26
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +5 -5
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +11 -11
- machineconfig/cluster/templates/utils.py +3 -3
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
- machineconfig/jobs/python/check_installations.py +8 -9
- machineconfig/jobs/python/python_cargo_build_share.py +2 -2
- machineconfig/jobs/python/vscode/link_ve.py +7 -7
- machineconfig/jobs/python/vscode/select_interpreter.py +7 -7
- machineconfig/jobs/python/vscode/sync_code.py +5 -5
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +2 -2
- machineconfig/jobs/python_custom_installers/dev/aider.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/brave.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +5 -5
- machineconfig/jobs/python_custom_installers/dev/code.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/cursor.py +9 -9
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/espanso.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/goes.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/lvim.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/redis.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/winget.py +27 -27
- machineconfig/jobs/python_custom_installers/docker.py +3 -3
- machineconfig/jobs/python_custom_installers/gh.py +7 -7
- machineconfig/jobs/python_custom_installers/hx.py +1 -1
- machineconfig/jobs/python_custom_installers/warp-cli.py +3 -3
- machineconfig/jobs/python_generic_installers/config.json +412 -389
- machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
- machineconfig/logger.py +50 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/profile/create.py +23 -16
- machineconfig/profile/create_hardlinks.py +8 -8
- machineconfig/profile/shell.py +41 -37
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/linux/devops +2 -2
- machineconfig/scripts/linux/fire +1 -0
- machineconfig/scripts/linux/fire_agents +0 -1
- machineconfig/scripts/linux/mcinit +27 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
- machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
- machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
- machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
- machineconfig/scripts/python/ai/mcinit.py +103 -0
- machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
- machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +47 -0
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +3 -3
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/cloud_copy.py +19 -18
- machineconfig/scripts/python/cloud_mount.py +9 -7
- machineconfig/scripts/python/cloud_repo_sync.py +11 -11
- machineconfig/scripts/python/cloud_sync.py +1 -1
- machineconfig/scripts/python/croshell.py +14 -14
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_add_identity.py +8 -6
- machineconfig/scripts/python/devops_add_ssh_key.py +18 -18
- machineconfig/scripts/python/devops_backup_retrieve.py +13 -13
- machineconfig/scripts/python/devops_devapps_install.py +3 -3
- machineconfig/scripts/python/devops_update_repos.py +1 -1
- machineconfig/scripts/python/dotfile.py +2 -2
- machineconfig/scripts/python/fire_agents.py +183 -41
- machineconfig/scripts/python/fire_jobs.py +17 -11
- machineconfig/scripts/python/ftpx.py +2 -2
- machineconfig/scripts/python/gh_models.py +94 -94
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
- machineconfig/scripts/python/helpers/helpers2.py +1 -1
- machineconfig/scripts/python/helpers/helpers4.py +8 -6
- machineconfig/scripts/python/helpers/helpers5.py +7 -7
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
- machineconfig/scripts/python/mount_nfs.py +3 -2
- machineconfig/scripts/python/mount_nw_drive.py +4 -4
- machineconfig/scripts/python/mount_ssh.py +3 -2
- machineconfig/scripts/python/repos.py +8 -8
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +8 -7
- machineconfig/scripts/python/start_terminals.py +1 -1
- machineconfig/scripts/python/viewer.py +40 -40
- machineconfig/scripts/python/wifi_conn.py +65 -66
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/scripts/windows/mcinit.ps1 +4 -0
- machineconfig/settings/linters/.ruff.toml +2 -2
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
- machineconfig/settings/shells/wt/settings.json +8 -8
- machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +10 -7
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +9 -7
- machineconfig/utils/ai/browser_user_wrapper.py +5 -5
- machineconfig/utils/ai/generate_file_checklist.py +11 -12
- machineconfig/utils/ai/url2md.py +1 -1
- machineconfig/utils/cloud/onedrive/setup_oauth.py +4 -4
- machineconfig/utils/cloud/onedrive/transaction.py +129 -129
- machineconfig/utils/code.py +13 -6
- machineconfig/utils/installer.py +51 -53
- machineconfig/utils/installer_utils/installer_abc.py +21 -10
- machineconfig/utils/installer_utils/installer_class.py +42 -16
- machineconfig/utils/io_save.py +3 -15
- machineconfig/utils/options.py +10 -3
- machineconfig/utils/path.py +5 -0
- machineconfig/utils/path_reduced.py +201 -149
- machineconfig/utils/procs.py +23 -23
- machineconfig/utils/scheduling.py +11 -12
- machineconfig/utils/ssh.py +270 -0
- machineconfig/utils/terminal.py +180 -0
- machineconfig/utils/utils.py +1 -2
- machineconfig/utils/utils2.py +43 -0
- machineconfig/utils/utils5.py +163 -34
- machineconfig/utils/ve.py +2 -2
- {machineconfig-1.96.dist-info â machineconfig-2.0.dist-info}/METADATA +13 -8
- {machineconfig-1.96.dist-info â machineconfig-2.0.dist-info}/RECORD +163 -144
- machineconfig/cluster/self_ssh.py +0 -57
- {machineconfig-1.96.dist-info â machineconfig-2.0.dist-info}/WHEEL +0 -0
- {machineconfig-1.96.dist-info â machineconfig-2.0.dist-info}/top_level.txt +0 -0
machineconfig/utils/code.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
import platform
|
|
3
|
+
import subprocess
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.panel import Panel
|
|
5
6
|
from rich.syntax import Syntax
|
|
6
7
|
from machineconfig.utils.utils2 import randstr
|
|
7
8
|
from machineconfig.utils.path_reduced import P as PathExtended
|
|
8
|
-
from crocodile.meta import Terminal
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
PROGRAM_PATH = (PathExtended.home().joinpath("tmp_results", "shells", "python_return_command") + (".ps1" if platform.system() == "Windows" else ".sh"))
|
|
@@ -35,7 +35,7 @@ def write_shell_script_to_file(shell_script: str):
|
|
|
35
35
|
else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
36
36
|
shell_file = PathExtended.tmp().joinpath("tmp_scripts", "shell", randstr() + suffix)
|
|
37
37
|
shell_file.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
-
shell_file.write_text(shell_script)
|
|
38
|
+
shell_file.write_text(shell_script, encoding="utf-8")
|
|
39
39
|
return shell_file
|
|
40
40
|
|
|
41
41
|
# Enhanced print/log/error/exception statements for better clarity and consistency
|
|
@@ -50,9 +50,17 @@ def write_shell_script_to_default_program_path(program: str, desc: str, preserve
|
|
|
50
50
|
program = 'orig_path=$(cd -- "." && pwd)\n' + program + '\ncd "$orig_path" || exit'
|
|
51
51
|
if display: print_code(code=program, lexer="shell", desc=desc, subtitle=str(PROGRAM_PATH))
|
|
52
52
|
PROGRAM_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
-
PROGRAM_PATH.write_text(program)
|
|
53
|
+
PROGRAM_PATH.write_text(program, encoding="utf-8")
|
|
54
54
|
if execute:
|
|
55
|
-
|
|
55
|
+
result = subprocess.run(f". {PROGRAM_PATH}", shell=True, capture_output=True, text=True)
|
|
56
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
57
|
+
if not success:
|
|
58
|
+
print(f"â đ ī¸ EXECUTION | Shell script running failed")
|
|
59
|
+
if result.stdout:
|
|
60
|
+
print(f"STDOUT: {result.stdout}")
|
|
61
|
+
if result.stderr:
|
|
62
|
+
print(f"STDERR: {result.stderr}")
|
|
63
|
+
print(f"Return code: {result.returncode}")
|
|
56
64
|
return None
|
|
57
65
|
|
|
58
66
|
def get_shell_file_executing_python_script(python_script: str, ve_name: str, verbose: bool=True):
|
|
@@ -70,7 +78,7 @@ except ImportError:
|
|
|
70
78
|
""" + python_script
|
|
71
79
|
python_file = PathExtended.tmp().joinpath("tmp_scripts", "python", randstr() + ".py")
|
|
72
80
|
python_file.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
-
python_file.write_text(python_script)
|
|
81
|
+
python_file.write_text(python_script, encoding="utf-8")
|
|
74
82
|
shell_script = get_shell_script_executing_python_file(python_file=str(python_file), ve_name=ve_name)
|
|
75
83
|
shell_file = write_shell_script_to_file(shell_script)
|
|
76
84
|
return shell_file
|
|
@@ -82,4 +90,3 @@ def print_code(code: str, lexer: str, desc: str, subtitle: str=""):
|
|
|
82
90
|
else: raise NotImplementedError(f"Platform {platform.system()} not supported for lexer {lexer}")
|
|
83
91
|
console = Console()
|
|
84
92
|
console.print(Panel(Syntax(code=code, lexer=lexer), title=f"đ {desc}", subtitle=subtitle), style="bold red")
|
|
85
|
-
|
machineconfig/utils/installer.py
CHANGED
|
@@ -6,7 +6,6 @@ from rich.console import Console
|
|
|
6
6
|
from rich.panel import Panel # Added import
|
|
7
7
|
|
|
8
8
|
from machineconfig.utils.path_reduced import P as PathExtended
|
|
9
|
-
from crocodile.meta import Terminal
|
|
10
9
|
from machineconfig.utils.utils import INSTALL_VERSION_ROOT
|
|
11
10
|
from machineconfig.utils.utils2 import read_json
|
|
12
11
|
|
|
@@ -23,14 +22,14 @@ def check_latest():
|
|
|
23
22
|
# installers += get_installers(system=platform.system(), dev=True)
|
|
24
23
|
installers_github = []
|
|
25
24
|
for inst__ in installers:
|
|
26
|
-
if "ntop" in inst__.name:
|
|
25
|
+
if "ntop" in inst__.name:
|
|
27
26
|
print(f"âī¸ Skipping {inst__.name} (ntop)")
|
|
28
27
|
continue
|
|
29
28
|
if "github" not in inst__.repo_url:
|
|
30
29
|
print(f"âī¸ Skipping {inst__.name} (not a GitHub release)")
|
|
31
30
|
continue
|
|
32
31
|
installers_github.append(inst__)
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
print(f"\nđ Checking {len(installers_github)} GitHub-based installers...\n")
|
|
35
34
|
|
|
36
35
|
def func(inst: Installer):
|
|
@@ -41,9 +40,9 @@ def check_latest():
|
|
|
41
40
|
|
|
42
41
|
print("\nâŗ Processing installers...\n")
|
|
43
42
|
res = [func(inst) for inst in installers_github]
|
|
44
|
-
|
|
43
|
+
|
|
45
44
|
print("\nđ Generating results table...\n")
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
# Convert to list of dictionaries and group by status
|
|
48
47
|
result_data = []
|
|
49
48
|
for tool, status, current_ver, new_ver in res:
|
|
@@ -53,17 +52,17 @@ def check_latest():
|
|
|
53
52
|
"Current Version": current_ver,
|
|
54
53
|
"New Version": new_ver
|
|
55
54
|
})
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
# Group by status
|
|
58
|
-
grouped_data = {}
|
|
57
|
+
grouped_data: dict[str, list[dict[str, Any]]] = {}
|
|
59
58
|
for item in result_data:
|
|
60
59
|
status = item["Status"]
|
|
61
60
|
if status not in grouped_data:
|
|
62
61
|
grouped_data[status] = []
|
|
63
62
|
grouped_data[status].append(item)
|
|
64
|
-
|
|
63
|
+
|
|
65
64
|
console.print(Panel("đ INSTALLATION STATUS SUMMARY", title="Status", expand=False))
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
# Print each group
|
|
68
67
|
for status, items in grouped_data.items():
|
|
69
68
|
print(f"\n{status.upper()}:")
|
|
@@ -76,16 +75,16 @@ def check_latest():
|
|
|
76
75
|
|
|
77
76
|
def get_installed_cli_apps():
|
|
78
77
|
print(f"\n{'='*80}\nđ LISTING INSTALLED CLI APPS đ\n{'='*80}")
|
|
79
|
-
if platform.system() == "Windows":
|
|
78
|
+
if platform.system() == "Windows":
|
|
80
79
|
print("đĒ Searching for Windows executables...")
|
|
81
80
|
apps = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps").search("*.exe", not_in=["notepad"])
|
|
82
|
-
elif platform.system() in ["Linux", "Darwin"]:
|
|
81
|
+
elif platform.system() in ["Linux", "Darwin"]:
|
|
83
82
|
print(f"đ§ Searching for {platform.system()} executables...")
|
|
84
83
|
if platform.system() == "Linux":
|
|
85
84
|
apps = PathExtended(LINUX_INSTALL_PATH).search("*") + PathExtended("/usr/local/bin").search("*")
|
|
86
85
|
else: # Darwin/macOS
|
|
87
86
|
apps = PathExtended("/usr/local/bin").search("*") + PathExtended("/opt/homebrew/bin").search("*")
|
|
88
|
-
else:
|
|
87
|
+
else:
|
|
89
88
|
error_msg = f"â ERROR: System {platform.system()} not supported"
|
|
90
89
|
print(error_msg)
|
|
91
90
|
raise NotImplementedError(error_msg)
|
|
@@ -111,11 +110,11 @@ def get_installers(system: str, dev: bool) -> list[Installer]:
|
|
|
111
110
|
|
|
112
111
|
def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
|
|
113
112
|
print(f"\n{'='*80}\nđ LOADING CONFIGURATION FILES đ\n{'='*80}")
|
|
114
|
-
|
|
113
|
+
|
|
115
114
|
print(f"đ Importing OS-specific installers for {system}...")
|
|
116
|
-
if system == "Windows":
|
|
115
|
+
if system == "Windows":
|
|
117
116
|
import machineconfig.jobs.python_windows_installers as os_specific_installer
|
|
118
|
-
else:
|
|
117
|
+
else:
|
|
119
118
|
import machineconfig.jobs.python_linux_installers as os_specific_installer
|
|
120
119
|
|
|
121
120
|
print("đ Importing generic installers...")
|
|
@@ -133,10 +132,10 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
|
|
|
133
132
|
|
|
134
133
|
print(f"""đ Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
|
|
135
134
|
res_final["OS_GENERIC"] = read_json(path=path_os_generic.joinpath("config.json"))
|
|
136
|
-
|
|
135
|
+
|
|
137
136
|
print(f"""đ Loading OS-specific dev config from: {path_os_specific_dev.joinpath("config.json")}""")
|
|
138
137
|
res_final["OS_SPECIFIC_DEV"] = read_json(path=path_os_specific_dev.joinpath("config.json"))
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
print(f"""đ Loading OS-generic dev config from: {path_os_generic_dev.joinpath("config.json")}""")
|
|
141
140
|
res_final["OS_GENERIC_DEV"] = read_json(path=path_os_generic_dev.joinpath("config.json"))
|
|
142
141
|
|
|
@@ -166,74 +165,73 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
|
|
|
166
165
|
|
|
167
166
|
res_final["CUSTOM"] = res_custom
|
|
168
167
|
res_final["CUSTOM_DEV"] = res_custom_dev
|
|
169
|
-
|
|
168
|
+
|
|
170
169
|
print(f"â
Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC'])} items\n - CUSTOM: {len(res_final['CUSTOM'])} items\n{'='*80}")
|
|
171
170
|
return res_final
|
|
172
171
|
|
|
173
172
|
|
|
174
173
|
def install_all(installers: list[Installer], safe: bool=False, jobs: int = 10, fresh: bool=False):
|
|
175
174
|
print(f"\n{'='*80}\nđ BULK INSTALLATION PROCESS đ\n{'='*80}")
|
|
176
|
-
if fresh:
|
|
175
|
+
if fresh:
|
|
177
176
|
print("đ§š Fresh install requested - clearing version cache...")
|
|
178
177
|
INSTALL_VERSION_ROOT.delete(sure=True)
|
|
179
178
|
print("â
Version cache cleared")
|
|
180
|
-
|
|
179
|
+
|
|
181
180
|
if safe:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
pass
|
|
182
|
+
# print("â ī¸ Safe installation mode activated...")
|
|
183
|
+
# from machineconfig.jobs.python.check_installations import APP_SUMMARY_PATH
|
|
184
|
+
# if platform.system().lower() == "windows":
|
|
185
|
+
# print("đĒ Moving applications to Windows Apps folder...")
|
|
186
|
+
# # PathExtended.get_env().WindowsPaths().WindowsApps)
|
|
187
|
+
# folder = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps")
|
|
188
|
+
# apps_dir.search("*").apply(lambda app: app.move(folder=folder))
|
|
189
|
+
# elif platform.system().lower() in ["linux", "darwin"]:
|
|
190
|
+
# system_name = "Linux" if platform.system().lower() == "linux" else "macOS"
|
|
191
|
+
# print(f"đ§ Moving applications to {system_name} bin folder...")
|
|
192
|
+
# if platform.system().lower() == "linux":
|
|
193
|
+
# install_path = LINUX_INSTALL_PATH
|
|
194
|
+
# else: # Darwin/macOS
|
|
195
|
+
# install_path = "/usr/local/bin"
|
|
196
|
+
# Terminal().run(f"sudo mv {apps_dir.as_posix()}/* {install_path}/").capture().print_if_unsuccessful(desc=f"MOVING executable to {install_path}", strict_err=True, strict_returncode=True)
|
|
197
|
+
# else:
|
|
198
|
+
# error_msg = f"â ERROR: System {platform.system()} not supported"
|
|
199
|
+
# print(error_msg)
|
|
200
|
+
# raise NotImplementedError(error_msg)
|
|
201
|
+
|
|
202
|
+
# apps_dir.delete(sure=True)
|
|
203
|
+
# print(f"â
Safe installation completed\n{'='*80}")
|
|
204
|
+
# return None
|
|
205
|
+
|
|
208
206
|
print(f"đ Starting installation of {len(installers)} packages...")
|
|
209
207
|
print(f"\n{'='*80}\nđĻ INSTALLING FIRST PACKAGE đĻ\n{'='*80}")
|
|
210
208
|
installers[0].install(version=None)
|
|
211
209
|
installers_remaining = installers[1:]
|
|
212
210
|
print(f"\n{'='*80}\nđĻ INSTALLING REMAINING PACKAGES đĻ\n{'='*80}")
|
|
213
|
-
|
|
211
|
+
|
|
214
212
|
# Use joblib for parallel processing of remaining installers
|
|
215
213
|
res = Parallel(n_jobs=jobs)(
|
|
216
|
-
delayed(lambda x: x.install_robust(version=None))(installer)
|
|
214
|
+
delayed(lambda x: x.install_robust(version=None))(installer)
|
|
217
215
|
for installer in installers_remaining
|
|
218
216
|
)
|
|
219
|
-
|
|
217
|
+
|
|
220
218
|
console = Console()
|
|
221
|
-
|
|
219
|
+
|
|
222
220
|
print("\n")
|
|
223
221
|
console.rule("đ INSTALLATION RESULTS SUMMARY đ")
|
|
224
|
-
|
|
222
|
+
|
|
225
223
|
print("\n")
|
|
226
224
|
console.rule("â Same Version Apps")
|
|
227
225
|
same_version_results = [r for r in res if r and 'same version' in str(r)]
|
|
228
226
|
for result in same_version_results:
|
|
229
227
|
print(f" {result}")
|
|
230
|
-
|
|
228
|
+
|
|
231
229
|
print("\n")
|
|
232
230
|
console.rule("âŦī¸ Updated Apps")
|
|
233
231
|
updated_results = [r for r in res if r and 'updated from' in str(r)]
|
|
234
232
|
for result in updated_results:
|
|
235
233
|
print(f" {result}")
|
|
236
|
-
|
|
234
|
+
|
|
237
235
|
print("\n")
|
|
238
236
|
console.rule("â Failed Apps")
|
|
239
237
|
failed_results = [r for r in res if r and 'Failed at' in str(r)]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from machineconfig.utils.path_reduced import P as PathExtended
|
|
2
|
-
from crocodile.meta import Terminal
|
|
3
2
|
from typing import Optional, TypeAlias, Literal
|
|
3
|
+
import subprocess
|
|
4
4
|
|
|
5
5
|
# LINUX_INSTALL_PATH = '/usr/local/bin'
|
|
6
6
|
# LINUX_INSTALL_PATH = '~/.local/bin'
|
|
@@ -18,12 +18,12 @@ def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optio
|
|
|
18
18
|
else:
|
|
19
19
|
print(f"đ Searching for executable in: {downloaded_file_path}")
|
|
20
20
|
if exe_name is None:
|
|
21
|
-
exe = downloaded_file_path.search("*.exe", r=True)
|
|
21
|
+
exe = downloaded_file_path.search("*.exe", r=True)[0]
|
|
22
22
|
print(f"â
Found executable: {exe}")
|
|
23
23
|
else:
|
|
24
24
|
tmp = downloaded_file_path.search(f"{exe_name}.exe", r=True)
|
|
25
25
|
if len(tmp) == 1:
|
|
26
|
-
exe = tmp
|
|
26
|
+
exe = tmp[0]
|
|
27
27
|
print(f"â
Found exact match for {exe_name}.exe: {exe}")
|
|
28
28
|
else:
|
|
29
29
|
search_res = downloaded_file_path.search("*.exe", r=True)
|
|
@@ -31,10 +31,10 @@ def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optio
|
|
|
31
31
|
print(f"â ERROR: No executable found in {downloaded_file_path}")
|
|
32
32
|
raise IndexError(f"No executable found in {downloaded_file_path}")
|
|
33
33
|
elif len(search_res) == 1:
|
|
34
|
-
exe = search_res
|
|
34
|
+
exe = search_res[0]
|
|
35
35
|
print(f"â
Found single executable: {exe}")
|
|
36
36
|
else:
|
|
37
|
-
exe = search_res
|
|
37
|
+
exe = max(search_res, key=lambda x: x.size("kb"))
|
|
38
38
|
print(f"â
Selected largest executable ({exe.size('kb')} KB): {exe}")
|
|
39
39
|
if rename_to and exe.name != rename_to:
|
|
40
40
|
print(f"đˇī¸ Renaming '{exe.name}' to '{rename_to}'")
|
|
@@ -62,7 +62,7 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
|
|
|
62
62
|
print(f"đ Searching for executable in: {downloaded}")
|
|
63
63
|
res = downloaded.search(f"*{tool_name}*", folders=False, r=True)
|
|
64
64
|
if len(res) == 1:
|
|
65
|
-
exe = res
|
|
65
|
+
exe = res[0]
|
|
66
66
|
print(f"â
Found match for pattern '*{tool_name}*': {exe}")
|
|
67
67
|
else:
|
|
68
68
|
exe_search_res = downloaded.search(tool_name, folders=False, r=True)
|
|
@@ -70,10 +70,10 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
|
|
|
70
70
|
print(f"â ERROR: No search results for `{tool_name}` in `{downloaded}`")
|
|
71
71
|
raise IndexError(f"No executable found in {downloaded}")
|
|
72
72
|
elif len(exe_search_res) == 1:
|
|
73
|
-
exe = exe_search_res
|
|
73
|
+
exe = exe_search_res[0]
|
|
74
74
|
print(f"â
Found exact match for '{tool_name}': {exe}")
|
|
75
75
|
else:
|
|
76
|
-
exe = exe_search_res
|
|
76
|
+
exe = max(exe_search_res, key=lambda x: x.size("kb"))
|
|
77
77
|
print(f"â
Selected largest executable ({exe.size('kb')} KB): {exe}")
|
|
78
78
|
|
|
79
79
|
if rename_to and exe.name != rename_to:
|
|
@@ -87,7 +87,18 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
|
|
|
87
87
|
# exe.move(folder=LINUX_INSTALL_PATH, overwrite=False)
|
|
88
88
|
if "/usr" in LINUX_INSTALL_PATH:
|
|
89
89
|
print("đ Using sudo to move file to system directory...")
|
|
90
|
-
|
|
90
|
+
cmd = f"sudo mv {exe} {LINUX_INSTALL_PATH}/"
|
|
91
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
92
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
93
|
+
if not success:
|
|
94
|
+
desc = f"MOVING executable `{exe}` to {LINUX_INSTALL_PATH}"
|
|
95
|
+
print(f"â {desc} failed")
|
|
96
|
+
if result.stdout:
|
|
97
|
+
print(f"STDOUT: {result.stdout}")
|
|
98
|
+
if result.stderr:
|
|
99
|
+
print(f"STDERR: {result.stderr}")
|
|
100
|
+
print(f"Return code: {result.returncode}")
|
|
101
|
+
raise RuntimeError(f"Failed to move executable: {result.stderr or result.stdout}")
|
|
91
102
|
else:
|
|
92
103
|
exe.move(folder=LINUX_INSTALL_PATH, overwrite=True)
|
|
93
104
|
|
|
@@ -98,4 +109,4 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
|
|
|
98
109
|
|
|
99
110
|
exe_new_location = PathExtended(LINUX_INSTALL_PATH).joinpath(exe.name)
|
|
100
111
|
print(f"â
Executable installed at: {exe_new_location}\n{'='*80}")
|
|
101
|
-
return exe_new_location
|
|
112
|
+
return exe_new_location
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
from machineconfig.utils.path_reduced import P as PathExtended
|
|
3
|
-
from crocodile.meta import Terminal
|
|
4
3
|
from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
|
|
5
4
|
from machineconfig.utils.utils import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT, check_tool_exists
|
|
6
5
|
from machineconfig.utils.utils2 import pprint, read_json
|
|
7
6
|
|
|
8
7
|
import platform
|
|
8
|
+
import subprocess
|
|
9
9
|
from typing import Any, Optional
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
@@ -68,12 +68,14 @@ class Installer:
|
|
|
68
68
|
def install_robust(self, version: Optional[str]):
|
|
69
69
|
try:
|
|
70
70
|
print(f"\n{'='*80}\nđ INSTALLING {self.exe_name.upper()} đ\n{'='*80}")
|
|
71
|
-
|
|
71
|
+
result_old = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
|
|
72
|
+
old_version_cli = result_old.stdout.strip()
|
|
72
73
|
print(f"đ Current version: {old_version_cli or 'Not installed'}")
|
|
73
74
|
|
|
74
75
|
self.install(version=version)
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
result_new = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
|
|
78
|
+
new_version_cli = result_new.stdout.strip()
|
|
77
79
|
print(f"đ New version: {new_version_cli}")
|
|
78
80
|
|
|
79
81
|
if old_version_cli == new_version_cli:
|
|
@@ -104,9 +106,16 @@ class Installer:
|
|
|
104
106
|
program: str = runpy.run_path(str(installer_path), run_name=None)['main'](version=version)
|
|
105
107
|
# print(program)
|
|
106
108
|
print("đ Running installation script...")
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
if platform.system() == "Linux": script = "#!/bin/bash" + "\n" + program
|
|
110
|
+
else: script = program
|
|
111
|
+
script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if platform.system() == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline=None if platform.system() == "Windows" else "\n")
|
|
112
|
+
if platform.system() == "Windows":
|
|
113
|
+
start_cmd = "powershell"
|
|
114
|
+
full_command = f"{start_cmd} {script_file}"
|
|
115
|
+
else:
|
|
116
|
+
start_cmd = "bash"
|
|
117
|
+
full_command = f"{start_cmd} {script_file}"
|
|
118
|
+
subprocess.run(full_command, stdin=None, stdout=None, stderr=None, shell=True, text=True)
|
|
110
119
|
version_to_be_installed = str(version)
|
|
111
120
|
print(f"â
Custom installation completed\n{'='*80}")
|
|
112
121
|
|
|
@@ -116,7 +125,15 @@ class Installer:
|
|
|
116
125
|
desc = package_manager + " installation"
|
|
117
126
|
version_to_be_installed = package_manager + "Latest"
|
|
118
127
|
print(f"đ Running: {self.repo_url}")
|
|
119
|
-
|
|
128
|
+
result = subprocess.run(self.repo_url, shell=True, capture_output=True, text=True)
|
|
129
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
130
|
+
if not success:
|
|
131
|
+
print(f"â {desc} failed")
|
|
132
|
+
if result.stdout:
|
|
133
|
+
print(f"STDOUT: {result.stdout}")
|
|
134
|
+
if result.stderr:
|
|
135
|
+
print(f"STDERR: {result.stderr}")
|
|
136
|
+
print(f"Return code: {result.returncode}")
|
|
120
137
|
print(f"â
Package manager installation completed\n{'='*80}")
|
|
121
138
|
|
|
122
139
|
else:
|
|
@@ -125,7 +142,16 @@ class Installer:
|
|
|
125
142
|
if str(downloaded).endswith(".deb"):
|
|
126
143
|
print(f"đĻ Installing .deb package: {downloaded}")
|
|
127
144
|
assert platform.system() == "Linux"
|
|
128
|
-
|
|
145
|
+
result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
|
|
146
|
+
success = result.returncode == 0 and result.stderr == ""
|
|
147
|
+
if not success:
|
|
148
|
+
desc = "Installing .deb"
|
|
149
|
+
print(f"â {desc} failed")
|
|
150
|
+
if result.stdout:
|
|
151
|
+
print(f"STDOUT: {result.stdout}")
|
|
152
|
+
if result.stderr:
|
|
153
|
+
print(f"STDERR: {result.stderr}")
|
|
154
|
+
print(f"Return code: {result.returncode}")
|
|
129
155
|
print("đī¸ Cleaning up .deb package...")
|
|
130
156
|
downloaded.delete(sure=True)
|
|
131
157
|
print(f"â
DEB package installation completed\n{'='*80}")
|
|
@@ -154,7 +180,7 @@ class Installer:
|
|
|
154
180
|
|
|
155
181
|
print(f"đž Saving version information to: {INSTALL_VERSION_ROOT.joinpath(self.exe_name)}")
|
|
156
182
|
INSTALL_VERSION_ROOT.joinpath(self.exe_name).parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
-
INSTALL_VERSION_ROOT.joinpath(self.exe_name).write_text(version_to_be_installed)
|
|
183
|
+
INSTALL_VERSION_ROOT.joinpath(self.exe_name).write_text(version_to_be_installed, encoding="utf-8")
|
|
158
184
|
print(f"â
Installation completed successfully!\n{'='*80}")
|
|
159
185
|
|
|
160
186
|
def download(self, version: Optional[str]):
|
|
@@ -240,19 +266,19 @@ class Installer:
|
|
|
240
266
|
if use_cache:
|
|
241
267
|
print("đī¸ Using cached version information...")
|
|
242
268
|
if tmp_path.exists():
|
|
243
|
-
existing_version = tmp_path.read_text().rstrip()
|
|
269
|
+
existing_version = tmp_path.read_text(encoding="utf-8").rstrip()
|
|
244
270
|
print(f"đ Found cached version: {existing_version}")
|
|
245
271
|
else:
|
|
246
272
|
existing_version = None
|
|
247
273
|
print("âšī¸ No cached version information found")
|
|
248
274
|
else:
|
|
249
275
|
print("đ Checking installed version directly...")
|
|
250
|
-
|
|
251
|
-
if
|
|
276
|
+
result = subprocess.run([exe_name, "--version"], check=False, capture_output=True, text=True)
|
|
277
|
+
if result.stdout.strip() == '':
|
|
252
278
|
existing_version = None
|
|
253
279
|
print("âšī¸ Could not detect installed version")
|
|
254
280
|
else:
|
|
255
|
-
existing_version =
|
|
281
|
+
existing_version = result.stdout.strip()
|
|
256
282
|
print(f"đ Detected installed version: {existing_version}")
|
|
257
283
|
|
|
258
284
|
if existing_version is not None:
|
|
@@ -262,11 +288,11 @@ class Installer:
|
|
|
262
288
|
return ("â
Uptodate", version.strip(), version_to_be_installed.strip())
|
|
263
289
|
else:
|
|
264
290
|
print(f"đ {exe_name} needs update: {existing_version.rstrip()} â {version_to_be_installed}")
|
|
265
|
-
tmp_path.write_text(version_to_be_installed)
|
|
291
|
+
tmp_path.write_text(version_to_be_installed, encoding="utf-8")
|
|
266
292
|
return ("â Outdated", existing_version.strip(), version_to_be_installed.strip())
|
|
267
293
|
else:
|
|
268
294
|
print(f"đĻ {exe_name} is not installed. Will install version: {version_to_be_installed}")
|
|
269
|
-
tmp_path.write_text(version_to_be_installed)
|
|
295
|
+
tmp_path.write_text(version_to_be_installed, encoding="utf-8")
|
|
270
296
|
|
|
271
297
|
print(f"{'='*80}")
|
|
272
|
-
return ("â ī¸ NotInstalled", "None", version_to_be_installed.strip())
|
|
298
|
+
return ("â ī¸ NotInstalled", "None", version_to_be_installed.strip())
|
machineconfig/utils/io_save.py
CHANGED
|
@@ -5,16 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
import json
|
|
6
6
|
import pickle
|
|
7
7
|
import configparser
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import toml # type: ignore
|
|
11
|
-
except Exception: # pragma: no cover
|
|
12
|
-
toml = None # type: ignore
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
import yaml # type: ignore
|
|
16
|
-
except Exception: # pragma: no cover
|
|
17
|
-
yaml = None # type: ignore
|
|
8
|
+
import toml
|
|
9
|
+
import yaml
|
|
18
10
|
|
|
19
11
|
|
|
20
12
|
PathLike = Union[str, Path]
|
|
@@ -46,8 +38,6 @@ def save_json(obj: Any, path: PathLike, indent: Optional[int] = None, verbose: b
|
|
|
46
38
|
|
|
47
39
|
|
|
48
40
|
def save_toml(obj: Mapping[str, Any], path: PathLike, verbose: bool = False) -> Path:
|
|
49
|
-
if toml is None:
|
|
50
|
-
raise RuntimeError("toml package is required to write TOML files")
|
|
51
41
|
path_obj = _ensure_parent(path)
|
|
52
42
|
with open(path_obj, "w", encoding="utf-8") as fh:
|
|
53
43
|
toml.dump(obj, fh)
|
|
@@ -57,8 +47,6 @@ def save_toml(obj: Mapping[str, Any], path: PathLike, verbose: bool = False) ->
|
|
|
57
47
|
|
|
58
48
|
|
|
59
49
|
def save_yaml(obj: Any, path: PathLike, verbose: bool = False) -> Path:
|
|
60
|
-
if yaml is None:
|
|
61
|
-
raise RuntimeError("PyYAML is required to write YAML files")
|
|
62
50
|
path_obj = _ensure_parent(path)
|
|
63
51
|
with open(path_obj, "w", encoding="utf-8") as fh:
|
|
64
52
|
yaml.safe_dump(obj, fh, sort_keys=False)
|
|
@@ -80,7 +68,7 @@ def save_ini(path: PathLike, obj: Mapping[str, Mapping[str, Any]], verbose: bool
|
|
|
80
68
|
|
|
81
69
|
|
|
82
70
|
class Save:
|
|
83
|
-
"""
|
|
71
|
+
"""
|
|
84
72
|
|
|
85
73
|
Provides static methods for common serialization formats while ensuring
|
|
86
74
|
parent directories exist and returning a `P` path object.
|
machineconfig/utils/options.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from rich.text import Text
|
|
4
4
|
from rich.panel import Panel
|
|
@@ -27,7 +27,13 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
27
27
|
if res is False and install_script is not None:
|
|
28
28
|
console = Console()
|
|
29
29
|
console.print(Panel(f"đĨ INSTALLING TOOL | Installing {tool_name}...", border_style="bold blue", expand=False))
|
|
30
|
-
|
|
30
|
+
result = subprocess.run(install_script, shell=True, capture_output=True, text=True)
|
|
31
|
+
print(f"Command: {install_script}")
|
|
32
|
+
if result.stdout:
|
|
33
|
+
print(f"STDOUT: {result.stdout}")
|
|
34
|
+
if result.stderr:
|
|
35
|
+
print(f"STDERR: {result.stderr}")
|
|
36
|
+
print(f"Return code: {result.returncode}")
|
|
31
37
|
return check_tool_exists(tool_name=tool_name, install_script=None)
|
|
32
38
|
return res
|
|
33
39
|
|
|
@@ -130,7 +136,8 @@ def display_options(msg: str, options: Iterable[T], header: str="", tail: str=""
|
|
|
130
136
|
def choose_cloud_interactively() -> str:
|
|
131
137
|
console = Console()
|
|
132
138
|
console.print(Panel("đ LISTING CLOUD REMOTES | Fetching available cloud remotes...", border_style="bold blue", expand=False))
|
|
133
|
-
|
|
139
|
+
result = subprocess.run("rclone listremotes", shell=True, capture_output=True, text=True)
|
|
140
|
+
tmp = result.stdout if result.returncode == 0 else None
|
|
134
141
|
if isinstance(tmp, str):
|
|
135
142
|
remotes: list[str] = [x.replace(":", "") for x in tmp.splitlines()]
|
|
136
143
|
|
machineconfig/utils/path.py
CHANGED
|
@@ -13,6 +13,11 @@ console = Console()
|
|
|
13
13
|
|
|
14
14
|
def sanitize_path(a_path: PathExtended) -> PathExtended:
|
|
15
15
|
path = PathExtended(a_path)
|
|
16
|
+
if Path.cwd() == Path.home() and not path.exists():
|
|
17
|
+
result = input("Current working directory is home, and passed path is not full path, are you sure you want to continue, [y]/n? ") or "y"
|
|
18
|
+
if result == "y":
|
|
19
|
+
import sys
|
|
20
|
+
sys.exit()
|
|
16
21
|
if path.as_posix().startswith("/home") or path.as_posix().startswith("/Users"):
|
|
17
22
|
if platform.system() == "Windows": # path copied from Linux/Mac to Windows
|
|
18
23
|
# For Linux: /home/username, for Mac: /Users/username
|