machineconfig 7.49__py3-none-any.whl → 7.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/utils/maker.py +21 -11
- machineconfig/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +16 -12
- machineconfig/jobs/installer/custom_dev/brave.py +1 -1
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/code.py +4 -1
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -10
- machineconfig/jobs/installer/custom_dev/sysabc.py +119 -0
- machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
- machineconfig/jobs/installer/installer_data.json +739 -25
- machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
- machineconfig/jobs/installer/package_groups.py +49 -83
- machineconfig/logger.py +0 -1
- machineconfig/profile/create_links_export.py +21 -7
- machineconfig/profile/mapper.toml +1 -4
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/croshell.py +20 -43
- machineconfig/scripts/python/devops.py +3 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +53 -39
- machineconfig/scripts/python/ftpx.py +4 -2
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +0 -12
- machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_config.py +3 -19
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
- machineconfig/scripts/python/helpers_devops/cli_self.py +12 -6
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_utils.py +1 -152
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
- machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -20
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +1 -1
- machineconfig/scripts/python/helpers_repos/clone.py +0 -1
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +9 -3
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
- machineconfig/scripts/python/helpers_repos/record.py +2 -1
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +7 -7
- machineconfig/scripts/python/helpers_utils/download.py +151 -0
- machineconfig/scripts/python/helpers_utils/path.py +106 -0
- machineconfig/scripts/python/interactive.py +17 -26
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +21 -5
- machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
- machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
- machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
- machineconfig/scripts/python/sessions.py +37 -22
- machineconfig/scripts/python/utils.py +8 -3
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/shells/nushell/init.nu +2 -2
- machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
- machineconfig/settings/shells/zsh/init.sh +1 -8
- machineconfig/settings/yazi/init.lua +45 -24
- machineconfig/settings/yazi/keymap_windows.toml +1 -2
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
- machineconfig/setup_linux/__init__.py +0 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -10
- machineconfig/setup_mac/__init__.py +2 -3
- machineconfig/setup_windows/__init__.py +0 -3
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +16 -0
- machineconfig/utils/code.py +2 -2
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/installer_class.py +42 -40
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +61 -101
- machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -68
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +11 -51
- machineconfig/utils/io.py +0 -1
- machineconfig/utils/meta.py +29 -15
- machineconfig/utils/options.py +1 -1
- machineconfig/utils/path_extended.py +40 -19
- machineconfig/utils/path_helper.py +75 -21
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/ssh.py +3 -3
- machineconfig-7.64.dist-info/METADATA +124 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/RECORD +84 -87
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
- machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
- machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig/setup_linux/apps.sh +0 -66
- machineconfig/setup_mac/apps.sh +0 -73
- machineconfig/setup_windows/apps.ps1 +0 -62
- machineconfig-7.49.dist-info/METADATA +0 -92
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
- /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/WHEEL +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/entry_points.txt +0 -0
- {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""package manager"""
|
|
2
2
|
|
|
3
|
-
from machineconfig.utils.installer_utils.
|
|
3
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_if_installed_already
|
|
4
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
5
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
|
|
6
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
7
7
|
from machineconfig.utils.path_extended import PathExtended
|
|
8
8
|
from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT, LINUX_INSTALL_PATH
|
|
9
9
|
from machineconfig.utils.io import read_json
|
|
@@ -18,7 +18,7 @@ from joblib import Parallel, delayed
|
|
|
18
18
|
def check_latest():
|
|
19
19
|
console = Console() # Added console initialization
|
|
20
20
|
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=["
|
|
21
|
+
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["termabc"])
|
|
22
22
|
installers_github = []
|
|
23
23
|
for inst__ in installers:
|
|
24
24
|
app_name = inst__["appName"]
|
|
@@ -91,7 +91,7 @@ def get_installed_cli_apps():
|
|
|
91
91
|
return apps
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: Optional[list[
|
|
94
|
+
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: Optional[list[str]]) -> list[InstallerData]:
|
|
95
95
|
res_all = get_all_installer_data_files()
|
|
96
96
|
acceptable_apps_names: list[str] | None = None
|
|
97
97
|
if which_cats is not None:
|
|
@@ -105,8 +105,13 @@ def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: O
|
|
|
105
105
|
if acceptable_apps_names is not None:
|
|
106
106
|
if installer_data["appName"] not in acceptable_apps_names:
|
|
107
107
|
continue
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
try:
|
|
109
|
+
if installer_data["fileNamePattern"][arch][os] is None:
|
|
110
|
+
continue
|
|
111
|
+
except KeyError as ke:
|
|
112
|
+
print(f"❌ ERROR: Missing key in installer data: {ke}")
|
|
113
|
+
print(f"Installer data: {installer_data}")
|
|
114
|
+
raise KeyError(f"Missing key in installer data: {ke}")
|
|
110
115
|
all_installers.append(installer_data)
|
|
111
116
|
return all_installers
|
|
112
117
|
|
|
@@ -119,27 +124,6 @@ def get_all_installer_data_files() -> list[InstallerData]:
|
|
|
119
124
|
return res_final
|
|
120
125
|
|
|
121
126
|
|
|
122
|
-
def dynamically_extract_installers_system_groups_from_scripts():
|
|
123
|
-
res_final: list[InstallerData] = []
|
|
124
|
-
from platform import system
|
|
125
|
-
if system() == "Windows":
|
|
126
|
-
from machineconfig.setup_windows import APPS
|
|
127
|
-
options_system = parse_apps_installer_windows(APPS.read_text(encoding="utf-8"))
|
|
128
|
-
elif system() == "Linux":
|
|
129
|
-
from machineconfig.setup_linux import APPS
|
|
130
|
-
options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
|
|
131
|
-
elif system() == "Darwin":
|
|
132
|
-
from machineconfig.setup_mac import APPS
|
|
133
|
-
options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
|
|
134
|
-
else:
|
|
135
|
-
raise NotImplementedError(f"❌ System {system()} not supported")
|
|
136
|
-
os_name = get_os_name()
|
|
137
|
-
for group_name, (docs, script) in options_system.items():
|
|
138
|
-
item: InstallerData = {"appName": group_name, "doc": docs, "repoURL": "CMD", "fileNamePattern": {"amd64": {os_name: script}, "arm64": {os_name: script}}}
|
|
139
|
-
res_final.append(item)
|
|
140
|
-
return res_final
|
|
141
|
-
|
|
142
|
-
|
|
143
127
|
def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs: int = 10, fresh: bool = False):
|
|
144
128
|
print("🚀 BULK INSTALLATION PROCESS 🚀")
|
|
145
129
|
if fresh:
|
|
@@ -148,30 +132,6 @@ def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs:
|
|
|
148
132
|
print("✅ Version cache cleared")
|
|
149
133
|
if safe:
|
|
150
134
|
pass
|
|
151
|
-
# print("⚠️ Safe installation mode activated...")
|
|
152
|
-
# from machineconfig.jobs.python.check_installations import APP_SUMMARY_PATH
|
|
153
|
-
# if platform.system().lower() == "windows":
|
|
154
|
-
# print("🪟 Moving applications to Windows Apps folder...")
|
|
155
|
-
# # PathExtended.get_env().WindowsPaths().WindowsApps)
|
|
156
|
-
# folder = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps")
|
|
157
|
-
# apps_dir.search("*").apply(lambda app: app.move(folder=folder))
|
|
158
|
-
# elif platform.system().lower() in ["linux", "darwin"]:
|
|
159
|
-
# system_name = "Linux" if platform.system().lower() == "linux" else "macOS"
|
|
160
|
-
# print(f"🐧 Moving applications to {system_name} bin folder...")
|
|
161
|
-
# if platform.system().lower() == "linux":
|
|
162
|
-
# install_path = LINUX_INSTALL_PATH
|
|
163
|
-
# else: # Darwin/macOS
|
|
164
|
-
# install_path = "/usr/local/bin"
|
|
165
|
-
# 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)
|
|
166
|
-
# else:
|
|
167
|
-
# error_msg = f"❌ ERROR: System {platform.system()} not supported"
|
|
168
|
-
# print(error_msg)
|
|
169
|
-
# raise NotImplementedError(error_msg)
|
|
170
|
-
|
|
171
|
-
# apps_dir.delete(sure=True)
|
|
172
|
-
# print(f"✅ Safe installation completed\n{'='*80}")
|
|
173
|
-
# return None
|
|
174
|
-
|
|
175
135
|
print(f"🚀 Starting installation of {len(installers_data)} packages...")
|
|
176
136
|
print("📦 INSTALLING FIRST PACKAGE 📦")
|
|
177
137
|
Installer(installers_data[0]).install(version=None)
|
machineconfig/utils/io.py
CHANGED
machineconfig/utils/meta.py
CHANGED
|
@@ -55,6 +55,16 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
55
55
|
import types as _types
|
|
56
56
|
from pathlib import Path as _Path
|
|
57
57
|
|
|
58
|
+
def _stringify_annotation(annotation: Any) -> Any:
|
|
59
|
+
if annotation is _inspect.Signature.empty or annotation is _inspect.Parameter.empty:
|
|
60
|
+
return annotation
|
|
61
|
+
if isinstance(annotation, str):
|
|
62
|
+
return annotation
|
|
63
|
+
try:
|
|
64
|
+
return _inspect.formatannotation(annotation)
|
|
65
|
+
except Exception:
|
|
66
|
+
return str(annotation)
|
|
67
|
+
|
|
58
68
|
# sanity checks
|
|
59
69
|
if not (callable(lmb) and isinstance(lmb, _types.LambdaType)):
|
|
60
70
|
raise TypeError("Expected a lambda function object")
|
|
@@ -174,16 +184,18 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
174
184
|
else:
|
|
175
185
|
new_default = param.default
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
normalized_annotation = _stringify_annotation(param.annotation)
|
|
188
|
+
|
|
178
189
|
if new_default is _inspect.Parameter.empty:
|
|
179
|
-
new_param = _inspect.Parameter(name, param.kind, annotation=
|
|
190
|
+
new_param = _inspect.Parameter(name, param.kind, annotation=normalized_annotation)
|
|
180
191
|
else:
|
|
181
192
|
new_param = _inspect.Parameter(
|
|
182
|
-
name, param.kind, default=new_default, annotation=
|
|
193
|
+
name, param.kind, default=new_default, annotation=normalized_annotation
|
|
183
194
|
)
|
|
184
195
|
new_params.append(new_param)
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
return_annotation = _stringify_annotation(sig.return_annotation)
|
|
198
|
+
new_sig = _inspect.Signature(parameters=new_params, return_annotation=return_annotation)
|
|
187
199
|
|
|
188
200
|
# If in_global mode, return kwargs as global assignments + dedented body
|
|
189
201
|
if in_global:
|
|
@@ -200,15 +212,11 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
200
212
|
|
|
201
213
|
# Build type annotation string if available
|
|
202
214
|
if param.annotation is not _inspect.Parameter.empty:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
type_str = str(param.annotation)
|
|
209
|
-
except Exception:
|
|
210
|
-
type_str = str(param.annotation)
|
|
211
|
-
global_assignments.append(f"{name}: {type_str} = {repr(value)}")
|
|
215
|
+
annotation_literal = _stringify_annotation(param.annotation)
|
|
216
|
+
if isinstance(annotation_literal, str):
|
|
217
|
+
global_assignments.append(f"{name}: {repr(annotation_literal)} = {repr(value)}")
|
|
218
|
+
else:
|
|
219
|
+
global_assignments.append(f"{name} = {repr(value)}")
|
|
212
220
|
else:
|
|
213
221
|
global_assignments.append(f"{name} = {repr(value)}")
|
|
214
222
|
|
|
@@ -233,10 +241,16 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
233
241
|
|
|
234
242
|
if "Optional" in result_text or "Any" in result_text or "Union" in result_text or "Literal" in result_text:
|
|
235
243
|
result_text = "from typing import Optional, Any, Union, Literal\n\n" + result_text
|
|
236
|
-
|
|
237
244
|
if import_prefix:
|
|
238
245
|
result_text = f"{import_prefix}{result_text}"
|
|
239
246
|
return result_text
|
|
240
247
|
|
|
241
248
|
if __name__ == "__main__":
|
|
242
|
-
|
|
249
|
+
from machineconfig.utils.code import print_code
|
|
250
|
+
import_code_robust = "<import_code_robust>"
|
|
251
|
+
res = lambda_to_python_script(
|
|
252
|
+
lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
|
|
253
|
+
# in_global=True, import_module=False
|
|
254
|
+
in_global=True, import_module=False
|
|
255
|
+
)
|
|
256
|
+
print(res)
|
machineconfig/utils/options.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from machineconfig.utils.installer_utils.
|
|
2
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
3
3
|
from rich.text import Text
|
|
4
4
|
from rich.panel import Panel
|
|
5
5
|
from rich.console import Console
|
|
@@ -16,6 +16,7 @@ OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
|
|
|
16
16
|
PLike: TypeAlias = Union[str, "PathExtended", Path]
|
|
17
17
|
FILE_MODE: TypeAlias = Literal["r", "w", "x", "a"]
|
|
18
18
|
SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
|
|
19
|
+
DECOMPRESS_SUPPORTED_FORMATS = [".tar.gz", ".tgz", ".tar", ".gz", ".tar.bz", ".tbz", ".tar.xz", ".zip", ".7z"]
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _is_user_admin() -> bool:
|
|
@@ -152,7 +153,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
152
153
|
# ======================================= File Editing / Reading ===================================
|
|
153
154
|
def download(self, folder: OPLike = None, name: Optional[str] = None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> "PathExtended":
|
|
154
155
|
import requests
|
|
155
|
-
|
|
156
156
|
response = requests.get(self.as_url_str(), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
|
|
157
157
|
assert response.status_code == 200, f"Download failed with status code {response.status_code}\n{response.text}"
|
|
158
158
|
if name is not None:
|
|
@@ -480,9 +480,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
480
480
|
**kwargs: Any,
|
|
481
481
|
) -> "PathExtended":
|
|
482
482
|
path_resolved, slf = self._resolve_path(folder, name, path, self.name).expanduser().resolve(), self.expanduser().resolve()
|
|
483
|
-
# if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
|
|
484
|
-
# path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
|
|
485
|
-
# with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
|
|
486
483
|
arcname_obj = PathExtended(arcname or slf.name)
|
|
487
484
|
if arcname_obj.name != slf.name:
|
|
488
485
|
arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
|
|
@@ -555,15 +552,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
555
552
|
folder = folder if not content else folder.parent
|
|
556
553
|
if slf.suffix == ".7z":
|
|
557
554
|
raise NotImplementedError("I have not implemented this yet")
|
|
558
|
-
# if overwrite: P(folder).delete(sure=True)
|
|
559
|
-
# result = folder
|
|
560
|
-
# import py7zr
|
|
561
|
-
# with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
|
|
562
|
-
# if pattern is not None:
|
|
563
|
-
# import re
|
|
564
|
-
# pat = re.compile(pattern)
|
|
565
|
-
# archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
|
|
566
|
-
# else: archive.extractall(path=folder)
|
|
567
555
|
else:
|
|
568
556
|
if overwrite:
|
|
569
557
|
if not content:
|
|
@@ -698,19 +686,52 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
698
686
|
return ret
|
|
699
687
|
|
|
700
688
|
def decompress(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
|
|
701
|
-
if ".tar.gz"
|
|
689
|
+
if str(self).endswith(".tar.gz") or str(self).endswith(".tgz"):
|
|
702
690
|
# res = self.ungz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
703
691
|
return self.ungz(name=f"tmp_{randstr()}.tar", inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose) # this works for .tgz suffix as well as .tar.gz
|
|
704
|
-
elif
|
|
692
|
+
elif str(self).endswith(".tar"):
|
|
693
|
+
res = self.untar(folder=folder, name=name, path=path, inplace=inplace, orig=orig, verbose=verbose)
|
|
694
|
+
elif str(self).endswith(".gz"):
|
|
705
695
|
res = self.ungz(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
706
|
-
elif ".tar.bz"
|
|
696
|
+
elif str(self).endswith(".tar.bz") or str(self).endswith(".tbz"):
|
|
707
697
|
res = self.unbz(name=f"tmp_{randstr()}.tar", inplace=inplace)
|
|
708
698
|
return res.untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
|
|
709
|
-
elif ".tar.xz"
|
|
699
|
+
elif str(self).endswith(".tar.xz"):
|
|
710
700
|
# res = self.unxz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
711
701
|
res = self.unxz(inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
|
|
712
|
-
elif ".zip"
|
|
702
|
+
elif str(self).endswith(".zip"):
|
|
713
703
|
res = self.unzip(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
704
|
+
elif str(self).endswith(".7z"):
|
|
705
|
+
def unzip_7z(archive_path: str, dest_dir: Optional[str] = None) -> Path:
|
|
706
|
+
"""
|
|
707
|
+
Uncompresses a .7z archive to a directory and returns the Path to the extraction directory.
|
|
708
|
+
|
|
709
|
+
:param archive_path: path to the .7z archive file
|
|
710
|
+
:param dest_dir: optional path to directory to extract into; if None a temporary dir will be created
|
|
711
|
+
:return: pathlib.Path pointing to the destination directory where contents were extracted
|
|
712
|
+
:raises: FileNotFoundError if archive does not exist; py7zr.Bad7zFile or other error if extraction fails
|
|
713
|
+
"""
|
|
714
|
+
import py7zr
|
|
715
|
+
import tempfile
|
|
716
|
+
from pathlib import Path
|
|
717
|
+
archive_path_obj = Path(archive_path)
|
|
718
|
+
if not archive_path_obj.is_file():
|
|
719
|
+
raise FileNotFoundError(f"Archive file not found: {archive_path_obj!r}")
|
|
720
|
+
if dest_dir is None:
|
|
721
|
+
# create a temporary directory
|
|
722
|
+
dest = Path(tempfile.mkdtemp(prefix=f"unzip7z_{archive_path_obj.stem}_"))
|
|
723
|
+
else:
|
|
724
|
+
dest = Path(dest_dir)
|
|
725
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
726
|
+
# Perform extraction
|
|
727
|
+
with py7zr.SevenZipFile(str(archive_path_obj), mode='r') as archive:
|
|
728
|
+
archive.extractall(path=str(dest))
|
|
729
|
+
# Return the extraction directory path
|
|
730
|
+
return dest
|
|
731
|
+
from machineconfig.utils.code import run_lambda_function
|
|
732
|
+
destination_dir = str(self.expanduser().resolve()).replace(".7z", "")
|
|
733
|
+
run_lambda_function(lambda: unzip_7z(archive_path=str(self), dest_dir=destination_dir), uv_project_dir=None, uv_with=["py7zr"])
|
|
734
|
+
res = PathExtended(destination_dir)
|
|
714
735
|
else:
|
|
715
736
|
res = self
|
|
716
737
|
return res
|
|
@@ -788,7 +809,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
788
809
|
path = self
|
|
789
810
|
else:
|
|
790
811
|
try:
|
|
791
|
-
path = self.
|
|
812
|
+
path = PathExtended(self.expanduser().absolute().relative_to(Path.home()))
|
|
792
813
|
except ValueError as ve:
|
|
793
814
|
if strict:
|
|
794
815
|
raise ve
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
from machineconfig.utils.path_extended import
|
|
2
|
-
from machineconfig.utils.options import choose_from_options
|
|
1
|
+
# from machineconfig.utils.path_extended import Path
|
|
3
2
|
from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
|
|
4
3
|
from rich.console import Console
|
|
5
4
|
from rich.panel import Panel
|
|
6
5
|
import platform
|
|
7
6
|
import subprocess
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
|
|
8
|
+
from typing import Optional
|
|
10
9
|
|
|
11
10
|
console = Console()
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
def sanitize_path(a_path: str) ->
|
|
15
|
-
path =
|
|
13
|
+
def sanitize_path(a_path: str) -> Path:
|
|
14
|
+
path = Path(a_path)
|
|
16
15
|
if Path.cwd() == Path.home() and not path.exists():
|
|
17
16
|
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
17
|
if result == "y":
|
|
@@ -23,13 +22,13 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
23
22
|
if platform.system() == "Windows": # path copied from Linux/Mac to Windows
|
|
24
23
|
# For Linux: /home/username, for Mac: /Users/username
|
|
25
24
|
skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
|
|
26
|
-
path =
|
|
25
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
27
26
|
assert path.exists(), f"File not found: {path}"
|
|
28
27
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
29
28
|
console.print(Panel(f"🔗 PATH MAPPING | {source_os} → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
30
|
-
elif platform.system() in ["Linux", "Darwin"] and
|
|
29
|
+
elif platform.system() in ["Linux", "Darwin"] and Path.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
|
|
31
30
|
skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
|
|
32
|
-
path =
|
|
31
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
33
32
|
assert path.exists(), f"File not found: {path}"
|
|
34
33
|
current_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
35
34
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
@@ -37,12 +36,12 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
37
36
|
elif path.as_posix().startswith("C:"):
|
|
38
37
|
if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
|
|
39
38
|
xx = str(a_path).replace("\\\\", "/")
|
|
40
|
-
path =
|
|
39
|
+
path = Path.home().joinpath(*Path(xx).parts[3:]) # exclude C:\\Users\\username
|
|
41
40
|
assert path.exists(), f"File not found: {path}"
|
|
42
41
|
target_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
43
42
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → {target_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
44
|
-
elif platform.system() == "Windows" and
|
|
45
|
-
path =
|
|
43
|
+
elif platform.system() == "Windows" and Path.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
|
|
44
|
+
path = Path.home().joinpath(*path.parts[2:])
|
|
46
45
|
assert path.exists(), f"File not found: {path}"
|
|
47
46
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
48
47
|
return path.expanduser().absolute()
|
|
@@ -67,12 +66,12 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
|
|
|
67
66
|
return filename_matches, partial_path_matches
|
|
68
67
|
|
|
69
68
|
|
|
70
|
-
def match_file_name(sub_string: str, search_root:
|
|
69
|
+
def match_file_name(sub_string: str, search_root: Path, suffixes: set[str]) -> Path:
|
|
71
70
|
search_root_obj = search_root.absolute()
|
|
72
71
|
# assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
|
|
73
72
|
filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
|
|
74
73
|
if len(filename_matches) == 1:
|
|
75
|
-
return
|
|
74
|
+
return Path(filename_matches[0])
|
|
76
75
|
console.print(Panel(f"Partial filename {search_root_obj} match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
|
|
77
76
|
if len(filename_matches) < 20:
|
|
78
77
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
|
|
@@ -81,35 +80,44 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
81
80
|
# let's see if avoiding .lower() helps narrowing down to one result
|
|
82
81
|
reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
|
|
83
82
|
if len(reduced_scripts) == 1:
|
|
84
|
-
return
|
|
83
|
+
return Path(reduced_scripts[0])
|
|
85
84
|
elif len(reduced_scripts) > 1:
|
|
85
|
+
from machineconfig.utils.options import choose_from_options
|
|
86
86
|
choice = choose_from_options(multi=False, msg="Multiple matches found", options=reduced_scripts, fzf=True)
|
|
87
|
-
return
|
|
87
|
+
return Path(choice)
|
|
88
88
|
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
89
89
|
if len(reduced_scripts) < 10:
|
|
90
90
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in reduced_scripts]))
|
|
91
91
|
|
|
92
92
|
console.print(Panel(f"Partial path match with case-insensitivity failed. This generated #{len(partial_path_matches)} results.", title="Search", expand=False))
|
|
93
93
|
if len(partial_path_matches) == 1:
|
|
94
|
-
return
|
|
94
|
+
return Path(partial_path_matches[0])
|
|
95
95
|
elif len(partial_path_matches) > 1:
|
|
96
96
|
print("Try to narrow down partial_path_matches search by case-sensitivity.")
|
|
97
97
|
reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
|
|
98
98
|
if len(reduced_scripts) == 1:
|
|
99
|
-
return
|
|
100
|
-
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
99
|
+
return Path(reduced_scripts[0])
|
|
100
|
+
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
101
|
+
|
|
101
102
|
try:
|
|
102
|
-
|
|
103
|
+
|
|
104
|
+
if len(partial_path_matches) == 0:
|
|
105
|
+
print("No partial path matches found, trying to do fd with --no-ignore ...")
|
|
106
|
+
fzf_cmd = f"cd '{search_root_obj}'; fd --no-ignore --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
|
|
107
|
+
else:
|
|
108
|
+
fzf_cmd = f"cd '{search_root_obj}'; fd --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
|
|
103
109
|
console.print(Panel(f"🔍 Second attempt: SEARCH STRATEGY | Using fd to search for '{sub_string}' in '{search_root_obj}' ...\n{fzf_cmd}", title="Search Strategy", expand=False))
|
|
104
110
|
search_res_raw = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout
|
|
105
|
-
search_res = search_res_raw.strip().split("
|
|
111
|
+
search_res = search_res_raw.strip().split("\n")
|
|
106
112
|
except subprocess.CalledProcessError as cpe:
|
|
107
113
|
console.print(Panel(f"❌ ERROR | FZF search failed with '{sub_string}' in '{search_root_obj}'.\n{cpe}", title="Error", expand=False))
|
|
108
114
|
import sys
|
|
109
|
-
|
|
110
115
|
sys.exit(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results.")
|
|
111
116
|
if len(search_res) == 1:
|
|
112
117
|
return search_root_obj.joinpath(search_res_raw)
|
|
118
|
+
elif len(search_res) == 0:
|
|
119
|
+
msg = Panel(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results", title="File Not Found", expand=False)
|
|
120
|
+
raise FileNotFoundError(msg)
|
|
113
121
|
|
|
114
122
|
print(f"⚠️ WARNING | Multiple search results found for `{sub_string}`:\n'{search_res}'")
|
|
115
123
|
cmd = f"cd '{search_root_obj}'; fd --type file | fzf --select-1 --query={sub_string}"
|
|
@@ -121,3 +129,49 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
121
129
|
msg = Panel(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results", title="File Not Found", expand=False)
|
|
122
130
|
raise FileNotFoundError(msg) from cpe
|
|
123
131
|
return search_root_obj.joinpath(res)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def search_for_files_of_interest(path_obj: Path, suffixes: set[str]) -> list[Path]:
|
|
135
|
+
if path_obj.is_file():
|
|
136
|
+
return [path_obj]
|
|
137
|
+
files: list[Path] = []
|
|
138
|
+
directories_to_visit: list[Path] = [path_obj]
|
|
139
|
+
while directories_to_visit:
|
|
140
|
+
current_dir = directories_to_visit.pop()
|
|
141
|
+
for entry in current_dir.iterdir():
|
|
142
|
+
if entry.is_dir():
|
|
143
|
+
if entry.name == ".venv":
|
|
144
|
+
continue
|
|
145
|
+
directories_to_visit.append(entry)
|
|
146
|
+
continue
|
|
147
|
+
if entry.suffix not in suffixes:
|
|
148
|
+
continue
|
|
149
|
+
if entry.suffix == ".py" and entry.name == "__init__.py":
|
|
150
|
+
continue
|
|
151
|
+
files.append(entry)
|
|
152
|
+
return files
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_choice_file(path: str, suffixes: Optional[set[str]]):
|
|
156
|
+
path_obj = sanitize_path(path)
|
|
157
|
+
if suffixes is None:
|
|
158
|
+
import platform
|
|
159
|
+
if platform.system() == "Windows":
|
|
160
|
+
suffixes = {".py", ".ps1", ".sh"}
|
|
161
|
+
elif platform.system() in ["Linux", "Darwin"]:
|
|
162
|
+
suffixes = {".py", ".sh"}
|
|
163
|
+
else:
|
|
164
|
+
suffixes = {".py"}
|
|
165
|
+
if not path_obj.exists():
|
|
166
|
+
print(f"🔍 Searching for file matching `{path}` under `{Path.cwd()}`, but only if suffix matches {suffixes}")
|
|
167
|
+
choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
|
|
168
|
+
elif path_obj.is_dir():
|
|
169
|
+
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
170
|
+
files = search_for_files_of_interest(path_obj, suffixes=suffixes)
|
|
171
|
+
print(f"🔍 Got #{len(files)} results.")
|
|
172
|
+
from machineconfig.utils.options import choose_from_options
|
|
173
|
+
choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
|
|
174
|
+
choice_file = Path(choice_file)
|
|
175
|
+
else:
|
|
176
|
+
choice_file = path_obj
|
|
177
|
+
return choice_file
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -2,13 +2,13 @@ from typing import Callable, Optional, Any, Union, cast
|
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import platform
|
|
5
|
-
from machineconfig.scripts.python.
|
|
5
|
+
from machineconfig.scripts.python.helpers_utils.path import MachineSpecs
|
|
6
6
|
import rich.console
|
|
7
7
|
from machineconfig.utils.terminal import Response
|
|
8
8
|
from machineconfig.utils.accessories import pprint, randstr
|
|
9
9
|
from machineconfig.utils.meta import lambda_to_python_script
|
|
10
10
|
UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
|
|
11
|
-
MACHINECONFIG_VERSION = "machineconfig>=7.
|
|
11
|
+
MACHINECONFIG_VERSION = "machineconfig>=7.64"
|
|
12
12
|
DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
|
|
13
13
|
|
|
14
14
|
class SSH:
|
|
@@ -113,7 +113,7 @@ class SSH:
|
|
|
113
113
|
if self.progress and self.task is not None:
|
|
114
114
|
self.progress.update(self.task, completed=transferred, total=total)
|
|
115
115
|
self.tqdm_wrap = RichProgressWrapper
|
|
116
|
-
from machineconfig.scripts.python.
|
|
116
|
+
from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
|
|
117
117
|
self.local_specs: MachineSpecs = get_machine_specs()
|
|
118
118
|
resp = self.run_shell(command="""~/.local/bin/utils get-machine-specs """, verbose_output=False, description="Getting remote machine specs", strict_stderr=False, strict_return_code=False)
|
|
119
119
|
json_str = resp.op
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: machineconfig
|
|
3
|
+
Version: 7.64
|
|
4
|
+
Summary: Dotfiles management package
|
|
5
|
+
Author-email: Alex Al-Saffar <programmer@usa.com>
|
|
6
|
+
License: Apache 2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/thisismygitrepo/machineconfig
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/thisismygitrepo/machineconfig/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: cryptography>=44.0.2
|
|
15
|
+
Requires-Dist: fire>=0.7.0
|
|
16
|
+
Requires-Dist: joblib>=1.5.2
|
|
17
|
+
Requires-Dist: paramiko>=3.5.1
|
|
18
|
+
Requires-Dist: randomname>=0.2.1
|
|
19
|
+
Requires-Dist: requests>=2.32.5
|
|
20
|
+
Requires-Dist: rich>=14.0.0
|
|
21
|
+
Requires-Dist: tenacity>=9.1.2
|
|
22
|
+
Requires-Dist: psutil>=7.0.0
|
|
23
|
+
Requires-Dist: gitpython>=3.1.44
|
|
24
|
+
Requires-Dist: pyfzf>=0.3.1
|
|
25
|
+
Requires-Dist: rclone-python>=0.1.23
|
|
26
|
+
Requires-Dist: questionary>=2.1.1
|
|
27
|
+
Requires-Dist: typer-slim>=0.19.2
|
|
28
|
+
Requires-Dist: typer>=0.19.2
|
|
29
|
+
Provides-Extra: windows
|
|
30
|
+
Requires-Dist: pywin32; extra == "windows"
|
|
31
|
+
Provides-Extra: plot
|
|
32
|
+
Requires-Dist: sqlalchemy>=2.0.43; extra == "plot"
|
|
33
|
+
Requires-Dist: ipykernel>=6.30.1; extra == "plot"
|
|
34
|
+
Requires-Dist: ipython>=9.5.0; extra == "plot"
|
|
35
|
+
Requires-Dist: jupyterlab>=4.4.9; extra == "plot"
|
|
36
|
+
Requires-Dist: kaleido>=1.1.0; extra == "plot"
|
|
37
|
+
Requires-Dist: matplotlib>=3.10.6; extra == "plot"
|
|
38
|
+
Requires-Dist: nbformat>=5.10.4; extra == "plot"
|
|
39
|
+
Requires-Dist: numpy>=2.3.3; extra == "plot"
|
|
40
|
+
Requires-Dist: plotly>=6.3.0; extra == "plot"
|
|
41
|
+
Requires-Dist: polars>=1.33.1; extra == "plot"
|
|
42
|
+
Requires-Dist: python-magic>=0.4.27; extra == "plot"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
|
|
47
|
+
<a href="https://github.com/thisismygitrepo/machineconfig/commits">
|
|
48
|
+
<img src="https://img.shields.io/github/commit-activity/m/thisismygitrepo/machineconfig" />
|
|
49
|
+
</a>
|
|
50
|
+
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# 🧠 Welcome to **Machineconfig**
|
|
55
|
+
|
|
56
|
+
**Machineconfig** is a cli-based **Digital Life Manager** — It's called so because no existing category of software fully captures its scope. At the same time, it is a *Package Manager*, *Configuration Manager*, *Automation Tool*, *Dotfiles Manager*, *Data Solution*, and *Code Manager*, among other functionalities covered, all rolled into one seamless experience.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## 💡 Motivation
|
|
60
|
+
|
|
61
|
+
But why do we need such a tool to combine all those functionalities?? Because you need one tool to manager your stack and dev-environment, put it together and maintain it.
|
|
62
|
+
Consider this concrete scenario: When setting up a new machine, VM, or Docker container, you often face dependency chains like this:
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
flowchart TD
|
|
66
|
+
A["Need to setup my [dev] environment"] --> B["need my tool x, e.g.: yadm"]
|
|
67
|
+
B --> C["Requires git"]
|
|
68
|
+
C --> D["Requires package manager, e.g. brew"]
|
|
69
|
+
D --> E["Requires curl"]
|
|
70
|
+
E --> F["Requires network setup / system update"]
|
|
71
|
+
F --> G["Requires system configuration access"]
|
|
72
|
+
G --> H["Finally ready to start setup the tool x."]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Machineconfig builds on shoulder of giants. A suite of best-in-class stack of projects on github are used, the most starred, active and written in Rust tools are used when possible. The goal is to provide a seamless experience that abstracts away the complexity of setting up and maintaining your digital environment. The goal of machineconfig is to replicate your setup, config, code, data and secrets on any machine, any os, in 5 minutes, using minimal user input. Then, from that point, machineconfig will help you maintain, update, backup and sync your digital life across all your devices, automatically.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## ⚙️ Functional Overview
|
|
79
|
+
|
|
80
|
+
| Category | Comparable Tools | Description |
|
|
81
|
+
|------------------------|----------------------------------------------|-----------------------------------------------------------|
|
|
82
|
+
| **Package Manager** | `winget`, `apt`, `brew`, `nix` | Installs and manages software packages across systems. |
|
|
83
|
+
| **Configuration Manager** | `Ansible`, `Chef`, `Puppet` | Configures and maintains system‐level preferences. |
|
|
84
|
+
| **Automation Tool** | `Airflow`, `Prefect`, `Dagster`, `Celery` | Automates repetitive tasks, pipelines, orchestration. |
|
|
85
|
+
| **Dotfiles Manager** | `chezmoi`, `yadm`, `rcm`, `GNU Stow` | Synchronises dotfiles & personal configs across systems. |
|
|
86
|
+
| **Data Solution** | `rclone`, `rsync` | Handles backups, mirroring and secure file sync. |
|
|
87
|
+
| **Code Manager** | `strong‐box`, `Vault` | Manages and protects code snippets, secrets and creds. |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Install On Windows:
|
|
93
|
+
|
|
94
|
+
```powershell
|
|
95
|
+
# install tool the tool only:
|
|
96
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # Skip if UV is already installed
|
|
97
|
+
uv tool install --upgrade --python 3.14 machineconfig
|
|
98
|
+
# interactive install of machineconfig and following on to run it and make basic machine configuration (RECOMMENDED):
|
|
99
|
+
iex (iwr bit.ly/cfgwindows).Content # Or, if UV is installed: iex (uvx machineconfig define)
|
|
100
|
+
# Quick install and configure (optionals are accepted by default):
|
|
101
|
+
iex (iwr bit.ly/cfgwq).Content
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Install On Linux and MacOS
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# install the tool only:
|
|
109
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # Skip if UV is already installed
|
|
110
|
+
uv tool install --upgrade --python 3.14 machineconfig
|
|
111
|
+
# interactive install of machineconfig and following on to run it and make basic machine configuration (RECOMMENDED):
|
|
112
|
+
. <(curl -L bit.ly/cfglinux) # Or, if UV is installed: . <(uvx machineconfig define)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Author
|
|
117
|
+
Alex Al-Saffar. [email](mailto:programmer@usa.com)
|
|
118
|
+
|
|
119
|
+
# Contributor
|
|
120
|
+
Ruby Chan. [email](mailto:ruby.chan@sa.gov.au)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
[](https://github.com/ashutosh00710/github-readme-activity-graph)
|
|
124
|
+
|