machineconfig 5.17__py3-none-any.whl → 5.19__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/wt_local.py +6 -1
- machineconfig/cluster/sessions_managers/wt_local_manager.py +4 -2
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +4 -2
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +4 -2
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -1
- machineconfig/profile/create.py +108 -140
- machineconfig/profile/create_frontend.py +58 -0
- machineconfig/profile/shell.py +45 -9
- machineconfig/scripts/python/ai/solutions/_shared.py +9 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +1 -1
- machineconfig/scripts/python/ai/solutions/generic.py +12 -2
- machineconfig/scripts/python/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/devops.py +90 -54
- machineconfig/scripts/python/dotfile.py +14 -8
- machineconfig/scripts/python/interactive.py +3 -21
- machineconfig/scripts/python/share_terminal.py +1 -1
- machineconfig/setup_linux/__init__.py +11 -0
- machineconfig/setup_linux/{openssh_all.sh → ssh/openssh_all.sh} +1 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_windows/__init__.py +12 -0
- machineconfig/setup_windows/apps.ps1 +1 -0
- machineconfig/setup_windows/{openssh_all.ps1 → ssh/openssh_all.ps1} +5 -5
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/code.py +7 -4
- machineconfig/utils/files/dbms.py +355 -0
- machineconfig/utils/files/read.py +2 -2
- machineconfig/utils/installer.py +5 -5
- machineconfig/utils/links.py +128 -104
- machineconfig/utils/procs.py +4 -4
- machineconfig/utils/scheduler.py +10 -14
- {machineconfig-5.17.dist-info → machineconfig-5.19.dist-info}/METADATA +1 -1
- {machineconfig-5.17.dist-info → machineconfig-5.19.dist-info}/RECORD +41 -51
- machineconfig/scripts/windows/dotfile.ps1 +0 -1
- machineconfig/setup_linux/others/openssh-server_add_pub_key.sh +0 -57
- machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -11
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -52
- machineconfig/setup_windows/symlinks.ps1 +0 -5
- machineconfig/setup_windows/symlinks2linux.ps1 +0 -1
- machineconfig/setup_windows/web_shortcuts/all.ps1 +0 -18
- machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +0 -36
- machineconfig/setup_windows/web_shortcuts/croshell.ps1 +0 -16
- machineconfig/setup_windows/web_shortcuts/ssh.ps1 +0 -11
- machineconfig/setup_windows/wsl_refresh.ps1 +0 -8
- machineconfig/setup_windows/wt_and_pwsh.ps1 +0 -9
- /machineconfig/setup_linux/{openssh_wsl.sh → ssh/openssh_wsl.sh} +0 -0
- /machineconfig/setup_windows/{quirks.ps1 → others/power_options.ps1} +0 -0
- /machineconfig/setup_windows/{openssh-server.ps1 → ssh/openssh-server.ps1} +0 -0
- /machineconfig/setup_windows/{openssh-server_add-sshkey.ps1 → ssh/openssh-server_add-sshkey.ps1} +0 -0
- /machineconfig/setup_windows/{openssh-server_add_identity.ps1 → ssh/openssh-server_add_identity.ps1} +0 -0
- {machineconfig-5.17.dist-info → machineconfig-5.19.dist-info}/WHEEL +0 -0
- {machineconfig-5.17.dist-info → machineconfig-5.19.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.17.dist-info → machineconfig-5.19.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Windows Terminal local layout generator and session manager.
|
|
4
4
|
Equivalent to zellij_local.py but for Windows Terminal.
|
|
5
|
+
|
|
6
|
+
https://github.com/ruby9455/app_management/tree/main/app_management
|
|
7
|
+
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
import shlex
|
|
@@ -13,11 +16,13 @@ import platform
|
|
|
13
16
|
from typing import Dict, List, Optional, Any
|
|
14
17
|
from pathlib import Path
|
|
15
18
|
import logging
|
|
19
|
+
from rich.console import Console
|
|
16
20
|
|
|
17
21
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
18
22
|
|
|
19
23
|
logging.basicConfig(level=logging.INFO)
|
|
20
24
|
logger = logging.getLogger(__name__)
|
|
25
|
+
console = Console()
|
|
21
26
|
TMP_LAYOUT_DIR = Path.home().joinpath("tmp_results", "session_manager", "wt", "layout_manager")
|
|
22
27
|
|
|
23
28
|
# Check if we're on Windows
|
|
@@ -341,7 +346,7 @@ try {{
|
|
|
341
346
|
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
342
347
|
pid = proc.get("pid", "Unknown")
|
|
343
348
|
name = proc.get("name", "Unknown")
|
|
344
|
-
print(f" └─ PID {pid}: {name}")
|
|
349
|
+
console.print(f" [dim]└─[/dim] PID {pid}: {name}")
|
|
345
350
|
print()
|
|
346
351
|
|
|
347
352
|
print("=" * 80)
|
|
@@ -6,6 +6,7 @@ import logging
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TypedDict, Optional, Dict, List, Any
|
|
9
|
+
from rich.console import Console
|
|
9
10
|
from machineconfig.utils.scheduler import Scheduler
|
|
10
11
|
from machineconfig.cluster.sessions_managers.wt_local import WTLayoutGenerator
|
|
11
12
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
@@ -21,6 +22,7 @@ class WTSessionReport(TypedDict):
|
|
|
21
22
|
|
|
22
23
|
logging.basicConfig(level=logging.INFO)
|
|
23
24
|
logger = logging.getLogger(__name__)
|
|
25
|
+
console = Console()
|
|
24
26
|
|
|
25
27
|
TMP_SERIALIZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "wt", "local_manager")
|
|
26
28
|
|
|
@@ -220,11 +222,11 @@ class WTLocalManager:
|
|
|
220
222
|
cmd_text = cmd_status.get("command", "Unknown")[:50]
|
|
221
223
|
if len(cmd_status.get("command", "")) > 50:
|
|
222
224
|
cmd_text += "..."
|
|
223
|
-
print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
225
|
+
console.print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
224
226
|
|
|
225
227
|
if cmd_status.get("processes"):
|
|
226
228
|
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
227
|
-
print(f" └─ PID {proc['pid']}: {proc['name']}")
|
|
229
|
+
console.print(f" [dim]└─[/dim] PID {proc['pid']}: {proc['name']}")
|
|
228
230
|
print()
|
|
229
231
|
|
|
230
232
|
print("=" * 80)
|
|
@@ -4,6 +4,7 @@ import uuid
|
|
|
4
4
|
import logging
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Optional, Any
|
|
7
|
+
from rich.console import Console
|
|
7
8
|
from machineconfig.utils.scheduler import Scheduler
|
|
8
9
|
from machineconfig.cluster.sessions_managers.wt_local import run_command_in_wt_tab
|
|
9
10
|
from machineconfig.cluster.sessions_managers.wt_remote import WTRemoteLayoutGenerator
|
|
@@ -13,6 +14,7 @@ TMP_SERIALIZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "
|
|
|
13
14
|
|
|
14
15
|
# Module-level logger to be used throughout this module
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
17
|
+
console = Console()
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class WTSessionManager:
|
|
@@ -315,11 +317,11 @@ class WTSessionManager:
|
|
|
315
317
|
cmd_text = cmd_status.get("command", "Unknown")[:50]
|
|
316
318
|
if len(cmd_status.get("command", "")) > 50:
|
|
317
319
|
cmd_text += "..."
|
|
318
|
-
print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
320
|
+
console.print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
319
321
|
|
|
320
322
|
if cmd_status.get("processes"):
|
|
321
323
|
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
322
|
-
print(f" └─ PID {proc.get('pid', 'Unknown')}: {proc.get('name', 'Unknown')}")
|
|
324
|
+
console.print(f" [dim]└─[/dim] PID {proc.get('pid', 'Unknown')}: {proc.get('name', 'Unknown')}")
|
|
323
325
|
print()
|
|
324
326
|
|
|
325
327
|
print("=" * 80)
|
|
@@ -5,11 +5,13 @@ Status reporting utilities for Windows Terminal layouts and sessions.
|
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Dict, Any, List
|
|
8
|
+
from rich.console import Console
|
|
8
9
|
from machineconfig.cluster.sessions_managers.wt_utils.process_monitor import WTProcessMonitor
|
|
9
10
|
from machineconfig.cluster.sessions_managers.wt_utils.session_manager import WTSessionManager
|
|
10
11
|
from machineconfig.utils.schemas.layouts.layout_types import TabConfig
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
14
|
+
console = Console()
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class WTStatusReporter:
|
|
@@ -82,11 +84,11 @@ class WTStatusReporter:
|
|
|
82
84
|
for proc in processes:
|
|
83
85
|
pid = proc.get("pid", "Unknown")
|
|
84
86
|
name = proc.get("name", "Unknown")
|
|
85
|
-
print(f" └─ PID {pid}: {name}")
|
|
87
|
+
console.print(f" [dim]└─[/dim] PID {pid}: {name}")
|
|
86
88
|
|
|
87
89
|
if len(cmd_status["processes"]) > 3:
|
|
88
90
|
remaining = len(cmd_status["processes"]) - 3
|
|
89
|
-
print(f" └─ ... and {remaining} more processes")
|
|
91
|
+
console.print(f" [dim]└─[/dim] ... and {remaining} more processes")
|
|
90
92
|
|
|
91
93
|
if cmd_status.get("error"):
|
|
92
94
|
print(f" Error: {cmd_status['error']}")
|
|
@@ -245,7 +245,7 @@ class ZellijLocalManager:
|
|
|
245
245
|
|
|
246
246
|
if cmd_status.get("processes"):
|
|
247
247
|
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
248
|
-
print(f" └─ PID {proc['pid']}: {proc['name']} ({proc['status']})")
|
|
248
|
+
console.print(f" [dim]└─[/dim] PID {proc['pid']}: {proc['name']} ({proc['status']})")
|
|
249
249
|
print()
|
|
250
250
|
|
|
251
251
|
print("=" * 80)
|
|
@@ -5,11 +5,13 @@ Status reporting utilities for Zellij remote layouts.
|
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Dict, Any
|
|
8
|
+
from rich.console import Console
|
|
8
9
|
from machineconfig.cluster.sessions_managers.zellij_utils.process_monitor import ProcessMonitor
|
|
9
10
|
from machineconfig.cluster.sessions_managers.zellij_utils.session_manager import SessionManager
|
|
10
11
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
14
|
+
console = Console()
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class StatusReporter:
|
|
@@ -70,7 +72,7 @@ class StatusReporter:
|
|
|
70
72
|
print(f"✅ {tab_name}: Running on {remote_name}")
|
|
71
73
|
if cmd_status.get("processes"):
|
|
72
74
|
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
73
|
-
print(f" └─ PID {proc['pid']}: {proc['name']} ({proc['status']})")
|
|
75
|
+
console.print(f" [dim]└─[/dim] PID {proc['pid']}: {proc['name']} ({proc['status']})")
|
|
74
76
|
else:
|
|
75
77
|
print(f"❌ {tab_name}: Not running on {remote_name}")
|
|
76
78
|
print(f" Command: {cmd_status.get('command', 'Unknown')}")
|
machineconfig/profile/create.py
CHANGED
|
@@ -11,18 +11,15 @@ from rich.text import Text
|
|
|
11
11
|
from rich.table import Table
|
|
12
12
|
|
|
13
13
|
from machineconfig.utils.path_extended import PathExtended
|
|
14
|
-
from machineconfig.utils.links import
|
|
15
|
-
from machineconfig.utils.
|
|
16
|
-
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, REPO_ROOT
|
|
17
|
-
from machineconfig.profile.shell import create_default_shell_profile
|
|
14
|
+
from machineconfig.utils.links import symlink_map, copy_map
|
|
15
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
18
16
|
|
|
19
17
|
import platform
|
|
20
|
-
import os
|
|
21
|
-
import ctypes
|
|
22
18
|
import subprocess
|
|
23
19
|
import tomllib
|
|
24
20
|
from typing import Optional, Any, TypedDict, Literal
|
|
25
21
|
|
|
22
|
+
|
|
26
23
|
system = platform.system() # Linux or Windows
|
|
27
24
|
ERROR_LIST: list[Any] = [] # append to this after every exception captured.
|
|
28
25
|
SYSTEM = system.lower()
|
|
@@ -38,10 +35,43 @@ def get_other_systems(current_system: str) -> list[str]:
|
|
|
38
35
|
OTHER_SYSTEMS = get_other_systems(SYSTEM)
|
|
39
36
|
|
|
40
37
|
|
|
41
|
-
class
|
|
38
|
+
class Base(TypedDict):
|
|
42
39
|
this: str
|
|
43
40
|
to_this: str
|
|
44
41
|
contents: Optional[bool]
|
|
42
|
+
copy: Optional[bool]
|
|
43
|
+
|
|
44
|
+
class ConfigMapper(TypedDict):
|
|
45
|
+
file_name: str
|
|
46
|
+
config_file_default_path: str
|
|
47
|
+
self_managed_config_file_path: str
|
|
48
|
+
contents: Optional[bool]
|
|
49
|
+
copy: Optional[bool]
|
|
50
|
+
class MapperFileData(TypedDict):
|
|
51
|
+
public: dict[str, list[ConfigMapper]]
|
|
52
|
+
private: dict[str, list[ConfigMapper]]
|
|
53
|
+
def read_mapper() -> MapperFileData:
|
|
54
|
+
mapper_data: dict[str, dict[str, Base]] = tomllib.loads(LIBRARY_ROOT.joinpath("profile/mapper.toml").read_text(encoding="utf-8"))
|
|
55
|
+
public: dict[str, list[ConfigMapper]] = {}
|
|
56
|
+
private: dict[str, list[ConfigMapper]] = {}
|
|
57
|
+
for program_key, program_map in mapper_data.items():
|
|
58
|
+
for file_name, file_base in program_map.items():
|
|
59
|
+
file_map: ConfigMapper = {
|
|
60
|
+
"file_name": file_name,
|
|
61
|
+
"config_file_default_path": file_base["this"],
|
|
62
|
+
"self_managed_config_file_path": file_base["to_this"],
|
|
63
|
+
"contents": file_base.get("contents"),
|
|
64
|
+
"copy": file_base.get("copy"),
|
|
65
|
+
}
|
|
66
|
+
if "LIBRARY_ROOT" in file_map["self_managed_config_file_path"]:
|
|
67
|
+
if program_key not in public:
|
|
68
|
+
public[program_key] = []
|
|
69
|
+
public[program_key].append(file_map)
|
|
70
|
+
else:
|
|
71
|
+
if program_key not in private:
|
|
72
|
+
private[program_key] = []
|
|
73
|
+
private[program_key].append(file_map)
|
|
74
|
+
return {"public": public, "private": private}
|
|
45
75
|
|
|
46
76
|
|
|
47
77
|
class OperationRecord(TypedDict):
|
|
@@ -69,156 +99,113 @@ class OperationRecord(TypedDict):
|
|
|
69
99
|
status: str
|
|
70
100
|
|
|
71
101
|
|
|
72
|
-
def apply_mapper(
|
|
73
|
-
|
|
74
|
-
|
|
102
|
+
def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
103
|
+
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"],
|
|
104
|
+
method: Literal["symlink", "copy"]
|
|
105
|
+
):
|
|
75
106
|
operation_records: list[OperationRecord] = []
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return # terminate function.
|
|
91
|
-
elif len(choice_selected) == 1 and choice_selected[0] == "all":
|
|
92
|
-
choice_selected = "all" # i.e. program_keys = program_keys
|
|
93
|
-
else:
|
|
94
|
-
choice_selected = choice
|
|
95
|
-
|
|
96
|
-
if isinstance(choice_selected, str):
|
|
97
|
-
if str(choice_selected) == "all" and system == "Windows":
|
|
98
|
-
if os.name == "nt":
|
|
99
|
-
try:
|
|
100
|
-
is_admin = ctypes.windll.shell32.IsUserAnAdmin()
|
|
101
|
-
except Exception:
|
|
102
|
-
is_admin = False
|
|
103
|
-
else:
|
|
104
|
-
is_admin = False
|
|
105
|
-
if not is_admin:
|
|
106
|
-
warning_body = "\n".join([
|
|
107
|
-
"[bold yellow]Administrator privileges required[/]",
|
|
108
|
-
"Run the terminal as admin and try again to avoid repeated elevation prompts.",
|
|
109
|
-
])
|
|
110
|
-
console.print(
|
|
111
|
-
Panel.fit(
|
|
112
|
-
warning_body,
|
|
113
|
-
title="⚠️ Permission Needed",
|
|
114
|
-
border_style="yellow",
|
|
115
|
-
padding=(1, 2),
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
raise RuntimeError("Run terminal as admin and try again, otherwise, there will be too many popups for admin requests and no chance to terminate the program.")
|
|
119
|
-
elif choice_selected == "all":
|
|
108
|
+
import os
|
|
109
|
+
if os.name == "nt":
|
|
110
|
+
import ctypes
|
|
111
|
+
try:
|
|
112
|
+
is_admin = ctypes.windll.shell32.IsUserAnAdmin()
|
|
113
|
+
except Exception:
|
|
114
|
+
is_admin = False
|
|
115
|
+
total_length = sum(len(item) for item in mapper_data.values())
|
|
116
|
+
if not is_admin and method == "symlink" and total_length > 5:
|
|
117
|
+
warning_body = "\n".join([
|
|
118
|
+
"[bold yellow]Administrator privileges required[/]",
|
|
119
|
+
"Run the terminal as admin and try again to avoid repeated elevation prompts.",
|
|
120
|
+
])
|
|
120
121
|
console.print(
|
|
121
|
-
Panel(
|
|
122
|
-
|
|
123
|
-
title="
|
|
124
|
-
border_style="
|
|
122
|
+
Panel.fit(
|
|
123
|
+
warning_body,
|
|
124
|
+
title="⚠️ Permission Needed",
|
|
125
|
+
border_style="yellow",
|
|
125
126
|
padding=(1, 2),
|
|
126
127
|
)
|
|
127
128
|
)
|
|
128
|
-
|
|
129
|
-
else:
|
|
130
|
-
program_keys = [choice_selected]
|
|
131
|
-
else:
|
|
132
|
-
program_keys = choice_selected
|
|
129
|
+
raise RuntimeError("Run terminal as admin and try again, otherwise, there will be too many popups for admin requests and no chance to terminate the program.")
|
|
133
130
|
|
|
134
|
-
for
|
|
135
|
-
console.rule(f"🔄 Processing [bold]{
|
|
136
|
-
for
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
for program_name, program_files in mapper_data.items():
|
|
132
|
+
console.rule(f"🔄 Processing [bold]{program_name}[/] symlinks", style="cyan")
|
|
133
|
+
for a_mapper in program_files:
|
|
134
|
+
config_file_default_path = PathExtended(a_mapper["config_file_default_path"])
|
|
135
|
+
self_managed_config_file_path = PathExtended(a_mapper["self_managed_config_file_path"].replace("LIBRARY_ROOT", LIBRARY_ROOT.as_posix()))
|
|
136
|
+
|
|
137
|
+
# Determine whether to use copy or symlink
|
|
138
|
+
use_copy = method == "copy" or "copy" in a_mapper
|
|
139
139
|
|
|
140
|
-
if "contents" in
|
|
140
|
+
if "contents" in a_mapper:
|
|
141
141
|
try:
|
|
142
|
-
targets = list(
|
|
142
|
+
targets = list(self_managed_config_file_path.expanduser().search("*"))
|
|
143
143
|
for a_target in targets:
|
|
144
|
-
|
|
144
|
+
if use_copy:
|
|
145
|
+
result = copy_map(config_file_default_path=config_file_default_path.joinpath(a_target.name), self_managed_config_file_path=a_target, on_conflict=on_conflict)
|
|
146
|
+
operation_type = "contents_copy"
|
|
147
|
+
else:
|
|
148
|
+
result = symlink_map(config_file_default_path=config_file_default_path.joinpath(a_target.name), self_managed_config_file_path=a_target, on_conflict=on_conflict)
|
|
149
|
+
operation_type = "contents_symlink"
|
|
145
150
|
operation_records.append({
|
|
146
|
-
"program":
|
|
147
|
-
"file_key":
|
|
148
|
-
"source": str(
|
|
151
|
+
"program": program_name,
|
|
152
|
+
"file_key": a_mapper["file_name"],
|
|
153
|
+
"source": str(config_file_default_path.joinpath(a_target.name)),
|
|
149
154
|
"target": str(a_target),
|
|
150
|
-
"operation":
|
|
155
|
+
"operation": operation_type,
|
|
151
156
|
"action": result["action"],
|
|
152
157
|
"details": result["details"],
|
|
153
158
|
"status": "success"
|
|
154
159
|
})
|
|
155
160
|
except Exception as ex:
|
|
156
|
-
console.print(f"❌ [red]Config error[/red]: {
|
|
161
|
+
console.print(f"❌ [red]Config error[/red]: {program_name} | {a_mapper['file_name']} | missing keys 'config_file_default_path ==> self_managed_config_file_path'. {ex}")
|
|
157
162
|
operation_records.append({
|
|
158
|
-
"program":
|
|
159
|
-
"file_key":
|
|
160
|
-
"source": str(
|
|
161
|
-
"target": str(
|
|
162
|
-
"operation": "contents_symlink",
|
|
163
|
+
"program": program_name,
|
|
164
|
+
"file_key": a_mapper["file_name"],
|
|
165
|
+
"source": str(config_file_default_path),
|
|
166
|
+
"target": str(self_managed_config_file_path),
|
|
167
|
+
"operation": "contents_symlink" if not use_copy else "contents_copy",
|
|
163
168
|
"action": "error",
|
|
164
169
|
"details": f"Failed to process contents: {str(ex)}",
|
|
165
170
|
"status": f"error: {str(ex)}"
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
elif "copy" in file_map:
|
|
169
|
-
try:
|
|
170
|
-
result = symlink_copy(this=this, to_this=to_this, prioritize_to_this=prioritize_to_this)
|
|
171
|
-
operation_records.append({
|
|
172
|
-
"program": program_key,
|
|
173
|
-
"file_key": file_key,
|
|
174
|
-
"source": str(this),
|
|
175
|
-
"target": str(to_this),
|
|
176
|
-
"operation": "copy",
|
|
177
|
-
"action": result["action"],
|
|
178
|
-
"details": result["details"],
|
|
179
|
-
"status": "success"
|
|
180
|
-
})
|
|
181
|
-
except Exception as ex:
|
|
182
|
-
console.print(f"❌ [red]Config error[/red]: {program_key} | {file_key} | {ex}")
|
|
183
|
-
operation_records.append({
|
|
184
|
-
"program": program_key,
|
|
185
|
-
"file_key": file_key,
|
|
186
|
-
"source": str(this),
|
|
187
|
-
"target": str(to_this),
|
|
188
|
-
"operation": "copy",
|
|
189
|
-
"action": "error",
|
|
190
|
-
"details": f"Failed to copy: {str(ex)}",
|
|
191
|
-
"status": f"error: {str(ex)}"
|
|
192
|
-
})
|
|
171
|
+
})
|
|
193
172
|
else:
|
|
194
173
|
try:
|
|
195
|
-
|
|
174
|
+
if use_copy:
|
|
175
|
+
result = copy_map(config_file_default_path=config_file_default_path, self_managed_config_file_path=self_managed_config_file_path, on_conflict=on_conflict)
|
|
176
|
+
operation_type = "copy"
|
|
177
|
+
else:
|
|
178
|
+
result = symlink_map(config_file_default_path=config_file_default_path, self_managed_config_file_path=self_managed_config_file_path, on_conflict=on_conflict)
|
|
179
|
+
operation_type = "symlink"
|
|
196
180
|
operation_records.append({
|
|
197
|
-
"program":
|
|
198
|
-
"file_key":
|
|
199
|
-
"source": str(
|
|
200
|
-
"target": str(
|
|
201
|
-
"operation":
|
|
181
|
+
"program": program_name,
|
|
182
|
+
"file_key": a_mapper["file_name"],
|
|
183
|
+
"source": str(config_file_default_path),
|
|
184
|
+
"target": str(self_managed_config_file_path),
|
|
185
|
+
"operation": operation_type,
|
|
202
186
|
"action": result["action"],
|
|
203
187
|
"details": result["details"],
|
|
204
188
|
"status": "success"
|
|
205
189
|
})
|
|
206
190
|
except Exception as ex:
|
|
207
|
-
console.print(f"❌ [red]Config error[/red]: {
|
|
191
|
+
console.print(f"❌ [red]Config error[/red]: {program_name} | {a_mapper['file_name']} | missing keys 'config_file_default_path ==> self_managed_config_file_path'. {ex}")
|
|
208
192
|
operation_records.append({
|
|
209
|
-
"program":
|
|
210
|
-
"file_key":
|
|
211
|
-
"source": str(
|
|
212
|
-
"target": str(
|
|
213
|
-
"operation": "symlink",
|
|
193
|
+
"program": program_name,
|
|
194
|
+
"file_key": a_mapper["file_name"],
|
|
195
|
+
"source": str(config_file_default_path),
|
|
196
|
+
"target": str(self_managed_config_file_path),
|
|
197
|
+
"operation": "symlink" if not use_copy else "copy",
|
|
214
198
|
"action": "error",
|
|
215
|
-
"details": f"Failed to create symlink: {str(ex)}",
|
|
199
|
+
"details": f"Failed to create {'symlink' if not use_copy else 'copy'}: {str(ex)}",
|
|
216
200
|
"status": f"error: {str(ex)}"
|
|
217
201
|
})
|
|
218
202
|
|
|
219
|
-
if
|
|
203
|
+
if program_name == "ssh" and system == "Linux": # permissions of ~/dotfiles/.ssh should be adjusted
|
|
220
204
|
try:
|
|
221
205
|
console.print("\n[bold]🔒 Setting secure permissions for SSH files...[/bold]")
|
|
206
|
+
# run_shell_script("sudo chmod 600 $HOME/.ssh/*")
|
|
207
|
+
# run_shell_script("sudo chmod 700 $HOME/.ssh")
|
|
208
|
+
|
|
222
209
|
subprocess.run("chmod 700 ~/.ssh/", check=True)
|
|
223
210
|
subprocess.run("chmod 700 ~/dotfiles/creds/.ssh/", check=True) # may require sudo
|
|
224
211
|
subprocess.run("chmod 600 ~/dotfiles/creds/.ssh/*", check=True)
|
|
@@ -280,24 +267,5 @@ def apply_mapper(choice: Optional[str], prioritize_to_this: bool):
|
|
|
280
267
|
)
|
|
281
268
|
|
|
282
269
|
|
|
283
|
-
def main_symlinks():
|
|
284
|
-
console.print("")
|
|
285
|
-
console.rule("[bold blue]🔗 CREATING SYMLINKS 🔗")
|
|
286
|
-
apply_mapper(choice="all", prioritize_to_this=True)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def main_profile():
|
|
290
|
-
console.print("")
|
|
291
|
-
console.rule("[bold green]🐚 CREATING SHELL PROFILE 🐚")
|
|
292
|
-
create_default_shell_profile()
|
|
293
|
-
console.print(
|
|
294
|
-
Panel.fit(
|
|
295
|
-
Text("✨ Configuration setup complete! ✨", justify="center"),
|
|
296
|
-
title="Profile Setup",
|
|
297
|
-
border_style="green",
|
|
298
|
-
)
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
270
|
if __name__ == "__main__":
|
|
303
271
|
pass
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
import typer
|
|
3
|
+
from typing import Optional, Literal
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main_public_from_parser(method: Literal["symlink", "copy"] = typer.Option(..., help="Method to use for setting up the config file."),
|
|
8
|
+
on_conflict: Literal["throwError", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option(..., help="Action to take on conflict"),
|
|
9
|
+
which: Optional[str] = typer.Option(None, help="Specific items to process"),
|
|
10
|
+
interactive: bool = typer.Option(False, help="Run in interactive mode")):
|
|
11
|
+
"""Terminology:
|
|
12
|
+
SOURCE = Self-Managed-Config-File-Path
|
|
13
|
+
TARGET = Config-File-Default-Path
|
|
14
|
+
For public config files, the source always exists, because we know it comes from machineconfig repo."""
|
|
15
|
+
from machineconfig.profile.create import ConfigMapper, read_mapper
|
|
16
|
+
if method == "symlink":
|
|
17
|
+
machineconfig_repo_path = Path.home().joinpath("code/machineconfig")
|
|
18
|
+
if not machineconfig_repo_path.exists() or not machineconfig_repo_path.is_dir():
|
|
19
|
+
raise FileNotFoundError(f"machineconfig repo not found at {machineconfig_repo_path}. Cannot create symlinks to non-existing source files.")
|
|
20
|
+
|
|
21
|
+
mapper_full = read_mapper()["public"]
|
|
22
|
+
if which is None:
|
|
23
|
+
assert interactive is True
|
|
24
|
+
from machineconfig.utils.options import choose_from_options
|
|
25
|
+
items_chosen = choose_from_options(msg="Which symlink to create?", options=list(mapper_full.keys()), fzf=True, multi=True)
|
|
26
|
+
else:
|
|
27
|
+
assert interactive is False
|
|
28
|
+
if which == "all":
|
|
29
|
+
items_chosen = list(mapper_full.keys())
|
|
30
|
+
else:
|
|
31
|
+
items_chosen = which.split(",")
|
|
32
|
+
items_objections: dict[str, list[ConfigMapper]] = {item: mapper_full[item] for item in items_chosen if item in mapper_full}
|
|
33
|
+
|
|
34
|
+
from machineconfig.profile.create import apply_mapper
|
|
35
|
+
apply_mapper(mapper_data=items_objections, on_conflict=on_conflict, method=method)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main_private_from_parser(method: Literal["symlink", "copy"] = typer.Option(..., help="Method to use for linking files"),
|
|
39
|
+
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", help="Action to take on conflict"),
|
|
40
|
+
which: Optional[str] = typer.Option(None, help="Specific items to process"),
|
|
41
|
+
interactive: bool = typer.Option(False, help="Run in interactive mode")):
|
|
42
|
+
from machineconfig.profile.create import ConfigMapper, read_mapper
|
|
43
|
+
|
|
44
|
+
mapper_full = read_mapper()["private"]
|
|
45
|
+
if which is None:
|
|
46
|
+
assert interactive is True
|
|
47
|
+
from machineconfig.utils.options import choose_from_options
|
|
48
|
+
items_chosen = choose_from_options(msg="Which symlink to create?", options=list(mapper_full.keys()), fzf=True, multi=True)
|
|
49
|
+
else:
|
|
50
|
+
assert interactive is False
|
|
51
|
+
if which == "all":
|
|
52
|
+
items_chosen = list(mapper_full.keys())
|
|
53
|
+
else:
|
|
54
|
+
items_chosen = which.split(",")
|
|
55
|
+
items_objections: dict[str, list[ConfigMapper]] = {item: mapper_full[item] for item in items_chosen if item in mapper_full}
|
|
56
|
+
|
|
57
|
+
from machineconfig.profile.create import apply_mapper
|
|
58
|
+
apply_mapper(mapper_data=items_objections, on_conflict=on_conflict, method=method)
|
machineconfig/profile/shell.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""shell"""
|
|
2
2
|
|
|
3
|
+
from typing import Literal
|
|
3
4
|
from machineconfig.utils.path_extended import PathExtended
|
|
4
|
-
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
5
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, CONFIG_PATH
|
|
5
6
|
|
|
6
7
|
import platform
|
|
7
8
|
import os
|
|
@@ -35,25 +36,60 @@ def get_shell_profile_path() -> PathExtended:
|
|
|
35
36
|
return profile_path
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def create_default_shell_profile() -> None:
|
|
39
|
+
def create_default_shell_profile(method: Literal["copy", "reference"]) -> None:
|
|
40
|
+
if method == "reference":
|
|
41
|
+
machineconfig_repo_path = PathExtended.home().joinpath("code/machineconfig")
|
|
42
|
+
if not machineconfig_repo_path.exists() or not machineconfig_repo_path.is_dir():
|
|
43
|
+
raise FileNotFoundError(f"machineconfig repo not found at {machineconfig_repo_path}. Cannot create symlinks to non-existing source files.")
|
|
44
|
+
|
|
39
45
|
shell_profile_path = get_shell_profile_path()
|
|
40
46
|
shell_profile = shell_profile_path.read_text(encoding="utf-8")
|
|
47
|
+
|
|
41
48
|
if system == "Windows":
|
|
42
|
-
|
|
49
|
+
init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/pwsh/init.ps1")
|
|
50
|
+
|
|
51
|
+
init_script_copy_path = PathExtended(CONFIG_PATH).joinpath("profile/init.ps1").collapseuser()
|
|
52
|
+
init_script_copy_path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
init_script.copy(path=init_script_copy_path, overwrite=True)
|
|
54
|
+
|
|
55
|
+
source_using_copy = f""". {str(init_script_copy_path).replace("~", "$HOME")}"""
|
|
56
|
+
source_using_reference = f""". {str(init_script.collapseuser()).replace("~", "$HOME")}"""
|
|
43
57
|
else:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/bash/init.sh")
|
|
59
|
+
init_script_copy_path = PathExtended(CONFIG_PATH).joinpath("profile/init.sh").collapseuser()
|
|
60
|
+
init_script_copy_path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
init_script.copy(path=init_script_copy_path, overwrite=True)
|
|
62
|
+
|
|
63
|
+
source_using_reference = f"""source {str(init_script.collapseuser()).replace("~", "$HOME")}"""
|
|
64
|
+
source_using_copy = f"""source {str(init_script_copy_path).replace("~", "$HOME")}"""
|
|
65
|
+
|
|
66
|
+
match method:
|
|
67
|
+
case "copy":
|
|
68
|
+
line_of_interest = source_using_copy
|
|
69
|
+
line_other = source_using_reference
|
|
70
|
+
case "reference":
|
|
71
|
+
line_of_interest = source_using_reference
|
|
72
|
+
line_other = source_using_copy
|
|
73
|
+
|
|
74
|
+
was_shell_updated = False
|
|
75
|
+
if line_other in shell_profile: # always remove this irrelevant line
|
|
76
|
+
shell_profile = shell_profile.replace(line_other, "")
|
|
77
|
+
was_shell_updated = True
|
|
78
|
+
|
|
79
|
+
if line_of_interest in shell_profile:
|
|
80
|
+
console.print(Panel("🔄 PROFILE | Skipping init script sourcing - already present in profile", title="[bold blue]Profile[/bold blue]", border_style="blue"))
|
|
47
81
|
else:
|
|
48
82
|
console.print(Panel("📝 PROFILE | Adding init script sourcing to profile", title="[bold blue]Profile[/bold blue]", border_style="blue"))
|
|
49
|
-
shell_profile += "\n" +
|
|
83
|
+
shell_profile += "\n" + line_of_interest + "\n"
|
|
50
84
|
if system == "Linux":
|
|
51
85
|
result = subprocess.run(["cat", "/proc/version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
|
|
52
86
|
if result.returncode == 0 and result.stdout:
|
|
53
87
|
version_info = result.stdout.lower()
|
|
54
88
|
if "microsoft" in version_info or "wsl" in version_info:
|
|
55
|
-
shell_profile += "\ncd
|
|
56
|
-
console.print("📌 WSL detected - adding 'cd
|
|
89
|
+
shell_profile += "\ncd $HOME"
|
|
90
|
+
console.print("📌 WSL detected - adding 'cd $HOME' to profile to avoid Windows filesystem")
|
|
91
|
+
was_shell_updated = True
|
|
92
|
+
if was_shell_updated:
|
|
57
93
|
shell_profile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
94
|
shell_profile_path.write_text(shell_profile, encoding="utf-8")
|
|
59
95
|
console.print(Panel("✅ Profile updated successfully", title="[bold blue]Profile[/bold blue]", border_style="blue"))
|
|
@@ -2,4 +2,12 @@ from pathlib import Path
|
|
|
2
2
|
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
3
3
|
|
|
4
4
|
def get_generic_instructions_path() -> Path:
|
|
5
|
-
|
|
5
|
+
path = LIBRARY_ROOT.joinpath("scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md")
|
|
6
|
+
text = path.read_text(encoding="utf-8")
|
|
7
|
+
import platform
|
|
8
|
+
if platform.system().lower() == "windows":
|
|
9
|
+
text = text.replace("bash", "powershell").replace(".sh", ".ps1")
|
|
10
|
+
import tempfile
|
|
11
|
+
temp_path = Path(tempfile.gettempdir()).joinpath("generic_instructions.md")
|
|
12
|
+
temp_path.write_text(data=text, encoding="utf-8")
|
|
13
|
+
return temp_path
|
|
@@ -34,7 +34,7 @@ applyTo: "**/*.py"
|
|
|
34
34
|
* when finished, run a linting static analysis check against files you touched, Any fix any mistakes.
|
|
35
35
|
* Please run `uv run -m pyright $file_touched` and address all issues. if `pyright is not there, first run `uv add pyright --dev`.
|
|
36
36
|
* For all type checkers and linters, like mypy, pyright, pyrefly and pylint, there are config files at different levels of the repo all the way up to home directory level. You don't need to worry about them, just be mindful that they exist. The tools themselves will respect the configs therein.
|
|
37
|
-
* If you want to run all linters and pycheckers agains the entire project to make sure everything is clean, I prepared a nice shell script, you can run it from the repo root as `./.scripts/
|
|
37
|
+
* If you want to run all linters and pycheckers agains the entire project to make sure everything is clean, I prepared a nice shell script, you can run it from the repo root as `./.scripts/lint_and_type_check.sh`. It will produce markdown files that are you are meant to look at @ ./.linters/*.md
|
|
38
38
|
|
|
39
39
|
# General Programming Ethos:
|
|
40
40
|
* Make sure all the code is rigorous, no lazy stuff.
|