machineconfig 5.20__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/cluster/sessions_managers/zellij_local.py +1 -3
- machineconfig/jobs/installer/custom_dev/brave.py +0 -6
- machineconfig/jobs/installer/package_groups.py +12 -12
- 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 +18 -9
- machineconfig/scripts/python/devops_status.py +521 -0
- machineconfig/scripts/python/devops_update_repos.py +1 -3
- machineconfig/scripts/python/fire_jobs.py +15 -50
- machineconfig/scripts/python/fire_jobs_args_helper.py +4 -1
- machineconfig/scripts/python/fire_jobs_route_helper.py +46 -0
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -40
- machineconfig/scripts/python/onetimeshare.py +0 -1
- machineconfig/scripts/python/sessions.py +7 -10
- machineconfig/scripts/python/sessions_multiprocess.py +56 -0
- 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.20.dist-info → machineconfig-5.22.dist-info}/METADATA +1 -1
- {machineconfig-5.20.dist-info → machineconfig-5.22.dist-info}/RECORD +26 -24
- {machineconfig-5.20.dist-info → machineconfig-5.22.dist-info}/WHEEL +0 -0
- {machineconfig-5.20.dist-info → machineconfig-5.22.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.20.dist-info → machineconfig-5.22.dist-info}/top_level.txt +0 -0
|
@@ -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] == "":
|
|
@@ -237,11 +237,9 @@ def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
|
|
|
237
237
|
padding=(1, 2),
|
|
238
238
|
)
|
|
239
239
|
)
|
|
240
|
-
|
|
241
240
|
# Run uv sync for repositories where pyproject.toml changed but sync wasn't run yet
|
|
242
241
|
for repo_path in repos_with_changes:
|
|
243
242
|
run_uv_sync(repo_path)
|
|
244
|
-
|
|
245
243
|
# Generate and display summary
|
|
246
244
|
_display_summary(results)
|
|
247
245
|
|