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.

Files changed (127) hide show
  1. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
  2. machineconfig/cluster/sessions_managers/layout_types.py +29 -0
  3. machineconfig/cluster/sessions_managers/wt_local.py +68 -62
  4. machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
  5. machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
  6. machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
  7. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
  8. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
  9. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
  10. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
  11. machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
  12. machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
  13. machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
  14. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
  15. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
  16. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
  17. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
  18. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
  19. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
  20. machineconfig/cluster/templates/utils.py +0 -35
  21. machineconfig/jobs/python/check_installations.py +1 -1
  22. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  23. machineconfig/jobs/python_generic_installers/config.json +1 -1
  24. machineconfig/profile/create.py +13 -4
  25. machineconfig/profile/create_hardlinks.py +3 -1
  26. machineconfig/profile/shell.py +8 -7
  27. machineconfig/scripts/__init__.py +0 -2
  28. machineconfig/scripts/linux/devops +6 -4
  29. machineconfig/scripts/python/ai/generate_files.py +14 -15
  30. machineconfig/scripts/python/ai/mcinit.py +8 -5
  31. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  32. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  33. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  34. machineconfig/scripts/python/cloud_copy.py +22 -13
  35. machineconfig/scripts/python/cloud_mount.py +35 -23
  36. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  37. machineconfig/scripts/python/cloud_sync.py +4 -4
  38. machineconfig/scripts/python/croshell.py +37 -28
  39. machineconfig/scripts/python/devops.py +46 -27
  40. machineconfig/scripts/python/devops_add_identity.py +15 -25
  41. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  42. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  43. machineconfig/scripts/python/devops_devapps_install.py +26 -20
  44. machineconfig/scripts/python/devops_update_repos.py +142 -57
  45. machineconfig/scripts/python/dotfile.py +16 -14
  46. machineconfig/scripts/python/fire_agents.py +30 -23
  47. machineconfig/scripts/python/fire_jobs.py +86 -98
  48. machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
  49. machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
  50. machineconfig/scripts/python/ftpx.py +24 -14
  51. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  52. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  53. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  54. machineconfig/scripts/python/helpers/helpers4.py +44 -31
  55. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  56. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  57. machineconfig/scripts/python/mount_nfs.py +8 -15
  58. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  59. machineconfig/scripts/python/mount_ssh.py +8 -6
  60. machineconfig/scripts/python/repos.py +215 -57
  61. machineconfig/scripts/python/snapshot.py +0 -1
  62. machineconfig/scripts/python/start_slidev.py +10 -5
  63. machineconfig/scripts/python/start_terminals.py +22 -16
  64. machineconfig/scripts/python/viewer_template.py +0 -1
  65. machineconfig/scripts/python/wifi_conn.py +49 -76
  66. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  67. machineconfig/settings/lf/linux/lfrc +1 -0
  68. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  70. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  71. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  72. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  73. machineconfig/utils/code.py +2 -3
  74. machineconfig/utils/installer.py +2 -2
  75. machineconfig/utils/installer_utils/installer_abc.py +2 -4
  76. machineconfig/utils/installer_utils/installer_class.py +6 -4
  77. machineconfig/utils/links.py +103 -33
  78. machineconfig/utils/notifications.py +52 -38
  79. machineconfig/utils/options.py +14 -21
  80. machineconfig/utils/path.py +12 -12
  81. machineconfig/utils/path_reduced.py +239 -200
  82. machineconfig/utils/procs.py +1 -1
  83. machineconfig/utils/source_of_truth.py +27 -0
  84. machineconfig/utils/ssh.py +9 -19
  85. machineconfig/utils/terminal.py +4 -2
  86. machineconfig/utils/upgrade_packages.py +91 -0
  87. machineconfig/utils/utils2.py +1 -2
  88. machineconfig/utils/utils5.py +23 -11
  89. machineconfig/utils/ve.py +4 -1
  90. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
  91. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
  92. machineconfig-2.3.dist-info/entry_points.txt +2 -0
  93. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
  94. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
  95. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  96. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  97. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  98. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  99. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  100. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  112. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  113. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  114. machineconfig/utils/utils.py +0 -97
  115. /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
  116. /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
  117. /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
  118. /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
  119. /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
  120. /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
  121. /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
  122. /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
  123. /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
  124. /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
  125. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  126. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
  127. {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.utils import LIBRARY_ROOT, DEFAULTS_PATH, print_code, choose_cloud_interactively, choose_multiple_options
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)['general']['rclone_config_name']
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=['all'] + list(bu_file.keys()))
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 += 'z' if item['zip'] == 'True' else ''
57
- flags += 'e' if item['encrypt'] == 'True' else ''
58
- flags += 'r' if item['rel2home'] == 'True' else ''
59
- flags += 'o' if system().lower() in item_name else ''
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: flags = "-" + flags
62
+ if flags:
63
+ flags = "-" + flags
62
64
  if direction == "BACKUP":
63
- program += f"""\ncloud_copy "{PathExtended(item['path']).as_posix()}" $cloud {flags}\n"""
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['path']).as_posix()}" {flags}\n"""
67
+ program += f"""\ncloud_copy $cloud "{PathExtended(item["path"]).as_posix()}" {flags}\n"""
66
68
  else:
67
- 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"))
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.utils import write_shell_script_to_default_program_path
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.utils import LIBRARY_ROOT, choose_multiple_options
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 (which.split(",") if type(which) == str else which):
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": options_system = parse_apps_installer_windows(LIBRARY_ROOT.joinpath("setup_windows/apps.ps1").read_text(encoding="utf-8"))
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: raise NotImplementedError(f"āŒ System {system()} not supported")
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"): sub_program = sub_program[1:]
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 "PrecheckedCloudInstaller":
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('----')[0].rstrip().lstrip()
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
- {'-' * 50}
131
+ {"-" * 50}
129
132
  {chunk}
130
- {'-' * 50}""")
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: continue
139
- if idx == 1: chunks.append(item)
140
- else: chunks.append("winget install" + item)
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('--name ')[1]
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(' --Id ', maxsplit=1)[0].strip('"').strip('"')
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
- {'-' * 50}
160
+ {"-" * 50}
155
161
  {a_chunk}
156
- {'-' * 50}""")
162
+ {"-" * 50}""")
157
163
  raise e
158
164
  return res
159
165
 
160
166
 
161
- if __name__ == '__main__':
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.utils import DEFAULTS_PATH
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 _get_file_hash(file_path: Path) -> str | None:
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 _set_permissions_recursive(path: Path, executable: bool = True) -> None:
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
- _set_permissions_recursive(item, executable)
31
+ for item in path.rglob("*"):
32
+ set_permissions_recursive(item, executable)
33
33
 
34
34
 
35
- def _run_uv_sync(repo_path: Path) -> None:
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 _update_repository(repo: git.Repo) -> bool:
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 = _get_file_hash(pyproject_path)
68
- uv_lock_hash_before = _get_file_hash(uv_lock_path)
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
- # Perform git pull for each remote
71
+ # Use subprocess for git pull to get better output control
72
72
  dependencies_changed = False
73
- for remote in repo.remotes:
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"šŸ“„ Pulling from {remote.name} {repo.active_branch.name}")
76
- pull_info = remote.pull(repo.active_branch.name)
77
- for info in pull_info:
78
- if info.flags & info.FAST_FORWARD:
79
- print("āœ… Fast-forward pull completed")
80
- elif info.flags & info.NEW_HEAD:
81
- print("āœ… Repository updated")
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(f"āœ… Pull completed: {info.flags}")
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 = _get_file_hash(pyproject_path)
90
- uv_lock_hash_after = _get_file_hash(uv_lock_path)
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
- _set_permissions_recursive(scripts_path)
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
- _set_permissions_recursive(linux_jobs_path)
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
- _set_permissions_recursive(lf_exe_path)
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/machineconfig", ]
213
+ repos: list[str] = ["~/code/machineconfig", "~/code/crocodile"]
129
214
  try:
130
- tmp = read_ini(DEFAULTS_PATH)['general']['repos'].split(",")
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 = _update_repository(repo)
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
- {'-' * 50}""")
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
- _run_uv_sync(repo_path)
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__ == '__main__':
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.profile.create import symlink_func
7
- from machineconfig.utils.utils import LIBRARY_ROOT, REPO_ROOT
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='FTP client')
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): junction = orig_path.split(at="Local", sep=-1)[1]
24
- elif "Roaming" in str(orig_path): junction = orig_path.split(at="Roaming", sep=-1)[1]
25
- elif ".config" in str(orig_path): junction = orig_path.split(at=".config", sep=-1)[1]
26
- else: junction = orig_path.rel2home()
27
- new_path = REPO_ROOT.joinpath(junction)
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('.')[0]} = {{ this = '{orig_path.collapseuser().as_posix()}', to_this = '{new_path.collapseuser().as_posix()}' }}
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__ == '__main__':
51
+ if __name__ == "__main__":
50
52
  main()