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
|
@@ -19,7 +19,7 @@ def main():
|
|
|
19
19
|
โ ๐ FTP File Transfer
|
|
20
20
|
โ ๐ Starting transfer process...
|
|
21
21
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
22
|
-
parser = argparse.ArgumentParser(description=
|
|
22
|
+
parser = argparse.ArgumentParser(description="FTP client")
|
|
23
23
|
|
|
24
24
|
parser.add_argument("source", help="source path (machine:path)")
|
|
25
25
|
parser.add_argument("target", help="target path (machine:path)")
|
|
@@ -38,36 +38,44 @@ def main():
|
|
|
38
38
|
source_parts = args.source.split(":")
|
|
39
39
|
machine = source_parts[0]
|
|
40
40
|
if len(source_parts) > 1 and source_parts[1] == ES: # the source path is to be inferred from target.
|
|
41
|
-
if args.target == ES:
|
|
41
|
+
if args.target == ES:
|
|
42
|
+
raise ValueError(f"""
|
|
42
43
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
43
44
|
โ โ Configuration Error
|
|
44
45
|
โ Cannot use expand symbol `{ES}` in both source and target
|
|
45
46
|
โ This creates a cyclical inference dependency
|
|
46
47
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
47
|
-
else:
|
|
48
|
+
else:
|
|
49
|
+
target = PathExtended(args.target).expanduser().absolute()
|
|
48
50
|
source = target.collapseuser().as_posix()
|
|
49
51
|
else:
|
|
50
52
|
source = ":".join(args.source.split(":")[1:])
|
|
51
|
-
if args.target == ES:
|
|
52
|
-
|
|
53
|
+
if args.target == ES:
|
|
54
|
+
target = None
|
|
55
|
+
else:
|
|
56
|
+
target = PathExtended(args.target).expanduser().absolute().as_posix()
|
|
53
57
|
|
|
54
58
|
elif ":" in args.target and (args.target[1] != ":" if len(args.target) > 1 else True): # avoid the case of "C:/":
|
|
55
59
|
source_is_remote = False
|
|
56
60
|
target_parts = args.target.split(":")
|
|
57
61
|
machine = target_parts[0]
|
|
58
62
|
if len(target_parts) > 1 and target_parts[1] == ES:
|
|
59
|
-
if args.source == ES:
|
|
63
|
+
if args.source == ES:
|
|
64
|
+
raise ValueError(f"""
|
|
60
65
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
61
66
|
โ โ Configuration Error
|
|
62
67
|
โ Cannot use expand symbol `{ES}` in both source and target
|
|
63
68
|
โ This creates a cyclical inference dependency
|
|
64
69
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
65
|
-
else:
|
|
70
|
+
else:
|
|
71
|
+
source = args.source
|
|
66
72
|
target = None
|
|
67
73
|
else:
|
|
68
74
|
target = ":".join(args.target.split(":")[1:])
|
|
69
|
-
if args.source == ES:
|
|
70
|
-
|
|
75
|
+
if args.source == ES:
|
|
76
|
+
source = None
|
|
77
|
+
else:
|
|
78
|
+
source = PathExtended(args.source).expanduser().absolute()
|
|
71
79
|
|
|
72
80
|
else:
|
|
73
81
|
raise ValueError("""
|
|
@@ -80,8 +88,9 @@ def main():
|
|
|
80
88
|
pprint({"source": str(source), "target": str(target), "machine": machine}, "CLI Resolution")
|
|
81
89
|
|
|
82
90
|
from paramiko.ssh_exception import AuthenticationException # type: ignore
|
|
91
|
+
|
|
83
92
|
try:
|
|
84
|
-
ssh = SSH(rf
|
|
93
|
+
ssh = SSH(rf"{machine}")
|
|
85
94
|
except AuthenticationException:
|
|
86
95
|
print("""
|
|
87
96
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -92,8 +101,9 @@ def main():
|
|
|
92
101
|
โ This exception only handles password authentication
|
|
93
102
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
94
103
|
import getpass
|
|
104
|
+
|
|
95
105
|
pwd = getpass.getpass()
|
|
96
|
-
ssh = SSH(rf
|
|
106
|
+
ssh = SSH(rf"{machine}", pwd=pwd)
|
|
97
107
|
|
|
98
108
|
if args.cloud:
|
|
99
109
|
print("""
|
|
@@ -118,7 +128,7 @@ def main():
|
|
|
118
128
|
โ ๐ฅ Transfer Mode: Remote โ Local
|
|
119
129
|
โ Source: {source}
|
|
120
130
|
โ Target: {target}
|
|
121
|
-
โ Options: {
|
|
131
|
+
โ Options: {"ZIP compression" if args.zipFirst else "No compression"}, {"Recursive" if args.recursive else "Non-recursive"}
|
|
122
132
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
123
133
|
received_file = ssh.copy_to_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
|
|
124
134
|
else:
|
|
@@ -129,7 +139,7 @@ def main():
|
|
|
129
139
|
โ ๐ค Transfer Mode: Local โ Remote
|
|
130
140
|
โ Source: {source}
|
|
131
141
|
โ Target: {target}
|
|
132
|
-
โ Options: {
|
|
142
|
+
โ Options: {"ZIP compression" if args.zipFirst else "No compression"}, {"Recursive" if args.recursive else "Non-recursive"}
|
|
133
143
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
134
144
|
received_file = ssh.copy_from_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
|
|
135
145
|
|
|
@@ -147,5 +157,5 @@ def main():
|
|
|
147
157
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ""")
|
|
148
158
|
|
|
149
159
|
|
|
150
|
-
if __name__ ==
|
|
160
|
+
if __name__ == "__main__":
|
|
151
161
|
main()
|
|
@@ -3,12 +3,13 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
def get_zellij_cmd(wd1: Path, wd2: Path) -> str:
|
|
5
5
|
_ = wd1, wd2
|
|
6
|
-
lines = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
lines = [
|
|
7
|
+
""" zellij action new-tab --name gitdiff""",
|
|
8
|
+
"""zellij action new-pane --direction down --name local --cwd ./data """,
|
|
9
|
+
"""zellij action write-chars "cd '{wd1}'; git status" """,
|
|
10
|
+
"""zellij action move-focus up; zellij action close-pane """,
|
|
11
|
+
"""zellij action new-pane --direction down --name remote --cwd code """,
|
|
12
|
+
"""zellij action write-chars "cd '{wd2}' """,
|
|
13
|
+
"""git status" """,
|
|
13
14
|
]
|
|
14
15
|
return "; ".join(lines)
|
|
@@ -4,10 +4,10 @@ from pydantic import ConfigDict
|
|
|
4
4
|
from pydantic.dataclasses import dataclass
|
|
5
5
|
from typing import Optional
|
|
6
6
|
import os
|
|
7
|
-
from machineconfig.utils.
|
|
7
|
+
from machineconfig.utils.source_of_truth import DEFAULTS_PATH
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.panel import Panel
|
|
10
|
-
from rich import box
|
|
10
|
+
from rich import box # Import box
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
console = Console()
|
|
@@ -16,10 +16,10 @@ console = Console()
|
|
|
16
16
|
class ArgsDefaults:
|
|
17
17
|
# source: str=None
|
|
18
18
|
# target: str=None
|
|
19
|
-
encrypt: bool=False
|
|
20
|
-
zip_: bool=False
|
|
21
|
-
overwrite: bool=False
|
|
22
|
-
share: bool=False
|
|
19
|
+
encrypt: bool = False
|
|
20
|
+
zip_: bool = False
|
|
21
|
+
overwrite: bool = False
|
|
22
|
+
share: bool = False
|
|
23
23
|
rel2home = False
|
|
24
24
|
root = None
|
|
25
25
|
os_specific = False
|
|
@@ -28,12 +28,12 @@ class ArgsDefaults:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
@dataclass(config=ConfigDict(extra="forbid", frozen=False))
|
|
31
|
-
class Args
|
|
31
|
+
class Args:
|
|
32
32
|
cloud: Optional[str] = None
|
|
33
33
|
|
|
34
|
-
zip: bool=ArgsDefaults.zip_
|
|
35
|
-
overwrite: bool=ArgsDefaults.overwrite
|
|
36
|
-
share: bool=ArgsDefaults.share
|
|
34
|
+
zip: bool = ArgsDefaults.zip_
|
|
35
|
+
overwrite: bool = ArgsDefaults.overwrite
|
|
36
|
+
share: bool = ArgsDefaults.share
|
|
37
37
|
|
|
38
38
|
root: Optional[str] = ArgsDefaults.root
|
|
39
39
|
os_specific: bool = ArgsDefaults.os_specific
|
|
@@ -67,15 +67,16 @@ def find_cloud_config(path: Path):
|
|
|
67
67
|
|
|
68
68
|
def absolute(path: str) -> Path:
|
|
69
69
|
obj = Path(path).expanduser()
|
|
70
|
-
if not path.startswith(".") and
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if not path.startswith(".") and obj.exists():
|
|
71
|
+
return obj
|
|
72
|
+
try_absing = Path.cwd().joinpath(path)
|
|
73
|
+
if try_absing.exists():
|
|
74
|
+
return try_absing
|
|
73
75
|
display_warning(f"Path {path} could not be resolved to absolute path.")
|
|
74
76
|
display_warning("Trying to resolve symlinks (this may result in unintended paths).")
|
|
75
77
|
return obj.absolute()
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
|
|
79
80
|
def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Args:
|
|
80
81
|
console = Console()
|
|
81
82
|
console.print(Panel("๐ Secure Share Cloud Configuration", expand=False))
|
|
@@ -88,10 +89,10 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
|
|
|
88
89
|
console.print(f"โ๏ธ Using cloud from environment: {cloud}")
|
|
89
90
|
else:
|
|
90
91
|
try:
|
|
91
|
-
default_cloud__ = read_ini(DEFAULTS_PATH)[
|
|
92
|
+
default_cloud__ = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
92
93
|
except Exception:
|
|
93
|
-
default_cloud__ =
|
|
94
|
-
if default_cloud__ ==
|
|
94
|
+
default_cloud__ = "No default cloud found."
|
|
95
|
+
if default_cloud__ == "No default cloud found." or interactive:
|
|
95
96
|
# assert default_cloud is not None
|
|
96
97
|
cloud = input(f"โ๏ธ Enter cloud name (default {default_cloud__}): ") or default_cloud__
|
|
97
98
|
else:
|
|
@@ -106,32 +107,36 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
|
|
|
106
107
|
pwd = ""
|
|
107
108
|
default_message = "no default password found"
|
|
108
109
|
pwd = input(f"๐ Enter encryption password ({default_message}): ") or pwd
|
|
109
|
-
res = Args(cloud=cloud,
|
|
110
|
-
pwd=pwd, encrypt=True,
|
|
111
|
-
zip=True, overwrite=True, share=True,
|
|
112
|
-
rel2home=True, root="myshare", os_specific=False,)
|
|
110
|
+
res = Args(cloud=cloud, pwd=pwd, encrypt=True, zip=True, overwrite=True, share=True, rel2home=True, root="myshare", os_specific=False)
|
|
113
111
|
|
|
114
112
|
display_success("Using SecureShare cloud config")
|
|
115
113
|
pprint(res.__dict__, "SecureShare Config")
|
|
116
114
|
return res
|
|
117
115
|
|
|
116
|
+
|
|
118
117
|
def display_header(title: str):
|
|
119
|
-
console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left"))
|
|
118
|
+
console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left")) # Replace print with Panel
|
|
119
|
+
|
|
120
120
|
|
|
121
121
|
def display_subheader(title: str):
|
|
122
|
-
console.print(Panel(title, box=box.ROUNDED, title_align="left"))
|
|
122
|
+
console.print(Panel(title, box=box.ROUNDED, title_align="left")) # Replace print with Panel
|
|
123
|
+
|
|
123
124
|
|
|
124
125
|
def display_content(content: str):
|
|
125
|
-
console.print(Panel(content, box=box.ROUNDED, title_align="left"))
|
|
126
|
+
console.print(Panel(content, box=box.ROUNDED, title_align="left")) # Replace print with Panel
|
|
127
|
+
|
|
126
128
|
|
|
127
129
|
def display_status(status: str):
|
|
128
|
-
console.print(Panel(status, box=box.ROUNDED, title_align="left"))
|
|
130
|
+
console.print(Panel(status, box=box.ROUNDED, title_align="left")) # Replace print with Panel
|
|
131
|
+
|
|
129
132
|
|
|
130
133
|
def display_success(message: str):
|
|
131
|
-
console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left"))
|
|
134
|
+
console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left")) # Replace print with Panel
|
|
135
|
+
|
|
132
136
|
|
|
133
137
|
def display_warning(message: str):
|
|
134
|
-
console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left"))
|
|
138
|
+
console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left")) # Replace print with Panel
|
|
139
|
+
|
|
135
140
|
|
|
136
141
|
def display_error(message: str):
|
|
137
|
-
console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left"))
|
|
142
|
+
console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left")) # Replace print with Panel
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from machineconfig.scripts.python.helpers.cloud_helpers import Args, ArgsDefaults, absolute, find_cloud_config, get_secure_share_cloud_config
|
|
2
|
-
from machineconfig.utils.
|
|
2
|
+
from machineconfig.utils.source_of_truth import DEFAULTS_PATH
|
|
3
3
|
from machineconfig.utils.utils2 import read_ini, pprint
|
|
4
4
|
from typing import Optional
|
|
5
5
|
from rich.console import Console
|
|
@@ -17,7 +17,8 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
17
17
|
if config == "ss":
|
|
18
18
|
cloud_maybe: Optional[str] = target.split(":")[0]
|
|
19
19
|
# if cloud_maybe == "": cloud_maybe = source.split(":")[0]
|
|
20
|
-
if cloud_maybe == "":
|
|
20
|
+
if cloud_maybe == "":
|
|
21
|
+
cloud_maybe = None
|
|
21
22
|
print("cloud_maybe:", cloud_maybe)
|
|
22
23
|
maybe_config = get_secure_share_cloud_config(interactive=True, cloud=cloud_maybe)
|
|
23
24
|
elif config is not None:
|
|
@@ -27,13 +28,20 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
27
28
|
maybe_config = None
|
|
28
29
|
|
|
29
30
|
if maybe_config is not None:
|
|
30
|
-
if args.zip == ArgsDefaults.zip_:
|
|
31
|
-
|
|
32
|
-
if args.
|
|
33
|
-
|
|
34
|
-
if args.
|
|
35
|
-
|
|
36
|
-
if args.
|
|
31
|
+
if args.zip == ArgsDefaults.zip_:
|
|
32
|
+
args.zip = maybe_config.zip
|
|
33
|
+
if args.encrypt == ArgsDefaults.encrypt:
|
|
34
|
+
args.encrypt = maybe_config.encrypt
|
|
35
|
+
if args.share == ArgsDefaults.share:
|
|
36
|
+
args.share = maybe_config.share
|
|
37
|
+
if args.root == ArgsDefaults.root:
|
|
38
|
+
args.root = maybe_config.root
|
|
39
|
+
if args.rel2home == ArgsDefaults.rel2home:
|
|
40
|
+
args.rel2home = maybe_config.rel2home
|
|
41
|
+
if args.pwd == ArgsDefaults.pwd:
|
|
42
|
+
args.pwd = maybe_config.pwd
|
|
43
|
+
if args.os_specific == ArgsDefaults.os_specific:
|
|
44
|
+
args.os_specific = maybe_config.os_specific
|
|
37
45
|
|
|
38
46
|
root = args.root
|
|
39
47
|
rel2home = args.rel2home
|
|
@@ -53,7 +61,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
53
61
|
maybe_config = tmp_maybe_config
|
|
54
62
|
|
|
55
63
|
if maybe_config is None:
|
|
56
|
-
default_cloud: str=read_ini(DEFAULTS_PATH)[
|
|
64
|
+
default_cloud: str = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
57
65
|
console.print(Panel(f"โ ๏ธ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
|
|
58
66
|
source = default_cloud + ":" + source[1:]
|
|
59
67
|
else:
|
|
@@ -73,7 +81,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
73
81
|
maybe_config = find_cloud_config(path)
|
|
74
82
|
|
|
75
83
|
if maybe_config is None:
|
|
76
|
-
default_cloud = read_ini(DEFAULTS_PATH)[
|
|
84
|
+
default_cloud = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
77
85
|
console.print(Panel(f"โ ๏ธ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
|
|
78
86
|
target = default_cloud + ":" + target[1:]
|
|
79
87
|
print("target mutated to:", target, f"because of default cloud being {default_cloud}")
|
|
@@ -88,7 +96,6 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
88
96
|
zip_arg = tmp.zip
|
|
89
97
|
share = tmp.share
|
|
90
98
|
|
|
91
|
-
|
|
92
99
|
if ":" in source and (source[1] != ":" if len(source) > 1 else True): # avoid the deceptive case of "C:/"
|
|
93
100
|
source_parts: list[str] = source.split(":")
|
|
94
101
|
cloud = source_parts[0]
|
|
@@ -97,6 +104,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
97
104
|
assert ES not in target, f"You can't use expand symbol `{ES}` in both source and target. Cyclical inference dependency arised."
|
|
98
105
|
target_obj = absolute(target)
|
|
99
106
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
107
|
+
|
|
100
108
|
remote_path = PathExtended(target_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
|
|
101
109
|
source = f"{cloud}:{remote_path.as_posix()}"
|
|
102
110
|
else: # source path is mentioned, target? maybe.
|
|
@@ -116,6 +124,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
116
124
|
assert ES not in source, "You can't use $ in both source and target. Cyclical inference dependency arised."
|
|
117
125
|
source_obj = absolute(source)
|
|
118
126
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
127
|
+
|
|
119
128
|
remote_path = PathExtended(source_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
|
|
120
129
|
target = f"{cloud}:{remote_path.as_posix()}"
|
|
121
130
|
else: # target path is mentioned, source? maybe.
|
|
@@ -124,8 +133,10 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
|
|
|
124
133
|
raise NotImplementedError("There is no .get_local_path method yet")
|
|
125
134
|
else:
|
|
126
135
|
source_obj = absolute(source)
|
|
127
|
-
if zip_arg and ".zip" not in target:
|
|
128
|
-
|
|
136
|
+
if zip_arg and ".zip" not in target:
|
|
137
|
+
target += ".zip"
|
|
138
|
+
if encrypt and ".enc" not in target:
|
|
139
|
+
target += ".enc"
|
|
129
140
|
else:
|
|
130
141
|
console.print(Panel("โ ERROR: Invalid path configuration\nEither source or target must be a remote path (i.e. machine:path)", title="[bold red]Error[/bold red]", border_style="red"))
|
|
131
142
|
raise ValueError(f"Either source or target must be a remote path (i.e. machine:path)\nGot: source: `{source}`, target: `{target}`")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
1
|
from typing import Any, Callable, Optional
|
|
3
2
|
import inspect
|
|
4
3
|
import os
|
|
4
|
+
|
|
5
5
|
# import argparse
|
|
6
6
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
7
7
|
# from machineconfig.utils.utils import choose_ssh_host
|
|
@@ -27,7 +27,7 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
|
|
|
27
27
|
# https://google.github.io/python-fire/guide/
|
|
28
28
|
# https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing
|
|
29
29
|
if not kwargs: # empty dict
|
|
30
|
-
kwargs_str =
|
|
30
|
+
kwargs_str = ""
|
|
31
31
|
else:
|
|
32
32
|
# For fire module, all keyword arguments should be passed as --key value pairs
|
|
33
33
|
tmp_list: list[str] = []
|
|
@@ -40,40 +40,47 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
|
|
|
40
40
|
def parse_pyfile(file_path: str):
|
|
41
41
|
print(f"๐ Loading {file_path} ...")
|
|
42
42
|
from typing import NamedTuple
|
|
43
|
+
|
|
43
44
|
args_spec = NamedTuple("args_spec", [("name", str), ("type", str), ("default", Optional[str])])
|
|
44
45
|
func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
|
|
45
|
-
|
|
46
46
|
import ast
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
for node in ast.walk(parsed_ast)
|
|
51
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
52
|
-
]
|
|
47
|
+
|
|
48
|
+
parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding="utf-8"))
|
|
49
|
+
functions = [node for node in ast.walk(parsed_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
|
|
53
50
|
module__doc__ = ast.get_docstring(parsed_ast)
|
|
54
51
|
main_option = f"RUN AS MAIN -- {module__doc__ if module__doc__ is not None else 'NoDocs'}"
|
|
55
52
|
options = [main_option]
|
|
56
53
|
for function in functions:
|
|
57
|
-
if function.name.startswith(
|
|
58
|
-
|
|
59
|
-
if any(arg.arg ==
|
|
54
|
+
if function.name.startswith("__") and function.name.endswith("__"):
|
|
55
|
+
continue
|
|
56
|
+
if any(arg.arg == "self" for arg in function.args.args):
|
|
57
|
+
continue
|
|
58
|
+
if any(arg.arg == "self" for arg in function.args.args):
|
|
59
|
+
continue
|
|
60
60
|
doc_string_tmp: str | None = ast.get_docstring(function)
|
|
61
|
-
if doc_string_tmp is None:
|
|
62
|
-
|
|
61
|
+
if doc_string_tmp is None:
|
|
62
|
+
doc_string = "NoDocs"
|
|
63
|
+
else:
|
|
64
|
+
doc_string = doc_string_tmp.replace("\n", " ")
|
|
63
65
|
options.append(f"{function.name} -- {', '.join([arg.arg for arg in function.args.args])} -- {doc_string}")
|
|
64
66
|
tmp = []
|
|
65
67
|
for idx, arg in enumerate(function.args.args):
|
|
66
68
|
if arg.annotation is not None:
|
|
67
|
-
try:
|
|
69
|
+
try:
|
|
70
|
+
type_ = arg.annotation.__dict__["id"]
|
|
68
71
|
except KeyError as ke:
|
|
69
72
|
type_ = "Any" # e.g. a callable object
|
|
70
73
|
_ = ke
|
|
71
|
-
else:
|
|
74
|
+
else:
|
|
75
|
+
type_ = "Any"
|
|
72
76
|
default_tmp = function.args.defaults[idx] if idx < len(function.args.defaults) else None
|
|
73
|
-
if default_tmp is None:
|
|
77
|
+
if default_tmp is None:
|
|
78
|
+
default = None
|
|
74
79
|
else:
|
|
75
|
-
if hasattr(default_tmp, "__dict__"):
|
|
76
|
-
|
|
80
|
+
if hasattr(default_tmp, "__dict__"):
|
|
81
|
+
default = default_tmp.__dict__.get("value", None)
|
|
82
|
+
else:
|
|
83
|
+
default = None
|
|
77
84
|
tmp.append(args_spec(name=arg.arg, type=type_, default=default))
|
|
78
85
|
func_args.append(tmp)
|
|
79
86
|
return options, func_args
|
|
@@ -85,26 +92,32 @@ def interactively_run_function(func: Callable[[Any], Any]):
|
|
|
85
92
|
args = []
|
|
86
93
|
kwargs = {}
|
|
87
94
|
for param in params:
|
|
88
|
-
if param.annotation is not inspect.Parameter.empty:
|
|
89
|
-
|
|
95
|
+
if param.annotation is not inspect.Parameter.empty:
|
|
96
|
+
hint = f" ({param.annotation.__name__})"
|
|
97
|
+
else:
|
|
98
|
+
hint = ""
|
|
90
99
|
if param.default is not inspect.Parameter.empty:
|
|
91
100
|
default = param.default
|
|
92
101
|
value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) (default = {default}) : ")
|
|
93
|
-
if value == "":
|
|
94
|
-
|
|
102
|
+
if value == "":
|
|
103
|
+
value = default
|
|
104
|
+
else:
|
|
105
|
+
value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
|
|
95
106
|
try:
|
|
96
107
|
if param.annotation is not inspect.Parameter.empty:
|
|
97
108
|
value = param.annotation
|
|
98
109
|
except (TypeError, ValueError) as err:
|
|
99
110
|
raise ValueError(f"Invalid input: {value} is not of type {param.annotation}") from err
|
|
100
|
-
if param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
101
|
-
|
|
111
|
+
if param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
112
|
+
kwargs[param.name] = value
|
|
113
|
+
else:
|
|
114
|
+
args.append((param.name, value))
|
|
102
115
|
args_to_kwargs = dict(args)
|
|
103
116
|
return args_to_kwargs, kwargs
|
|
104
117
|
|
|
105
118
|
|
|
106
119
|
def get_attrs_recursively(obj: Any):
|
|
107
|
-
if hasattr(obj,
|
|
120
|
+
if hasattr(obj, "__dict__"):
|
|
108
121
|
res = {}
|
|
109
122
|
for k, v in obj.__dict__.items():
|
|
110
123
|
res[k] = get_attrs_recursively(v)
|
|
@@ -127,8 +140,8 @@ def get_attrs_recursively(obj: Any):
|
|
|
127
140
|
|
|
128
141
|
|
|
129
142
|
def find_repo_root_path(start_path: str) -> Optional[str]:
|
|
130
|
-
root_files = [
|
|
131
|
-
path: str=start_path
|
|
143
|
+
root_files = ["setup.py", "pyproject.toml", ".git"]
|
|
144
|
+
path: str = start_path
|
|
132
145
|
trials = 0
|
|
133
146
|
root_path = os.path.abspath(os.sep)
|
|
134
147
|
while path != root_path and trials < 20:
|
|
@@ -144,12 +157,12 @@ def find_repo_root_path(start_path: str) -> Optional[str]:
|
|
|
144
157
|
def get_import_module_code(module_path: str):
|
|
145
158
|
root_path = find_repo_root_path(module_path)
|
|
146
159
|
if root_path is None: # just make a desperate attempt to import it
|
|
147
|
-
module_name = module_path.lstrip(os.sep).replace(os.sep,
|
|
160
|
+
module_name = module_path.lstrip(os.sep).replace(os.sep, ".")
|
|
148
161
|
if module_name.endswith(".py"):
|
|
149
162
|
module_name = module_name[:-3]
|
|
150
163
|
else:
|
|
151
|
-
relative_path = module_path.replace(root_path,
|
|
152
|
-
module_name = relative_path.lstrip(os.sep).replace(os.sep,
|
|
164
|
+
relative_path = module_path.replace(root_path, "")
|
|
165
|
+
module_name = relative_path.lstrip(os.sep).replace(os.sep, ".")
|
|
153
166
|
if module_name.endswith(".py"):
|
|
154
167
|
module_name = module_name[:-3]
|
|
155
168
|
# module_name = module_name.replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
2
2
|
from machineconfig.utils.terminal import Terminal
|
|
3
3
|
from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
|
|
4
|
-
from machineconfig.utils.
|
|
4
|
+
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
5
5
|
from machineconfig.utils.utils2 import read_ini
|
|
6
6
|
from machineconfig.utils.code import write_shell_script_to_file
|
|
7
7
|
import platform
|
|
@@ -19,10 +19,12 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
|
|
|
19
19
|
print("๐งน Removed temporary remote copy")
|
|
20
20
|
from git.remote import Remote
|
|
21
21
|
from git.repo import Repo
|
|
22
|
+
|
|
22
23
|
try:
|
|
23
24
|
Remote.remove(Repo(repo_root_path), "originEnc")
|
|
24
25
|
console.print(Panel("๐ Removed originEnc remote reference", border_style="blue"))
|
|
25
|
-
except Exception:
|
|
26
|
+
except Exception:
|
|
27
|
+
pass # type: ignore
|
|
26
28
|
console.print(Panel("๐ Deleting remote repository copy and pushing local changes", width=150, border_style="blue"))
|
|
27
29
|
|
|
28
30
|
repo_root_path.to_cloud(cloud=cloud, zip=True, encrypt=True, rel2home=True, os_specific=False)
|
|
@@ -37,7 +39,7 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
|
|
|
37
39
|
def get_wt_cmd(wd1: PathExtended, wd2: PathExtended) -> str:
|
|
38
40
|
lines = [
|
|
39
41
|
f"""wt --window 0 new-tab --profile pwsh --title "gitdiff" --tabColor `#3b04d1 --startingDirectory {wd1} ` --colorScheme "Solarized Dark" """,
|
|
40
|
-
f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """
|
|
42
|
+
f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """,
|
|
41
43
|
]
|
|
42
44
|
return " `; ".join(lines)
|
|
43
45
|
|
|
@@ -53,24 +55,24 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
|
|
|
53
55
|
program = get_zellij_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_remote_root))
|
|
54
56
|
write_shell_script_to_file(shell_script=program)
|
|
55
57
|
return None
|
|
56
|
-
else:
|
|
58
|
+
else:
|
|
59
|
+
raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
def fetch_dotfiles():
|
|
60
63
|
console.print(Panel("๐ Fetching Dotfiles", title="[bold blue]Dotfiles[/bold blue]", border_style="blue"))
|
|
61
64
|
|
|
62
|
-
cloud_resolved = read_ini(DEFAULTS_PATH)[
|
|
65
|
+
cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
63
66
|
console.print(Panel(f"โ ๏ธ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", width=150, border_style="yellow"))
|
|
64
67
|
|
|
65
68
|
dotfiles_local = PathExtended.home().joinpath("dotfiles")
|
|
66
69
|
CONFIG_PATH.joinpath("remote").mkdir(parents=True, exist_ok=True)
|
|
67
|
-
dotfiles_remote = CONFIG_PATH.joinpath("remote", dotfiles_local.rel2home())
|
|
70
|
+
dotfiles_remote = PathExtended(CONFIG_PATH).joinpath("remote", dotfiles_local.rel2home())
|
|
68
71
|
remote_path = dotfiles_local.get_remote_path(rel2home=True, os_specific=False, root="myhome") + ".zip.enc"
|
|
69
72
|
|
|
70
73
|
console.print(Panel("๐ฅ Downloading dotfiles from cloud...", width=150, border_style="blue"))
|
|
71
74
|
|
|
72
|
-
dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved,
|
|
73
|
-
unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
|
|
75
|
+
dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved, unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
|
|
74
76
|
|
|
75
77
|
console.print(Panel("๐๏ธ Removing old dotfiles and replacing with cloud version...", width=150, border_style="blue"))
|
|
76
78
|
|
|
@@ -80,7 +82,8 @@ def fetch_dotfiles():
|
|
|
80
82
|
# rm -rf {dotfiles_local}
|
|
81
83
|
# mv {dotfiles_remote} {dotfiles_local}
|
|
82
84
|
"""
|
|
83
|
-
if platform.system() == "Linux":
|
|
85
|
+
if platform.system() == "Linux":
|
|
86
|
+
script += """
|
|
84
87
|
sudo chmod 600 $HOME/.ssh/*
|
|
85
88
|
sudo chmod 700 $HOME/.ssh
|
|
86
89
|
sudo chmod +x $HOME/dotfiles/scripts/linux -R
|
|
@@ -90,3 +93,22 @@ sudo chmod +x $HOME/dotfiles/scripts/linux -R
|
|
|
90
93
|
|
|
91
94
|
console.print(Panel("โ
Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
|
|
92
95
|
|
|
96
|
+
|
|
97
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
|
|
98
|
+
dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
|
|
99
|
+
from git import Repo
|
|
100
|
+
|
|
101
|
+
repo = Repo(path=dotfiles_path)
|
|
102
|
+
last_commit = repo.head.commit
|
|
103
|
+
dtm = last_commit.committed_datetime
|
|
104
|
+
from datetime import datetime # make it tz unaware
|
|
105
|
+
|
|
106
|
+
dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
|
|
107
|
+
res = dtm > datetime.fromisoformat(commit_dtm)
|
|
108
|
+
if res is False and update is True:
|
|
109
|
+
console = Console()
|
|
110
|
+
console.print(Panel(f"๐ UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
|
|
111
|
+
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
112
|
+
|
|
113
|
+
main(cloud=None, path=dotfiles_path)
|
|
114
|
+
return res
|