machineconfig 2.1__py3-none-any.whl → 2.2__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/archive/create_zellij_template.py +2 -1
- 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 +10 -5
- machineconfig/profile/create_hardlinks.py +3 -1
- machineconfig/profile/shell.py +8 -7
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/linux/devops +6 -4
- 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_agents.cpython-313.pyc +0 -0
- 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 +45 -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 +25 -20
- machineconfig/scripts/python/devops_update_repos.py +142 -57
- machineconfig/scripts/python/dotfile.py +16 -14
- machineconfig/scripts/python/fire_agents.py +24 -17
- machineconfig/scripts/python/fire_jobs.py +91 -55
- 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 -30
- 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 -75
- 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 +3 -3
- machineconfig/utils/installer.py +2 -2
- machineconfig/utils/installer_utils/installer_abc.py +3 -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 +16 -23
- machineconfig/utils/path_reduced.py +239 -205
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +9 -29
- 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.2.dist-info}/METADATA +13 -13
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/RECORD +78 -86
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.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/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
"""ID
|
|
2
|
-
"""
|
|
3
|
-
|
|
1
|
+
"""ID"""
|
|
4
2
|
|
|
5
3
|
# from platform import system
|
|
6
|
-
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
4
|
+
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
7
5
|
from machineconfig.utils.options import display_options
|
|
8
6
|
from rich.panel import Panel
|
|
9
7
|
from rich.text import Text
|
|
@@ -53,35 +51,27 @@ def main():
|
|
|
53
51
|
|
|
54
52
|
print(Panel("📝 Updating SSH configuration...", expand=False))
|
|
55
53
|
|
|
56
|
-
# Inline the previous
|
|
54
|
+
# Inline the previous modify_text behavior (now deprecated):
|
|
57
55
|
# - If file doesn't exist, seed content with txt_search
|
|
58
|
-
# -
|
|
56
|
+
# - Otherwise, replace a matching line or append if not found
|
|
59
57
|
if config_path.exists():
|
|
60
58
|
current = config_path.read_text(encoding="utf-8")
|
|
61
59
|
print(Panel("✏️ Updated existing SSH config file", expand=False))
|
|
62
60
|
else:
|
|
63
61
|
current = txt
|
|
64
62
|
print(Panel("📄 Created new SSH config file", expand=False))
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
lines = current.split("\n")
|
|
64
|
+
found = False
|
|
65
|
+
for i, line in enumerate(lines):
|
|
66
|
+
if txt in line:
|
|
67
|
+
lines[i] = txt
|
|
68
|
+
found = True
|
|
69
|
+
if not found:
|
|
70
|
+
lines.insert(0, txt)
|
|
71
|
+
new_content = "\n".join(lines)
|
|
73
72
|
config_path.write_text(new_content, encoding="utf-8")
|
|
74
73
|
|
|
75
|
-
panel_complete = Panel(
|
|
76
|
-
Text(
|
|
77
|
-
"✅ SSH IDENTITY CONFIGURATION COMPLETE\n"
|
|
78
|
-
"Identity added to SSH config file\n"
|
|
79
|
-
"Consider reloading the SSH config to apply changes",
|
|
80
|
-
justify="center"
|
|
81
|
-
),
|
|
82
|
-
expand=False,
|
|
83
|
-
border_style="green"
|
|
84
|
-
)
|
|
74
|
+
panel_complete = Panel(Text("✅ SSH IDENTITY CONFIGURATION COMPLETE\nIdentity added to SSH config file\nConsider reloading the SSH config to apply changes", justify="center"), expand=False, border_style="green")
|
|
85
75
|
program = f"echo '{panel_complete}'"
|
|
86
76
|
|
|
87
77
|
success_message = f"🎉 CONFIGURATION SUCCESSFUL\nIdentity added: {path_to_key.name}\nConfig file: {config_path}"
|
|
@@ -90,5 +80,5 @@ def main():
|
|
|
90
80
|
return program
|
|
91
81
|
|
|
92
82
|
|
|
93
|
-
if __name__ ==
|
|
83
|
+
if __name__ == "__main__":
|
|
94
84
|
pass
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
"""SSH
|
|
2
|
-
"""
|
|
3
|
-
|
|
1
|
+
"""SSH"""
|
|
4
2
|
|
|
5
3
|
from platform import system
|
|
6
|
-
from machineconfig.utils.
|
|
4
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
5
|
+
from machineconfig.utils.options import display_options
|
|
7
6
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
from rich.panel import Panel
|
|
10
|
-
from rich import box
|
|
9
|
+
from rich import box # Import box
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
console = Console()
|
|
@@ -46,7 +45,8 @@ def get_add_ssh_key_script(path_to_key: PathExtended):
|
|
|
46
45
|
assert place_holder in program, f"This section performs string manipulation on the script {program_path} to add the key to the authorized_keys file. The script has changed and the string {place_holder} is not found."
|
|
47
46
|
program = program.replace(place_holder, f'$sshfile = "{path_to_key}"')
|
|
48
47
|
console.print(Panel("🔧 Configured PowerShell script for Windows\n📝 Replaced placeholder with actual key path", title="[bold blue]Configuration[/bold blue]"))
|
|
49
|
-
else:
|
|
48
|
+
else:
|
|
49
|
+
raise NotImplementedError
|
|
50
50
|
else:
|
|
51
51
|
console.print(Panel(f"📝 Creating new authorized_keys file\n🔑 Using key: {path_to_key.name}", title="[bold blue]Action[/bold blue]"))
|
|
52
52
|
if system() == "Linux":
|
|
@@ -114,5 +114,5 @@ def main():
|
|
|
114
114
|
return program
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
if __name__ ==
|
|
117
|
+
if __name__ == "__main__":
|
|
118
118
|
pass
|
|
@@ -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)
|
|
@@ -70,12 +69,14 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
70
69
|
program = ""
|
|
71
70
|
|
|
72
71
|
case "SystemInstallers":
|
|
73
|
-
if system() == "Windows":
|
|
72
|
+
if system() == "Windows":
|
|
73
|
+
options_system = parse_apps_installer_windows(LIBRARY_ROOT.joinpath("setup_windows/apps.ps1").read_text(encoding="utf-8"))
|
|
74
74
|
elif system() == "Linux":
|
|
75
75
|
options_system_1 = parse_apps_installer_linux(LIBRARY_ROOT.joinpath("setup_linux/apps_dev.sh").read_text(encoding="utf-8"))
|
|
76
76
|
options_system_2 = parse_apps_installer_linux(LIBRARY_ROOT.joinpath("setup_linux/apps.sh").read_text(encoding="utf-8"))
|
|
77
77
|
options_system = {**options_system_1, **options_system_2}
|
|
78
|
-
else:
|
|
78
|
+
else:
|
|
79
|
+
raise NotImplementedError(f"❌ System {system()} not supported")
|
|
79
80
|
program_names = choose_multiple_options(msg="", options=sorted(list(options_system.keys())), header="🚀 CHOOSE DEV APP")
|
|
80
81
|
program = ""
|
|
81
82
|
for name in program_names:
|
|
@@ -84,7 +85,8 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
84
85
|
│ ⚙️ Installing: {name}
|
|
85
86
|
└────────────────────────────────────────────────────""")
|
|
86
87
|
sub_program = options_system[name]
|
|
87
|
-
if sub_program.startswith("#winget"):
|
|
88
|
+
if sub_program.startswith("#winget"):
|
|
89
|
+
sub_program = sub_program[1:]
|
|
88
90
|
program += "\n" + sub_program
|
|
89
91
|
|
|
90
92
|
# case "OtherDevApps":
|
|
@@ -105,7 +107,7 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
105
107
|
# print(f"Installing {name}")
|
|
106
108
|
# sub_program = installers[idx].install_robust(version=None) # finish the task
|
|
107
109
|
|
|
108
|
-
case
|
|
110
|
+
case "PrecheckedCloudInstaller":
|
|
109
111
|
# from machineconfig.jobs.python.check_installations import PrecheckedCloudInstaller
|
|
110
112
|
# ci = PrecheckedCloudInstaller()
|
|
111
113
|
# ci.download_safe_apps(name="AllEssentials")
|
|
@@ -119,15 +121,15 @@ def parse_apps_installer_linux(txt: str) -> dict[str, Any]:
|
|
|
119
121
|
res = {}
|
|
120
122
|
for chunk in txts[1:]:
|
|
121
123
|
try:
|
|
122
|
-
k = chunk.split(
|
|
124
|
+
k = chunk.split("----")[0].rstrip().lstrip()
|
|
123
125
|
v = "\n".join(chunk.split("\n")[1:])
|
|
124
126
|
res[k] = v
|
|
125
127
|
except IndexError as e:
|
|
126
128
|
print(f"""
|
|
127
129
|
❌ Error parsing chunk:
|
|
128
|
-
{
|
|
130
|
+
{"-" * 50}
|
|
129
131
|
{chunk}
|
|
130
|
-
{
|
|
132
|
+
{"-" * 50}""")
|
|
131
133
|
raise e
|
|
132
134
|
return res
|
|
133
135
|
|
|
@@ -135,28 +137,31 @@ def parse_apps_installer_linux(txt: str) -> dict[str, Any]:
|
|
|
135
137
|
def parse_apps_installer_windows(txt: str) -> dict[str, Any]:
|
|
136
138
|
chunks: list[str] = []
|
|
137
139
|
for idx, item in enumerate(txt.split(sep="winget install")):
|
|
138
|
-
if idx == 0:
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
if idx == 0:
|
|
141
|
+
continue
|
|
142
|
+
if idx == 1:
|
|
143
|
+
chunks.append(item)
|
|
144
|
+
else:
|
|
145
|
+
chunks.append("winget install" + item)
|
|
141
146
|
# progs = L(txt.splitlines()).filter(lambda x: x.startswith("winget ") or x.startswith("#winget"))
|
|
142
147
|
res: dict[str, str] = {}
|
|
143
148
|
for a_chunk in chunks:
|
|
144
149
|
try:
|
|
145
|
-
name = a_chunk.split(
|
|
150
|
+
name = a_chunk.split("--name ")[1]
|
|
146
151
|
if "--Id" not in name:
|
|
147
152
|
print(f"⚠️ Warning: {name} does not have an Id, skipping")
|
|
148
153
|
continue
|
|
149
|
-
name = name.split(
|
|
154
|
+
name = name.split(" --Id ", maxsplit=1)[0].strip('"').strip('"')
|
|
150
155
|
res[name] = a_chunk
|
|
151
156
|
except IndexError as e:
|
|
152
157
|
print(f"""
|
|
153
158
|
❌ Error parsing chunk:
|
|
154
|
-
{
|
|
159
|
+
{"-" * 50}
|
|
155
160
|
{a_chunk}
|
|
156
|
-
{
|
|
161
|
+
{"-" * 50}""")
|
|
157
162
|
raise e
|
|
158
163
|
return res
|
|
159
164
|
|
|
160
165
|
|
|
161
|
-
if __name__ ==
|
|
166
|
+
if __name__ == "__main__":
|
|
162
167
|
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()
|