machineconfig 6.61__py3-none-any.whl → 6.63__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.

@@ -183,7 +183,7 @@ def ftpx(
183
183
  padding=(1, 2),
184
184
  )
185
185
  )
186
- received_file = ssh.copy_from_here(source_path=resolved_source, target_path=resolved_target, compress_with_zip=zipFirst, recursive=recursive, overwrite_existing=False)
186
+ received_file = ssh.copy_from_here(source_path=resolved_source, target_rel2home=resolved_target, compress_with_zip=zipFirst, recursive=recursive, overwrite_existing=False)
187
187
 
188
188
  if source_is_remote and isinstance(received_file, PathExtended):
189
189
  console.print(
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  import typer
4
- from typing import Annotated, Optional
4
+ from typing import Annotated, Optional, TypedDict
5
5
  from pathlib import Path
6
6
  import subprocess
7
7
  import requests
@@ -115,3 +115,28 @@ def merge_pdfs(
115
115
  from machineconfig.utils.code import run_shell_script, get_uv_command_executing_python_script
116
116
  uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=["pypdf"], uv_project_dir=None)
117
117
  run_shell_script(uv_command)
118
+
119
+
120
+ class MachineSpecs(TypedDict):
121
+ system: str
122
+ distro: str
123
+ home_dir: str
124
+ def get_machine_specs() -> MachineSpecs:
125
+ """Write print and return the local machine specs."""
126
+ import platform
127
+ UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
128
+ command = f"""{UV_RUN_CMD} --with distro python -c "import distro; print(distro.name(pretty=True))" """
129
+ import subprocess
130
+ distro = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
131
+ specs: MachineSpecs = {
132
+ "system": platform.system(),
133
+ "distro": distro,
134
+ "home_dir": str(Path.home()),
135
+ }
136
+ print(specs)
137
+ from machineconfig.utils.source_of_truth import CONFIG_ROOT
138
+ path = CONFIG_ROOT.joinpath("machine_specs.json")
139
+ CONFIG_ROOT.mkdir(parents=True, exist_ok=True)
140
+ import json
141
+ path.write_text(json.dumps(specs, indent=4), encoding="utf-8")
142
+ return specs
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- from machineconfig.scripts.python.helpers_devops.cli_utils import download, merge_pdfs
3
+ from machineconfig.scripts.python.helpers_devops.cli_utils import download, merge_pdfs, get_machine_specs
4
4
  import typer
5
5
 
6
6
 
@@ -11,6 +11,8 @@ def get_app() -> typer.Typer:
11
11
  app.command(name="d", no_args_is_help=True, hidden=True)(download)
12
12
  app.command(name="merge-pdfs", no_args_is_help=True, help="[m] Merge two PDF files into one.")(merge_pdfs)
13
13
  app.command(name="m", no_args_is_help=True, hidden=True)(merge_pdfs)
14
+ app.command(name="get-machine-specs", no_args_is_help=False, help="[g] Get machine specifications.")(get_machine_specs)
15
+ app.command(name="g", no_args_is_help=False, hidden=True)(get_machine_specs)
14
16
  return app
15
17
 
16
18
 
@@ -1,16 +1,16 @@
1
- from typing import Callable, Optional, Any, Union
1
+ from typing import Callable, Optional, Any, Union, cast
2
2
  import os
3
3
  from pathlib import Path
4
4
  import platform
5
+ from machineconfig.scripts.python.helpers_devops.cli_utils import MachineSpecs
5
6
  import rich.console
6
- from machineconfig.utils.terminal import Response, MACHINE
7
- from machineconfig.utils.accessories import pprint
8
-
7
+ from machineconfig.utils.terminal import Response
8
+ from machineconfig.utils.accessories import pprint, randstr
9
+ from machineconfig.utils.meta import lambda_to_python_script
9
10
  UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
10
11
  MACHINECONFIG_VERSION = "machineconfig>=6.61"
11
12
  DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
12
13
 
13
-
14
14
  class SSH:
15
15
  def __init__(
16
16
  self, host: Optional[str], username: Optional[str], hostname: Optional[str], ssh_key_path: Optional[str], password: Optional[str], port: int, enable_compression: bool):
@@ -22,7 +22,6 @@ class SSH:
22
22
  self.username: str
23
23
  self.port: int = port
24
24
  self.proxycommand: Optional[str] = None
25
- import platform
26
25
  import paramiko # type: ignore
27
26
  import getpass
28
27
 
@@ -109,13 +108,16 @@ class SSH:
109
108
  def view_bar(self, transferred: int, total: int) -> None:
110
109
  if self.progress and self.task is not None:
111
110
  self.progress.update(self.task, completed=transferred, total=total)
112
-
113
111
  self.tqdm_wrap = RichProgressWrapper
114
- self._local_distro: Optional[str] = None
115
- self._remote_distro: Optional[str] = None
116
- self._remote_machine: Optional[MACHINE] = None
112
+ from machineconfig.scripts.python.helpers_devops.cli_utils import get_machine_specs
113
+ self.local_specs: MachineSpecs = get_machine_specs()
114
+ resp = self.run_shell(command=f"""~/.local/bin/utils get-machine-specs """, verbose_output=False, description="Getting remote machine specs", strict_stderr=False, strict_return_code=False)
115
+ # import json
116
+ json_str = resp.op
117
+ print(f"Remote machine specs JSON: {resp.op}")
118
+ import ast
119
+ self.remote_specs: MachineSpecs = cast(MachineSpecs, ast.literal_eval(json_str))
117
120
  self.terminal_responses: list[Response] = []
118
- self.platform = platform
119
121
 
120
122
  def __enter__(self) -> "SSH":
121
123
  return self
@@ -126,34 +128,11 @@ class SSH:
126
128
  self.sftp.close()
127
129
  self.sftp = None
128
130
  self.ssh.close()
129
- def get_remote_machine(self) -> MACHINE:
130
- if self._remote_machine is None:
131
- windows_test1 = self.run_shell(command="$env:OS", verbose_output=False, description="Testing Remote OS Type", strict_stderr=False, strict_return_code=False).op
132
- windows_test2 = self.run_shell(command="echo %OS%", verbose_output=False, description="Testing Remote OS Type Again", strict_stderr=False, strict_return_code=False).op
133
- if windows_test1 == "Windows_NT" or windows_test2 == "Windows_NT":
134
- self._remote_machine = "Windows"
135
- else:
136
- self._remote_machine = "Linux"
137
- return self._remote_machine
138
- def get_local_distro(self) -> str:
139
- if self._local_distro is None:
140
- command = f"""{UV_RUN_CMD} --with distro python -c "import distro; print(distro.name(pretty=True))" """
141
- import subprocess
142
- res = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
143
- self._local_distro = res
144
- return res
145
- return self._local_distro
146
- def get_remote_distro(self) -> str:
147
- if self._remote_distro is None:
148
- command_str = f"""{UV_RUN_CMD} --with distro python -c "import distro; print(distro.name(pretty=True))" """
149
- res = self.run_shell(command=command_str, verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
150
- self._remote_distro = res.op_if_successfull_or_default() or ""
151
- return self._remote_distro
152
131
  def restart_computer(self) -> Response:
153
- return self.run_shell(command="Restart-Computer -Force" if self.get_remote_machine() == "Windows" else "sudo reboot", verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
132
+ return self.run_shell(command="Restart-Computer -Force" if self.remote_specs["system"] == "Windows" else "sudo reboot", verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
154
133
  def send_ssh_key(self) -> Response:
155
- self.copy_from_here(source_path=Path("~/.ssh/id_rsa.pub"), target_path=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
156
- if self.get_remote_machine() != "Windows":
134
+ self.copy_from_here(source_path="~/.ssh/id_rsa.pub", target_rel2home=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
135
+ if self.remote_specs["system"] != "Windows":
157
136
  raise RuntimeError("send_ssh_key is only supported for Windows remote machines")
158
137
  code_url = "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/refs/heads/main/src/machineconfig/setup_windows/openssh-server_add-sshkey.ps1"
159
138
  import urllib.request
@@ -162,17 +141,17 @@ class SSH:
162
141
  return self.run_shell(command=code, verbose_output=True, description="", strict_stderr=False, strict_return_code=False)
163
142
 
164
143
  def get_remote_repr(self, add_machine: bool = False) -> str:
165
- return f"{self.username}@{self.hostname}:{self.port}" + (f" [{self.get_remote_machine()}][{self.get_remote_distro()}]" if add_machine else "")
144
+ return f"{self.username}@{self.hostname}:{self.port}" + (f" [{self.remote_specs['system']}][{self.remote_specs['distro']}]" if add_machine else "")
166
145
  def get_local_repr(self, add_machine: bool = False) -> str:
167
146
  import getpass
168
- return f"{getpass.getuser()}@{self.platform.node()}" + (f" [{self.platform.system()}][{self.get_local_distro()}]" if add_machine else "")
147
+ return f"{getpass.getuser()}@{platform.node()}" + (f" [{platform.system()}][{self.local_specs['distro']}]" if add_machine else "")
169
148
  def get_ssh_conn_str(self, command: str) -> str:
170
149
  return "ssh " + (f" -i {self.ssh_key_path}" if self.ssh_key_path else "") + self.get_remote_repr(add_machine=False).replace(":", " -p ") + (f" -t {command} " if command != "" else " ")
171
150
  def __repr__(self) -> str:
172
151
  return f"local {self.get_local_repr(add_machine=True)} >>> SSH TO >>> remote {self.get_remote_repr(add_machine=True)}"
173
152
 
174
153
  def run_locally(self, command: str) -> Response:
175
- print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.platform.node()} Command: {command}""")
154
+ print(f"""💻 [LOCAL EXECUTION] Running command on node: {self.local_specs['system']} Command: {command}""")
176
155
  res = Response(cmd=command)
177
156
  res.output.returncode = os.system(command)
178
157
  return res
@@ -184,15 +163,14 @@ class SSH:
184
163
  res.print()
185
164
  else:
186
165
  res.capture().print_if_unsuccessful(desc=description, strict_err=strict_stderr, strict_returncode=strict_return_code, assert_success=False)
187
- self.terminal_responses.append(res)
166
+ # self.terminal_responses.append(res)
188
167
  return res
189
168
  def run_py(self, python_code: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str],
190
169
  description: str, verbose_output: bool, strict_stderr: bool, strict_return_code: bool) -> Response:
191
- from machineconfig.utils.accessories import randstr
192
170
  py_path = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/runpy_{randstr()}.py")
193
171
  py_path.parent.mkdir(parents=True, exist_ok=True)
194
172
  py_path.write_text(python_code, encoding="utf-8")
195
- self.copy_from_here(source_path=py_path, target_path=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
173
+ self.copy_from_here(source_path=str(py_path), target_rel2home=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
196
174
  if uv_with is not None and len(uv_with) > 0:
197
175
  with_clause = " --with " + '"' + ",".join(uv_with) + '"'
198
176
  else:
@@ -204,8 +182,7 @@ class SSH:
204
182
  uv_cmd = f"""{UV_RUN_CMD} {with_clause} python {py_path.relative_to(Path.home())}"""
205
183
  return self.run_shell(command=uv_cmd, verbose_output=verbose_output, description=description or f"run_py on {self.get_remote_repr(add_machine=False)}", strict_stderr=strict_stderr, strict_return_code=strict_return_code)
206
184
 
207
- def run_py_func(self, func: Callable[..., Any], uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> Response:
208
- from machineconfig.utils.meta import lambda_to_python_script
185
+ def run_lambda_function(self, func: Callable[..., Any], uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> Response:
209
186
  command = lambda_to_python_script(lmb=func, in_global=True)
210
187
  return self.run_py(python_code=command, uv_with=uv_with, uv_project_dir=uv_project_dir,
211
188
  description=f"run_py_func {func.__name__} on {self.get_remote_repr(add_machine=False)}",
@@ -218,77 +195,47 @@ class SSH:
218
195
  local_path.parent.mkdir(parents=True, exist_ok=True)
219
196
  self.sftp.get(remotepath=remote_path, localpath=str(local_path))
220
197
 
221
- def _create_remote_target_dir(self, target_path: Union[str, Path], overwrite_existing: bool) -> str:
198
+ def create_dir(self, path_rel2home: str, overwrite_existing: bool) -> None:
222
199
  """Helper to create a directory on remote machine and return its path."""
223
- def create_target_dir(target_dir_path: str, overwrite: bool, json_output_path: str) -> str:
200
+ def create_target_dir(target_rel2home: str, overwrite: bool):
224
201
  from pathlib import Path
225
202
  import shutil
226
- import json
227
- directory_path = Path(target_dir_path).expanduser()
203
+ directory_path = Path(target_rel2home).expanduser()
228
204
  if overwrite and directory_path.exists():
229
205
  if directory_path.is_dir():
230
206
  shutil.rmtree(directory_path)
231
207
  else:
232
208
  directory_path.unlink()
233
209
  directory_path.parent.mkdir(parents=True, exist_ok=True)
234
- result_path_posix = directory_path.as_posix()
235
- json_result_path = Path(json_output_path)
236
- json_result_path.parent.mkdir(parents=True, exist_ok=True)
237
- json_result_path.write_text(json.dumps(result_path_posix, indent=2), encoding="utf-8")
238
- print(json_result_path.as_posix())
239
- return result_path_posix
240
- from machineconfig.utils.meta import lambda_to_python_script
241
- from machineconfig.utils.accessories import randstr
242
- remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
243
- # command = function_to_script(func=create_target_dir, call_with_kwargs={"target_dir_path": Path(target_path).as_posix(), "overwrite": overwrite_existing, "json_output_path": remote_json_output})
244
- command = lambda_to_python_script(lmb=lambda: create_target_dir(target_dir_path=str(target_path), overwrite=overwrite_existing, json_output_path=remote_json_output), in_global=True)
245
- response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None,
246
- description=f"Creating target directory `{Path(target_path).parent.as_posix()}` @ {self.get_remote_repr(add_machine=False)}",
247
- verbose_output=False, strict_stderr=False, strict_return_code=False)
248
- remote_json_path = response.op.strip()
249
- if not remote_json_path:
250
- raise RuntimeError(f"Failed to create target directory {target_path} - no response from remote")
251
- from machineconfig.utils.accessories import randstr
252
- local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
253
- self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
254
- import json
255
- try:
256
- result = json.loads(local_json.read_text(encoding="utf-8"))
257
- except (json.JSONDecodeError, FileNotFoundError) as err:
258
- raise RuntimeError(f"Failed to create target directory {target_path} - invalid JSON response: {err}") from err
259
- finally:
260
- if local_json.exists():
261
- local_json.unlink()
262
- assert isinstance(result, str), f"Failed to create target directory {target_path} on remote"
263
- return result
210
+ command = lambda_to_python_script(lmb=lambda: create_target_dir(target_rel2home=path_rel2home, overwrite=overwrite_existing), in_global=True)
211
+ tmp_py_file = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/create_target_dir_{randstr()}.py")
212
+ tmp_py_file.parent.mkdir(parents=True, exist_ok=True)
213
+ tmp_py_file.write_text(command, encoding="utf-8")
214
+ # self.copy_from_here(source_path=str(tmp_py_file), target_rel2home=".tmp_file.py", compress_with_zip=False, recursive=False, overwrite_existing=True)
215
+ assert self.sftp is not None
216
+ tmp_remote_path = ".tmp_pyfile.py"
217
+ self.sftp.put(localpath=str(tmp_py_file), remotepath=str(Path(self.remote_specs["home_dir"]).joinpath(tmp_remote_path)))
218
+ self.run_shell(command=f"""{UV_RUN_CMD} python {tmp_remote_path}""", verbose_output=False, description=f"Creating target dir {path_rel2home}", strict_stderr=True, strict_return_code=True)
264
219
 
265
-
266
- def copy_from_here(self, source_path: Union[str, Path], target_path: Optional[Union[str, Path]], compress_with_zip: bool, recursive: bool, overwrite_existing: bool) -> Path:
267
- if self.sftp is None:
268
- raise RuntimeError(f"SFTP connection not available for {self.hostname}. Cannot transfer files.")
269
-
220
+ def copy_from_here(self, source_path: str, target_rel2home: Optional[str], compress_with_zip: bool, recursive: bool, overwrite_existing: bool) -> None:
221
+ if self.sftp is None: raise RuntimeError(f"SFTP connection not available for {self.hostname}. Cannot transfer files.")
270
222
  source_obj = Path(source_path).expanduser().absolute()
271
- if not source_obj.exists():
272
- raise RuntimeError(f"SSH Error: source `{source_obj}` does not exist!")
273
-
274
- if target_path is None:
275
- try:
276
- target_path_relative = source_obj.relative_to(Path.home())
277
- target_path = Path("~") / target_path_relative
223
+ if not source_obj.exists(): raise RuntimeError(f"SSH Error: source `{source_obj}` does not exist!")
224
+ if target_rel2home is None:
225
+ try: target_rel2home = str(source_obj.relative_to(Path.home()))
278
226
  except ValueError:
279
227
  raise RuntimeError(f"If target is not specified, source must be relative to home directory, but got: {source_obj}")
280
-
281
228
  if not compress_with_zip and source_obj.is_dir():
282
229
  if not recursive:
283
230
  raise RuntimeError(f"SSH Error: source `{source_obj}` is a directory! Set `recursive=True` for recursive sending or `compress_with_zip=True` to zip it first.")
284
231
  file_paths_to_upload: list[Path] = [file_path for file_path in source_obj.rglob("*") if file_path.is_file()]
285
- remote_root = self._create_remote_target_dir(target_path=target_path, overwrite_existing=overwrite_existing)
232
+ self.create_dir(path_rel2home=target_rel2home, overwrite_existing=overwrite_existing)
286
233
  for idx, file_path in enumerate(file_paths_to_upload):
287
234
  print(f" {idx + 1:03d}. {file_path}")
288
235
  for file_path in file_paths_to_upload:
289
- remote_file_target = Path(remote_root).joinpath(file_path.relative_to(source_obj))
290
- self.copy_from_here(source_path=file_path, target_path=remote_file_target, compress_with_zip=False, recursive=False, overwrite_existing=overwrite_existing)
291
- return Path(remote_root)
236
+ remote_file_target = Path(target_rel2home).joinpath(file_path.relative_to(source_obj))
237
+ self.copy_from_here(source_path=str(file_path), target_rel2home=str(remote_file_target), compress_with_zip=False, recursive=False, overwrite_existing=overwrite_existing)
238
+ return None
292
239
  if compress_with_zip:
293
240
  print("🗜️ ZIPPING ...")
294
241
  import shutil
@@ -298,16 +245,14 @@ class SSH:
298
245
  else:
299
246
  shutil.make_archive(str(zip_path), "zip", source_obj.parent, source_obj.name)
300
247
  source_obj = Path(str(zip_path) + ".zip")
301
- if not str(target_path).endswith(".zip"):
302
- target_path = Path(str(target_path) + ".zip")
303
- remotepath_str = self._create_remote_target_dir(target_path=target_path, overwrite_existing=overwrite_existing)
304
- remotepath = Path(remotepath_str)
305
- print(f"""📤 [SFTP UPLOAD] Sending file: {repr(source_obj)} ==> Remote Path: {remotepath.as_posix()}""")
248
+ if not target_rel2home.endswith(".zip"): target_rel2home = target_rel2home + ".zip"
249
+ self.create_dir(path_rel2home=target_rel2home, overwrite_existing=overwrite_existing)
250
+ print(f"""📤 [SFTP UPLOAD] Sending file: {repr(source_obj)} ==> Remote Path: {target_rel2home}""")
306
251
  try:
307
252
  with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
308
253
  if self.sftp is None: # type: ignore[unreachable]
309
254
  raise RuntimeError(f"SFTP connection lost for {self.hostname}")
310
- self.sftp.put(localpath=str(source_obj), remotepath=remotepath.as_posix(), callback=pbar.view_bar) # type: ignore
255
+ self.sftp.put(localpath=str(source_obj), remotepath=str(Path(self.remote_specs["home_dir"]).joinpath(target_rel2home)), callback=pbar.view_bar)
311
256
  except Exception:
312
257
  if compress_with_zip and source_obj.exists() and str(source_obj).endswith("_archive.zip"):
313
258
  source_obj.unlink()
@@ -325,11 +270,14 @@ class SSH:
325
270
  with zipfile.ZipFile(archive_path, "r") as archive_handle:
326
271
  archive_handle.extractall(extraction_directory)
327
272
  archive_path.unlink()
328
- from machineconfig.utils.meta import lambda_to_python_script
329
- command = lambda_to_python_script(lmb=lambda: unzip_archive(zip_file_path=remotepath.as_posix(), overwrite_flag=overwrite_existing), in_global=True)
330
- _resp = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"UNZIPPING {remotepath.as_posix()}", verbose_output=False, strict_stderr=True, strict_return_code=True)
273
+ command = lambda_to_python_script(lmb=lambda: unzip_archive(zip_file_path=str(Path(self.remote_specs["home_dir"]).joinpath(target_rel2home)), overwrite_flag=overwrite_existing), in_global=True)
274
+ tmp_py_file = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/create_target_dir_{randstr()}.py")
275
+ tmp_py_file.parent.mkdir(parents=True, exist_ok=True)
276
+ tmp_py_file.write_text(command, encoding="utf-8")
277
+ transferred_py_file = self.copy_from_here(source_path=str(tmp_py_file), target_rel2home=None, compress_with_zip=True, recursive=False, overwrite_existing=True)
278
+ self.run_shell(command=f"""{UV_RUN_CMD} python {transferred_py_file}""", verbose_output=False, description=f"UNZIPPING {target_rel2home}", strict_stderr=True, strict_return_code=True)
331
279
  source_obj.unlink()
332
- return source_obj
280
+ return None
333
281
 
334
282
  def _check_remote_is_dir(self, source_path: Union[str, Path]) -> bool:
335
283
  """Helper to check if a remote path is a directory."""
@@ -342,16 +290,13 @@ class SSH:
342
290
  json_result_path.write_text(json.dumps(is_directory, indent=2), encoding="utf-8")
343
291
  print(json_result_path.as_posix())
344
292
  return is_directory
345
-
346
- from machineconfig.utils.meta import lambda_to_python_script
347
- from machineconfig.utils.accessories import randstr
348
293
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
349
294
  command = lambda_to_python_script(lmb=lambda: check_is_dir(path_to_check=str(source_path), json_output_path=remote_json_output), in_global=True)
350
295
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Check if source `{source_path}` is a dir", verbose_output=False, strict_stderr=False, strict_return_code=False)
351
296
  remote_json_path = response.op.strip()
352
297
  if not remote_json_path:
353
298
  raise RuntimeError(f"Failed to check if {source_path} is directory - no response from remote")
354
- from machineconfig.utils.accessories import randstr
299
+
355
300
  local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
356
301
  self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
357
302
  import json
@@ -377,15 +322,15 @@ class SSH:
377
322
  print(json_result_path.as_posix())
378
323
  return expanded_path_posix
379
324
 
380
- from machineconfig.utils.meta import lambda_to_python_script
381
- from machineconfig.utils.accessories import randstr
325
+
326
+
382
327
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
383
328
  command = lambda_to_python_script(lmb=lambda: expand_source(path_to_expand=str(source_path), json_output_path=remote_json_output), in_global=True)
384
329
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Resolving source path by expanding user", verbose_output=False, strict_stderr=False, strict_return_code=False)
385
330
  remote_json_path = response.op.strip()
386
331
  if not remote_json_path:
387
332
  raise RuntimeError(f"Could not resolve source path {source_path} - no response from remote")
388
- from machineconfig.utils.accessories import randstr
333
+
389
334
  local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
390
335
  self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
391
336
  import json
@@ -426,15 +371,15 @@ class SSH:
426
371
  print(json_result_path.as_posix())
427
372
  return file_paths_list
428
373
 
429
- from machineconfig.utils.meta import lambda_to_python_script
430
- from machineconfig.utils.accessories import randstr
374
+
375
+
431
376
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
432
377
  command = lambda_to_python_script(lmb=lambda: search_files(directory_path=expanded_source, json_output_path=remote_json_output), in_global=True)
433
378
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Searching for files in source", verbose_output=False, strict_stderr=False, strict_return_code=False)
434
379
  remote_json_path = response.op.strip()
435
380
  if not remote_json_path:
436
381
  raise RuntimeError(f"Could not resolve source path {source} - no response from remote")
437
- from machineconfig.utils.accessories import randstr
382
+
438
383
  local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
439
384
  self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
440
385
  import json
@@ -464,15 +409,15 @@ class SSH:
464
409
  except ValueError:
465
410
  raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
466
411
 
467
- from machineconfig.utils.meta import lambda_to_python_script
468
- from machineconfig.utils.accessories import randstr
412
+
413
+
469
414
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
470
415
  command = lambda_to_python_script(lmb=lambda: collapse_to_home_dir(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True)
471
416
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
472
417
  remote_json_path_dir = response.op.strip()
473
418
  if not remote_json_path_dir:
474
419
  raise RuntimeError("Could not resolve target path - no response from remote")
475
- from machineconfig.utils.accessories import randstr
420
+
476
421
  local_json_dir = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
477
422
  self._simple_sftp_get(remote_path=remote_json_path_dir, local_path=local_json_dir)
478
423
  import json
@@ -516,15 +461,15 @@ class SSH:
516
461
  print(json_result_path.as_posix())
517
462
  return zip_file_path
518
463
 
519
- from machineconfig.utils.meta import lambda_to_python_script
520
- from machineconfig.utils.accessories import randstr
464
+
465
+
521
466
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
522
467
  command = lambda_to_python_script(lmb=lambda: zip_source(path_to_zip=expanded_source, json_output_path=remote_json_output), in_global=True)
523
468
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Zipping source file {source}", verbose_output=False, strict_stderr=False, strict_return_code=False)
524
469
  remote_json_path = response.op.strip()
525
470
  if not remote_json_path:
526
471
  raise RuntimeError(f"Could not zip {source} - no response from remote")
527
- from machineconfig.utils.accessories import randstr
472
+
528
473
  local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
529
474
  self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
530
475
  import json
@@ -555,15 +500,15 @@ class SSH:
555
500
  except ValueError:
556
501
  raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
557
502
 
558
- from machineconfig.utils.meta import lambda_to_python_script
559
- from machineconfig.utils.accessories import randstr
503
+
504
+
560
505
  remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
561
506
  command = lambda_to_python_script(lmb=lambda: collapse_to_home(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True)
562
507
  response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
563
508
  remote_json_path = response.op.strip()
564
509
  if not remote_json_path:
565
510
  raise RuntimeError("Could not resolve target path - no response from remote")
566
- from machineconfig.utils.accessories import randstr
511
+
567
512
  local_json = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/local_{randstr()}.json")
568
513
  self._simple_sftp_get(remote_path=remote_json_path, local_path=local_json)
569
514
  import json
@@ -613,10 +558,12 @@ class SSH:
613
558
  else:
614
559
  file_or_dir_path.unlink()
615
560
 
616
- from machineconfig.utils.meta import lambda_to_python_script
561
+
617
562
  command = lambda_to_python_script(lmb=lambda: delete_temp_zip(path_to_delete=expanded_source), in_global=True)
618
563
  self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Cleaning temp zip files @ remote.", verbose_output=False, strict_stderr=True, strict_return_code=True)
619
564
 
620
565
  print("\n")
621
566
  return target_obj
622
567
 
568
+ if __name__ == "__main__":
569
+ ssh = SSH(host="p51s", username=None, hostname=None, ssh_key_path=None, password=None, port=22, enable_compression=False)
@@ -35,7 +35,8 @@ class Response:
35
35
  def __call__(self, *args: Any, **kwargs: Any) -> Optional[str]:
36
36
  _ = args, kwargs
37
37
  return self.op.rstrip() if type(self.op) is str else None
38
-
38
+ def __repr__(self) -> str:
39
+ return f"Response({self.input=}, {self.output=}, {self.desc=}, {self.op=}, {self.ip=}, {self.err=}, {self.returncode=})"
39
40
  @property
40
41
  def op(self) -> str:
41
42
  return self.output.stdout
@@ -97,115 +98,3 @@ class Response:
97
98
  txt = tmp1 + Text(str(self.input), style="white") + tmp2 + Text("\n".join(list_str), style="white")
98
99
  con.print(Panel(txt, title=f"🖥️ {self.desc}", subtitle=f"📋 {desc}", width=150, style="bold cyan on black"))
99
100
  return self
100
-
101
-
102
- '''
103
- class Terminal:
104
- def __init__(self, stdout: Optional[int] = subprocess.PIPE, stderr: Optional[int] = subprocess.PIPE, stdin: Optional[int] = subprocess.PIPE, elevated: bool = False):
105
- self.machine: str = platform.system()
106
- self.elevated: bool = elevated
107
- self.stdout = stdout
108
- self.stderr = stderr
109
- self.stdin = stdin
110
-
111
- # def set_std_system(self): self.stdout = sys.stdout; self.stderr = sys.stderr; self.stdin = sys.stdin
112
- # def set_std_pipe(self):
113
- # self.stdout = subprocess.PIPE
114
- # self.stderr = subprocess.PIPE
115
- # self.stdin = subprocess.PIPE
116
-
117
- # def set_std_null(self):
118
- # self.stdout, self.stderr, self.stdin = subprocess.DEVNULL, subprocess.DEVNULL, subprocess.DEVNULL # Equivalent to `echo 'foo' &> /dev/null`
119
-
120
- def run(self, *cmds: str, shell: Optional[SHELLS] = "default", check: bool = False, ip: Optional[str] = None) -> Response: # Runs SYSTEM commands like subprocess.run
121
- """Blocking operation. Thus, if you start a shell via this method, it will run in the main and won't stop until you exit manually IF stdin is set to sys.stdin, otherwise it will run and close quickly. Other combinations of stdin, stdout can lead to funny behaviour like no output but accept input or opposite.
122
- * This method is short for: res = subprocess.run("powershell command", capture_output=True, shell=True, text=True) and unlike os.system(cmd), subprocess.run(cmd) gives much more control over the output and input.
123
- * `shell=True` loads up the profile of the shell called so more specific commands can be run. Importantly, on Windows, the `start` command becomes availalbe and new windows can be launched.
124
- * `capture_output` prevents the stdout to redirect to the stdout of the script automatically, instead it will be stored in the Response object returned. # `capture_output=True` same as `stdout=subprocess.PIPE, stderr=subprocess.PIPE`"""
125
- my_list = list(
126
- cmds
127
- ) # `subprocess.Popen` (process open) is the most general command. Used here to create asynchronous job. `subprocess.run` is a thin wrapper around Popen that makes it wait until it finishes the task. `suprocess.call` is an archaic command for pre-Python-3.5.
128
- if self.machine == "Windows" and shell in {"powershell", "pwsh"}:
129
- my_list = [shell, "-Command"] + my_list # alternatively, one can run "cmd"
130
- if self.elevated is False or self.is_user_admin():
131
- resp: subprocess.CompletedProcess[str] = subprocess.run(my_list, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=check, input=ip)
132
- else:
133
- resp = __import__("ctypes").windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
134
- return Response.from_completed_process(resp)
135
- @staticmethod
136
- def is_user_admin() -> bool: # adopted from: https://stackoverflow.com/questions/19672352/how-to-run-script-with-elevated-privilege-on-windows"""
137
- if os.name == "nt":
138
- try:
139
- return __import__("ctypes").windll.shell32.IsUserAnAdmin()
140
- except Exception:
141
- import traceback
142
-
143
- traceback.print_exc()
144
- print("Admin check failed, assuming not an admin.")
145
- return False
146
- else:
147
- return os.getuid() == 0 # Check for root on Posix
148
- '''
149
-
150
- # def run_shell_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
151
- # if self.machine == "Linux":
152
- # script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
153
- # script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts")
154
- # script_file.write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
155
- # if shell == "default":
156
- # if self.machine == "Windows":
157
- # start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
158
- # full_command: Union[list[str], str] = [start_cmd, str(script_file)] # shell=True will cause this to be a string anyway (with space separation)
159
- # else:
160
- # start_cmd = "bash"
161
- # full_command = f"{start_cmd} {script_file}" # full_command = [start_cmd, str(script_file)]
162
- # else:
163
- # full_command = f"{shell} {script_file}" # full_command = [shell, str(tmp_file)]
164
- # if verbose:
165
- # desc = "Script to be executed:"
166
- # if platform.system() == "Windows":
167
- # lexer = "powershell"
168
- # elif platform.system() == "Linux":
169
- # lexer = "sh"
170
- # elif platform.system() == "Darwin":
171
- # lexer = "sh" # macOS uses similar shell to Linux
172
- # else:
173
- # raise NotImplementedError(f"Platform {platform.system()} not supported.")
174
- # from rich.console import Console
175
- # from rich.panel import Panel
176
- # from rich.syntax import Syntax
177
- # import rich.progress as pb
178
-
179
- # console = Console()
180
- # console.print(Panel(Syntax(code=script, lexer=lexer), title=f"📄 {desc}"), style="bold red")
181
- # with pb.Progress(transient=True) as progress:
182
- # _task = progress.add_task(f"Running Script @ {script_file}", total=None)
183
- # resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
184
- # else:
185
- # resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
186
- # return Response.from_completed_process(resp)
187
-
188
- # def run_py(self, script: str, wdir: OPLike = None, interactive: bool = True, ipython: bool = True, shell: Optional[str] = None, terminal: str = "", new_window: bool = True, header: bool = True): # async run, since sync run is meaningless.
189
- # script = (Terminal.get_header(wdir=wdir, toolbox=True) if header else "") + script + ("\nDisplayData.set_pandas_auto_width()\n" if terminal in {"wt", "powershell", "pwsh"} else "")
190
- # py_script = PathExtended.tmpfile(name="tmp_python_script", suffix=".py", folder="tmp_scripts/terminal")
191
- # py_script.write_text(f"""print(r'''{script}''')""" + "\n" + script)
192
- # print(f"""🚀 [ASYNC PYTHON SCRIPT] Script URI:
193
- # {py_script.absolute().as_uri()}""")
194
- # print("Script to be executed asyncronously: ", py_script.absolute().as_uri())
195
- # shell_script = f"""
196
- # {f"cd {wdir}" if wdir is not None else ""}
197
- # {"ipython" if ipython else "python"} {"-i" if interactive else ""} {py_script}
198
- # """
199
- # shell_path = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell")
200
- # shell_path.write_text(shell_script)
201
- # if shell is None and self.machine == "Windows":
202
- # shell = "pwsh"
203
- # window = "start" if new_window and self.machine == "Windows" else ""
204
- # os.system(f"{window} {terminal} {shell} {shell_script}")
205
-
206
- # @staticmethod
207
- # def run_as_admin(file: PLike, params: Any, wait: bool = False):
208
- # proce_info = install_n_import(library="win32com", package="pywin32", fromlist=["shell.shell.ShellExecuteEx"]).shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=file, lpParameters=params)
209
- # # TODO update PATH for this to take effect immediately.
210
- # if wait: time.sleep(1)
211
- # return proce_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 6.61
3
+ Version: 6.63
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -127,10 +127,10 @@ machineconfig/scripts/python/devops.py,sha256=Lv4d-UlyOREj4VTcu_pxswYo54Mawe3XGe
127
127
  machineconfig/scripts/python/devops_navigator.py,sha256=5Cm384D4S8_GsvMzTwr0C16D0ktf8_5Mk5bEJncwDO8,237
128
128
  machineconfig/scripts/python/entry.py,sha256=liCf186Msa6R-EjcQ0I6K80Km7Wi8qckJB6rXIHeHU0,2488
129
129
  machineconfig/scripts/python/fire_jobs.py,sha256=My7sFn1R2vh21uIHGfNppgX99WTEitCFgJ1MSasBUOQ,13597
130
- machineconfig/scripts/python/ftpx.py,sha256=vm4QNJA0z1Vu-85wFliGNoDHMZZ-Yy8zQACL6x7Wo6U,9760
130
+ machineconfig/scripts/python/ftpx.py,sha256=A13hL_tDYfcsaK9PkshK-0lrUS6KPhPCtwqWtLSo6IM,9764
131
131
  machineconfig/scripts/python/interactive.py,sha256=zt3g6nGKR_Y5A57UnR4Y5-JpLzrpnCOSaqU1bnaikK0,11666
132
132
  machineconfig/scripts/python/sessions.py,sha256=UERxO472EDtN7nKHEULbn6G3S5PJIpsDG9Gq3TlByqI,9823
133
- machineconfig/scripts/python/utils.py,sha256=rlrTomPlvhzaMf2UWzVeyO_kTwNWFj1iASXIF2g9cx8,710
133
+ machineconfig/scripts/python/utils.py,sha256=c9HsKG40i5ggwqKuS3O-LCScuFpmxMVKqWpsFHx2dJc,934
134
134
  machineconfig/scripts/python/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
135
  machineconfig/scripts/python/ai/generate_files.py,sha256=VfjKdwgF8O6E4oiRtfWNliibLmmwGe7f9ld6wpOsXTw,14498
136
136
  machineconfig/scripts/python/ai/initai.py,sha256=9SZtWOcRuwk8ZU3wHOfPzjInERD79ZTYFY8tVACgza4,2260
@@ -187,7 +187,7 @@ machineconfig/scripts/python/helpers_devops/cli_repos.py,sha256=Xwkv1adqHZvTfRSP
187
187
  machineconfig/scripts/python/helpers_devops/cli_self.py,sha256=0-R7YDIQS2cH2n9C7lShFoBRwbytQz3BCuHGQrus7FU,6225
188
188
  machineconfig/scripts/python/helpers_devops/cli_share_server.py,sha256=q9pFJ6AxPuygMr3onMNOKEuuQHbVE_6Qoyo7xRT5FX0,4196
189
189
  machineconfig/scripts/python/helpers_devops/cli_terminal.py,sha256=k_PzXaiGyE0vXr0Ii1XcJz2A7UvyPJrR31TRWt4RKRI,6019
190
- machineconfig/scripts/python/helpers_devops/cli_utils.py,sha256=sCYKR6bXzr6DgVnQrCa_pjKdc7tOOY3T8h1A41UURBU,5066
190
+ machineconfig/scripts/python/helpers_devops/cli_utils.py,sha256=jN_GeZyy-9tAJoBoASsmn9_tI7ZgkgXp7Ijgi2x_ssw,6053
191
191
  machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py,sha256=Dn8luB6QJzxKiiFSC-NMqiYddWZoca3A8eOjMYZDzTc,5598
192
192
  machineconfig/scripts/python/helpers_devops/devops_status.py,sha256=PJVPhfhXq8der6Xd-_fjZfnizfM-RGfJApkRGhGBmNo,20525
193
193
  machineconfig/scripts/python/helpers_devops/devops_update_repos.py,sha256=kSln8_-Wn7Qu0NaKdt-QTN_bBVyTIAWHH8xVYKK-vCM,10133
@@ -407,8 +407,8 @@ machineconfig/utils/procs.py,sha256=YPA_vEYQGwPd_o_Lc6nOTBo5BrB1tSs8PJ42XiGpenM,
407
407
  machineconfig/utils/scheduler.py,sha256=44CASABJg3epccxhAwv2CX7TVgZh6zVy3K4vqHKTuf4,14228
408
408
  machineconfig/utils/scheduling.py,sha256=6x5zLA7sY5gohrEtN6zGrXIqNFasMoyBfwLcOjrjiME,11109
409
409
  machineconfig/utils/source_of_truth.py,sha256=ZAnCRltiM07ig--P6g9_6nEAvNFC4X4ERFTVcvpIYsE,764
410
- machineconfig/utils/ssh.py,sha256=R2yOmz1HXwPpW1XlhzOZbneu3xejjoUoAIT1QnQ-ecA,39382
411
- machineconfig/utils/terminal.py,sha256=IlmOByfQG-vjhaFFxxzU5rWzP5_qUzmalRfuey3PAmc,11801
410
+ machineconfig/utils/ssh.py,sha256=Y_n9KKf40VvKAT677B8bMNB0Qs1uIivcFlIQ9xuqo5o,36001
411
+ machineconfig/utils/terminal.py,sha256=VDgsjTjBmMGgZN0YIc0pJ8YksLDrBtiXON1EThy7_is,4264
412
412
  machineconfig/utils/tst.py,sha256=6u1GI49NdcpxH2BYGAusNfY5q9G_ytCGVzFM5b6HYpM,674
413
413
  machineconfig/utils/upgrade_packages.py,sha256=mSFyKvB3JhHte_x1dtmEgrJZCAXgTUQoaJUSx1OXQ3Y,4145
414
414
  machineconfig/utils/ve.py,sha256=L-6PBXnQGXThiwWgheJMQoisAZOZA6SVCbGw2J-GFnI,2414
@@ -436,8 +436,8 @@ machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoS
436
436
  machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
437
437
  machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
438
438
  machineconfig/utils/ssh_utils/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
439
- machineconfig-6.61.dist-info/METADATA,sha256=W4hUI9UB2KnHVsMoV1OKaTStImXnD7iWzZ2tIkJGucs,2928
440
- machineconfig-6.61.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
441
- machineconfig-6.61.dist-info/entry_points.txt,sha256=NTW7hbUlpt5Vx9DdQrONLkYMCuBXpvYh1dt0AtlGxeI,466
442
- machineconfig-6.61.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
443
- machineconfig-6.61.dist-info/RECORD,,
439
+ machineconfig-6.63.dist-info/METADATA,sha256=i_pMCNUzi-v7pZWWUl96i4R5aU1H6JSwVRZr9A3-LbQ,2928
440
+ machineconfig-6.63.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
441
+ machineconfig-6.63.dist-info/entry_points.txt,sha256=NTW7hbUlpt5Vx9DdQrONLkYMCuBXpvYh1dt0AtlGxeI,466
442
+ machineconfig-6.63.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
443
+ machineconfig-6.63.dist-info/RECORD,,