machineconfig 5.19__py3-none-any.whl → 5.21__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/create.py +84 -66
- machineconfig/scripts/python/devops.py +29 -15
- machineconfig/scripts/python/devops_update_repos.py +0 -2
- machineconfig/scripts/python/fire_jobs.py +15 -50
- machineconfig/scripts/python/fire_jobs_route_helper.py +46 -0
- machineconfig/scripts/python/interactive.py +11 -13
- machineconfig/scripts/python/repos.py +10 -10
- machineconfig/scripts/python/sessions.py +7 -10
- machineconfig/scripts/python/sessions_multiprocess.py +56 -0
- machineconfig/utils/links.py +137 -101
- {machineconfig-5.19.dist-info → machineconfig-5.21.dist-info}/METADATA +1 -1
- {machineconfig-5.19.dist-info → machineconfig-5.21.dist-info}/RECORD +18 -17
- {machineconfig-5.19.dist-info → machineconfig-5.21.dist-info}/WHEEL +0 -0
- {machineconfig-5.19.dist-info → machineconfig-5.21.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.19.dist-info → machineconfig-5.21.dist-info}/top_level.txt +0 -0
|
@@ -19,8 +19,8 @@ for better user experience with checkbox selections.
|
|
|
19
19
|
|
|
20
20
|
import sys
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from platform import system
|
|
23
22
|
from typing import cast
|
|
23
|
+
import platform
|
|
24
24
|
|
|
25
25
|
import questionary
|
|
26
26
|
from questionary import Choice
|
|
@@ -80,14 +80,14 @@ def get_installation_choices() -> list[str]:
|
|
|
80
80
|
Choice(value="retrieve_data", title="💾 Retrieve Data - Backup restoration", checked=False),
|
|
81
81
|
]
|
|
82
82
|
# Add Windows-specific options
|
|
83
|
-
if system() == "Windows":
|
|
83
|
+
if platform.system() == "Windows":
|
|
84
84
|
choices.append(Choice(value="install_windows_desktop", title="💻 Install Windows Desktop Apps - Brave, Windows Terminal, PowerShell, VSCode (Windows only)", checked=False))
|
|
85
85
|
selected = questionary.checkbox("Select the installation options you want to execute:", choices=choices, show_description=True).ask()
|
|
86
86
|
return selected or []
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def execute_installations(selected_options: list[str]) -> None:
|
|
90
|
-
if system() == "Windows":
|
|
90
|
+
if platform.system() == "Windows":
|
|
91
91
|
from machineconfig import setup_windows as module
|
|
92
92
|
script_path = Path(module.__file__).parent / "ve.ps1"
|
|
93
93
|
run_shell_script(script_path.read_text(encoding="utf-8"))
|
|
@@ -109,26 +109,24 @@ def execute_installations(selected_options: list[str]) -> None:
|
|
|
109
109
|
run_shell_script(". $HOME/.bashrc")
|
|
110
110
|
|
|
111
111
|
if "upgrade_system" in selected_options:
|
|
112
|
-
if system() == "Windows":
|
|
112
|
+
if platform.system() == "Windows":
|
|
113
113
|
console.print("❌ System upgrade is not applicable on Windows via this script.", style="bold red")
|
|
114
|
-
elif system() == "Linux":
|
|
114
|
+
elif platform.system() == "Linux":
|
|
115
115
|
console.print(Panel("🔄 [bold magenta]SYSTEM UPDATE[/bold magenta]\n[italic]Package management[/italic]", border_style="magenta"))
|
|
116
116
|
run_shell_script("sudo nala upgrade -y")
|
|
117
117
|
else:
|
|
118
|
-
console.print(f"❌ System upgrade not supported on {system()}.", style="bold red")
|
|
118
|
+
console.print(f"❌ System upgrade not supported on {platform.system()}.", style="bold red")
|
|
119
119
|
if "install_repos" in selected_options:
|
|
120
120
|
console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Virtual environment setup[/italic]", border_style="green"))
|
|
121
|
-
if system() == "Windows":
|
|
122
|
-
from machineconfig import
|
|
123
|
-
script_path = Path(module.__file__).parent / "repos.ps1"
|
|
121
|
+
if platform.system() == "Windows":
|
|
122
|
+
from machineconfig.setup_windows import REPOS
|
|
124
123
|
else:
|
|
125
|
-
from machineconfig import
|
|
126
|
-
|
|
127
|
-
run_shell_script(script_path.read_text(encoding="utf-8"))
|
|
124
|
+
from machineconfig.setup_linux import REPOS
|
|
125
|
+
run_shell_script(REPOS.read_text(encoding="utf-8"))
|
|
128
126
|
|
|
129
127
|
if "install_ssh_server" in selected_options:
|
|
130
128
|
console.print(Panel("🔒 [bold red]SSH SERVER[/bold red]\n[italic]Remote access setup[/italic]", border_style="red"))
|
|
131
|
-
if system() == "Windows":
|
|
129
|
+
if platform.system() == "Windows":
|
|
132
130
|
powershell_script = """Write-Host "🔧 Installing and configuring SSH server..."
|
|
133
131
|
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
|
134
132
|
Start-Service sshd
|
|
@@ -35,7 +35,7 @@ CloudOption = Annotated[
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
@app.command()
|
|
38
|
+
@app.command(no_args_is_help=True)
|
|
39
39
|
def push(directory: DirectoryArgument = None,
|
|
40
40
|
recursive: RecursiveOption = False,
|
|
41
41
|
no_sync: NoSyncOption = False,
|
|
@@ -43,7 +43,7 @@ def push(directory: DirectoryArgument = None,
|
|
|
43
43
|
"""🚀 Push changes across repositories."""
|
|
44
44
|
from machineconfig.scripts.python.repos_helper import git_operations
|
|
45
45
|
git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, no_sync=no_sync)
|
|
46
|
-
@app.command()
|
|
46
|
+
@app.command(no_args_is_help=True)
|
|
47
47
|
def pull(
|
|
48
48
|
directory: DirectoryArgument = None,
|
|
49
49
|
recursive: RecursiveOption = False,
|
|
@@ -52,7 +52,7 @@ def pull(
|
|
|
52
52
|
"""⬇️ Pull changes across repositories."""
|
|
53
53
|
from machineconfig.scripts.python.repos_helper import git_operations
|
|
54
54
|
git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, no_sync=no_sync)
|
|
55
|
-
@app.command()
|
|
55
|
+
@app.command(no_args_is_help=True)
|
|
56
56
|
def commit(
|
|
57
57
|
directory: DirectoryArgument = None,
|
|
58
58
|
recursive: RecursiveOption = False,
|
|
@@ -61,8 +61,8 @@ def commit(
|
|
|
61
61
|
"""💾 Commit changes across repositories."""
|
|
62
62
|
from machineconfig.scripts.python.repos_helper import git_operations
|
|
63
63
|
git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, no_sync=no_sync)
|
|
64
|
-
@app.command()
|
|
65
|
-
def
|
|
64
|
+
@app.command(no_args_is_help=True)
|
|
65
|
+
def cleanup(
|
|
66
66
|
directory: DirectoryArgument = None,
|
|
67
67
|
recursive: RecursiveOption = False,
|
|
68
68
|
no_sync: NoSyncOption = False,
|
|
@@ -72,7 +72,7 @@ def all(
|
|
|
72
72
|
git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, no_sync=no_sync)
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
@sync_app.command()
|
|
75
|
+
@sync_app.command(no_args_is_help=True)
|
|
76
76
|
def capture(
|
|
77
77
|
directory: DirectoryArgument = None,
|
|
78
78
|
cloud: CloudOption = None,
|
|
@@ -86,7 +86,7 @@ def capture(
|
|
|
86
86
|
from machineconfig.utils.path_extended import PathExtended
|
|
87
87
|
if cloud is not None:
|
|
88
88
|
PathExtended(save_path).to_cloud(rel2home=True, cloud=cloud)
|
|
89
|
-
@sync_app.command()
|
|
89
|
+
@sync_app.command(no_args_is_help=True)
|
|
90
90
|
def clone(
|
|
91
91
|
directory: DirectoryArgument = None,
|
|
92
92
|
cloud: CloudOption = None,
|
|
@@ -97,7 +97,7 @@ def clone(
|
|
|
97
97
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=False)
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
@sync_app.command(name="checkout-to-commit")
|
|
100
|
+
@sync_app.command(name="checkout-to-commit", no_args_is_help=True)
|
|
101
101
|
def checkout_command(
|
|
102
102
|
directory: DirectoryArgument = None,
|
|
103
103
|
cloud: CloudOption = None,
|
|
@@ -108,7 +108,7 @@ def checkout_command(
|
|
|
108
108
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=True)
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
@sync_app.command(name="checkout-to-branch")
|
|
111
|
+
@sync_app.command(name="checkout-to-branch", no_args_is_help=True)
|
|
112
112
|
def checkout_to_branch_command(
|
|
113
113
|
directory: DirectoryArgument = None,
|
|
114
114
|
cloud: CloudOption = None,
|
|
@@ -119,7 +119,7 @@ def checkout_to_branch_command(
|
|
|
119
119
|
clone_from_specs(directory, cloud, checkout_branch_flag=True, checkout_commit_flag=False)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
@app.command()
|
|
122
|
+
@app.command(no_args_is_help=True)
|
|
123
123
|
def analyze(
|
|
124
124
|
directory: DirectoryArgument = None,
|
|
125
125
|
) -> None:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Optional, Literal
|
|
4
4
|
import typer
|
|
5
|
-
|
|
5
|
+
from machineconfig.scripts.python.sessions_multiprocess import create_from_function
|
|
6
6
|
|
|
7
7
|
def balance_load(layout_path: Path = typer.Argument(..., help="Path to the layout.json file"),
|
|
8
8
|
max_thresh: int = typer.Option(..., help="Maximum tabs per layout"),
|
|
@@ -69,7 +69,7 @@ def find_layout_file(layout_path: str, ) -> Path:
|
|
|
69
69
|
return choice_file
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def
|
|
72
|
+
def run(ctx: typer.Context,
|
|
73
73
|
layout_path: Optional[str] = typer.Argument(None, help="Path to the layout.json file"),
|
|
74
74
|
max_tabs: int = typer.Option(10, help="A Sanity checker that throws an error if any layout exceeds the maximum number of tabs to launch."),
|
|
75
75
|
max_layouts: int = typer.Option(10, help="A Sanity checker that throws an error if the total number of layouts exceeds this number."),
|
|
@@ -133,14 +133,11 @@ def launch(ctx: typer.Context,
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def main_from_parser():
|
|
136
|
-
layouts_app = typer.Typer(help="Layouts management subcommands")
|
|
137
|
-
layouts_app.command("
|
|
138
|
-
layouts_app.command("
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
layouts_app(["--help"])
|
|
142
|
-
else:
|
|
143
|
-
layouts_app()
|
|
136
|
+
layouts_app = typer.Typer(help="Layouts management subcommands", no_args_is_help=True)
|
|
137
|
+
layouts_app.command("create-from-function", no_args_is_help=True)(create_from_function)
|
|
138
|
+
layouts_app.command("run", no_args_is_help=True)(run)
|
|
139
|
+
layouts_app.command("balance-load", no_args_is_help=True)(balance_load)
|
|
140
|
+
layouts_app()
|
|
144
141
|
|
|
145
142
|
|
|
146
143
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_from_function(
|
|
9
|
+
num_process: int = typer.Option(..., "--num-process", "-n", help="Number of parallel processes to run"),
|
|
10
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to a Python or Shell script file or a directory containing such files"),
|
|
11
|
+
function: Optional[str] = typer.Option(None, "--function", "-f", help="Function to run from the Python file. If not provided, you will be prompted to choose."),
|
|
12
|
+
):
|
|
13
|
+
from machineconfig.utils.ve import get_ve_activate_line, get_ve_path_and_ipython_profile
|
|
14
|
+
from machineconfig.utils.options import choose_from_options
|
|
15
|
+
from machineconfig.utils.path_helper import match_file_name, sanitize_path
|
|
16
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
17
|
+
from machineconfig.utils.accessories import get_repo_root
|
|
18
|
+
|
|
19
|
+
path_obj = sanitize_path(path)
|
|
20
|
+
if not path_obj.exists():
|
|
21
|
+
suffixes = {".py"}
|
|
22
|
+
choice_file = match_file_name(sub_string=path, search_root=PathExtended.cwd(), suffixes=suffixes)
|
|
23
|
+
elif path_obj.is_dir():
|
|
24
|
+
from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
|
|
25
|
+
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
26
|
+
files = search_for_files_of_interest(path_obj)
|
|
27
|
+
print(f"🔍 Got #{len(files)} results.")
|
|
28
|
+
choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
|
|
29
|
+
choice_file = PathExtended(choice_file)
|
|
30
|
+
else:
|
|
31
|
+
choice_file = path_obj
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
repo_root = get_repo_root(Path(choice_file))
|
|
35
|
+
print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
|
|
36
|
+
ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
|
|
37
|
+
if ipy_profile is None:
|
|
38
|
+
ipy_profile = "default"
|
|
39
|
+
|
|
40
|
+
_activate_ve_line = get_ve_activate_line(ve_root=ve_root_from_file or "$HOME/code/machineconfig/.venv")
|
|
41
|
+
|
|
42
|
+
# ========================= choosing function to run
|
|
43
|
+
if function is None or function.strip() == "":
|
|
44
|
+
from machineconfig.scripts.python.fire_jobs_route_helper import choose_function_or_lines
|
|
45
|
+
choice_function, choice_file, _kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict={})
|
|
46
|
+
else:
|
|
47
|
+
choice_function = function
|
|
48
|
+
|
|
49
|
+
from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
|
|
50
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
51
|
+
layout: LayoutConfig = {"layoutName": "fireNprocess", "layoutTabs": []}
|
|
52
|
+
for an_arg in range(num_process):
|
|
53
|
+
layout["layoutTabs"].append({"tabName": f"tab{an_arg}", "startDir": str(PathExtended.cwd()), "command": f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={num_process}"})
|
|
54
|
+
print(layout)
|
|
55
|
+
run_zellij_layout(layout_config=layout)
|
|
56
|
+
|
machineconfig/utils/links.py
CHANGED
|
@@ -8,39 +8,40 @@ from typing import TypedDict, Literal
|
|
|
8
8
|
console = Console()
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
ActionType = Literal[
|
|
12
|
+
"already_linked",
|
|
13
|
+
"relinking",
|
|
14
|
+
"fixing_broken_link",
|
|
15
|
+
"identical_files",
|
|
16
|
+
"backupConfigDefaultPath",
|
|
17
|
+
"backing_up_source",
|
|
18
|
+
"backing_up_target",
|
|
19
|
+
"relink2newSelfManagedPath",
|
|
20
|
+
"relinking_to_new_target",
|
|
21
|
+
"move2selfManagedPath",
|
|
22
|
+
"moving_to_target",
|
|
23
|
+
"new_link",
|
|
24
|
+
"newLinkAndSelfManagedPath",
|
|
25
|
+
"new_link_and_target",
|
|
26
|
+
"linking",
|
|
27
|
+
"copying",
|
|
28
|
+
"error"
|
|
29
|
+
]
|
|
30
|
+
|
|
27
31
|
|
|
32
|
+
class OperationResult(TypedDict):
|
|
33
|
+
action: ActionType
|
|
34
|
+
details: str
|
|
28
35
|
|
|
29
|
-
class
|
|
30
|
-
action:
|
|
31
|
-
"already_linked",
|
|
32
|
-
"relinking",
|
|
33
|
-
"fixing_broken_link",
|
|
34
|
-
"backing_up_source",
|
|
35
|
-
"backing_up_target",
|
|
36
|
-
"relinking_to_new_target",
|
|
37
|
-
"moving_to_target",
|
|
38
|
-
"new_link",
|
|
39
|
-
"new_link_and_target",
|
|
40
|
-
"copying",
|
|
41
|
-
"error"
|
|
42
|
-
]
|
|
36
|
+
class OperationRecord(TypedDict):
|
|
37
|
+
action: ActionType
|
|
43
38
|
details: str
|
|
39
|
+
program: str
|
|
40
|
+
file_key: str
|
|
41
|
+
defaultPath: str
|
|
42
|
+
selfManaged: str
|
|
43
|
+
operation: str
|
|
44
|
+
status: str
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def files_are_identical(file1: PathExtended, file2: PathExtended) -> bool:
|
|
@@ -79,14 +80,24 @@ def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
|
|
|
79
80
|
tmp_results_root.mkdir(parents=True, exist_ok=True)
|
|
80
81
|
target_dirs_filtered.append((tmp_results_root, "tmp_results"))
|
|
81
82
|
|
|
83
|
+
links_dir = repo_root_obj.joinpath("links")
|
|
84
|
+
links_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
|
|
82
86
|
for a_target_path, a_name in target_dirs_filtered:
|
|
83
|
-
links_path =
|
|
84
|
-
links_path.
|
|
87
|
+
links_path = links_dir.joinpath(a_name)
|
|
88
|
+
if links_path.exists() or links_path.is_symlink():
|
|
89
|
+
if links_path.is_symlink() and links_path.resolve() == a_target_path.resolve():
|
|
90
|
+
continue
|
|
91
|
+
links_path.unlink(missing_ok=True)
|
|
92
|
+
try:
|
|
93
|
+
links_path.symlink_to(target=a_target_path)
|
|
94
|
+
except OSError as ex:
|
|
95
|
+
console.print(Panel(f"❌ Failed to create symlink {links_path} -> {a_target_path}: {ex}", title="Symlink Error", expand=False))
|
|
85
96
|
|
|
86
97
|
|
|
87
98
|
def symlink_map(config_file_default_path: PathExtended, self_managed_config_file_path: PathExtended,
|
|
88
99
|
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"]
|
|
89
|
-
) ->
|
|
100
|
+
) -> OperationResult:
|
|
90
101
|
"""helper function. creates a symlink from `config_file_default_path` to `self_managed_config_file_path`.
|
|
91
102
|
|
|
92
103
|
Returns a dict with 'action' and 'details' keys describing what was done.
|
|
@@ -103,6 +114,10 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
103
114
|
"""
|
|
104
115
|
config_file_default_path = PathExtended(config_file_default_path).expanduser().absolute()
|
|
105
116
|
self_managed_config_file_path = PathExtended(self_managed_config_file_path).expanduser().absolute()
|
|
117
|
+
|
|
118
|
+
if config_file_default_path.resolve() == self_managed_config_file_path.resolve():
|
|
119
|
+
raise ValueError(f"config_file_default_path and self_managed_config_file_path resolve to the same location: {config_file_default_path.resolve()}")
|
|
120
|
+
|
|
106
121
|
action_taken = ""
|
|
107
122
|
details = ""
|
|
108
123
|
|
|
@@ -112,7 +127,7 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
112
127
|
if config_file_default_path.is_symlink():
|
|
113
128
|
# Check if symlink already points to correct target
|
|
114
129
|
try:
|
|
115
|
-
if config_file_default_path.
|
|
130
|
+
if config_file_default_path.resolve() == self_managed_config_file_path.resolve():
|
|
116
131
|
# Case: config_file_default_path exists AND self_managed_config_file_path exists AND config_file_default_path is a symlink pointing to self_managed_config_file_path
|
|
117
132
|
action_taken = "already_linked"
|
|
118
133
|
details = "Symlink already correctly points to target"
|
|
@@ -135,7 +150,7 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
135
150
|
if files_are_identical(config_file_default_path, self_managed_config_file_path):
|
|
136
151
|
# Files are identical, just delete this and create symlink
|
|
137
152
|
action_taken = "identical_files"
|
|
138
|
-
details = "Files identical, removed
|
|
153
|
+
details = "Files identical, removed config_file_default_path and will create symlink"
|
|
139
154
|
console.print(Panel(f"🔗 IDENTICAL FILES | Files are identical, deleting {config_file_default_path} and creating symlink to {self_managed_config_file_path}", title="Identical Files", expand=False))
|
|
140
155
|
config_file_default_path.delete(sure=True)
|
|
141
156
|
else:
|
|
@@ -156,13 +171,13 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
156
171
|
self_managed_config_file_path.move(path=backup_name)
|
|
157
172
|
config_file_default_path.move(path=self_managed_config_file_path)
|
|
158
173
|
elif on_conflict == "overwriteDefaultPath":
|
|
159
|
-
action_taken = "
|
|
174
|
+
action_taken = "backupConfigDefaultPath"
|
|
160
175
|
details = "Overwriting default path, creating symlink to self-managed config"
|
|
161
176
|
console.print(Panel(f"📦 OVERWRITE DEFAULT | Deleting {config_file_default_path}, creating symlink to {self_managed_config_file_path}", title="Overwrite Default", expand=False))
|
|
162
177
|
config_file_default_path.delete(sure=True)
|
|
163
178
|
elif on_conflict == "backupDefaultPath":
|
|
164
179
|
backup_name = f"{config_file_default_path}.orig_{randstr()}"
|
|
165
|
-
action_taken = "
|
|
180
|
+
action_taken = "backupConfigDefaultPath"
|
|
166
181
|
details = f"Backed up default path to {backup_name}"
|
|
167
182
|
console.print(Panel(f"📦 BACKUP DEFAULT | Moving {config_file_default_path} to {backup_name}, creating symlink to {self_managed_config_file_path}", title="Backup Default", expand=False))
|
|
168
183
|
config_file_default_path.move(path=backup_name)
|
|
@@ -170,8 +185,8 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
170
185
|
# self_managed_config_file_path doesn't exist
|
|
171
186
|
if config_file_default_path.is_symlink():
|
|
172
187
|
# Case: config_file_default_path exists AND self_managed_config_file_path doesn't exist AND config_file_default_path is a symlink (pointing anywhere)
|
|
173
|
-
action_taken = "
|
|
174
|
-
details = "Removed existing symlink, will create
|
|
188
|
+
action_taken = "relink2newSelfManagedPath"
|
|
189
|
+
details = "Removed existing symlink, will create self_managed_config_file_path and new symlink"
|
|
175
190
|
console.print(Panel(f"🔄 RELINKING | Updating symlink from {config_file_default_path} ➡️ {self_managed_config_file_path}", title="Relinking", expand=False))
|
|
176
191
|
config_file_default_path.delete(sure=True)
|
|
177
192
|
# Create self_managed_config_file_path
|
|
@@ -179,8 +194,8 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
179
194
|
self_managed_config_file_path.touch()
|
|
180
195
|
else:
|
|
181
196
|
# Case: config_file_default_path exists AND self_managed_config_file_path doesn't exist AND config_file_default_path is a concrete path
|
|
182
|
-
action_taken = "
|
|
183
|
-
details = "Moved
|
|
197
|
+
action_taken = "move2selfManagedPath"
|
|
198
|
+
details = "Moved config_file_default_path to self_managed_config_file_path location, will create symlink"
|
|
184
199
|
console.print(Panel(f"📁 MOVING | Moving {config_file_default_path} to {self_managed_config_file_path}, then creating symlink", title="Moving", expand=False))
|
|
185
200
|
config_file_default_path.move(path=self_managed_config_file_path)
|
|
186
201
|
else:
|
|
@@ -188,22 +203,23 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
188
203
|
if self_managed_config_file_path.exists():
|
|
189
204
|
# Case: config_file_default_path doesn't exist AND self_managed_config_file_path exists
|
|
190
205
|
action_taken = "new_link"
|
|
191
|
-
details = "Creating new symlink to existing
|
|
206
|
+
details = "Creating new symlink to existing self_managed_config_file_path"
|
|
192
207
|
console.print(Panel(f"🆕 NEW LINK | Creating new symlink from {config_file_default_path} ➡️ {self_managed_config_file_path}", title="New Link", expand=False))
|
|
193
208
|
else:
|
|
194
209
|
# Case: config_file_default_path doesn't exist AND self_managed_config_file_path doesn't exist
|
|
195
|
-
action_taken = "
|
|
196
|
-
details = "Creating
|
|
210
|
+
action_taken = "newLinkAndSelfManagedPath"
|
|
211
|
+
details = "Creating self_managed_config_file_path file and new symlink"
|
|
197
212
|
console.print(Panel(f"🆕 NEW LINK & TARGET | Creating {self_managed_config_file_path} and symlink from {config_file_default_path} ➡️ {self_managed_config_file_path}", title="New Link & Target", expand=False))
|
|
198
213
|
self_managed_config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
199
214
|
self_managed_config_file_path.touch()
|
|
200
215
|
|
|
201
216
|
# Create the symlink
|
|
202
217
|
try:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
218
|
+
if not action_taken:
|
|
219
|
+
action_taken = "linking"
|
|
220
|
+
details = "Creating symlink"
|
|
221
|
+
console.print(Panel(f"🔗 LINKING | Creating symlink from {config_file_default_path} ➡️ {self_managed_config_file_path}", title="Linking", expand=False))
|
|
222
|
+
PathExtended(config_file_default_path).symlink_to(target=self_managed_config_file_path, verbose=True, overwrite=False)
|
|
207
223
|
return {"action": action_taken, "details": details}
|
|
208
224
|
except Exception as ex:
|
|
209
225
|
action_taken = "error"
|
|
@@ -212,87 +228,105 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
|
|
|
212
228
|
return {"action": action_taken, "details": details}
|
|
213
229
|
|
|
214
230
|
|
|
215
|
-
def copy_map(config_file_default_path: PathExtended, self_managed_config_file_path: PathExtended, on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"]) ->
|
|
231
|
+
def copy_map(config_file_default_path: PathExtended, self_managed_config_file_path: PathExtended, on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"]) -> OperationResult:
|
|
216
232
|
config_file_default_path = PathExtended(config_file_default_path).expanduser().absolute()
|
|
217
233
|
self_managed_config_file_path = PathExtended(self_managed_config_file_path).expanduser().absolute()
|
|
234
|
+
|
|
235
|
+
if config_file_default_path.resolve() == self_managed_config_file_path.resolve():
|
|
236
|
+
raise ValueError(f"config_file_default_path and self_managed_config_file_path resolve to the same location: {config_file_default_path.resolve()}")
|
|
237
|
+
|
|
218
238
|
action_taken = ""
|
|
219
239
|
details = ""
|
|
220
240
|
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
match (config_file_default_path.exists(), self_managed_config_file_path.exists()):
|
|
242
|
+
case (True, True):
|
|
243
|
+
# Both files exist
|
|
223
244
|
if config_file_default_path.is_symlink():
|
|
224
245
|
try:
|
|
225
|
-
if config_file_default_path.
|
|
246
|
+
if config_file_default_path.resolve() == self_managed_config_file_path.resolve():
|
|
226
247
|
action_taken = "already_linked"
|
|
227
|
-
details = "
|
|
228
|
-
console.print(Panel(f"✅ ALREADY
|
|
248
|
+
details = "File at default path is already a symlink to self-managed config"
|
|
249
|
+
console.print(Panel(f"✅ ALREADY CORRECT | {config_file_default_path} already points to {self_managed_config_file_path}", title="Already Correct", expand=False))
|
|
229
250
|
return {"action": action_taken, "details": details}
|
|
230
251
|
else:
|
|
231
252
|
action_taken = "relinking"
|
|
232
|
-
details = "
|
|
233
|
-
console.print(Panel(f"🔄
|
|
253
|
+
details = "Removing symlink at default path that points elsewhere"
|
|
254
|
+
console.print(Panel(f"🔄 REMOVING SYMLINK | Removing symlink {config_file_default_path} (points elsewhere), will copy from {self_managed_config_file_path}", title="Removing Symlink", expand=False))
|
|
234
255
|
config_file_default_path.delete(sure=True)
|
|
235
256
|
except OSError:
|
|
236
257
|
action_taken = "fixing_broken_link"
|
|
237
|
-
details = "Removed broken symlink
|
|
238
|
-
console.print(Panel(f"🔄 FIXING BROKEN
|
|
258
|
+
details = "Removed broken symlink at default path"
|
|
259
|
+
console.print(Panel(f"🔄 FIXING BROKEN SYMLINK | Removing broken symlink {config_file_default_path}, will copy from {self_managed_config_file_path}", title="Fixing Broken Symlink", expand=False))
|
|
239
260
|
config_file_default_path.delete(sure=True)
|
|
240
261
|
else:
|
|
241
|
-
if
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
action_taken = "
|
|
245
|
-
details = "
|
|
246
|
-
console.print(Panel(f"
|
|
247
|
-
self_managed_config_file_path.delete(sure=True)
|
|
248
|
-
config_file_default_path.move(path=self_managed_config_file_path)
|
|
249
|
-
elif on_conflict == "backupSelfManaged":
|
|
250
|
-
backup_name = f"{self_managed_config_file_path}.orig_{randstr()}"
|
|
251
|
-
action_taken = "backing_up_target"
|
|
252
|
-
details = f"Backed up self-managed config to {backup_name}"
|
|
253
|
-
console.print(Panel(f"📦 BACKUP SELF-MANAGED | Moving {self_managed_config_file_path} to {backup_name}, moving {config_file_default_path} to {self_managed_config_file_path}", title="Backup Self-Managed", expand=False))
|
|
254
|
-
self_managed_config_file_path.move(path=backup_name)
|
|
255
|
-
config_file_default_path.move(path=self_managed_config_file_path)
|
|
256
|
-
elif on_conflict == "overwriteDefaultPath":
|
|
257
|
-
action_taken = "backing_up_source"
|
|
258
|
-
details = "Overwriting default path, creating symlink to self-managed config"
|
|
259
|
-
console.print(Panel(f"📦 OVERWRITE DEFAULT | Deleting {config_file_default_path}, copying {self_managed_config_file_path}", title="Overwrite Default", expand=False))
|
|
262
|
+
# Check if files are identical first
|
|
263
|
+
if files_are_identical(config_file_default_path, self_managed_config_file_path):
|
|
264
|
+
# Files are identical, just delete this and proceed with copy
|
|
265
|
+
action_taken = "identical_files"
|
|
266
|
+
details = "Files identical, removed config_file_default_path and will copy"
|
|
267
|
+
console.print(Panel(f"🔗 IDENTICAL FILES | Files are identical, deleting {config_file_default_path} and copying from {self_managed_config_file_path}", title="Identical Files", expand=False))
|
|
260
268
|
config_file_default_path.delete(sure=True)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
else:
|
|
270
|
+
# Files are different, use on_conflict strategy
|
|
271
|
+
match on_conflict:
|
|
272
|
+
case "throwError":
|
|
273
|
+
raise RuntimeError(f"Conflict detected: {config_file_default_path} and {self_managed_config_file_path} both exist with different content")
|
|
274
|
+
case "overwriteSelfManaged":
|
|
275
|
+
action_taken = "backing_up_target"
|
|
276
|
+
details = "Overwriting self-managed config with default path content"
|
|
277
|
+
console.print(Panel(f"📦 OVERWRITE SELF-MANAGED | Deleting {self_managed_config_file_path}, moving {config_file_default_path} to {self_managed_config_file_path}", title="Overwrite Self-Managed", expand=False))
|
|
278
|
+
self_managed_config_file_path.delete(sure=True)
|
|
279
|
+
config_file_default_path.move(path=self_managed_config_file_path)
|
|
280
|
+
case "backupSelfManaged":
|
|
281
|
+
backup_name = f"{self_managed_config_file_path}.orig_{randstr()}"
|
|
282
|
+
action_taken = "backing_up_target"
|
|
283
|
+
details = f"Backed up self-managed config to {backup_name}"
|
|
284
|
+
console.print(Panel(f"📦 BACKUP SELF-MANAGED | Moving {self_managed_config_file_path} to {backup_name}, moving {config_file_default_path} to {self_managed_config_file_path}", title="Backup Self-Managed", expand=False))
|
|
285
|
+
self_managed_config_file_path.move(path=backup_name)
|
|
286
|
+
config_file_default_path.move(path=self_managed_config_file_path)
|
|
287
|
+
case "overwriteDefaultPath":
|
|
288
|
+
action_taken = "backupConfigDefaultPath"
|
|
289
|
+
details = "Overwriting default path with self-managed config"
|
|
290
|
+
console.print(Panel(f"📦 OVERWRITE DEFAULT | Deleting {config_file_default_path}, will copy from {self_managed_config_file_path}", title="Overwrite Default", expand=False))
|
|
291
|
+
config_file_default_path.delete(sure=True)
|
|
292
|
+
case "backupDefaultPath":
|
|
293
|
+
backup_name = f"{config_file_default_path}.orig_{randstr()}"
|
|
294
|
+
action_taken = "backupConfigDefaultPath"
|
|
295
|
+
details = f"Backed up default path to {backup_name}"
|
|
296
|
+
console.print(Panel(f"📦 BACKUP DEFAULT | Moving {config_file_default_path} to {backup_name}, will copy from {self_managed_config_file_path}", title="Backup Default", expand=False))
|
|
297
|
+
config_file_default_path.move(path=backup_name)
|
|
298
|
+
case (True, False):
|
|
299
|
+
# config_file_default_path exists, self_managed_config_file_path doesn't
|
|
268
300
|
if config_file_default_path.is_symlink():
|
|
269
|
-
action_taken = "
|
|
270
|
-
details = "Removed existing symlink, will create
|
|
271
|
-
console.print(Panel(f"🔄
|
|
301
|
+
action_taken = "relink2newSelfManagedPath"
|
|
302
|
+
details = "Removed existing symlink, will create self_managed_config_file_path and copy"
|
|
303
|
+
console.print(Panel(f"🔄 REMOVING SYMLINK | Removing symlink {config_file_default_path}, creating {self_managed_config_file_path}", title="Removing Symlink", expand=False))
|
|
272
304
|
config_file_default_path.delete(sure=True)
|
|
273
305
|
self_managed_config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
274
306
|
self_managed_config_file_path.touch()
|
|
275
307
|
else:
|
|
276
|
-
action_taken = "
|
|
277
|
-
details = "Moved
|
|
278
|
-
console.print(Panel(f"📁 MOVING | Moving {config_file_default_path} to {self_managed_config_file_path}, then copying", title="Moving", expand=False))
|
|
308
|
+
action_taken = "move2selfManagedPath"
|
|
309
|
+
details = "Moved config_file_default_path to self_managed_config_file_path location, will copy back"
|
|
310
|
+
console.print(Panel(f"📁 MOVING | Moving {config_file_default_path} to {self_managed_config_file_path}, then copying back", title="Moving", expand=False))
|
|
279
311
|
config_file_default_path.move(path=self_managed_config_file_path)
|
|
280
|
-
|
|
281
|
-
|
|
312
|
+
case (False, True):
|
|
313
|
+
# config_file_default_path doesn't exist, self_managed_config_file_path does
|
|
282
314
|
action_taken = "new_link"
|
|
283
|
-
details = "Copying existing
|
|
284
|
-
console.print(Panel(f"🆕 NEW
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
details = "Copying existing self_managed_config_file_path to config_file_default_path location"
|
|
316
|
+
console.print(Panel(f"🆕 NEW COPY | Copying {self_managed_config_file_path} to {config_file_default_path}", title="New Copy", expand=False))
|
|
317
|
+
case (False, False):
|
|
318
|
+
# Neither exists
|
|
319
|
+
action_taken = "newLinkAndSelfManagedPath"
|
|
320
|
+
details = "Creating self_managed_config_file_path file and copying to config_file_default_path"
|
|
321
|
+
console.print(Panel(f"🆕 NEW FILE & COPY | Creating {self_managed_config_file_path} and copying to {config_file_default_path}", title="New File & Copy", expand=False))
|
|
289
322
|
self_managed_config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
323
|
self_managed_config_file_path.touch()
|
|
291
324
|
|
|
292
325
|
try:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
326
|
+
if not action_taken:
|
|
327
|
+
action_taken = "copying"
|
|
328
|
+
details = "Copying file"
|
|
329
|
+
console.print(Panel(f"📋 COPYING | Copying {self_managed_config_file_path} to {config_file_default_path}", title="Copying", expand=False))
|
|
296
330
|
self_managed_config_file_path.copy(path=config_file_default_path, overwrite=True, verbose=True)
|
|
297
331
|
return {"action": action_taken, "details": details}
|
|
298
332
|
except Exception as ex:
|
|
@@ -300,3 +334,5 @@ def copy_map(config_file_default_path: PathExtended, self_managed_config_file_pa
|
|
|
300
334
|
details = f"Failed to copy file: {str(ex)}"
|
|
301
335
|
console.print(Panel(f"❌ ERROR | Failed at copying {self_managed_config_file_path} to {config_file_default_path}. Reason: {ex}", title="Error", expand=False))
|
|
302
336
|
return {"action": action_taken, "details": details}
|
|
337
|
+
|
|
338
|
+
|