machineconfig 2.1__py3-none-any.whl ā 2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
- machineconfig/cluster/sessions_managers/layout_types.py +29 -0
- machineconfig/cluster/sessions_managers/wt_local.py +68 -62
- machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
- machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
- machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
- machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
- machineconfig/cluster/templates/utils.py +0 -35
- machineconfig/jobs/python/check_installations.py +1 -1
- machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
- machineconfig/jobs/python_generic_installers/config.json +1 -1
- machineconfig/profile/create.py +13 -4
- machineconfig/profile/create_hardlinks.py +3 -1
- machineconfig/profile/shell.py +8 -7
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/linux/devops +6 -4
- machineconfig/scripts/python/ai/generate_files.py +14 -15
- machineconfig/scripts/python/ai/mcinit.py +8 -5
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +7 -7
- machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
- machineconfig/scripts/python/cloud_copy.py +22 -13
- machineconfig/scripts/python/cloud_mount.py +35 -23
- machineconfig/scripts/python/cloud_repo_sync.py +38 -25
- machineconfig/scripts/python/cloud_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +37 -28
- machineconfig/scripts/python/devops.py +46 -27
- machineconfig/scripts/python/devops_add_identity.py +15 -25
- machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
- machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
- machineconfig/scripts/python/devops_devapps_install.py +26 -20
- machineconfig/scripts/python/devops_update_repos.py +142 -57
- machineconfig/scripts/python/dotfile.py +16 -14
- machineconfig/scripts/python/fire_agents.py +30 -23
- machineconfig/scripts/python/fire_jobs.py +86 -98
- machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
- machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
- machineconfig/scripts/python/ftpx.py +24 -14
- machineconfig/scripts/python/get_zellij_cmd.py +8 -7
- machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
- machineconfig/scripts/python/helpers/helpers2.py +25 -14
- machineconfig/scripts/python/helpers/helpers4.py +44 -31
- machineconfig/scripts/python/helpers/helpers5.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
- machineconfig/scripts/python/mount_nfs.py +8 -15
- machineconfig/scripts/python/mount_nw_drive.py +10 -5
- machineconfig/scripts/python/mount_ssh.py +8 -6
- machineconfig/scripts/python/repos.py +215 -57
- machineconfig/scripts/python/snapshot.py +0 -1
- machineconfig/scripts/python/start_slidev.py +10 -5
- machineconfig/scripts/python/start_terminals.py +22 -16
- machineconfig/scripts/python/viewer_template.py +0 -1
- machineconfig/scripts/python/wifi_conn.py +49 -76
- machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
- machineconfig/settings/lf/linux/lfrc +1 -0
- machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
- machineconfig/utils/code.py +2 -3
- machineconfig/utils/installer.py +2 -2
- machineconfig/utils/installer_utils/installer_abc.py +2 -4
- machineconfig/utils/installer_utils/installer_class.py +6 -4
- machineconfig/utils/links.py +103 -33
- machineconfig/utils/notifications.py +52 -38
- machineconfig/utils/options.py +14 -21
- machineconfig/utils/path.py +12 -12
- machineconfig/utils/path_reduced.py +239 -200
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +9 -19
- machineconfig/utils/terminal.py +4 -2
- machineconfig/utils/upgrade_packages.py +91 -0
- machineconfig/utils/utils2.py +1 -2
- machineconfig/utils/utils5.py +23 -11
- machineconfig/utils/ve.py +4 -1
- {machineconfig-2.1.dist-info ā machineconfig-2.3.dist-info}/METADATA +13 -13
- {machineconfig-2.1.dist-info ā machineconfig-2.3.dist-info}/RECORD +105 -121
- machineconfig-2.3.dist-info/entry_points.txt +2 -0
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
- machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
- machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
- machineconfig/utils/utils.py +0 -97
- /machineconfig/cluster/{cloud_manager.py ā remote/cloud_manager.py} +0 -0
- /machineconfig/cluster/{data_transfer.py ā remote/data_transfer.py} +0 -0
- /machineconfig/cluster/{distribute.py ā remote/distribute.py} +0 -0
- /machineconfig/cluster/{file_manager.py ā remote/file_manager.py} +0 -0
- /machineconfig/cluster/{job_params.py ā remote/job_params.py} +0 -0
- /machineconfig/cluster/{loader_runner.py ā remote/loader_runner.py} +0 -0
- /machineconfig/cluster/{remote_machine.py ā remote/remote_machine.py} +0 -0
- /machineconfig/cluster/{script_execution.py ā remote/script_execution.py} +0 -0
- /machineconfig/cluster/{script_notify_upon_completion.py ā remote/script_notify_upon_completion.py} +0 -0
- /machineconfig/{cluster/sessions_managers/archive/__init__.py ā scripts/python/fire_jobs_streamlit_helper.py} +0 -0
- /machineconfig/setup_linux/web_shortcuts/{tmp.sh ā android.sh} +0 -0
- {machineconfig-2.1.dist-info ā machineconfig-2.3.dist-info}/WHEEL +0 -0
- {machineconfig-2.1.dist-info ā machineconfig-2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""BR: Backup and Retrieve
|
|
2
|
-
"""
|
|
1
|
+
"""BR: Backup and Retrieve"""
|
|
3
2
|
|
|
4
3
|
# import subprocess
|
|
5
4
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
6
5
|
from machineconfig.utils.utils2 import read_ini, read_toml
|
|
7
|
-
from machineconfig.utils.
|
|
6
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, DEFAULTS_PATH
|
|
7
|
+
from machineconfig.utils.code import print_code
|
|
8
|
+
from machineconfig.utils.options import choose_cloud_interactively, choose_multiple_options
|
|
8
9
|
from machineconfig.scripts.python.helpers.helpers2 import ES
|
|
9
10
|
from platform import system
|
|
10
11
|
from typing import Any, Literal, Optional
|
|
@@ -19,7 +20,7 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str] = None):
|
|
|
19
20
|
console = Console()
|
|
20
21
|
|
|
21
22
|
try:
|
|
22
|
-
cloud: str = read_ini(DEFAULTS_PATH)[
|
|
23
|
+
cloud: str = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
23
24
|
console.print(Panel(f"ā ļø DEFAULT CLOUD CONFIGURATION\nš„ļø Using default cloud: {cloud}", title="[bold blue]Cloud Configuration[/bold blue]", border_style="blue"))
|
|
24
25
|
except (FileNotFoundError, KeyError, IndexError):
|
|
25
26
|
console.print(Panel("š DEFAULT CLOUD NOT FOUND\nš Please select a cloud configuration from the options below", title="[bold red]Error: Cloud Not Found[/bold red]", border_style="red"))
|
|
@@ -38,7 +39,7 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str] = None):
|
|
|
38
39
|
|
|
39
40
|
if which is None:
|
|
40
41
|
console.print(Panel(f"š SELECT {direction} ITEMS\nš Choose which configuration entries to process", title="[bold blue]Select Items[/bold blue]", border_style="blue"))
|
|
41
|
-
choices = choose_multiple_options(msg=f"WHICH FILE of the following do you want to {direction}?", options=[
|
|
42
|
+
choices = choose_multiple_options(msg=f"WHICH FILE of the following do you want to {direction}?", options=["all"] + list(bu_file.keys()))
|
|
42
43
|
else:
|
|
43
44
|
choices = which.split(",") if which else []
|
|
44
45
|
console.print(Panel(f"š PRE-SELECTED ITEMS\nš Using: {', '.join(choices)}", title="[bold blue]Pre-selected Items[/bold blue]", border_style="blue"))
|
|
@@ -52,19 +53,20 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str] = None):
|
|
|
52
53
|
program = f"""$cloud = "{cloud}:{ES}" \n """ if system() == "Windows" else f"""cloud="{cloud}:{ES}" \n """
|
|
53
54
|
console.print(Panel(f"š GENERATING {direction} SCRIPT\nš„ļø Cloud: {cloud}\nšļø Items: {len(items)}", title="[bold blue]Script Generation[/bold blue]", border_style="blue"))
|
|
54
55
|
for item_name, item in items.items():
|
|
55
|
-
flags =
|
|
56
|
-
flags +=
|
|
57
|
-
flags +=
|
|
58
|
-
flags +=
|
|
59
|
-
flags +=
|
|
56
|
+
flags = ""
|
|
57
|
+
flags += "z" if item["zip"] == "True" else ""
|
|
58
|
+
flags += "e" if item["encrypt"] == "True" else ""
|
|
59
|
+
flags += "r" if item["rel2home"] == "True" else ""
|
|
60
|
+
flags += "o" if system().lower() in item_name else ""
|
|
60
61
|
console.print(Panel(f"š¦ PROCESSING: {item_name}\nš Path: {PathExtended(item['path']).as_posix()}\nš³ļø Flags: {flags or 'None'}", title=f"[bold blue]Processing Item: {item_name}[/bold blue]", border_style="blue"))
|
|
61
|
-
if flags:
|
|
62
|
+
if flags:
|
|
63
|
+
flags = "-" + flags
|
|
62
64
|
if direction == "BACKUP":
|
|
63
|
-
program += f"""\ncloud_copy "{PathExtended(item[
|
|
65
|
+
program += f"""\ncloud_copy "{PathExtended(item["path"]).as_posix()}" $cloud {flags}\n"""
|
|
64
66
|
elif direction == "RETRIEVE":
|
|
65
|
-
program += f"""\ncloud_copy $cloud "{PathExtended(item[
|
|
67
|
+
program += f"""\ncloud_copy $cloud "{PathExtended(item["path"]).as_posix()}" {flags}\n"""
|
|
66
68
|
else:
|
|
67
|
-
console.print(Panel(
|
|
69
|
+
console.print(Panel('ā ERROR: INVALID DIRECTION\nā ļø Direction must be either "BACKUP" or "RETRIEVE"', title="[bold red]Error: Invalid Direction[/bold red]", border_style="red"))
|
|
68
70
|
raise RuntimeError(f"Unknown direction: {direction}")
|
|
69
71
|
if item_name == "dotfiles" and system() == "Linux":
|
|
70
72
|
program += """\nchmod 700 ~/.ssh/*\n"""
|
|
@@ -80,7 +82,7 @@ def main(direction: OPTIONS, which: Optional[str] = None):
|
|
|
80
82
|
console.print(Panel(f"š {direction} OPERATION STARTED\nā±ļø {'-' * 58}", title="[bold blue]Operation Initiated[/bold blue]", border_style="blue"))
|
|
81
83
|
|
|
82
84
|
code = main_backup_retrieve(direction=direction, which=which)
|
|
83
|
-
from machineconfig.utils.
|
|
85
|
+
from machineconfig.utils.code import write_shell_script_to_default_program_path
|
|
84
86
|
|
|
85
87
|
console.print(Panel("š¾ GENERATING SHELL SCRIPT\nš Filename: backup_retrieve.sh", title="[bold blue]Shell Script Generation[/bold blue]", border_style="blue"))
|
|
86
88
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""Devops Devapps Install
|
|
2
|
-
"""
|
|
1
|
+
"""Devops Devapps Install"""
|
|
3
2
|
|
|
4
3
|
# import subprocess
|
|
5
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
6
5
|
from tqdm import tqdm
|
|
7
|
-
from machineconfig.utils.
|
|
6
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
7
|
+
from machineconfig.utils.options import choose_multiple_options
|
|
8
8
|
from machineconfig.utils.installer import get_installers, install_all, get_all_dicts
|
|
9
9
|
from platform import system
|
|
10
10
|
from typing import Any, Optional, Literal, TypeAlias, get_args
|
|
@@ -14,13 +14,12 @@ WHICH_CAT: TypeAlias = Literal["AllEssentials", "EssentialsAndOthers", "SystemIn
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def main(which: Optional[WHICH_CAT | str] = None):
|
|
17
|
-
|
|
18
17
|
if which is not None and which in get_args(WHICH_CAT): # install by category
|
|
19
18
|
return get_programs_by_category(program_name=which) # type: ignore
|
|
20
19
|
|
|
21
20
|
if which is not None: # install by name
|
|
22
21
|
program_total = ""
|
|
23
|
-
for a_which in
|
|
22
|
+
for a_which in which.split(",") if type(which) == str else which:
|
|
24
23
|
kv = {}
|
|
25
24
|
for _category, v in get_all_dicts(system=system()).items():
|
|
26
25
|
kv.update(v)
|
|
@@ -40,6 +39,7 @@ def main(which: Optional[WHICH_CAT | str] = None):
|
|
|
40
39
|
# interactive installation
|
|
41
40
|
installers = [Installer.from_dict(d=vd, name=name) for __kat, vds in get_all_dicts(system=system()).items() for name, vd in vds.items()]
|
|
42
41
|
options = [x.get_description() for x in tqdm(installers, desc="ā
Checking installed programs")] + list(get_args(WHICH_CAT))
|
|
42
|
+
# print("s"*1000)
|
|
43
43
|
program_names = choose_multiple_options(msg="", options=options, header="š CHOOSE DEV APP", default="AllEssentials")
|
|
44
44
|
|
|
45
45
|
total_program = ""
|
|
@@ -70,12 +70,14 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
70
70
|
program = ""
|
|
71
71
|
|
|
72
72
|
case "SystemInstallers":
|
|
73
|
-
if system() == "Windows":
|
|
73
|
+
if system() == "Windows":
|
|
74
|
+
options_system = parse_apps_installer_windows(LIBRARY_ROOT.joinpath("setup_windows/apps.ps1").read_text(encoding="utf-8"))
|
|
74
75
|
elif system() == "Linux":
|
|
75
76
|
options_system_1 = parse_apps_installer_linux(LIBRARY_ROOT.joinpath("setup_linux/apps_dev.sh").read_text(encoding="utf-8"))
|
|
76
77
|
options_system_2 = parse_apps_installer_linux(LIBRARY_ROOT.joinpath("setup_linux/apps.sh").read_text(encoding="utf-8"))
|
|
77
78
|
options_system = {**options_system_1, **options_system_2}
|
|
78
|
-
else:
|
|
79
|
+
else:
|
|
80
|
+
raise NotImplementedError(f"ā System {system()} not supported")
|
|
79
81
|
program_names = choose_multiple_options(msg="", options=sorted(list(options_system.keys())), header="š CHOOSE DEV APP")
|
|
80
82
|
program = ""
|
|
81
83
|
for name in program_names:
|
|
@@ -84,7 +86,8 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
84
86
|
ā āļø Installing: {name}
|
|
85
87
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā""")
|
|
86
88
|
sub_program = options_system[name]
|
|
87
|
-
if sub_program.startswith("#winget"):
|
|
89
|
+
if sub_program.startswith("#winget"):
|
|
90
|
+
sub_program = sub_program[1:]
|
|
88
91
|
program += "\n" + sub_program
|
|
89
92
|
|
|
90
93
|
# case "OtherDevApps":
|
|
@@ -105,7 +108,7 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
105
108
|
# print(f"Installing {name}")
|
|
106
109
|
# sub_program = installers[idx].install_robust(version=None) # finish the task
|
|
107
110
|
|
|
108
|
-
case
|
|
111
|
+
case "PrecheckedCloudInstaller":
|
|
109
112
|
# from machineconfig.jobs.python.check_installations import PrecheckedCloudInstaller
|
|
110
113
|
# ci = PrecheckedCloudInstaller()
|
|
111
114
|
# ci.download_safe_apps(name="AllEssentials")
|
|
@@ -119,15 +122,15 @@ def parse_apps_installer_linux(txt: str) -> dict[str, Any]:
|
|
|
119
122
|
res = {}
|
|
120
123
|
for chunk in txts[1:]:
|
|
121
124
|
try:
|
|
122
|
-
k = chunk.split(
|
|
125
|
+
k = chunk.split("----")[0].rstrip().lstrip()
|
|
123
126
|
v = "\n".join(chunk.split("\n")[1:])
|
|
124
127
|
res[k] = v
|
|
125
128
|
except IndexError as e:
|
|
126
129
|
print(f"""
|
|
127
130
|
ā Error parsing chunk:
|
|
128
|
-
{
|
|
131
|
+
{"-" * 50}
|
|
129
132
|
{chunk}
|
|
130
|
-
{
|
|
133
|
+
{"-" * 50}""")
|
|
131
134
|
raise e
|
|
132
135
|
return res
|
|
133
136
|
|
|
@@ -135,28 +138,31 @@ def parse_apps_installer_linux(txt: str) -> dict[str, Any]:
|
|
|
135
138
|
def parse_apps_installer_windows(txt: str) -> dict[str, Any]:
|
|
136
139
|
chunks: list[str] = []
|
|
137
140
|
for idx, item in enumerate(txt.split(sep="winget install")):
|
|
138
|
-
if idx == 0:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
if idx == 0:
|
|
142
|
+
continue
|
|
143
|
+
if idx == 1:
|
|
144
|
+
chunks.append(item)
|
|
145
|
+
else:
|
|
146
|
+
chunks.append("winget install" + item)
|
|
141
147
|
# progs = L(txt.splitlines()).filter(lambda x: x.startswith("winget ") or x.startswith("#winget"))
|
|
142
148
|
res: dict[str, str] = {}
|
|
143
149
|
for a_chunk in chunks:
|
|
144
150
|
try:
|
|
145
|
-
name = a_chunk.split(
|
|
151
|
+
name = a_chunk.split("--name ")[1]
|
|
146
152
|
if "--Id" not in name:
|
|
147
153
|
print(f"ā ļø Warning: {name} does not have an Id, skipping")
|
|
148
154
|
continue
|
|
149
|
-
name = name.split(
|
|
155
|
+
name = name.split(" --Id ", maxsplit=1)[0].strip('"').strip('"')
|
|
150
156
|
res[name] = a_chunk
|
|
151
157
|
except IndexError as e:
|
|
152
158
|
print(f"""
|
|
153
159
|
ā Error parsing chunk:
|
|
154
|
-
{
|
|
160
|
+
{"-" * 50}
|
|
155
161
|
{a_chunk}
|
|
156
|
-
{
|
|
162
|
+
{"-" * 50}""")
|
|
157
163
|
raise e
|
|
158
164
|
return res
|
|
159
165
|
|
|
160
166
|
|
|
161
|
-
if __name__ ==
|
|
167
|
+
if __name__ == "__main__":
|
|
162
168
|
pass
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
"""Update repositories with fancy output
|
|
2
|
-
|
|
1
|
+
"""Update repositories with fancy output"""
|
|
2
|
+
|
|
3
3
|
import git
|
|
4
4
|
import subprocess
|
|
5
5
|
import hashlib
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
8
|
-
from machineconfig.utils.
|
|
8
|
+
from machineconfig.utils.source_of_truth import DEFAULTS_PATH
|
|
9
9
|
from machineconfig.utils.utils2 import read_ini
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def get_file_hash(file_path: Path) -> str | None:
|
|
13
13
|
"""Get SHA256 hash of a file, return None if file doesn't exist."""
|
|
14
14
|
if not file_path.exists():
|
|
15
15
|
return None
|
|
16
16
|
return hashlib.sha256(file_path.read_bytes()).hexdigest()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def set_permissions_recursive(path: Path, executable: bool = True) -> None:
|
|
20
20
|
"""Set permissions recursively for a directory."""
|
|
21
21
|
if not path.exists():
|
|
22
22
|
return
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
if path.is_file():
|
|
25
25
|
if executable:
|
|
26
26
|
path.chmod(0o755)
|
|
@@ -28,107 +28,192 @@ def _set_permissions_recursive(path: Path, executable: bool = True) -> None:
|
|
|
28
28
|
path.chmod(0o644)
|
|
29
29
|
elif path.is_dir():
|
|
30
30
|
path.chmod(0o755)
|
|
31
|
-
for item in path.rglob(
|
|
32
|
-
|
|
31
|
+
for item in path.rglob("*"):
|
|
32
|
+
set_permissions_recursive(item, executable)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def
|
|
36
|
-
"""Run uv sync in the given repository path."""
|
|
35
|
+
def run_uv_sync(repo_path: Path) -> bool:
|
|
36
|
+
"""Run uv sync in the given repository path. Returns True if successful."""
|
|
37
37
|
try:
|
|
38
38
|
print(f"š Running uv sync in {repo_path}")
|
|
39
|
-
result = subprocess.run(
|
|
40
|
-
["uv", "sync"],
|
|
41
|
-
cwd=repo_path,
|
|
42
|
-
capture_output=True,
|
|
43
|
-
text=True,
|
|
44
|
-
check=True
|
|
45
|
-
)
|
|
39
|
+
result = subprocess.run(["uv", "sync"], cwd=repo_path, capture_output=True, text=True, check=True)
|
|
46
40
|
print("ā
uv sync completed successfully")
|
|
47
41
|
if result.stdout:
|
|
48
42
|
print(f"š Output: {result.stdout}")
|
|
43
|
+
return True
|
|
49
44
|
except subprocess.CalledProcessError as e:
|
|
50
45
|
print(f"ā uv sync failed: {e}")
|
|
51
46
|
if e.stderr:
|
|
52
47
|
print(f"š Error: {e.stderr}")
|
|
48
|
+
return False
|
|
53
49
|
except FileNotFoundError:
|
|
54
50
|
print("ā ļø uv command not found. Please install uv first.")
|
|
51
|
+
return False
|
|
55
52
|
|
|
56
53
|
|
|
57
|
-
def
|
|
54
|
+
def update_repository(repo: git.Repo, auto_sync: bool = True, allow_password_prompt: bool = False) -> bool:
|
|
58
55
|
"""Update a single repository and return True if pyproject.toml or uv.lock changed."""
|
|
59
56
|
repo_path = Path(repo.working_dir)
|
|
60
57
|
print(f"š {'Updating ' + str(repo_path):.^80}")
|
|
61
|
-
|
|
58
|
+
|
|
62
59
|
# Check if this repo has pyproject.toml or uv.lock
|
|
63
60
|
pyproject_path = repo_path / "pyproject.toml"
|
|
64
61
|
uv_lock_path = repo_path / "uv.lock"
|
|
65
|
-
|
|
62
|
+
|
|
66
63
|
# Get hashes before pull
|
|
67
|
-
pyproject_hash_before =
|
|
68
|
-
uv_lock_hash_before =
|
|
69
|
-
|
|
64
|
+
pyproject_hash_before = get_file_hash(pyproject_path)
|
|
65
|
+
uv_lock_hash_before = get_file_hash(uv_lock_path)
|
|
66
|
+
|
|
67
|
+
# Get current commit hash before pull
|
|
68
|
+
commit_before = repo.head.commit.hexsha
|
|
69
|
+
|
|
70
70
|
try:
|
|
71
|
-
#
|
|
71
|
+
# Use subprocess for git pull to get better output control
|
|
72
72
|
dependencies_changed = False
|
|
73
|
-
|
|
73
|
+
|
|
74
|
+
# Get list of remotes
|
|
75
|
+
remotes = list(repo.remotes)
|
|
76
|
+
if not remotes:
|
|
77
|
+
print("ā ļø No remotes configured for this repository")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
for remote in remotes:
|
|
74
81
|
try:
|
|
75
|
-
print(f"š„
|
|
76
|
-
|
|
77
|
-
for
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
print(f"š„ Fetching from {remote.name}...")
|
|
83
|
+
|
|
84
|
+
# Set up environment for git commands
|
|
85
|
+
env = None
|
|
86
|
+
if not allow_password_prompt:
|
|
87
|
+
# Disable interactive prompts
|
|
88
|
+
import os
|
|
89
|
+
|
|
90
|
+
env = os.environ.copy()
|
|
91
|
+
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
92
|
+
env["GIT_ASKPASS"] = "echo" # Returns empty string for any credential request
|
|
93
|
+
|
|
94
|
+
# First fetch to see what's available
|
|
95
|
+
fetch_result = subprocess.run(
|
|
96
|
+
["git", "fetch", remote.name, "--verbose"],
|
|
97
|
+
cwd=repo_path,
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
env=env,
|
|
101
|
+
timeout=30, # Add timeout to prevent hanging
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Check if fetch failed due to authentication
|
|
105
|
+
if fetch_result.returncode != 0 and not allow_password_prompt:
|
|
106
|
+
auth_error_indicators = [
|
|
107
|
+
"Authentication failed",
|
|
108
|
+
"Password for",
|
|
109
|
+
"Username for",
|
|
110
|
+
"could not read Username",
|
|
111
|
+
"could not read Password",
|
|
112
|
+
"fatal: Authentication failed",
|
|
113
|
+
"fatal: could not read Username",
|
|
114
|
+
"fatal: could not read Password",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
error_output = (fetch_result.stderr or "") + (fetch_result.stdout or "")
|
|
118
|
+
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
119
|
+
print(f"ā ļø Skipping {remote.name} - authentication required but password prompts are disabled")
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
if fetch_result.stdout:
|
|
123
|
+
print(f"š” Fetch output: {fetch_result.stdout.strip()}")
|
|
124
|
+
if fetch_result.stderr:
|
|
125
|
+
print(f"š” Fetch info: {fetch_result.stderr.strip()}")
|
|
126
|
+
|
|
127
|
+
# Now pull with verbose output
|
|
128
|
+
print(f"š„ Pulling from {remote.name}/{repo.active_branch.name}...")
|
|
129
|
+
pull_result = subprocess.run(["git", "pull", remote.name, repo.active_branch.name, "--verbose"], cwd=repo_path, capture_output=True, text=True, env=env, timeout=30)
|
|
130
|
+
|
|
131
|
+
# Check if pull failed due to authentication
|
|
132
|
+
if pull_result.returncode != 0 and not allow_password_prompt:
|
|
133
|
+
auth_error_indicators = [
|
|
134
|
+
"Authentication failed",
|
|
135
|
+
"Password for",
|
|
136
|
+
"Username for",
|
|
137
|
+
"could not read Username",
|
|
138
|
+
"could not read Password",
|
|
139
|
+
"fatal: Authentication failed",
|
|
140
|
+
"fatal: could not read Username",
|
|
141
|
+
"fatal: could not read Password",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
error_output = (pull_result.stderr or "") + (pull_result.stdout or "")
|
|
145
|
+
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
146
|
+
print(f"ā ļø Skipping pull from {remote.name} - authentication required but password prompts are disabled")
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if pull_result.stdout:
|
|
150
|
+
print(f"š¦ Pull output: {pull_result.stdout.strip()}")
|
|
151
|
+
if pull_result.stderr:
|
|
152
|
+
print(f"š¦ Pull info: {pull_result.stderr.strip()}")
|
|
153
|
+
|
|
154
|
+
# Check if pull was successful
|
|
155
|
+
if pull_result.returncode == 0:
|
|
156
|
+
# Check if commits changed
|
|
157
|
+
commit_after = repo.head.commit.hexsha
|
|
158
|
+
if commit_before != commit_after:
|
|
159
|
+
print(f"ā
Repository updated: {commit_before[:8]} ā {commit_after[:8]}")
|
|
82
160
|
else:
|
|
83
|
-
print(
|
|
161
|
+
print("ā
Already up to date")
|
|
162
|
+
else:
|
|
163
|
+
print(f"ā Pull failed with return code {pull_result.returncode}")
|
|
164
|
+
|
|
84
165
|
except Exception as e:
|
|
85
166
|
print(f"ā ļø Failed to pull from {remote.name}: {e}")
|
|
86
167
|
continue
|
|
87
|
-
|
|
168
|
+
|
|
88
169
|
# Check if pyproject.toml or uv.lock changed after pull
|
|
89
|
-
pyproject_hash_after =
|
|
90
|
-
uv_lock_hash_after =
|
|
91
|
-
|
|
170
|
+
pyproject_hash_after = get_file_hash(pyproject_path)
|
|
171
|
+
uv_lock_hash_after = get_file_hash(uv_lock_path)
|
|
172
|
+
|
|
92
173
|
if pyproject_hash_before != pyproject_hash_after:
|
|
93
174
|
print("š pyproject.toml has changed")
|
|
94
175
|
dependencies_changed = True
|
|
95
|
-
|
|
176
|
+
|
|
96
177
|
if uv_lock_hash_before != uv_lock_hash_after:
|
|
97
178
|
print("š uv.lock has changed")
|
|
98
179
|
dependencies_changed = True
|
|
99
|
-
|
|
180
|
+
|
|
100
181
|
# Special handling for machineconfig repository
|
|
101
182
|
if "machineconfig" in str(repo_path):
|
|
102
183
|
print("š Special handling for machineconfig repository...")
|
|
103
184
|
scripts_path = Path.home() / "scripts"
|
|
104
185
|
if scripts_path.exists():
|
|
105
|
-
|
|
186
|
+
set_permissions_recursive(scripts_path)
|
|
106
187
|
print(f"ā
Set permissions for {scripts_path}")
|
|
107
|
-
|
|
188
|
+
|
|
108
189
|
linux_jobs_path = repo_path / "src" / "machineconfig" / "jobs" / "linux"
|
|
109
190
|
if linux_jobs_path.exists():
|
|
110
|
-
|
|
191
|
+
set_permissions_recursive(linux_jobs_path)
|
|
111
192
|
print(f"ā
Set permissions for {linux_jobs_path}")
|
|
112
|
-
|
|
193
|
+
|
|
113
194
|
lf_exe_path = repo_path / "src" / "machineconfig" / "settings" / "lf" / "linux" / "exe"
|
|
114
195
|
if lf_exe_path.exists():
|
|
115
|
-
|
|
196
|
+
set_permissions_recursive(lf_exe_path)
|
|
116
197
|
print(f"ā
Set permissions for {lf_exe_path}")
|
|
117
|
-
|
|
198
|
+
|
|
199
|
+
# Run uv sync if dependencies changed and auto_sync is enabled
|
|
200
|
+
if dependencies_changed and auto_sync:
|
|
201
|
+
run_uv_sync(repo_path)
|
|
202
|
+
|
|
118
203
|
return dependencies_changed
|
|
119
|
-
|
|
204
|
+
|
|
120
205
|
except Exception as e:
|
|
121
206
|
print(f"ā Error updating repository {repo_path}: {e}")
|
|
122
207
|
return False
|
|
123
208
|
|
|
124
209
|
|
|
125
|
-
def main(verbose: bool = True) -> str:
|
|
210
|
+
def main(verbose: bool = True, allow_password_prompt: bool = False) -> str:
|
|
126
211
|
"""Main function to update all configured repositories."""
|
|
127
212
|
_ = verbose
|
|
128
|
-
repos: list[str] = ["~/code/machineconfig", "~/code/
|
|
213
|
+
repos: list[str] = ["~/code/machineconfig", "~/code/crocodile"]
|
|
129
214
|
try:
|
|
130
|
-
tmp = read_ini(DEFAULTS_PATH)[
|
|
131
|
-
if tmp[-1] == "":
|
|
215
|
+
tmp = read_ini(DEFAULTS_PATH)["general"]["repos"].split(",")
|
|
216
|
+
if tmp[-1] == "":
|
|
132
217
|
tmp = tmp[:-1]
|
|
133
218
|
repos += tmp
|
|
134
219
|
except (FileNotFoundError, KeyError, IndexError):
|
|
@@ -154,25 +239,25 @@ def main(verbose: bool = True) -> str:
|
|
|
154
239
|
try:
|
|
155
240
|
expanded_path = PathExtended(a_package_path).expanduser()
|
|
156
241
|
repo = git.Repo(str(expanded_path), search_parent_directories=True)
|
|
157
|
-
|
|
242
|
+
|
|
158
243
|
# Update repository and check if dependencies changed
|
|
159
|
-
dependencies_changed =
|
|
160
|
-
|
|
244
|
+
dependencies_changed = update_repository(repo, allow_password_prompt=allow_password_prompt)
|
|
245
|
+
|
|
161
246
|
if dependencies_changed:
|
|
162
247
|
repos_with_changes.append(Path(repo.working_dir))
|
|
163
|
-
|
|
248
|
+
|
|
164
249
|
except Exception as ex:
|
|
165
250
|
print(f"""ā Repository Error: Path: {a_package_path}
|
|
166
251
|
Exception: {ex}
|
|
167
|
-
{
|
|
252
|
+
{"-" * 50}""")
|
|
168
253
|
|
|
169
254
|
# Run uv sync for repositories where pyproject.toml or uv.lock changed
|
|
170
255
|
for repo_path in repos_with_changes:
|
|
171
|
-
|
|
256
|
+
run_uv_sync(repo_path)
|
|
172
257
|
|
|
173
258
|
# print("\nš All repositories updated successfully!")
|
|
174
259
|
return """echo "š All repositories updated successfully!" """
|
|
175
260
|
|
|
176
261
|
|
|
177
|
-
if __name__ ==
|
|
262
|
+
if __name__ == "__main__":
|
|
178
263
|
main()
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
"""Like yadm and dotter.
|
|
2
|
-
"""
|
|
3
|
-
|
|
1
|
+
"""Like yadm and dotter."""
|
|
4
2
|
|
|
5
3
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
6
|
-
from machineconfig.
|
|
7
|
-
from machineconfig.utils.
|
|
4
|
+
from machineconfig.utils.links import symlink_func
|
|
5
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, REPO_ROOT
|
|
8
6
|
import argparse
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
def main():
|
|
12
|
-
parser = argparse.ArgumentParser(description=
|
|
10
|
+
parser = argparse.ArgumentParser(description="FTP client")
|
|
13
11
|
|
|
14
12
|
parser.add_argument("file", help="file/folder path.", default="")
|
|
15
13
|
# FLAGS
|
|
@@ -20,11 +18,15 @@ def main():
|
|
|
20
18
|
args = parser.parse_args()
|
|
21
19
|
orig_path = PathExtended(args.file).expanduser().absolute()
|
|
22
20
|
if args.dest == "":
|
|
23
|
-
if "Local" in str(orig_path):
|
|
24
|
-
|
|
25
|
-
elif "
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
if "Local" in str(orig_path):
|
|
22
|
+
junction = orig_path.split(at="Local", sep=-1)[1]
|
|
23
|
+
elif "Roaming" in str(orig_path):
|
|
24
|
+
junction = orig_path.split(at="Roaming", sep=-1)[1]
|
|
25
|
+
elif ".config" in str(orig_path):
|
|
26
|
+
junction = orig_path.split(at=".config", sep=-1)[1]
|
|
27
|
+
else:
|
|
28
|
+
junction = orig_path.rel2home()
|
|
29
|
+
new_path = PathExtended(REPO_ROOT).joinpath(junction)
|
|
28
30
|
else:
|
|
29
31
|
dest_path = PathExtended(args.dest).expanduser().absolute()
|
|
30
32
|
dest_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -39,12 +41,12 @@ def main():
|
|
|
39
41
|
ā š To enshrine this mapping, add the following to mapper.toml:
|
|
40
42
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā""")
|
|
41
43
|
print(f"""
|
|
42
|
-
š Edit configuration file: nano {LIBRARY_ROOT}/symlinks/mapper.toml
|
|
44
|
+
š Edit configuration file: nano {PathExtended(LIBRARY_ROOT)}/symlinks/mapper.toml
|
|
43
45
|
|
|
44
46
|
[{new_path.parent.name}]
|
|
45
|
-
{orig_path.name.split(
|
|
47
|
+
{orig_path.name.split(".")[0]} = {{ this = '{orig_path.collapseuser().as_posix()}', to_this = '{new_path.collapseuser().as_posix()}' }}
|
|
46
48
|
""")
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
if __name__ ==
|
|
51
|
+
if __name__ == "__main__":
|
|
50
52
|
main()
|