machineconfig 5.25__py3-none-any.whl → 5.26__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/jobs/installer/custom/hx.py +1 -1
- machineconfig/jobs/installer/custom_dev/alacritty.py +4 -4
- machineconfig/jobs/installer/linux_scripts/wezterm.sh +1 -1
- machineconfig/scripts/python/agents.py +4 -4
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/{devops_update_repos.py → devops_helpers/devops_update_repos.py} +1 -1
- machineconfig/scripts/python/fire_jobs.py +3 -3
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +14 -1
- machineconfig/scripts/python/helpers_fire/__init__.py +0 -0
- machineconfig/scripts/python/{fire_agents_help_launch.py → helpers_fire/fire_agents_help_launch.py} +1 -1
- machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
- machineconfig/scripts/python/helpers_repos/grource.py +341 -0
- machineconfig/scripts/python/interactive.py +1 -1
- machineconfig/scripts/python/repos.py +75 -73
- machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py} +1 -1
- machineconfig/scripts/python/{repos_helper.py → repos_helpers/repos_helper.py} +4 -12
- machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py} +1 -1
- machineconfig/scripts/python/sessions_multiprocess.py +1 -1
- machineconfig/utils/files/ouch/__init__.py +0 -0
- machineconfig/utils/files/ouch/decompress.py +45 -0
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/source_of_truth.py +0 -1
- machineconfig/utils/ssh.py +33 -19
- {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/METADATA +3 -1
- {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/RECORD +46 -42
- machineconfig/scripts/python/get_zellij_cmd.py +0 -15
- machineconfig/scripts/python/t4.py +0 -17
- /machineconfig/jobs/{python → installer}/check_installations.py +0 -0
- /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → devops_helpers/__init__.py} +0 -0
- /machineconfig/scripts/python/{devops_add_identity.py → devops_helpers/devops_add_identity.py} +0 -0
- /machineconfig/scripts/python/{devops_add_ssh_key.py → devops_helpers/devops_add_ssh_key.py} +0 -0
- /machineconfig/scripts/python/{devops_backup_retrieve.py → devops_helpers/devops_backup_retrieve.py} +0 -0
- /machineconfig/scripts/python/{devops_status.py → devops_helpers/devops_status.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_help_search.py → helpers_fire/fire_agents_help_search.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_helper_types.py → helpers_fire/fire_agents_helper_types.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_load_balancer.py → helpers_fire/fire_agents_load_balancer.py} +0 -0
- /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
- /machineconfig/scripts/python/{fire_jobs_args_helper.py → helpers_fire_command/fire_jobs_args_helper.py} +0 -0
- /machineconfig/scripts/python/{fire_jobs_route_helper.py → helpers_fire_command/fire_jobs_route_helper.py} +0 -0
- /machineconfig/scripts/python/{count_lines.py → repos_helpers/count_lines.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_clone.py → repos_helpers/repos_helper_clone.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_record.py → repos_helpers/repos_helper_record.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_update.py → repos_helpers/repos_helper_update.py} +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/WHEEL +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/top_level.txt +0 -0
|
@@ -5,128 +5,130 @@ in the event that username@github.com is not mentioned in the remote url.
|
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
from typing import Annotated, Optional
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
app = typer.Typer(help="� Manage development repositories", no_args_is_help=True)
|
|
15
14
|
sync_app = typer.Typer(help="� Manage repository specifications and syncing", no_args_is_help=True)
|
|
16
15
|
app.add_typer(sync_app, name="sync", help="� Sync repositories using saved specs")
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
DirectoryArgument = Annotated[
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]
|
|
23
|
-
RecursiveOption = Annotated[
|
|
24
|
-
bool,
|
|
25
|
-
typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories."),
|
|
26
|
-
]
|
|
27
|
-
NoSyncOption = Annotated[
|
|
28
|
-
bool,
|
|
29
|
-
typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls."),
|
|
30
|
-
]
|
|
31
|
-
CloudOption = Annotated[
|
|
32
|
-
Optional[str],
|
|
33
|
-
typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote."),
|
|
34
|
-
]
|
|
35
|
-
|
|
18
|
+
DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Folder containing repos or the specs JSON file to use.")]
|
|
19
|
+
RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
|
|
20
|
+
NoSyncOption = Annotated[bool, typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls.")]
|
|
21
|
+
CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
|
|
36
22
|
|
|
37
23
|
|
|
38
24
|
@app.command(no_args_is_help=True)
|
|
39
|
-
def push(directory: DirectoryArgument = None,
|
|
40
|
-
recursive: RecursiveOption = False,
|
|
41
|
-
no_sync: NoSyncOption = False,
|
|
42
|
-
) -> None:
|
|
25
|
+
def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
43
26
|
"""🚀 Push changes across repositories."""
|
|
44
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
27
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
28
|
+
|
|
45
29
|
git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, no_sync=no_sync)
|
|
30
|
+
|
|
31
|
+
|
|
46
32
|
@app.command(no_args_is_help=True)
|
|
47
|
-
def pull(
|
|
48
|
-
directory: DirectoryArgument = None,
|
|
49
|
-
recursive: RecursiveOption = False,
|
|
50
|
-
no_sync: NoSyncOption = False,
|
|
51
|
-
) -> None:
|
|
33
|
+
def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
52
34
|
"""⬇️ Pull changes across repositories."""
|
|
53
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
35
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
36
|
+
|
|
54
37
|
git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, no_sync=no_sync)
|
|
38
|
+
|
|
39
|
+
|
|
55
40
|
@app.command(no_args_is_help=True)
|
|
56
|
-
def commit(
|
|
57
|
-
directory: DirectoryArgument = None,
|
|
58
|
-
recursive: RecursiveOption = False,
|
|
59
|
-
no_sync: NoSyncOption = False,
|
|
60
|
-
) -> None:
|
|
41
|
+
def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
61
42
|
"""💾 Commit changes across repositories."""
|
|
62
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
43
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
44
|
+
|
|
63
45
|
git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, no_sync=no_sync)
|
|
46
|
+
|
|
47
|
+
|
|
64
48
|
@app.command(no_args_is_help=True)
|
|
65
|
-
def cleanup(
|
|
66
|
-
directory: DirectoryArgument = None,
|
|
67
|
-
recursive: RecursiveOption = False,
|
|
68
|
-
no_sync: NoSyncOption = False,
|
|
69
|
-
) -> None:
|
|
49
|
+
def cleanup(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
70
50
|
"""🔄 Pull, commit, and push changes across repositories."""
|
|
71
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
51
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
52
|
+
|
|
72
53
|
git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, no_sync=no_sync)
|
|
73
54
|
|
|
74
55
|
|
|
75
56
|
@sync_app.command(no_args_is_help=True)
|
|
76
|
-
def capture(
|
|
77
|
-
directory: DirectoryArgument = None,
|
|
78
|
-
cloud: CloudOption = None,
|
|
79
|
-
) -> None:
|
|
57
|
+
def capture(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
80
58
|
"""📝 Record repositories into a repos.json specification."""
|
|
81
|
-
from machineconfig.scripts.python.repos_helper import
|
|
82
|
-
print_banner()
|
|
59
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import resolve_directory
|
|
83
60
|
repos_root = resolve_directory(directory)
|
|
84
|
-
from machineconfig.scripts.python.repos_helper_record import main as record_repos
|
|
61
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_record import main as record_repos
|
|
62
|
+
|
|
85
63
|
save_path = record_repos(repos_root=repos_root)
|
|
86
64
|
from machineconfig.utils.path_extended import PathExtended
|
|
65
|
+
|
|
87
66
|
if cloud is not None:
|
|
88
67
|
PathExtended(save_path).to_cloud(rel2home=True, cloud=cloud)
|
|
68
|
+
|
|
69
|
+
|
|
89
70
|
@sync_app.command(no_args_is_help=True)
|
|
90
|
-
def clone(
|
|
91
|
-
directory: DirectoryArgument = None,
|
|
92
|
-
cloud: CloudOption = None,
|
|
93
|
-
) -> None:
|
|
71
|
+
def clone(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
94
72
|
"""📥 Clone repositories described by a repos.json specification."""
|
|
95
|
-
from machineconfig.scripts.python.repos_helper import
|
|
96
|
-
|
|
73
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
74
|
+
|
|
75
|
+
|
|
97
76
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=False)
|
|
98
77
|
|
|
99
78
|
|
|
100
79
|
@sync_app.command(name="checkout-to-commit", no_args_is_help=True)
|
|
101
|
-
def checkout_command(
|
|
102
|
-
directory: DirectoryArgument = None,
|
|
103
|
-
cloud: CloudOption = None,
|
|
104
|
-
) -> None:
|
|
80
|
+
def checkout_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
105
81
|
"""🔀 Check out specific commits listed in the specification."""
|
|
106
|
-
from machineconfig.scripts.python.repos_helper import
|
|
107
|
-
|
|
82
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
83
|
+
|
|
84
|
+
|
|
108
85
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=True)
|
|
109
86
|
|
|
110
87
|
|
|
111
88
|
@sync_app.command(name="checkout-to-branch", no_args_is_help=True)
|
|
112
|
-
def checkout_to_branch_command(
|
|
113
|
-
directory: DirectoryArgument = None,
|
|
114
|
-
cloud: CloudOption = None,
|
|
115
|
-
) -> None:
|
|
89
|
+
def checkout_to_branch_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
116
90
|
"""🔀 Check out to the main branch defined in the specification."""
|
|
117
|
-
from machineconfig.scripts.python.repos_helper import
|
|
118
|
-
print_banner()
|
|
91
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
119
92
|
clone_from_specs(directory, cloud, checkout_branch_flag=True, checkout_commit_flag=False)
|
|
120
93
|
|
|
121
94
|
|
|
122
95
|
@app.command(no_args_is_help=True)
|
|
123
|
-
def analyze(
|
|
124
|
-
directory: DirectoryArgument = None,
|
|
125
|
-
) -> None:
|
|
96
|
+
def analyze(directory: DirectoryArgument = None) -> None:
|
|
126
97
|
"""📊 Analyze repository development over time."""
|
|
127
|
-
from machineconfig.scripts.python.repos_helper import print_banner
|
|
128
|
-
print_banner()
|
|
129
98
|
repo_path = directory if directory is not None else "."
|
|
130
|
-
from machineconfig.scripts.python.count_lines_frontend import analyze_repo_development
|
|
99
|
+
from machineconfig.scripts.python.repos_helpers.count_lines_frontend import analyze_repo_development
|
|
100
|
+
|
|
131
101
|
analyze_repo_development(repo_path=repo_path)
|
|
132
102
|
|
|
103
|
+
|
|
104
|
+
@app.command(no_args_is_help=True)
|
|
105
|
+
def viz(
|
|
106
|
+
repo: str = typer.Option(Path.cwd().__str__(), "--repo", "-r", help="Path to git repository to visualize"),
|
|
107
|
+
output_file: Optional[Path] = typer.Option(None, "--output", "-o", help="Output video file (e.g., output.mp4). If specified, gource will render to video."),
|
|
108
|
+
resolution: str = typer.Option("1920x1080", "--resolution", "-res", help="Video resolution (e.g., 1920x1080, 1280x720)"),
|
|
109
|
+
seconds_per_day: float = typer.Option(0.1, "--seconds-per-day", "-spd", help="Speed of simulation (lower = faster)"),
|
|
110
|
+
auto_skip_seconds: float = typer.Option(1.0, "--auto-skip-seconds", "-as", help="Skip to next entry if nothing happens for X seconds"),
|
|
111
|
+
title: Optional[str] = typer.Option(None, "--title", "-t", help="Title for the visualization"),
|
|
112
|
+
hide_items: list[str] = typer.Option([], "--hide", "-h", help="Items to hide: bloom, date, dirnames, files, filenames, mouse, progress, root, tree, users, usernames"),
|
|
113
|
+
key_items: bool = typer.Option(False, "--key", "-k", help="Show file extension key"),
|
|
114
|
+
fullscreen: bool = typer.Option(False, "--fullscreen", "-f", help="Run in fullscreen mode"),
|
|
115
|
+
viewport: Optional[str] = typer.Option(None, "--viewport", "-v", help="Camera viewport (e.g., '1000x1000')"),
|
|
116
|
+
start_date: Optional[str] = typer.Option(None, "--start-date", help="Start date (YYYY-MM-DD)"),
|
|
117
|
+
stop_date: Optional[str] = typer.Option(None, "--stop-date", help="Stop date (YYYY-MM-DD)"),
|
|
118
|
+
user_image_dir: Optional[Path] = typer.Option(None, "--user-image-dir", help="Directory with user avatar images"),
|
|
119
|
+
max_files: int = typer.Option(0, "--max-files", help="Maximum number of files to show (0 = no limit)"),
|
|
120
|
+
max_file_lag: float = typer.Option(5.0, "--max-file-lag", help="Max time files remain on screen after last change"),
|
|
121
|
+
file_idle_time: int = typer.Option(0, "--file-idle-time", help="Time in seconds files remain idle before being removed"),
|
|
122
|
+
framerate: int = typer.Option(60, "--framerate", help="Frames per second for video output"),
|
|
123
|
+
background_color: str = typer.Option("000000", "--background-color", help="Background color in hex (e.g., 000000 for black)"),
|
|
124
|
+
font_size: int = typer.Option(22, "--font-size", help="Font size"),
|
|
125
|
+
camera_mode: str = typer.Option("overview", "--camera-mode", help="Camera mode: overview or track"),
|
|
126
|
+
) -> None:
|
|
127
|
+
"""🎬 Visualize repository activity using Gource."""
|
|
128
|
+
from machineconfig.scripts.python.helpers_repos.grource import visualize
|
|
129
|
+
visualize(repo=repo, output_file=output_file, resolution=resolution, seconds_per_day=seconds_per_day,
|
|
130
|
+
auto_skip_seconds=auto_skip_seconds, title=title, hide_items=hide_items, key_items=key_items,
|
|
131
|
+
fullscreen=fullscreen, viewport=viewport, start_date=start_date, stop_date=stop_date,
|
|
132
|
+
user_image_dir=user_image_dir, max_files=max_files, max_file_lag=max_file_lag,
|
|
133
|
+
file_idle_time=file_idle_time, framerate=framerate, background_color=background_color,
|
|
134
|
+
font_size=font_size, camera_mode=camera_mode)
|
machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py}
RENAMED
|
@@ -3,7 +3,7 @@ import typer
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def analyze_repo_development(repo_path: str = typer.Argument(..., help="Path to the git repository")):
|
|
6
|
-
from machineconfig.scripts.python import count_lines
|
|
6
|
+
from machineconfig.scripts.python.repos_helpers import count_lines
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
count_lines_path = Path(count_lines.__file__)
|
|
9
9
|
# --project $HOME/code/machineconfig --group plot
|
|
@@ -8,14 +8,6 @@ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
def print_banner() -> None:
|
|
13
|
-
typer.echo("\n" + "=" * 50)
|
|
14
|
-
typer.echo("📂 Welcome to the Repository Manager")
|
|
15
|
-
typer.echo("=" * 50 + "\n")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
11
|
def resolve_directory(directory: Optional[str]) -> Path:
|
|
20
12
|
if directory is None:
|
|
21
13
|
directory = Path.cwd().as_posix()
|
|
@@ -30,10 +22,10 @@ def git_operations(
|
|
|
30
22
|
recursive: bool,
|
|
31
23
|
no_sync: bool,
|
|
32
24
|
) -> None:
|
|
33
|
-
|
|
25
|
+
|
|
34
26
|
repos_root = resolve_directory(directory)
|
|
35
27
|
auto_sync = not no_sync
|
|
36
|
-
from machineconfig.scripts.python.repos_helper_action import perform_git_operations
|
|
28
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_action import perform_git_operations
|
|
37
29
|
from machineconfig.utils.path_extended import PathExtended
|
|
38
30
|
perform_git_operations(
|
|
39
31
|
repos_root=PathExtended(repos_root),
|
|
@@ -73,10 +65,10 @@ def clone_from_specs(
|
|
|
73
65
|
checkout_branch_flag: bool,
|
|
74
66
|
checkout_commit_flag: bool,
|
|
75
67
|
) -> None:
|
|
76
|
-
|
|
68
|
+
|
|
77
69
|
typer.echo("\n📥 Cloning or checking out repositories...")
|
|
78
70
|
spec_path = resolve_spec_path(directory, cloud)
|
|
79
|
-
from machineconfig.scripts.python.repos_helper_clone import clone_repos
|
|
71
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_clone import clone_repos
|
|
80
72
|
clone_repos(
|
|
81
73
|
spec_path=spec_path,
|
|
82
74
|
preferred_remote=None,
|
machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from machineconfig.utils.path_extended import PathExtended
|
|
2
2
|
from machineconfig.utils.accessories import randstr
|
|
3
|
-
from machineconfig.scripts.python.repos_helper_update import update_repository
|
|
3
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_update import update_repository
|
|
4
4
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
from dataclasses import dataclass
|
|
@@ -41,7 +41,7 @@ def create_from_function(
|
|
|
41
41
|
|
|
42
42
|
# ========================= choosing function to run
|
|
43
43
|
if function is None or function.strip() == "":
|
|
44
|
-
from machineconfig.scripts.python.fire_jobs_route_helper import choose_function_or_lines
|
|
44
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
|
|
45
45
|
choice_function, choice_file, _kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict={})
|
|
46
46
|
else:
|
|
47
47
|
choice_function = function
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zipfile import ZipFile, BadZipFile
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def decompress_and_remove_zip(zip_path: Union[str, Path]) -> None:
|
|
9
|
+
"""
|
|
10
|
+
Decompress a ZIP file and remove it after extraction.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
zip_path (Union[str, Path]): Path to the ZIP file.
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
FileNotFoundError: If the zip file does not exist.
|
|
17
|
+
BadZipFile: If the file is not a valid ZIP archive.
|
|
18
|
+
PermissionError: If the file cannot be deleted due to permission issues.
|
|
19
|
+
Exception: For other unexpected errors.
|
|
20
|
+
"""
|
|
21
|
+
zip_path = Path(zip_path)
|
|
22
|
+
|
|
23
|
+
if not zip_path.exists():
|
|
24
|
+
raise FileNotFoundError(f"The file '{zip_path}' does not exist.")
|
|
25
|
+
|
|
26
|
+
if not zip_path.is_file():
|
|
27
|
+
raise FileNotFoundError(f"The path '{zip_path}' is not a file.")
|
|
28
|
+
|
|
29
|
+
extract_dir = zip_path.parent
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with ZipFile(zip_path, 'r') as zip_ref:
|
|
33
|
+
zip_ref.extractall(extract_dir)
|
|
34
|
+
except BadZipFile as e:
|
|
35
|
+
raise BadZipFile(f"The file '{zip_path}' is not a valid zip archive.") from e
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise Exception(f"Failed to extract '{zip_path}': {e}") from e
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
zip_path.unlink()
|
|
41
|
+
except PermissionError as e:
|
|
42
|
+
raise PermissionError(f"Permission denied when deleting '{zip_path}'.") from e
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise Exception(f"Failed to delete '{zip_path}': {e}") from e
|
|
45
|
+
|
|
@@ -6,7 +6,7 @@ capturing all user inputs collected during interactive execution.
|
|
|
6
6
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TypedDict, Literal, NotRequired
|
|
9
|
-
from machineconfig.scripts.python.fire_agents_help_launch import AGENTS
|
|
9
|
+
from machineconfig.scripts.python.helpers_fire.fire_agents_help_launch import AGENTS
|
|
10
10
|
|
|
11
11
|
SEARCH_STRATEGIES = Literal["file_path", "keyword_search", "filename_pattern"]
|
|
12
12
|
|
|
@@ -17,7 +17,6 @@ INSTALL_VERSION_ROOT = CONFIG_PATH.joinpath("cli_tools_installers/versions")
|
|
|
17
17
|
INSTALL_TMP_DIR = Path.home().joinpath("tmp_results", "tmp_installers")
|
|
18
18
|
|
|
19
19
|
# LINUX_INSTALL_PATH = '/usr/local/bin'
|
|
20
|
-
# LINUX_INSTALL_PATH = '~/.local/bin'
|
|
21
20
|
LINUX_INSTALL_PATH = Path.home().joinpath(".local/bin").__str__()
|
|
22
21
|
WINDOWS_INSTALL_PATH = Path.home().joinpath("AppData/Local/Microsoft/WindowsApps").__str__()
|
|
23
22
|
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -8,12 +8,7 @@ from machineconfig.utils.accessories import pprint
|
|
|
8
8
|
# from machineconfig.utils.ve import get_ve_activate_line
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def get_header(wdir:
|
|
12
|
-
return f"""
|
|
13
|
-
# >> Code prepended
|
|
14
|
-
{'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
|
|
15
|
-
# >> End of header, start of script passed
|
|
16
|
-
"""
|
|
11
|
+
def get_header(wdir: str): return f"""import sys; sys.path.insert(0, r'{wdir}')"""
|
|
17
12
|
|
|
18
13
|
|
|
19
14
|
@dataclass
|
|
@@ -165,7 +160,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
165
160
|
if self._local_distro is None:
|
|
166
161
|
command = """uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """
|
|
167
162
|
import subprocess
|
|
168
|
-
|
|
169
163
|
res = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
|
|
170
164
|
self._local_distro = res
|
|
171
165
|
return res
|
|
@@ -173,7 +167,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
173
167
|
|
|
174
168
|
def get_remote_distro(self):
|
|
175
169
|
if self._remote_distro is None:
|
|
176
|
-
res = self.run("""
|
|
170
|
+
res = self.run("""$HOME/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
|
|
177
171
|
self._remote_distro = res.op_if_successfull_or_default() or ""
|
|
178
172
|
return self._remote_distro
|
|
179
173
|
|
|
@@ -225,17 +219,22 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
225
219
|
assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
|
|
226
220
|
if not return_obj:
|
|
227
221
|
return self.run(
|
|
228
|
-
cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{
|
|
222
|
+
cmd=f"""$HOME/.local/bin/uv run --no-dev --project $HOME/code/machineconfig python -c "{cmd}\n""" + '"',
|
|
229
223
|
desc=desc or f"run_py on {self.get_remote_repr()}",
|
|
230
224
|
verbose=verbose,
|
|
231
225
|
strict_err=strict_err,
|
|
232
226
|
strict_returncode=strict_returncode,
|
|
233
227
|
)
|
|
234
228
|
assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
|
|
235
|
-
source_file = self.run_py(f"""{cmd}
|
|
229
|
+
source_file = self.run_py(f"""{cmd}
|
|
230
|
+
import pickle
|
|
231
|
+
import tempfile
|
|
232
|
+
from pathlib import Path
|
|
233
|
+
path = tempfile.emkstemp(suffix=".pkl")[1]
|
|
234
|
+
Path(path).write_bytes(pickle.dumps(obj))
|
|
235
|
+
print(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
|
|
236
236
|
res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
|
|
237
237
|
import pickle
|
|
238
|
-
|
|
239
238
|
return pickle.loads(res.read_bytes())
|
|
240
239
|
|
|
241
240
|
def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[PathExtended, list[PathExtended]]:
|
|
@@ -255,7 +254,11 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
255
254
|
source_list: list[PathExtended] = source_obj.search("*", folders=False, files=True, r=True)
|
|
256
255
|
remote_root = (
|
|
257
256
|
self.run_py(
|
|
258
|
-
f"
|
|
257
|
+
f"""
|
|
258
|
+
from machineconfig.utils.path_extended import PathExtended as P
|
|
259
|
+
path=P(r'{PathExtended(target).as_posix()}').expanduser()
|
|
260
|
+
{'path.delete(sure=True)' if overwrite else ''}
|
|
261
|
+
print(path.create())""",
|
|
259
262
|
desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}",
|
|
260
263
|
verbose=False,
|
|
261
264
|
).op
|
|
@@ -272,7 +275,10 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
272
275
|
source_obj = PathExtended(source_obj).expanduser().zip(content=True) # .append(f"_{randstr()}", inplace=True) # eventually, unzip will raise content flag, so this name doesn't matter.
|
|
273
276
|
remotepath = (
|
|
274
277
|
self.run_py(
|
|
275
|
-
f"
|
|
278
|
+
f"""
|
|
279
|
+
path=P(r'{PathExtended(target).as_posix()}').expanduser()
|
|
280
|
+
{'path.delete(sure=True)' if overwrite else ''}
|
|
281
|
+
print(path.parent.create())""",
|
|
276
282
|
desc=f"Creating Target directory `{PathExtended(target).parent.as_posix()}` @ {self.get_remote_repr()}",
|
|
277
283
|
verbose=False,
|
|
278
284
|
).op
|
|
@@ -283,7 +289,9 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
283
289
|
with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
|
|
284
290
|
self.sftp.put(localpath=PathExtended(source_obj).expanduser(), remotepath=remotepath.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
|
|
285
291
|
if z:
|
|
286
|
-
_resp = self.run_py(f"""
|
|
292
|
+
_resp = self.run_py(f"""
|
|
293
|
+
from machineconfig.utils.path_extended import PathExtended as P;
|
|
294
|
+
P(r'{remotepath.as_posix()}').expanduser().unzip(content=False, inplace=True, overwrite={overwrite})""", desc=f"UNZIPPING {remotepath.as_posix()}", verbose=False, strict_err=True, strict_returncode=True)
|
|
287
295
|
source_obj.delete(sure=True)
|
|
288
296
|
print("\n")
|
|
289
297
|
return source_obj
|
|
@@ -291,15 +299,20 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
291
299
|
def copy_to_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, init: bool = True) -> PathExtended:
|
|
292
300
|
if init:
|
|
293
301
|
print(f"{'⬇️' * 5} SFTP DOWNLOADING FROM `{source}` TO `{target}`")
|
|
294
|
-
if not z and self.run_py(f"
|
|
302
|
+
if not z and self.run_py(f"""
|
|
303
|
+
from machineconfig.utils.path_extended import PathExtended as P
|
|
304
|
+
print(P(r'{source}').expanduser().absolute().is_dir())""", desc=f"Check if source `{source}` is a dir", verbose=False, strict_returncode=True, strict_err=True).op.split("\n")[-1] == "True":
|
|
295
305
|
if r is False:
|
|
296
306
|
raise RuntimeError(f"source `{source}` is a directory! either set r=True for recursive sending or raise zip_first flag.")
|
|
297
|
-
source_list = self.run_py(f"
|
|
307
|
+
source_list = self.run_py(f"""
|
|
308
|
+
from machineconfig.utils.path_extended import PathExtended as P
|
|
309
|
+
obj=P(r'{source}').search(folders=False, r=True).collapseuser(strict=False)
|
|
310
|
+
""", desc="Searching for files in source", return_obj=True, verbose=False)
|
|
298
311
|
assert isinstance(source_list, List), f"Could not resolve source path {source} due to error"
|
|
299
312
|
for file in source_list:
|
|
300
313
|
self.copy_to_here(source=file.as_posix(), target=PathExtended(target).joinpath(PathExtended(file).relative_to(source)) if target else None, r=False)
|
|
301
314
|
if z:
|
|
302
|
-
tmp: Response = self.run_py(f"print(P(r'{source}').expanduser().zip(inplace=False, verbose=False))", desc=f"Zipping source file {source}", verbose=False)
|
|
315
|
+
tmp: Response = self.run_py(f"from machineconfig.utils.path_extended import PathExtended as P; print(P(r'{source}').expanduser().zip(inplace=False, verbose=False))", desc=f"Zipping source file {source}", verbose=False)
|
|
303
316
|
tmp2 = tmp.op2path(strict_returncode=True, strict_err=True)
|
|
304
317
|
if not isinstance(tmp2, PathExtended):
|
|
305
318
|
raise RuntimeError(f"Could not zip {source} due to {tmp.err}")
|
|
@@ -330,7 +343,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
330
343
|
self.sftp.get(remotepath=source.as_posix(), localpath=str(target_obj), callback=pbar.view_bar) # type: ignore
|
|
331
344
|
if z:
|
|
332
345
|
target_obj = target_obj.unzip(inplace=True, content=True)
|
|
333
|
-
self.run_py(f"P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True, verbose=False)
|
|
346
|
+
self.run_py(f"from machineconfig.utils.path_extended import PathExtended as P; P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True, verbose=False)
|
|
334
347
|
print("\n")
|
|
335
348
|
return target_obj
|
|
336
349
|
|
|
@@ -355,6 +368,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
355
368
|
self.sftp.get(remotepath=source.as_posix(), localpath=target.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
|
|
356
369
|
if z:
|
|
357
370
|
target = target.unzip(inplace=True, content=True)
|
|
358
|
-
self.run_py(f"
|
|
371
|
+
self.run_py(f"""
|
|
372
|
+
from machineconfig.utils.path_extended import PathExtended as P; P(r'{source.as_posix()}').delete(sure=True)""", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True)
|
|
359
373
|
print("\n")
|
|
360
374
|
return target
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: machineconfig
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.26
|
|
4
4
|
Summary: Dotfiles management package
|
|
5
5
|
Author-email: Alex Al-Saffar <programmer@usa.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -60,11 +60,13 @@ Additionally, files that contain data, sensitive information that should not be
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
# Windows:
|
|
63
|
+
|
|
63
64
|
```powershell
|
|
64
65
|
iex (iwr bit.ly/cfgiawindows).Content
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
# Linux and MacOS
|
|
69
|
+
|
|
68
70
|
```bash
|
|
69
71
|
. <(curl -sL bit.ly/cfgialinux)
|
|
70
72
|
```
|