machineconfig 5.21__py3-none-any.whl → 5.22__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/profile/shell.py +1 -1
- machineconfig/scripts/python/cloud_repo_sync.py +21 -22
- machineconfig/scripts/python/croshell.py +2 -4
- machineconfig/scripts/python/devops.py +7 -2
- machineconfig/scripts/python/devops_status.py +521 -0
- machineconfig/scripts/python/devops_update_repos.py +1 -1
- machineconfig/scripts/python/fire_jobs_args_helper.py +4 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -40
- machineconfig/scripts/python/onetimeshare.py +0 -1
- machineconfig/setup_linux/repos.sh +1 -29
- machineconfig/setup_windows/repos.ps1 +0 -12
- machineconfig/utils/files/read.py +4 -6
- machineconfig/utils/notifications.py +1 -1
- machineconfig/utils/ssh.py +2 -13
- {machineconfig-5.21.dist-info → machineconfig-5.22.dist-info}/METADATA +1 -1
- {machineconfig-5.21.dist-info → machineconfig-5.22.dist-info}/RECORD +19 -18
- {machineconfig-5.21.dist-info → machineconfig-5.22.dist-info}/WHEEL +0 -0
- {machineconfig-5.21.dist-info → machineconfig-5.22.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.21.dist-info → machineconfig-5.22.dist-info}/top_level.txt +0 -0
machineconfig/profile/shell.py
CHANGED
|
@@ -13,7 +13,7 @@ from rich.panel import Panel
|
|
|
13
13
|
|
|
14
14
|
system = platform.system()
|
|
15
15
|
sep = ";" if system == "Windows" else ":" # PATH separator, this is special for PATH object, not to be confused with PathExtended.sep (normal paths), usually / or \
|
|
16
|
-
PATH = os.environ["PATH"].split(sep)
|
|
16
|
+
PATH = os.environ["PATH"].split(sep)
|
|
17
17
|
console = Console()
|
|
18
18
|
BOX_WIDTH = 100 # Define BOX_WIDTH or get it from a config
|
|
19
19
|
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
1
|
+
import sys
|
|
4
2
|
import git
|
|
5
3
|
from machineconfig.utils.io import read_ini
|
|
6
4
|
from machineconfig.utils.path_extended import PathExtended
|
|
7
5
|
from machineconfig.utils.terminal import Response
|
|
8
6
|
|
|
9
|
-
from machineconfig.scripts.python.helpers.repo_sync_helpers import fetch_dotfiles
|
|
10
7
|
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
11
8
|
from machineconfig.utils.options import choose_from_options
|
|
12
9
|
from machineconfig.utils.code import get_shell_file_executing_python_script, write_shell_script_to_file
|
|
@@ -15,13 +12,18 @@ import subprocess
|
|
|
15
12
|
from typing import Optional, Literal
|
|
16
13
|
from rich.console import Console
|
|
17
14
|
from rich.panel import Panel
|
|
15
|
+
import typer
|
|
18
16
|
|
|
19
17
|
console = Console()
|
|
20
18
|
|
|
21
|
-
_ = fetch_dotfiles
|
|
22
|
-
|
|
23
19
|
|
|
24
|
-
def main(
|
|
20
|
+
def main(
|
|
21
|
+
cloud: Optional[str] = typer.Option(None, "--cloud", "-c", help="Cloud storage profile name. If not provided, uses default from config."),
|
|
22
|
+
path: Optional[str] = typer.Option(None, "--path", "-p", help="Path to the local repository. Defaults to current working directory."),
|
|
23
|
+
message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message for local changes."),
|
|
24
|
+
on_conflict: Literal["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"] = typer.Option("ask", "--on-conflict", "-oc", help="Action to take on merge conflict. Default is 'ask'."),
|
|
25
|
+
pwd: Optional[str] = typer.Option(None, "--password", help="Password for encryption/decryption of the remote repository."),
|
|
26
|
+
):
|
|
25
27
|
if cloud is None:
|
|
26
28
|
try:
|
|
27
29
|
cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
@@ -31,8 +33,6 @@ def main(cloud: Optional[str] = None, path: Optional[str] = None, message: Optio
|
|
|
31
33
|
return ""
|
|
32
34
|
else:
|
|
33
35
|
cloud_resolved = cloud
|
|
34
|
-
|
|
35
|
-
# repo_root = PathExtended(args.repo).expanduser().absolute()
|
|
36
36
|
repo_local_root = PathExtended.cwd() if path is None else PathExtended(path).expanduser().absolute()
|
|
37
37
|
repo_local_obj = git.Repo(repo_local_root, search_parent_directories=True)
|
|
38
38
|
repo_local_root = PathExtended(repo_local_obj.working_dir) # cwd might have been in a sub directory of repo_root, so its better to redefine it.
|
|
@@ -143,7 +143,7 @@ git commit -am "finished merging"
|
|
|
143
143
|
print(f"• 4️⃣ {option4:75} 👉 {shell_file_4}")
|
|
144
144
|
|
|
145
145
|
program_content = None
|
|
146
|
-
match
|
|
146
|
+
match on_conflict:
|
|
147
147
|
case "ask":
|
|
148
148
|
choice = choose_from_options(multi=False, msg="Choose one option", options=[option1, option2, option3, option4], fzf=False)
|
|
149
149
|
if choice == option1:
|
|
@@ -165,7 +165,7 @@ git commit -am "finished merging"
|
|
|
165
165
|
case "RemoveLocalRclone":
|
|
166
166
|
program_content = program_4
|
|
167
167
|
case _:
|
|
168
|
-
raise ValueError(f"Unknown action: {
|
|
168
|
+
raise ValueError(f"Unknown action: {on_conflict}")
|
|
169
169
|
# PROGRAM_PATH.write_text(program_content, encoding="utf-8")
|
|
170
170
|
subprocess.run(program_content, shell=True, check=True)
|
|
171
171
|
|
|
@@ -173,17 +173,16 @@ git commit -am "finished merging"
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def args_parser():
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
typer.run(main)
|
|
176
|
+
# Check if no arguments provided (excluding the script name)
|
|
177
|
+
if len(sys.argv) == 1:
|
|
178
|
+
app = typer.Typer(add_completion=False, help="Sync a local git repository with a remote encrypted cloud copy.")
|
|
179
|
+
app.command()(main)
|
|
180
|
+
app(["--help"])
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
app = typer.Typer(add_completion=False, no_args_is_help=True, help="Sync a local git repository with a remote encrypted cloud copy.")
|
|
184
|
+
app.command()(main)
|
|
185
|
+
app()
|
|
187
186
|
|
|
188
187
|
|
|
189
188
|
if __name__ == "__main__":
|
|
@@ -20,10 +20,8 @@ console = Console()
|
|
|
20
20
|
|
|
21
21
|
def add_print_header_pycode(path: str, title: str):
|
|
22
22
|
return f"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
except ImportError:
|
|
26
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
23
|
+
|
|
24
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
27
25
|
pycode = PathExtended(r'{path}').read_text(encoding="utf-8")
|
|
28
26
|
pycode = pycode.split("except Exception: print(pycode)")[2]
|
|
29
27
|
|
|
@@ -44,19 +44,24 @@ def interactive():
|
|
|
44
44
|
@self_app.command()
|
|
45
45
|
def status():
|
|
46
46
|
"""📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
|
|
47
|
-
|
|
47
|
+
import machineconfig.scripts.python.devops_status as helper
|
|
48
|
+
helper.main()
|
|
48
49
|
@self_app.command()
|
|
49
50
|
def clone():
|
|
50
|
-
"""📋 CLONE machienconfig locally for faster execution and nightly updates.
|
|
51
|
+
"""📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
|
|
51
52
|
import platform
|
|
52
53
|
from machineconfig.utils.code import run_shell_script
|
|
54
|
+
from machineconfig.profile.shell import create_default_shell_profile
|
|
53
55
|
if platform.system() == "Windows":
|
|
54
56
|
from machineconfig.setup_windows import REPOS
|
|
57
|
+
create_default_shell_profile(method="copy")
|
|
55
58
|
else:
|
|
56
59
|
from machineconfig.setup_linux import REPOS
|
|
60
|
+
create_default_shell_profile(method="reference")
|
|
57
61
|
run_shell_script(REPOS.read_text(encoding="utf-8"))
|
|
58
62
|
|
|
59
63
|
|
|
64
|
+
|
|
60
65
|
@config_apps.command(no_args_is_help=True)
|
|
61
66
|
def private(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for linking files"),
|
|
62
67
|
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""Machine Status Display - Comprehensive system and configuration overview"""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
14
|
+
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH, LIBRARY_ROOT
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _check_system_info() -> dict[str, str]:
|
|
21
|
+
"""Gather basic system information."""
|
|
22
|
+
import socket
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"hostname": socket.gethostname(),
|
|
27
|
+
"system": platform.system(),
|
|
28
|
+
"release": platform.release(),
|
|
29
|
+
"version": platform.version(),
|
|
30
|
+
"machine": platform.machine(),
|
|
31
|
+
"processor": platform.processor() or "Unknown",
|
|
32
|
+
"python_version": platform.python_version(),
|
|
33
|
+
"user": os.getenv("USER") or os.getenv("USERNAME") or "Unknown",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _check_shell_profile_status() -> dict[str, Any]:
|
|
38
|
+
"""Check shell profile configuration status."""
|
|
39
|
+
from machineconfig.profile.shell import get_shell_profile_path
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
profile_path = get_shell_profile_path()
|
|
43
|
+
profile_exists = profile_path.exists()
|
|
44
|
+
profile_content = profile_path.read_text(encoding="utf-8") if profile_exists else ""
|
|
45
|
+
|
|
46
|
+
system_name = platform.system()
|
|
47
|
+
if system_name == "Windows":
|
|
48
|
+
init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/pwsh/init.ps1")
|
|
49
|
+
init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.ps1").collapseuser()
|
|
50
|
+
source_reference = f". {str(init_script.collapseuser()).replace('~', '$HOME')}"
|
|
51
|
+
source_copy = f". {str(init_script_copy).replace('~', '$HOME')}"
|
|
52
|
+
else:
|
|
53
|
+
init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/bash/init.sh")
|
|
54
|
+
init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.sh").collapseuser()
|
|
55
|
+
source_reference = f"source {str(init_script.collapseuser()).replace('~', '$HOME')}"
|
|
56
|
+
source_copy = f"source {str(init_script_copy).replace('~', '$HOME')}"
|
|
57
|
+
|
|
58
|
+
configured = source_reference in profile_content or source_copy in profile_content
|
|
59
|
+
method = "reference" if source_reference in profile_content else ("copy" if source_copy in profile_content else "none")
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"profile_path": str(profile_path),
|
|
63
|
+
"exists": profile_exists,
|
|
64
|
+
"configured": configured,
|
|
65
|
+
"method": method,
|
|
66
|
+
"init_script_exists": init_script.exists(),
|
|
67
|
+
"init_script_copy_exists": init_script_copy.exists(),
|
|
68
|
+
}
|
|
69
|
+
except Exception as ex:
|
|
70
|
+
return {"profile_path": "Error", "exists": False, "configured": False, "method": "error", "error": str(ex), "init_script_exists": False, "init_script_copy_exists": False}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _check_machineconfig_repo() -> dict[str, Any]:
|
|
74
|
+
"""Check machineconfig repository status."""
|
|
75
|
+
repo_path = Path.home().joinpath("code/machineconfig")
|
|
76
|
+
if not repo_path.exists():
|
|
77
|
+
return {"exists": False, "is_repo": False, "clean": False, "branch": "N/A", "commit": "N/A", "remotes": []}
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
import git
|
|
81
|
+
|
|
82
|
+
repo = git.Repo(str(repo_path))
|
|
83
|
+
is_dirty = repo.is_dirty(untracked_files=True)
|
|
84
|
+
current_branch = repo.active_branch.name if not repo.head.is_detached else "DETACHED"
|
|
85
|
+
current_commit = repo.head.commit.hexsha[:8]
|
|
86
|
+
remotes = [remote.name for remote in repo.remotes]
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"exists": True,
|
|
90
|
+
"is_repo": True,
|
|
91
|
+
"clean": not is_dirty,
|
|
92
|
+
"branch": current_branch,
|
|
93
|
+
"commit": current_commit,
|
|
94
|
+
"remotes": remotes,
|
|
95
|
+
"path": str(repo_path),
|
|
96
|
+
}
|
|
97
|
+
except Exception as ex:
|
|
98
|
+
return {"exists": True, "is_repo": False, "clean": False, "branch": "Error", "commit": "N/A", "remotes": [], "error": str(ex)}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _check_repos_status() -> dict[str, Any]:
|
|
102
|
+
"""Check configured repositories status."""
|
|
103
|
+
from machineconfig.utils.io import read_ini
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
repos_str = read_ini(DEFAULTS_PATH)["general"]["repos"]
|
|
107
|
+
repo_paths = [Path(p.strip()).expanduser() for p in repos_str.split(",") if p.strip()]
|
|
108
|
+
|
|
109
|
+
repos_info = []
|
|
110
|
+
for repo_path in repo_paths:
|
|
111
|
+
if not repo_path.exists():
|
|
112
|
+
repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": False, "is_repo": False})
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
import git
|
|
117
|
+
|
|
118
|
+
repo = git.Repo(str(repo_path))
|
|
119
|
+
repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": True, "is_repo": True, "clean": not repo.is_dirty(untracked_files=True), "branch": repo.active_branch.name if not repo.head.is_detached else "DETACHED"})
|
|
120
|
+
except Exception:
|
|
121
|
+
repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": True, "is_repo": False})
|
|
122
|
+
|
|
123
|
+
return {"configured": True, "count": len(repos_info), "repos": repos_info}
|
|
124
|
+
except (FileNotFoundError, KeyError, IndexError):
|
|
125
|
+
return {"configured": False, "count": 0, "repos": []}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _check_ssh_status() -> dict[str, Any]:
|
|
129
|
+
"""Check SSH configuration status."""
|
|
130
|
+
ssh_dir = PathExtended.home().joinpath(".ssh")
|
|
131
|
+
if not ssh_dir.exists():
|
|
132
|
+
return {"ssh_dir_exists": False, "keys": [], "config_exists": False, "authorized_keys_exists": False, "known_hosts_exists": False}
|
|
133
|
+
|
|
134
|
+
keys = []
|
|
135
|
+
for pub_key in ssh_dir.glob("*.pub"):
|
|
136
|
+
private_key = pub_key.with_suffix("")
|
|
137
|
+
keys.append({"name": pub_key.stem, "public_exists": True, "private_exists": private_key.exists(), "public_path": str(pub_key), "private_path": str(private_key)})
|
|
138
|
+
|
|
139
|
+
config_file = ssh_dir.joinpath("config")
|
|
140
|
+
authorized_keys = ssh_dir.joinpath("authorized_keys")
|
|
141
|
+
known_hosts = ssh_dir.joinpath("known_hosts")
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"ssh_dir_exists": True,
|
|
145
|
+
"keys": keys,
|
|
146
|
+
"config_exists": config_file.exists(),
|
|
147
|
+
"authorized_keys_exists": authorized_keys.exists(),
|
|
148
|
+
"known_hosts_exists": known_hosts.exists(),
|
|
149
|
+
"ssh_dir_path": str(ssh_dir),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _check_config_files_status() -> dict[str, Any]:
|
|
154
|
+
"""Check public and private configuration files status."""
|
|
155
|
+
from machineconfig.profile.create import read_mapper
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
mapper = read_mapper()
|
|
159
|
+
public_configs = list(mapper.get("public", {}).keys())
|
|
160
|
+
private_configs = list(mapper.get("private", {}).keys())
|
|
161
|
+
|
|
162
|
+
public_count = len(public_configs)
|
|
163
|
+
private_count = len(private_configs)
|
|
164
|
+
|
|
165
|
+
public_linked = 0
|
|
166
|
+
for config_name in public_configs:
|
|
167
|
+
for config_item in mapper["public"][config_name]:
|
|
168
|
+
target_path = PathExtended(config_item["config_file_default_path"]).expanduser()
|
|
169
|
+
if target_path.exists():
|
|
170
|
+
public_linked += 1
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
private_linked = 0
|
|
174
|
+
for config_name in private_configs:
|
|
175
|
+
for config_item in mapper["private"][config_name]:
|
|
176
|
+
target_path = PathExtended(config_item["config_file_default_path"]).expanduser()
|
|
177
|
+
if target_path.exists():
|
|
178
|
+
private_linked += 1
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"public_count": public_count,
|
|
183
|
+
"public_linked": public_linked,
|
|
184
|
+
"private_count": private_count,
|
|
185
|
+
"private_linked": private_linked,
|
|
186
|
+
"public_configs": public_configs,
|
|
187
|
+
"private_configs": private_configs,
|
|
188
|
+
}
|
|
189
|
+
except Exception as ex:
|
|
190
|
+
return {"public_count": 0, "public_linked": 0, "private_count": 0, "private_linked": 0, "error": str(ex), "public_configs": [], "private_configs": []}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _check_important_tools() -> dict[str, dict[str, bool]]:
|
|
194
|
+
"""Check if important CLI tools are installed, organized by groups."""
|
|
195
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
196
|
+
|
|
197
|
+
group_status = {}
|
|
198
|
+
for group_name, tools in PACKAGE_GROUP2NAMES.items():
|
|
199
|
+
tool_status = {}
|
|
200
|
+
for tool in tools:
|
|
201
|
+
tool_status[tool] = shutil.which(tool) is not None
|
|
202
|
+
group_status[group_name] = tool_status
|
|
203
|
+
|
|
204
|
+
return group_status
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _check_backup_config() -> dict[str, Any]:
|
|
208
|
+
"""Check backup configuration status."""
|
|
209
|
+
from machineconfig.utils.io import read_ini
|
|
210
|
+
import tomllib
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
cloud_config = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
214
|
+
except (FileNotFoundError, KeyError, IndexError):
|
|
215
|
+
cloud_config = "Not configured"
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
backup_file = LIBRARY_ROOT.joinpath("profile/backup.toml")
|
|
219
|
+
if backup_file.exists():
|
|
220
|
+
backup_data = tomllib.loads(backup_file.read_text(encoding="utf-8"))
|
|
221
|
+
backup_items = list(backup_data.keys())
|
|
222
|
+
backup_items_count = len(backup_items)
|
|
223
|
+
else:
|
|
224
|
+
backup_items = []
|
|
225
|
+
backup_items_count = 0
|
|
226
|
+
except Exception:
|
|
227
|
+
backup_items = []
|
|
228
|
+
backup_items_count = 0
|
|
229
|
+
|
|
230
|
+
return {"cloud_config": cloud_config, "backup_items_count": backup_items_count, "backup_items": backup_items}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _display_system_info(info: dict[str, str]) -> None:
|
|
234
|
+
"""Display system information panel."""
|
|
235
|
+
console.rule("[bold blue]💻 System Information[/bold blue]")
|
|
236
|
+
|
|
237
|
+
table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
|
|
238
|
+
table.add_column("Property", style="cyan", no_wrap=True)
|
|
239
|
+
table.add_column("Value", style="white")
|
|
240
|
+
|
|
241
|
+
table.add_row("🏠 Hostname", info["hostname"])
|
|
242
|
+
table.add_row("💿 System", f"{info['system']} {info['release']}")
|
|
243
|
+
table.add_row("🖥️ Machine", info["machine"])
|
|
244
|
+
table.add_row("⚙️ Processor", info["processor"])
|
|
245
|
+
table.add_row("🐍 Python", info["python_version"])
|
|
246
|
+
table.add_row("👤 User", info["user"])
|
|
247
|
+
|
|
248
|
+
console.print(Panel(table, title="System", border_style="blue", padding=(1, 2), expand=False))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _display_shell_status(status: dict[str, Any]) -> None:
|
|
252
|
+
"""Display shell profile status panel."""
|
|
253
|
+
console.rule("[bold green]🐚 Shell Profile[/bold green]")
|
|
254
|
+
|
|
255
|
+
if "error" in status:
|
|
256
|
+
console.print(Panel(f"❌ Error: {status['error']}", title="Shell Profile", border_style="red", padding=(1, 2), expand=False))
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
from rich.columns import Columns
|
|
260
|
+
|
|
261
|
+
left_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
262
|
+
left_table.add_column("Item", style="cyan", no_wrap=True)
|
|
263
|
+
left_table.add_column("Status")
|
|
264
|
+
|
|
265
|
+
left_table.add_row("📄 Profile", status['profile_path'])
|
|
266
|
+
left_table.add_row(f"{'✅' if status['exists'] else '❌'} Exists", str(status['exists']))
|
|
267
|
+
left_table.add_row(f"{'✅' if status['configured'] else '❌'} Configured", str(status['configured']))
|
|
268
|
+
|
|
269
|
+
right_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
270
|
+
right_table.add_column("Item", style="cyan", no_wrap=True)
|
|
271
|
+
right_table.add_column("Status")
|
|
272
|
+
|
|
273
|
+
right_table.add_row("🔧 Method", status['method'])
|
|
274
|
+
right_table.add_row(f"{'✅' if status['init_script_exists'] else '❌'} Init (source)", str(status['init_script_exists']))
|
|
275
|
+
right_table.add_row(f"{'✅' if status['init_script_copy_exists'] else '❌'} Init (copy)", str(status['init_script_copy_exists']))
|
|
276
|
+
|
|
277
|
+
border_style = "green" if status["configured"] else "yellow"
|
|
278
|
+
console.print(Panel(Columns([left_table, right_table], equal=True, expand=True), title="Shell Profile", border_style=border_style, padding=(1, 2), expand=False))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _display_machineconfig_repo(info: dict[str, Any]) -> None:
|
|
282
|
+
"""Display machineconfig repository status."""
|
|
283
|
+
console.rule("[bold magenta]📦 Machineconfig Repository[/bold magenta]")
|
|
284
|
+
|
|
285
|
+
if not info["exists"]:
|
|
286
|
+
console.print(Panel("❌ Machineconfig repository not found at ~/code/machineconfig", title="Repository Status", border_style="red", padding=(1, 2), expand=False))
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
if not info["is_repo"]:
|
|
290
|
+
console.print(Panel(f"❌ Directory exists but is not a git repository\n{info.get('error', 'Unknown error')}", title="Repository Status", border_style="red", padding=(1, 2), expand=False))
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
|
|
294
|
+
table.add_column("Property", style="cyan", no_wrap=True)
|
|
295
|
+
table.add_column("Value", style="white")
|
|
296
|
+
|
|
297
|
+
table.add_row("📁 Path", info['path'])
|
|
298
|
+
table.add_row("🌿 Branch", info['branch'])
|
|
299
|
+
table.add_row("🔖 Commit", info['commit'])
|
|
300
|
+
table.add_row(f"{'✅' if info['clean'] else '⚠️'} Status", 'Clean' if info['clean'] else 'Uncommitted changes')
|
|
301
|
+
table.add_row("📡 Remotes", ', '.join(info['remotes']) if info['remotes'] else 'None')
|
|
302
|
+
|
|
303
|
+
border_style = "green" if info["clean"] else "yellow"
|
|
304
|
+
console.print(Panel(table, title="Machineconfig Repository", border_style=border_style, padding=(1, 2), expand=False))
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _display_repos_status(status: dict[str, Any]) -> None:
|
|
308
|
+
"""Display configured repositories status."""
|
|
309
|
+
console.rule("[bold cyan]📚 Configured Repositories[/bold cyan]")
|
|
310
|
+
|
|
311
|
+
if not status["configured"]:
|
|
312
|
+
console.print(Panel(f"⚠️ No repositories configured in {DEFAULTS_PATH}", title="Repositories", border_style="yellow", padding=(1, 2)))
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
if status["count"] == 0:
|
|
316
|
+
console.print(Panel("ℹ️ No repositories configured", title="Repositories", border_style="blue", padding=(1, 2)))
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
table = Table(show_lines=True, header_style="bold cyan")
|
|
320
|
+
table.add_column("Repository", style="bold")
|
|
321
|
+
table.add_column("Status")
|
|
322
|
+
table.add_column("Details")
|
|
323
|
+
|
|
324
|
+
for repo in status["repos"]:
|
|
325
|
+
name = repo["name"]
|
|
326
|
+
if not repo["exists"]:
|
|
327
|
+
table.add_row(f"❌ {name}", "Missing", f"Path: {repo['path']}")
|
|
328
|
+
elif not repo["is_repo"]:
|
|
329
|
+
table.add_row(f"⚠️ {name}", "Not a repo", f"Path: {repo['path']}")
|
|
330
|
+
else:
|
|
331
|
+
status_icon = "✅" if repo["clean"] else "⚠️"
|
|
332
|
+
status_text = "Clean" if repo["clean"] else "Uncommitted changes"
|
|
333
|
+
table.add_row(f"{status_icon} {name}", status_text, f"Branch: {repo['branch']}")
|
|
334
|
+
|
|
335
|
+
console.print(Panel(table, title=f"Repositories ({status['count']})", border_style="cyan", padding=(1, 2)))
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _display_ssh_status(status: dict[str, Any]) -> None:
|
|
339
|
+
"""Display SSH configuration status."""
|
|
340
|
+
console.rule("[bold yellow]🔐 SSH Configuration[/bold yellow]")
|
|
341
|
+
|
|
342
|
+
if not status["ssh_dir_exists"]:
|
|
343
|
+
console.print(Panel("❌ SSH directory (~/.ssh) does not exist", title="SSH Status", border_style="red", padding=(1, 2), expand=False))
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
from rich.columns import Columns
|
|
347
|
+
|
|
348
|
+
config_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
349
|
+
config_table.add_column("Item", style="cyan", no_wrap=True)
|
|
350
|
+
config_table.add_column("Status")
|
|
351
|
+
|
|
352
|
+
config_table.add_row("📁 Directory", status['ssh_dir_path'])
|
|
353
|
+
config_table.add_row(f"{'✅' if status['config_exists'] else '❌'} Config", str(status['config_exists']))
|
|
354
|
+
config_table.add_row(f"{'✅' if status['authorized_keys_exists'] else '❌'} Auth Keys", str(status['authorized_keys_exists']))
|
|
355
|
+
config_table.add_row(f"{'✅' if status['known_hosts_exists'] else '❌'} Known Hosts", str(status['known_hosts_exists']))
|
|
356
|
+
|
|
357
|
+
config_panel = Panel(config_table, title="SSH Config", border_style="yellow", padding=(1, 2), expand=False)
|
|
358
|
+
|
|
359
|
+
if status["keys"]:
|
|
360
|
+
keys_table = Table(show_header=True, box=None, padding=(0, 1), show_lines=False, expand=False)
|
|
361
|
+
keys_table.add_column("Key Name", style="bold cyan")
|
|
362
|
+
keys_table.add_column("Pub", justify="center")
|
|
363
|
+
keys_table.add_column("Priv", justify="center")
|
|
364
|
+
|
|
365
|
+
for key in status["keys"]:
|
|
366
|
+
pub_status = "✅" if key["public_exists"] else "❌"
|
|
367
|
+
priv_status = "✅" if key["private_exists"] else "❌"
|
|
368
|
+
keys_table.add_row(key["name"], pub_status, priv_status)
|
|
369
|
+
|
|
370
|
+
keys_panel = Panel(keys_table, title=f"SSH Keys ({len(status['keys'])})", border_style="yellow", padding=(1, 2), expand=False)
|
|
371
|
+
|
|
372
|
+
console.print(Columns([config_panel, keys_panel], equal=False, expand=True))
|
|
373
|
+
else:
|
|
374
|
+
console.print(config_panel)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _display_config_files_status(status: dict[str, Any]) -> None:
|
|
378
|
+
"""Display configuration files status."""
|
|
379
|
+
console.rule("[bold bright_blue]⚙️ Configuration Files[/bold bright_blue]")
|
|
380
|
+
|
|
381
|
+
if "error" in status:
|
|
382
|
+
console.print(Panel(f"❌ Error reading configuration: {status['error']}", title="Configuration Files", border_style="red", padding=(1, 2), expand=False))
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
public_percentage = (status["public_linked"] / status["public_count"] * 100) if status["public_count"] > 0 else 0
|
|
386
|
+
private_percentage = (status["private_linked"] / status["private_count"] * 100) if status["private_count"] > 0 else 0
|
|
387
|
+
|
|
388
|
+
table = Table(show_header=True, box=None, padding=(0, 2), expand=False)
|
|
389
|
+
table.add_column("Type", style="cyan", no_wrap=True)
|
|
390
|
+
table.add_column("Linked", justify="right")
|
|
391
|
+
table.add_column("Total", justify="right")
|
|
392
|
+
table.add_column("Progress", justify="right")
|
|
393
|
+
|
|
394
|
+
table.add_row("📂 Public", str(status['public_linked']), str(status['public_count']), f"{public_percentage:.0f}%")
|
|
395
|
+
table.add_row("🔒 Private", str(status['private_linked']), str(status['private_count']), f"{private_percentage:.0f}%")
|
|
396
|
+
|
|
397
|
+
overall_linked = status["public_linked"] + status["private_linked"]
|
|
398
|
+
overall_total = status["public_count"] + status["private_count"]
|
|
399
|
+
overall_percentage = (overall_linked / overall_total * 100) if overall_total > 0 else 0
|
|
400
|
+
|
|
401
|
+
border_style = "green" if overall_percentage > 80 else ("yellow" if overall_percentage > 50 else "red")
|
|
402
|
+
|
|
403
|
+
console.print(Panel(table, title=f"Configuration Files ({overall_percentage:.0f}% configured)", border_style=border_style, padding=(1, 2), expand=False))
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _display_tools_status(grouped_tools: dict[str, dict[str, bool]]) -> None:
|
|
407
|
+
"""Display important tools installation status organized by groups."""
|
|
408
|
+
console.rule("[bold bright_magenta]🛠️ Important Tools[/bold bright_magenta]")
|
|
409
|
+
|
|
410
|
+
from rich.columns import Columns
|
|
411
|
+
|
|
412
|
+
all_group_panels = []
|
|
413
|
+
total_installed = 0
|
|
414
|
+
total_tools = 0
|
|
415
|
+
|
|
416
|
+
for group_name, tools in grouped_tools.items():
|
|
417
|
+
sorted_tools = sorted(tools.keys())
|
|
418
|
+
installed = [tool for tool, status in tools.items() if status]
|
|
419
|
+
total_installed += len(installed)
|
|
420
|
+
total_tools += len(tools)
|
|
421
|
+
|
|
422
|
+
num_columns = 8
|
|
423
|
+
tools_per_column = (len(sorted_tools) + num_columns - 1) // num_columns
|
|
424
|
+
|
|
425
|
+
tables = []
|
|
426
|
+
for col_idx in range(num_columns):
|
|
427
|
+
table = Table(show_header=False, box=None, padding=(0, 0), collapse_padding=True)
|
|
428
|
+
table.add_column("Tool", style="cyan", no_wrap=True, width=None)
|
|
429
|
+
table.add_column("", justify="center", width=2, no_wrap=True)
|
|
430
|
+
|
|
431
|
+
start_idx = col_idx * tools_per_column
|
|
432
|
+
end_idx = min(start_idx + tools_per_column, len(sorted_tools))
|
|
433
|
+
|
|
434
|
+
for i in range(start_idx, end_idx):
|
|
435
|
+
tool = sorted_tools[i]
|
|
436
|
+
status_icon = "✅" if tools[tool] else "❌"
|
|
437
|
+
table.add_row(tool, status_icon)
|
|
438
|
+
|
|
439
|
+
if start_idx < len(sorted_tools):
|
|
440
|
+
tables.append(table)
|
|
441
|
+
|
|
442
|
+
installed_percentage = (len(installed) / len(tools) * 100) if tools else 0
|
|
443
|
+
border_style = "green" if installed_percentage > 80 else ("yellow" if installed_percentage > 50 else "red")
|
|
444
|
+
|
|
445
|
+
group_display_name = group_name.replace("_", " ").title()
|
|
446
|
+
group_panel = Panel(
|
|
447
|
+
Columns(tables, equal=False, expand=False, padding=(0, 1)),
|
|
448
|
+
title=f"{group_display_name} ({len(installed)}/{len(tools)})",
|
|
449
|
+
border_style=border_style,
|
|
450
|
+
padding=(0, 1),
|
|
451
|
+
expand=False
|
|
452
|
+
)
|
|
453
|
+
all_group_panels.append(group_panel)
|
|
454
|
+
|
|
455
|
+
overall_percentage = (total_installed / total_tools * 100) if total_tools else 0
|
|
456
|
+
master_border_style = "green" if overall_percentage > 80 else ("yellow" if overall_percentage > 50 else "red")
|
|
457
|
+
|
|
458
|
+
from rich.console import Group
|
|
459
|
+
master_panel = Panel(
|
|
460
|
+
Group(*all_group_panels),
|
|
461
|
+
title=f"🛠️ Tools Overview ({total_installed}/{total_tools} installed - {overall_percentage:.0f}%)",
|
|
462
|
+
border_style=master_border_style,
|
|
463
|
+
padding=(1, 2),
|
|
464
|
+
expand=False
|
|
465
|
+
)
|
|
466
|
+
console.print(master_panel)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _display_backup_status(status: dict[str, Any]) -> None:
|
|
470
|
+
"""Display backup configuration status."""
|
|
471
|
+
console.rule("[bold bright_cyan]💾 Backup Configuration[/bold bright_cyan]")
|
|
472
|
+
|
|
473
|
+
table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
|
|
474
|
+
table.add_column("Property", style="cyan", no_wrap=True)
|
|
475
|
+
table.add_column("Value", style="white")
|
|
476
|
+
|
|
477
|
+
table.add_row("🌥️ Cloud Config", status['cloud_config'])
|
|
478
|
+
table.add_row("📦 Backup Items", str(status['backup_items_count']))
|
|
479
|
+
|
|
480
|
+
border_style = "green" if status["cloud_config"] != "Not configured" else "yellow"
|
|
481
|
+
|
|
482
|
+
console.print(Panel(table, title="Backup Configuration", border_style=border_style, padding=(1, 2), expand=False))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def main() -> None:
|
|
486
|
+
"""Main function to display comprehensive machine status."""
|
|
487
|
+
console.print("\n")
|
|
488
|
+
console.print(Panel(Text("📊 Machine Status Report", justify="center", style="bold white"), style="bold blue", padding=(1, 2)))
|
|
489
|
+
console.print("\n")
|
|
490
|
+
|
|
491
|
+
system_info = _check_system_info()
|
|
492
|
+
_display_system_info(system_info)
|
|
493
|
+
|
|
494
|
+
shell_status = _check_shell_profile_status()
|
|
495
|
+
_display_shell_status(shell_status)
|
|
496
|
+
|
|
497
|
+
machineconfig_repo = _check_machineconfig_repo()
|
|
498
|
+
_display_machineconfig_repo(machineconfig_repo)
|
|
499
|
+
|
|
500
|
+
repos_status = _check_repos_status()
|
|
501
|
+
_display_repos_status(repos_status)
|
|
502
|
+
|
|
503
|
+
ssh_status = _check_ssh_status()
|
|
504
|
+
_display_ssh_status(ssh_status)
|
|
505
|
+
|
|
506
|
+
config_status = _check_config_files_status()
|
|
507
|
+
_display_config_files_status(config_status)
|
|
508
|
+
|
|
509
|
+
tools_status = _check_important_tools()
|
|
510
|
+
_display_tools_status(tools_status)
|
|
511
|
+
|
|
512
|
+
backup_status = _check_backup_config()
|
|
513
|
+
_display_backup_status(backup_status)
|
|
514
|
+
|
|
515
|
+
console.print("\n")
|
|
516
|
+
console.print(Panel(Text("✨ Status report complete!", justify="center", style="bold green"), style="green", padding=(1, 2)))
|
|
517
|
+
console.print("\n")
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
if __name__ == "__main__":
|
|
521
|
+
main()
|
|
@@ -148,7 +148,7 @@ def _display_summary(results: list[RepositoryUpdateResult]) -> None:
|
|
|
148
148
|
def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
|
|
149
149
|
"""Main function to update all configured repositories."""
|
|
150
150
|
_ = verbose
|
|
151
|
-
repos: list[Path] = [Path.home() / "code/machineconfig"
|
|
151
|
+
repos: list[Path] = [Path.home() / "code/machineconfig"]
|
|
152
152
|
try:
|
|
153
153
|
tmp = read_ini(DEFAULTS_PATH)["general"]["repos"].split(",")
|
|
154
154
|
if tmp[-1] == "":
|
|
@@ -24,7 +24,6 @@ class FireJobArgs:
|
|
|
24
24
|
PathExport: bool = False
|
|
25
25
|
git_pull: bool = False
|
|
26
26
|
optimized: bool = False
|
|
27
|
-
Nprocess: int = 1
|
|
28
27
|
zellij_tab: Optional[str] = None
|
|
29
28
|
watch: bool = False
|
|
30
29
|
|
|
@@ -96,6 +95,10 @@ def _convert_value_type(value: str) -> object:
|
|
|
96
95
|
elif value.lower() in ('false', '0', 'no', 'off'):
|
|
97
96
|
return False
|
|
98
97
|
|
|
98
|
+
# Try to convert None
|
|
99
|
+
if value.lower() == 'none':
|
|
100
|
+
return None
|
|
101
|
+
|
|
99
102
|
# Try to parse as list (comma-separated values)
|
|
100
103
|
if ',' in value:
|
|
101
104
|
items = [_convert_value_type(item.strip()) for item in value.split(',')]
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
from machineconfig.utils.path_extended import PathExtended
|
|
2
|
-
from machineconfig.utils.terminal import Response
|
|
3
2
|
from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
|
|
4
|
-
from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
5
|
-
from machineconfig.utils.io import read_ini
|
|
6
3
|
from machineconfig.utils.code import write_shell_script_to_file
|
|
7
4
|
import platform
|
|
8
|
-
import subprocess
|
|
9
5
|
from rich.console import Console
|
|
10
6
|
from rich.panel import Panel
|
|
11
7
|
|
|
@@ -60,42 +56,6 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
|
|
|
60
56
|
raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
61
57
|
|
|
62
58
|
|
|
63
|
-
def fetch_dotfiles():
|
|
64
|
-
console.print(Panel("📁 Fetching Dotfiles", title="[bold blue]Dotfiles[/bold blue]", border_style="blue"))
|
|
65
|
-
|
|
66
|
-
cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
|
|
67
|
-
console.print(Panel(f"⚠️ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", width=150, border_style="yellow"))
|
|
68
|
-
|
|
69
|
-
dotfiles_local = PathExtended.home().joinpath("dotfiles")
|
|
70
|
-
CONFIG_PATH.joinpath("remote").mkdir(parents=True, exist_ok=True)
|
|
71
|
-
dotfiles_remote = PathExtended(CONFIG_PATH).joinpath("remote", dotfiles_local.rel2home())
|
|
72
|
-
remote_path = dotfiles_local.get_remote_path(rel2home=True, os_specific=False, root="myhome") + ".zip.enc"
|
|
73
|
-
|
|
74
|
-
console.print(Panel("📥 Downloading dotfiles from cloud...", width=150, border_style="blue"))
|
|
75
|
-
|
|
76
|
-
dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved, unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
|
|
77
|
-
|
|
78
|
-
console.print(Panel("🗑️ Removing old dotfiles and replacing with cloud version...", width=150, border_style="blue"))
|
|
79
|
-
|
|
80
|
-
dotfiles_local.delete(sure=True)
|
|
81
|
-
dotfiles_remote.move(folder=PathExtended.home())
|
|
82
|
-
script = f"""
|
|
83
|
-
# rm -rf {dotfiles_local}
|
|
84
|
-
# mv {dotfiles_remote} {dotfiles_local}
|
|
85
|
-
"""
|
|
86
|
-
if platform.system() == "Linux":
|
|
87
|
-
script += """
|
|
88
|
-
sudo chmod 600 $HOME/.ssh/*
|
|
89
|
-
sudo chmod 700 $HOME/.ssh
|
|
90
|
-
sudo chmod +x $HOME/dotfiles/scripts/linux -R
|
|
91
|
-
"""
|
|
92
|
-
shell_path = write_shell_script_to_file(shell_script=script)
|
|
93
|
-
completed = subprocess.run(f". {shell_path}", capture_output=True, check=False, text=True, shell=True)
|
|
94
|
-
Response.from_completed_process(completed).capture().print()
|
|
95
|
-
|
|
96
|
-
console.print(Panel("✅ Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
|
|
97
|
-
|
|
98
|
-
|
|
99
59
|
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
|
|
100
60
|
dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
|
|
101
61
|
from git import Repo
|
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/bash
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
echo """
|
|
5
|
-
#=======================================================================
|
|
6
|
-
🔄 REPOSITORIES SETUP | Cloning project codebases
|
|
7
|
-
#=======================================================================
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
echo """📥 Setting up repositories...
|
|
11
|
-
🐊 crocodile - Main utility package
|
|
12
|
-
🔧 machineconfig - System configuration tools
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
mkdir -p $HOME/code
|
|
16
|
-
cd $HOME/code
|
|
17
|
-
# Setup crocodile repository
|
|
18
|
-
if [ -d "crocodile" ]; then
|
|
19
|
-
echo """🔄 crocodile directory exists, updating...
|
|
20
|
-
"""
|
|
21
|
-
cd crocodile
|
|
22
|
-
git reset --hard
|
|
23
|
-
git pull
|
|
24
|
-
cd ..
|
|
25
|
-
else
|
|
26
|
-
echo """⏳ Cloning crocodile repository...
|
|
27
|
-
"""
|
|
28
|
-
git clone https://github.com/thisismygitrepo/crocodile.git --depth 4
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Setup machineconfig repository
|
|
32
4
|
cd $HOME/code
|
|
33
5
|
if [ -d "machineconfig" ]; then
|
|
34
6
|
echo """🔄 machineconfig directory exists, updating...
|
|
@@ -45,4 +17,4 @@ fi
|
|
|
45
17
|
|
|
46
18
|
cd $HOME/code/machineconfig
|
|
47
19
|
$HOME/.local/bin/uv sync --no-dev
|
|
48
|
-
$HOME/.local/bin/uv cache clean
|
|
20
|
+
# $HOME/.local/bin/uv cache clean
|
|
@@ -9,18 +9,6 @@ if (-not (Get-Command git.exe -ErrorAction SilentlyContinue)) {
|
|
|
9
9
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
# Setup crocodile repository
|
|
13
|
-
if (Test-Path "crocodile") {
|
|
14
|
-
Write-Host "🔄 crocodile directory exists, updating..."
|
|
15
|
-
Set-Location crocodile
|
|
16
|
-
git reset --hard
|
|
17
|
-
git pull
|
|
18
|
-
Set-Location ..
|
|
19
|
-
} else {
|
|
20
|
-
Write-Host "⏳ Cloning crocodile repository..."
|
|
21
|
-
git clone https://github.com/thisismygitrepo/crocodile.git --depth 4
|
|
22
|
-
}
|
|
23
|
-
|
|
24
12
|
# Setup machineconfig repository
|
|
25
13
|
if (Test-Path "machineconfig") {
|
|
26
14
|
Write-Host "🔄 machineconfig directory exists, updating..."
|
|
@@ -12,12 +12,10 @@ class Read:
|
|
|
12
12
|
suffix = Path(path).suffix[1:]
|
|
13
13
|
if suffix == "": raise ValueError(f"File type could not be inferred from suffix. Suffix is empty. Path: {path}")
|
|
14
14
|
if suffix in ("sqlite", "sqlite3", "db", "duckdb"):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# return res
|
|
20
|
-
raise NotImplementedError("Reading database files is not implemented yet. Use `crocodile.database.DBMS` to connect to the database file.")
|
|
15
|
+
from machineconfig.utils.files.dbms import DBMS
|
|
16
|
+
res = DBMS.from_local_db(path=path)
|
|
17
|
+
print(res.describe_db())
|
|
18
|
+
return res
|
|
21
19
|
try: return getattr(Read, suffix)(str(path), **kwargs)
|
|
22
20
|
except AttributeError as err:
|
|
23
21
|
if "type object 'Read' has no attribute" not in str(err): raise AttributeError(err) from err
|
|
@@ -110,7 +110,7 @@ encryption = ssl
|
|
|
110
110
|
|
|
111
111
|
def send_message(self, to: str, subject: str, body: str, txt_to_html: bool = True, attachments: Optional[list[Any]] = None):
|
|
112
112
|
_ = attachments
|
|
113
|
-
body += "\n\nThis is an automated email sent via
|
|
113
|
+
body += "\n\nThis is an automated email sent via machineconfig.comms script."
|
|
114
114
|
# msg = message.EmailMessage()
|
|
115
115
|
msg = MIMEMultipart("alternative")
|
|
116
116
|
msg["subject"] = subject
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -8,20 +8,9 @@ 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: OPLike
|
|
12
|
-
if toolbox:
|
|
13
|
-
toobox_code = """
|
|
14
|
-
try:
|
|
15
|
-
from crocodile.toolbox import *
|
|
16
|
-
except ImportError:
|
|
17
|
-
print("Crocodile not found, skipping import.")
|
|
18
|
-
pass
|
|
19
|
-
"""
|
|
20
|
-
else:
|
|
21
|
-
toobox_code = "# No toolbox import."
|
|
11
|
+
def get_header(wdir: OPLike):
|
|
22
12
|
return f"""
|
|
23
13
|
# >> Code prepended
|
|
24
|
-
{toobox_code}
|
|
25
14
|
{'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
|
|
26
15
|
# >> End of header, start of script passed
|
|
27
16
|
"""
|
|
@@ -236,7 +225,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
236
225
|
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.'
|
|
237
226
|
if not return_obj:
|
|
238
227
|
return self.run(
|
|
239
|
-
cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{get_header(wdir=None
|
|
228
|
+
cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{get_header(wdir=None)}{cmd}\n""" + '"',
|
|
240
229
|
desc=desc or f"run_py on {self.get_remote_repr()}",
|
|
241
230
|
verbose=verbose,
|
|
242
231
|
strict_err=strict_err,
|
|
@@ -92,7 +92,7 @@ machineconfig/jobs/windows/msc/cli_agents.ps1,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
|
|
|
92
92
|
machineconfig/profile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
93
|
machineconfig/profile/create.py,sha256=n8AEpwWrGmzyWyIcFxTXQp8Ztw6MTHTjt1aPlsw7yhc,13974
|
|
94
94
|
machineconfig/profile/create_frontend.py,sha256=RlrQgsAzvJA3KQInPpavLJ6XIEayIjjq4it6dSLQR08,3352
|
|
95
|
-
machineconfig/profile/shell.py,sha256=
|
|
95
|
+
machineconfig/profile/shell.py,sha256=H6Wae0KxcFuS88Vj8ZDXIp3YQr9Y6evK1DTUNrz4QPw,10871
|
|
96
96
|
machineconfig/profile/records/generic/shares.toml,sha256=FduDztfyQtZcr5bfx-RSKhEEweweQSWfVXkKWnx8hCY,143
|
|
97
97
|
machineconfig/profile/records/linux/apps_summary_report.csv,sha256=pw9djvaRUPalKDLn2sl3odcbD2_Zx3aEupsQ8UPfaaY,2738
|
|
98
98
|
machineconfig/profile/records/linux/apps_summary_report.md,sha256=l77oofA6Rliql0ZgKGIZi8bstFoGyyGTxeS8p2PtOj0,5634
|
|
@@ -141,23 +141,24 @@ machineconfig/scripts/python/choose_wezterm_theme.py,sha256=Hlu_EOQhLM6wYdAdY25j
|
|
|
141
141
|
machineconfig/scripts/python/cloud_copy.py,sha256=fcWbSo2nGiubtMYjGci8s5tVjZ9D-u8mteCawZmbw3I,8379
|
|
142
142
|
machineconfig/scripts/python/cloud_manager.py,sha256=YN0DYLzPKtMBaks-EAVwFmkCu3XeHWMr1D21uqX5dDk,3429
|
|
143
143
|
machineconfig/scripts/python/cloud_mount.py,sha256=GwcXbd5ohoHGESfX5edtCEl2-umDDxH_AZapmFSzc9E,6740
|
|
144
|
-
machineconfig/scripts/python/cloud_repo_sync.py,sha256=
|
|
144
|
+
machineconfig/scripts/python/cloud_repo_sync.py,sha256=HtATW_pbd2j5M2g9dKFWaD_jq6GENX7KsCevVE6TGT8,9638
|
|
145
145
|
machineconfig/scripts/python/cloud_sync.py,sha256=RWGpAfJ9fnN18yNBSgN44dzA38Hmd4879JL5r2pcyrM,3514
|
|
146
146
|
machineconfig/scripts/python/count_lines.py,sha256=ZexMRsV70pe9fhLbGuens9EP5gCf078EwTDRHRZo5A0,15960
|
|
147
147
|
machineconfig/scripts/python/count_lines_frontend.py,sha256=HlzPLU9_oJYqPNbnoQ0Hm4CuYy1UUlkZPcE5tFBSEbo,545
|
|
148
|
-
machineconfig/scripts/python/croshell.py,sha256=
|
|
149
|
-
machineconfig/scripts/python/devops.py,sha256=
|
|
148
|
+
machineconfig/scripts/python/croshell.py,sha256=LgmwB17EVgNgRkXgwLOZPQWrHqSYDo7Kd-d6vkqA3PY,6459
|
|
149
|
+
machineconfig/scripts/python/devops.py,sha256=VWdoSf0MxMl1e-egKn4mAm_gQ43OFDwhS8PCXZy3l3M,7341
|
|
150
150
|
machineconfig/scripts/python/devops_add_identity.py,sha256=wvjNgqsLmqD2SxbNCW_usqfp0LI-TDvcJJKGOWt2oFw,3775
|
|
151
151
|
machineconfig/scripts/python/devops_add_ssh_key.py,sha256=BXB-9RvuSZO0YTbnM2azeABW2ngLW4SKhhAGAieMzfw,6873
|
|
152
152
|
machineconfig/scripts/python/devops_backup_retrieve.py,sha256=JLJHmi8JmZ_qVTeMW-qBEAYGt1fmfWXzZ7Gm-Q-GDcU,5585
|
|
153
|
-
machineconfig/scripts/python/
|
|
153
|
+
machineconfig/scripts/python/devops_status.py,sha256=qSmDCNopKq8DLcx3u1sLhCIZtILP2ZzdGYZuA7fvrJ8,22487
|
|
154
|
+
machineconfig/scripts/python/devops_update_repos.py,sha256=9p21ckEfqyiCoLWgNgkE1RSswb6b9YPeCf5lmBtudsQ,9393
|
|
154
155
|
machineconfig/scripts/python/dotfile.py,sha256=9W9i8Qbs6i2NfTq0knywB3StvE_sHaZYZ0RslTyoVz8,2734
|
|
155
156
|
machineconfig/scripts/python/fire_agents_help_launch.py,sha256=1ymWiszfjCyPv3ofinWzfOmbzLEt3d7ntac_afLh-V4,5017
|
|
156
157
|
machineconfig/scripts/python/fire_agents_help_search.py,sha256=qIfSS_su2YJ1Gb0_lu4cbjlJlYMBw0v52NTGiSrGjk8,2991
|
|
157
158
|
machineconfig/scripts/python/fire_agents_helper_types.py,sha256=zKu8Vr6iucaGSkCm_Tkt_WrYU7-6Nript3coYyzTXzY,295
|
|
158
159
|
machineconfig/scripts/python/fire_agents_load_balancer.py,sha256=mpqx3uaQdBXYieuvhdK-qsvLepf9oIMo3pwPj9mSEDI,1079
|
|
159
160
|
machineconfig/scripts/python/fire_jobs.py,sha256=r_iJbjdktxpgtQV7N4Smc1MMw6ssYUByIlQDNyIrW5A,13413
|
|
160
|
-
machineconfig/scripts/python/fire_jobs_args_helper.py,sha256
|
|
161
|
+
machineconfig/scripts/python/fire_jobs_args_helper.py,sha256=UUrGB2N_pR7PxFKtKTJxIUiS58WjQX0U50y2ft8Ul4w,4334
|
|
161
162
|
machineconfig/scripts/python/fire_jobs_route_helper.py,sha256=9zGuh_bMkQgfMS0nnFoa2oIWdmLAkSNtlEH4H-FprmM,5373
|
|
162
163
|
machineconfig/scripts/python/fire_jobs_streamlit_helper.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
163
164
|
machineconfig/scripts/python/ftpx.py,sha256=QfQTp-6jQP6yxfbLc5sKxiMtTgAgc8sjN7d17_uLiZc,9400
|
|
@@ -167,7 +168,7 @@ machineconfig/scripts/python/interactive.py,sha256=FYb-bhn5GYDSER_l_w8YS_LwEI1ae
|
|
|
167
168
|
machineconfig/scripts/python/mount_nfs.py,sha256=aECrL64j9g-9rF49sVJAjGmzaoGgcMnl3g9v17kQF4c,3239
|
|
168
169
|
machineconfig/scripts/python/mount_nw_drive.py,sha256=iru6AtnTyvyuk6WxlK5R4lDkuliVpPV5_uBTVVhXtjQ,1550
|
|
169
170
|
machineconfig/scripts/python/mount_ssh.py,sha256=k2fKq3f5dKq_7anrFOlqvJoI_3U4EWNHLRZ1o3Lsy6M,2268
|
|
170
|
-
machineconfig/scripts/python/onetimeshare.py,sha256=
|
|
171
|
+
machineconfig/scripts/python/onetimeshare.py,sha256=xRd8by6qUm-od2Umty2MYsXyJwzXw-CBTd7VellNaKY,2498
|
|
171
172
|
machineconfig/scripts/python/pomodoro.py,sha256=SPkfeoZGv8rylGiOyzQ7UK3aXZ3G2FIOuGkSuBUggOI,2019
|
|
172
173
|
machineconfig/scripts/python/repos.py,sha256=7OwnQVedmLVgDnIY-OJ9epa1AcmRoUptu8ihyRNe_u4,5105
|
|
173
174
|
machineconfig/scripts/python/repos_helper.py,sha256=3jLdnNf1canpzi3JXiz5VA6UTUmLeNHuhjOWVl_thP0,3006
|
|
@@ -219,7 +220,7 @@ machineconfig/scripts/python/helpers/cloud_helpers.py,sha256=GA-bxXouUmknk9fyQAs
|
|
|
219
220
|
machineconfig/scripts/python/helpers/helpers2.py,sha256=2QeQ2aii6hVc4S-oi3SVTSyPxKPTDUWBD7GnkCEr7Qs,7304
|
|
220
221
|
machineconfig/scripts/python/helpers/helpers4.py,sha256=iKR5vVJygaDIpFXhcdma9jOpyxKtUhmqcmalFxJmY0w,4749
|
|
221
222
|
machineconfig/scripts/python/helpers/helpers5.py,sha256=dPBvA9Tcyx9TMgM6On49A1CueGMhBdRzikDnlJGf3J0,1123
|
|
222
|
-
machineconfig/scripts/python/helpers/repo_sync_helpers.py,sha256=
|
|
223
|
+
machineconfig/scripts/python/helpers/repo_sync_helpers.py,sha256=okgsaY_Cc5wEGuUzoR9sKsioN5s8vnYOstOURQVKyBI,3461
|
|
223
224
|
machineconfig/scripts/windows/agents.ps1,sha256=DqdrC_Xc2rwQ6kGzT0xh5CJz4B_0p5ZwB7s8XE6rPCM,77
|
|
224
225
|
machineconfig/scripts/windows/choose_wezterm_theme.ps1,sha256=LiXJ0a4LKjb6E-oH_bAg6DjegV4SqDUdiMp_svGCFlI,95
|
|
225
226
|
machineconfig/scripts/windows/cloud_copy.ps1,sha256=llTFhN2uInZTcoZYZuuhJcf5Ifo5MF226I5MpOzvc3A,82
|
|
@@ -361,7 +362,7 @@ machineconfig/setup_linux/__init__.py,sha256=acmXv9KPYt9jdPuLGp6O392r-hSdzayyjn-
|
|
|
361
362
|
machineconfig/setup_linux/apps.sh,sha256=0PdPojpj2B-wOxE6ryvguKwBvL6vEt-43A1NdnwJumM,3198
|
|
362
363
|
machineconfig/setup_linux/apps_desktop.sh,sha256=nBt_24tbdgKYK68w15ZiktgcEEJ7ytLoPWIrDluTkIQ,5077
|
|
363
364
|
machineconfig/setup_linux/apps_gui.sh,sha256=ufsCqmShc8G3XyKBmchD1tJ85FZYjSDY2yiE_0Rl7v4,3031
|
|
364
|
-
machineconfig/setup_linux/repos.sh,sha256=
|
|
365
|
+
machineconfig/setup_linux/repos.sh,sha256=VHrMMadbu__P94eF5SRpy1OeXm1fNjAxFJjITioxKXQ,476
|
|
365
366
|
machineconfig/setup_linux/ve.sh,sha256=yHxDzRoiAkEmLvUHNHUZksyOWpwiirLJruM_tgsuwYs,579
|
|
366
367
|
machineconfig/setup_linux/nix/cli_installation.sh,sha256=AQ_wRmldeD1tPqCmU7qgz9ZrZFly4OYwBJDGRpb9IJ0,5470
|
|
367
368
|
machineconfig/setup_linux/others/mint_keyboard_shortcuts.sh,sha256=F5dbg0n9RHsKGPn8fIdZMn3p0RrHEkb8rWBGsdVGbus,1207
|
|
@@ -371,7 +372,7 @@ machineconfig/setup_linux/web_shortcuts/android.sh,sha256=gzep6bBhK7FCBvGcXK0fdJ
|
|
|
371
372
|
machineconfig/setup_linux/web_shortcuts/interactive.sh,sha256=l0ULuMUhidth8ksOeZatw5alrny0zRN5toL5MUQzRMQ,468
|
|
372
373
|
machineconfig/setup_windows/__init__.py,sha256=M8DFE3a4BW7-EAH9-F0F6U3WfYMF8wiLfXf9f3Mv2Go,438
|
|
373
374
|
machineconfig/setup_windows/apps.ps1,sha256=G5GqZ9G0aiQr_A-HaahtRdzpaTTdW6n3DRKMZWDTSPc,11214
|
|
374
|
-
machineconfig/setup_windows/repos.ps1,sha256=
|
|
375
|
+
machineconfig/setup_windows/repos.ps1,sha256=gIQBOLIh65oUXgSjYMeYeD6lU1Bu80LZ59xqRc3T3BA,918
|
|
375
376
|
machineconfig/setup_windows/ve.ps1,sha256=a0Vk7BJfp_dY5GpDYC20WrJbqZHIWWbCZ9hDpKobMDk,311
|
|
376
377
|
machineconfig/setup_windows/others/docker.ps1,sha256=M8NfsSxH8YlmY92J4rSe1xWOwTW8IFrdgb8cI8Riu2E,311
|
|
377
378
|
machineconfig/setup_windows/others/obs.ps1,sha256=2andchcXpxS3rqZjGaMpY5VShxTAKWvw6eCrayjuaLo,30
|
|
@@ -389,7 +390,7 @@ machineconfig/utils/code.py,sha256=CaDMxAoOKkjeMCr0Zw-yH0ghfz-4yvf3q7dPHKvHzrg,5
|
|
|
389
390
|
machineconfig/utils/installer.py,sha256=FitnRR2xOekkAygXgptKWdHHGdbXImKR8PcbgPmiPXw,10738
|
|
390
391
|
machineconfig/utils/io.py,sha256=ZXB3aataS1IZ_0WMcCRSmoN1nbkvEO-bWYcs-TpngqU,2872
|
|
391
392
|
machineconfig/utils/links.py,sha256=GQExBsMoxewOhwIrNdERuzk9HVKcmWgNUGO-RzPMS6M,22588
|
|
392
|
-
machineconfig/utils/notifications.py,sha256=
|
|
393
|
+
machineconfig/utils/notifications.py,sha256=tuXIudcip0tEioG-bm8BbLr3FMDve4f6BktlznBhKxM,9013
|
|
393
394
|
machineconfig/utils/options.py,sha256=vUO4Kej-vDOv64wHr2HNDyu6PATURpjd7xp6N8OOoJg,7083
|
|
394
395
|
machineconfig/utils/path_extended.py,sha256=Xjdn2AVnB8p1jfNMNe2kJutVa5zGnFFJVGZbw-Bp_hg,53200
|
|
395
396
|
machineconfig/utils/path_helper.py,sha256=0e3Xh3BAEv27oqcezNeVLHJllGmLEgLH4T1l90m-650,8014
|
|
@@ -397,7 +398,7 @@ machineconfig/utils/procs.py,sha256=w75oGKfR7FpT1pGTGd2XscnEOO0IHBWxohLbi69hLqg,
|
|
|
397
398
|
machineconfig/utils/scheduler.py,sha256=GbprwuxoJYdtkCsg7JZPXM8un9Z7v9tPjUoQjtS0oHU,14955
|
|
398
399
|
machineconfig/utils/scheduling.py,sha256=RF1iXJpqf4Dg18jdZWtBixz97KAHC6VKYqTFSpdLWuc,11188
|
|
399
400
|
machineconfig/utils/source_of_truth.py,sha256=GnjcVkKm11RyZFHGnPbne5YDEBYoZ5yryBNkpfGC7O4,854
|
|
400
|
-
machineconfig/utils/ssh.py,sha256=
|
|
401
|
+
machineconfig/utils/ssh.py,sha256=wIoIdeeTYCLvfdtFojUrKTlMRKUdf-eMqYae6g915w0,20666
|
|
401
402
|
machineconfig/utils/terminal.py,sha256=IlmOByfQG-vjhaFFxxzU5rWzP5_qUzmalRfuey3PAmc,11801
|
|
402
403
|
machineconfig/utils/upgrade_packages.py,sha256=H96zVJEWXJW07nh5vhjuSCrPtXGqoUb7xeJsFYYdmCI,3330
|
|
403
404
|
machineconfig/utils/ve.py,sha256=L-6PBXnQGXThiwWgheJMQoisAZOZA6SVCbGw2J-GFnI,2414
|
|
@@ -408,7 +409,7 @@ machineconfig/utils/cloud/onedrive/transaction.py,sha256=m-aNcnWj_gfZVvJOSpkdIqj
|
|
|
408
409
|
machineconfig/utils/files/ascii_art.py,sha256=cNJaJC07vx94fS44-tzgfbfBeCwXVrgpnWGBLUnfC38,5212
|
|
409
410
|
machineconfig/utils/files/dbms.py,sha256=xJTg6H_AvZWsduuDaSs4_KdUu__rO8LXrE_hrcXTgNM,17203
|
|
410
411
|
machineconfig/utils/files/headers.py,sha256=L54G11DfLadyZGyQXSQ7y8UI_tNvlld7zqP4qEAWL88,3647
|
|
411
|
-
machineconfig/utils/files/read.py,sha256=
|
|
412
|
+
machineconfig/utils/files/read.py,sha256=VGraV3_73gGFwqYZ-GJzsfERsmO21n2JkFLKvck4fmc,4561
|
|
412
413
|
machineconfig/utils/installer_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
413
414
|
machineconfig/utils/installer_utils/github_release_bulk.py,sha256=WJf_qZlF02SmIc6C7o1h4Gy4gAaJAfeAS8O9s2Itj-k,6535
|
|
414
415
|
machineconfig/utils/installer_utils/installer.py,sha256=H9wukQBSZDLlpB6Xn4Sa2hSV8iCUr9UQChcgOa3u-L8,9278
|
|
@@ -418,8 +419,8 @@ machineconfig/utils/schemas/fire_agents/fire_agents_input.py,sha256=pTxvLzIpD5RF
|
|
|
418
419
|
machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoSpdmTIdgS9LS-RvE-QZ-D260tD3o,1214
|
|
419
420
|
machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
|
|
420
421
|
machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
|
|
421
|
-
machineconfig-5.
|
|
422
|
-
machineconfig-5.
|
|
423
|
-
machineconfig-5.
|
|
424
|
-
machineconfig-5.
|
|
425
|
-
machineconfig-5.
|
|
422
|
+
machineconfig-5.22.dist-info/METADATA,sha256=xLortdqCUVyhyJucZzbnC87ptXeuApjvaHsLd_3i1Zw,8030
|
|
423
|
+
machineconfig-5.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
424
|
+
machineconfig-5.22.dist-info/entry_points.txt,sha256=2afE1mw-o4MUlfxyX73SV02XaQI4SV_LdL2r6_CzhPU,1074
|
|
425
|
+
machineconfig-5.22.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
|
|
426
|
+
machineconfig-5.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|