machineconfig 5.18__py3-none-any.whl → 5.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/profile/create.py +84 -66
- machineconfig/scripts/python/devops.py +33 -16
- machineconfig/scripts/python/interactive.py +12 -14
- machineconfig/scripts/python/repos.py +10 -10
- machineconfig/utils/links.py +137 -101
- {machineconfig-5.18.dist-info → machineconfig-5.20.dist-info}/METADATA +1 -1
- {machineconfig-5.18.dist-info → machineconfig-5.20.dist-info}/RECORD +10 -10
- {machineconfig-5.18.dist-info → machineconfig-5.20.dist-info}/WHEEL +0 -0
- {machineconfig-5.18.dist-info → machineconfig-5.20.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.18.dist-info → machineconfig-5.20.dist-info}/top_level.txt +0 -0
machineconfig/profile/create.py
CHANGED
|
@@ -4,6 +4,7 @@ This script Takes away all config files from the computer, place them in one dir
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from machineconfig.utils.links import OperationRecord
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from rich.panel import Panel
|
|
9
10
|
from rich.pretty import Pretty
|
|
@@ -12,7 +13,7 @@ from rich.table import Table
|
|
|
12
13
|
|
|
13
14
|
from machineconfig.utils.path_extended import PathExtended
|
|
14
15
|
from machineconfig.utils.links import symlink_map, copy_map
|
|
15
|
-
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
16
|
+
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, CONFIG_PATH
|
|
16
17
|
|
|
17
18
|
import platform
|
|
18
19
|
import subprocess
|
|
@@ -40,7 +41,6 @@ class Base(TypedDict):
|
|
|
40
41
|
to_this: str
|
|
41
42
|
contents: Optional[bool]
|
|
42
43
|
copy: Optional[bool]
|
|
43
|
-
|
|
44
44
|
class ConfigMapper(TypedDict):
|
|
45
45
|
file_name: str
|
|
46
46
|
config_file_default_path: str
|
|
@@ -74,31 +74,6 @@ def read_mapper() -> MapperFileData:
|
|
|
74
74
|
return {"public": public, "private": private}
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
class OperationRecord(TypedDict):
|
|
78
|
-
program: str
|
|
79
|
-
file_key: str
|
|
80
|
-
source: str
|
|
81
|
-
target: str
|
|
82
|
-
operation: str
|
|
83
|
-
action: Literal[
|
|
84
|
-
"already_linked",
|
|
85
|
-
"relinking",
|
|
86
|
-
"fixing_broken_link",
|
|
87
|
-
"identical_files",
|
|
88
|
-
"backing_up_source",
|
|
89
|
-
"backing_up_target",
|
|
90
|
-
"relinking_to_new_target",
|
|
91
|
-
"moving_to_target",
|
|
92
|
-
"new_link",
|
|
93
|
-
"new_link_and_target",
|
|
94
|
-
"linking",
|
|
95
|
-
"copying",
|
|
96
|
-
"error"
|
|
97
|
-
]
|
|
98
|
-
details: str
|
|
99
|
-
status: str
|
|
100
|
-
|
|
101
|
-
|
|
102
77
|
def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
103
78
|
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"],
|
|
104
79
|
method: Literal["symlink", "copy"]
|
|
@@ -135,80 +110,104 @@ def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
|
135
110
|
self_managed_config_file_path = PathExtended(a_mapper["self_managed_config_file_path"].replace("LIBRARY_ROOT", LIBRARY_ROOT.as_posix()))
|
|
136
111
|
|
|
137
112
|
# Determine whether to use copy or symlink
|
|
138
|
-
use_copy = method == "copy" or "copy"
|
|
113
|
+
use_copy = method == "copy" or a_mapper.get("copy", False)
|
|
139
114
|
|
|
140
115
|
if "contents" in a_mapper:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
116
|
+
targets = list(self_managed_config_file_path.expanduser().search("*"))
|
|
117
|
+
for a_target in targets:
|
|
118
|
+
operation_type = "contents_copy" if use_copy else "contents_symlink"
|
|
119
|
+
try:
|
|
144
120
|
if use_copy:
|
|
145
121
|
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
122
|
else:
|
|
148
123
|
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"
|
|
150
124
|
operation_records.append({
|
|
151
125
|
"program": program_name,
|
|
152
126
|
"file_key": a_mapper["file_name"],
|
|
153
|
-
"
|
|
154
|
-
"
|
|
127
|
+
"defaultPath": str(config_file_default_path.joinpath(a_target.name)),
|
|
128
|
+
"selfManaged": str(a_target),
|
|
155
129
|
"operation": operation_type,
|
|
156
130
|
"action": result["action"],
|
|
157
131
|
"details": result["details"],
|
|
158
132
|
"status": "success"
|
|
159
133
|
})
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
134
|
+
except ValueError as ex:
|
|
135
|
+
if "resolve to the same location" in str(ex):
|
|
136
|
+
operation_records.append({
|
|
137
|
+
"program": program_name,
|
|
138
|
+
"file_key": a_mapper["file_name"],
|
|
139
|
+
"defaultPath": str(config_file_default_path.joinpath(a_target.name)),
|
|
140
|
+
"selfManaged": str(a_target),
|
|
141
|
+
"operation": operation_type,
|
|
142
|
+
"action": "already_linked",
|
|
143
|
+
"details": "defaultPath and selfManaged resolve to same location - already correctly configured",
|
|
144
|
+
"status": "success"
|
|
145
|
+
})
|
|
146
|
+
else:
|
|
147
|
+
raise
|
|
148
|
+
except Exception as ex:
|
|
149
|
+
console.print(f"❌ [red]Config error[/red]: {program_name} | {a_mapper['file_name']} | {a_target.name}. {ex}")
|
|
150
|
+
operation_records.append({
|
|
151
|
+
"program": program_name,
|
|
152
|
+
"file_key": a_mapper["file_name"],
|
|
153
|
+
"defaultPath": str(config_file_default_path.joinpath(a_target.name)),
|
|
154
|
+
"selfManaged": str(a_target),
|
|
155
|
+
"operation": operation_type,
|
|
156
|
+
"action": "error",
|
|
157
|
+
"details": f"Failed to process contents: {str(ex)}",
|
|
158
|
+
"status": f"error: {str(ex)}"
|
|
159
|
+
})
|
|
172
160
|
else:
|
|
161
|
+
operation_type = "copy" if use_copy else "symlink"
|
|
173
162
|
try:
|
|
174
163
|
if use_copy:
|
|
175
164
|
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
165
|
else:
|
|
178
166
|
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"
|
|
180
167
|
operation_records.append({
|
|
181
168
|
"program": program_name,
|
|
182
169
|
"file_key": a_mapper["file_name"],
|
|
183
|
-
"
|
|
184
|
-
"
|
|
170
|
+
"defaultPath": str(config_file_default_path),
|
|
171
|
+
"selfManaged": str(self_managed_config_file_path),
|
|
185
172
|
"operation": operation_type,
|
|
186
173
|
"action": result["action"],
|
|
187
174
|
"details": result["details"],
|
|
188
175
|
"status": "success"
|
|
189
176
|
})
|
|
177
|
+
except ValueError as ex:
|
|
178
|
+
if "resolve to the same location" in str(ex):
|
|
179
|
+
operation_records.append({
|
|
180
|
+
"program": program_name,
|
|
181
|
+
"file_key": a_mapper["file_name"],
|
|
182
|
+
"defaultPath": str(config_file_default_path),
|
|
183
|
+
"selfManaged": str(self_managed_config_file_path),
|
|
184
|
+
"operation": operation_type,
|
|
185
|
+
"action": "already_linked",
|
|
186
|
+
"details": "defaultPath and selfManaged resolve to same location - already correctly configured",
|
|
187
|
+
"status": "success"
|
|
188
|
+
})
|
|
189
|
+
else:
|
|
190
|
+
raise
|
|
190
191
|
except Exception as ex:
|
|
191
|
-
console.print(f"❌ [red]Config error[/red]: {program_name} | {a_mapper['file_name']} |
|
|
192
|
+
console.print(f"❌ [red]Config error[/red]: {program_name} | {a_mapper['file_name']} | {ex}")
|
|
192
193
|
operation_records.append({
|
|
193
194
|
"program": program_name,
|
|
194
195
|
"file_key": a_mapper["file_name"],
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"operation":
|
|
196
|
+
"defaultPath": str(config_file_default_path),
|
|
197
|
+
"selfManaged": str(self_managed_config_file_path),
|
|
198
|
+
"operation": operation_type,
|
|
198
199
|
"action": "error",
|
|
199
|
-
"details": f"Failed to create {
|
|
200
|
+
"details": f"Failed to create {operation_type}: {str(ex)}",
|
|
200
201
|
"status": f"error: {str(ex)}"
|
|
201
202
|
})
|
|
202
203
|
|
|
203
204
|
if program_name == "ssh" and system == "Linux": # permissions of ~/dotfiles/.ssh should be adjusted
|
|
204
205
|
try:
|
|
205
206
|
console.print("\n[bold]🔒 Setting secure permissions for SSH files...[/bold]")
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
subprocess.run("chmod
|
|
210
|
-
subprocess.run("chmod 700 ~/dotfiles/creds/.ssh/", check=True) # may require sudo
|
|
211
|
-
subprocess.run("chmod 600 ~/dotfiles/creds/.ssh/*", check=True)
|
|
207
|
+
subprocess.run("chmod 700 $HOME/.ssh/", shell=True, check=True)
|
|
208
|
+
subprocess.run("chmod 700 $HOME/dotfiles/creds/.ssh/", shell=True, check=True)
|
|
209
|
+
subprocess.run("chmod 600 $HOME/dotfiles/creds/.ssh/*", shell=True, check=True)
|
|
210
|
+
subprocess.run("chmod 600 $HOME/.ssh/*", shell=True, check=True)
|
|
212
211
|
console.print("[green]✅ SSH permissions set successfully[/green]")
|
|
213
212
|
except Exception as e:
|
|
214
213
|
ERROR_LIST.append(e)
|
|
@@ -224,8 +223,8 @@ def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
|
224
223
|
table = Table(title="🔗 Symlink Operations Summary", show_header=True, header_style="bold magenta")
|
|
225
224
|
table.add_column("Program", style="cyan", no_wrap=True)
|
|
226
225
|
table.add_column("File Key", style="blue", no_wrap=True)
|
|
227
|
-
table.add_column("
|
|
228
|
-
table.add_column("
|
|
226
|
+
table.add_column("Default Path", style="green")
|
|
227
|
+
table.add_column("Self Managed", style="yellow")
|
|
229
228
|
table.add_column("Operation", style="magenta", no_wrap=True)
|
|
230
229
|
table.add_column("Action", style="red", no_wrap=True)
|
|
231
230
|
table.add_column("Details", style="white")
|
|
@@ -237,8 +236,8 @@ def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
|
237
236
|
table.add_row(
|
|
238
237
|
record["program"],
|
|
239
238
|
record["file_key"],
|
|
240
|
-
record["
|
|
241
|
-
record["
|
|
239
|
+
record["defaultPath"],
|
|
240
|
+
record["selfManaged"],
|
|
242
241
|
record["operation"],
|
|
243
242
|
f"[{action_style}]{record['action']}[/{action_style}]",
|
|
244
243
|
record["details"],
|
|
@@ -247,6 +246,25 @@ def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
|
|
|
247
246
|
|
|
248
247
|
console.print("\n")
|
|
249
248
|
console.print(table)
|
|
249
|
+
|
|
250
|
+
# Export operation records to CSV
|
|
251
|
+
import csv
|
|
252
|
+
from datetime import datetime
|
|
253
|
+
|
|
254
|
+
csv_dir = PathExtended(CONFIG_PATH).joinpath("symlink_operations")
|
|
255
|
+
csv_dir.mkdir(parents=True, exist_ok=True)
|
|
256
|
+
|
|
257
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
258
|
+
csv_filename = f"symlink_operations_{timestamp}.csv"
|
|
259
|
+
csv_path = csv_dir.joinpath(csv_filename)
|
|
260
|
+
|
|
261
|
+
with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
|
|
262
|
+
fieldnames = ["program", "file_key", "defaultPath", "selfManaged", "operation", "action", "details", "status"]
|
|
263
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
264
|
+
writer.writeheader()
|
|
265
|
+
writer.writerows(operation_records)
|
|
266
|
+
|
|
267
|
+
console.print(f"\n[bold green]📊 Operations exported to CSV:[/bold green] [cyan]{csv_path}[/cyan]")
|
|
250
268
|
|
|
251
269
|
if len(ERROR_LIST) > 0:
|
|
252
270
|
console.print(
|
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
"""devops with emojis"""
|
|
2
2
|
|
|
3
|
-
import machineconfig.utils.installer_utils.installer as installer_entry_point
|
|
4
3
|
import machineconfig.scripts.python.share_terminal as share_terminal
|
|
5
4
|
import machineconfig.scripts.python.repos as repos
|
|
6
|
-
|
|
7
|
-
import machineconfig.profile.create_frontend as create_frontend
|
|
5
|
+
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS
|
|
8
6
|
# import machineconfig.scripts.python.dotfile as dotfile_module
|
|
9
7
|
import typer
|
|
10
|
-
from typing import Literal, Annotated
|
|
8
|
+
from typing import Literal, Annotated, Optional, get_args
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
app = typer.Typer(help="🛠️ DevOps operations", no_args_is_help=True)
|
|
14
|
-
app.command(
|
|
15
|
-
|
|
12
|
+
@app.command(no_args_is_help=True)
|
|
13
|
+
def install( which: Optional[str] = typer.Option(None, "--which", "-w", help="Comma-separated list of program names to install."),
|
|
14
|
+
group: Optional[PACKAGE_GROUPS] = typer.Option(None, "--group", "-g", help=f"Group name (one of {list(get_args(PACKAGE_GROUPS))})"),
|
|
15
|
+
interactive: bool = typer.Option(False, "--interactive", "-ia", help="Interactive selection of programs to install."),
|
|
16
|
+
) -> None:
|
|
17
|
+
"""📦 Install essential packages"""
|
|
18
|
+
import machineconfig.utils.installer_utils.installer as installer_entry_point
|
|
19
|
+
installer_entry_point.main(which=which, group=group, interactive=interactive)
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
app.add_typer(repos.app, name="repos", help="📁 Manage git repositories")
|
|
18
23
|
config_apps = typer.Typer(help="⚙️ Configuration subcommands", no_args_is_help=True)
|
|
19
24
|
app.add_typer(config_apps, name="config")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
25
|
app_data = typer.Typer(help="💾 Data subcommands", no_args_is_help=True)
|
|
24
26
|
app.add_typer(app_data, name="data")
|
|
25
|
-
|
|
26
27
|
nw_apps = typer.Typer(help="🔐 Network subcommands", no_args_is_help=True)
|
|
27
28
|
nw_apps.command(name="share-terminal", help="📡 Share terminal via web browser")(share_terminal.main)
|
|
28
29
|
app.add_typer(nw_apps, name="network")
|
|
29
|
-
|
|
30
|
-
|
|
31
30
|
self_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
|
|
32
31
|
app.add_typer(self_app, name="self")
|
|
33
32
|
|
|
@@ -46,17 +45,35 @@ def interactive():
|
|
|
46
45
|
def status():
|
|
47
46
|
"""📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
|
|
48
47
|
pass
|
|
48
|
+
@self_app.command()
|
|
49
|
+
def clone():
|
|
50
|
+
"""📋 CLONE machienconfig locally for faster execution and nightly updates. """
|
|
51
|
+
import platform
|
|
52
|
+
from machineconfig.utils.code import run_shell_script
|
|
53
|
+
if platform.system() == "Windows":
|
|
54
|
+
from machineconfig.setup_windows import REPOS
|
|
55
|
+
else:
|
|
56
|
+
from machineconfig.setup_linux import REPOS
|
|
57
|
+
run_shell_script(REPOS.read_text(encoding="utf-8"))
|
|
49
58
|
|
|
50
59
|
|
|
51
60
|
@config_apps.command(no_args_is_help=True)
|
|
52
|
-
def private()
|
|
61
|
+
def private(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for linking files"),
|
|
62
|
+
on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
|
|
63
|
+
which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
|
|
64
|
+
interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
|
|
53
65
|
"""🔗 Manage private configuration files."""
|
|
54
|
-
create_frontend
|
|
66
|
+
import machineconfig.profile.create_frontend as create_frontend
|
|
67
|
+
create_frontend.main_private_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
|
|
55
68
|
|
|
56
69
|
@config_apps.command(no_args_is_help=True)
|
|
57
|
-
def public()
|
|
70
|
+
def public(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for setting up the config file."),
|
|
71
|
+
on_conflict: Literal["throwError", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
|
|
72
|
+
which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
|
|
73
|
+
interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
|
|
58
74
|
"""🔗 Manage public configuration files."""
|
|
59
|
-
create_frontend
|
|
75
|
+
import machineconfig.profile.create_frontend as create_frontend
|
|
76
|
+
create_frontend.main_public_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
|
|
60
77
|
|
|
61
78
|
# @config_apps.command(no_args_is_help=True)
|
|
62
79
|
# def dotfile():
|
|
@@ -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
|
|
@@ -142,7 +140,7 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
|
|
|
142
140
|
console.print("🔧 Configuring shell profile", style="bold cyan")
|
|
143
141
|
try:
|
|
144
142
|
from machineconfig.profile.shell import create_default_shell_profile
|
|
145
|
-
create_default_shell_profile()
|
|
143
|
+
create_default_shell_profile(method="copy")
|
|
146
144
|
console.print("✅ Shell profile configured successfully", style="bold green")
|
|
147
145
|
except Exception as e:
|
|
148
146
|
console.print(f"❌ Error configuring shell profile: {e}", style="bold red")
|
|
@@ -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:
|
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
|
+
|
|
@@ -90,7 +90,7 @@ machineconfig/jobs/windows/archive/openssh-server_copy-ssh-id.ps1,sha256=-7pElYi
|
|
|
90
90
|
machineconfig/jobs/windows/msc/cli_agents.bat,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
machineconfig/jobs/windows/msc/cli_agents.ps1,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
92
|
machineconfig/profile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
|
-
machineconfig/profile/create.py,sha256=
|
|
93
|
+
machineconfig/profile/create.py,sha256=n8AEpwWrGmzyWyIcFxTXQp8Ztw6MTHTjt1aPlsw7yhc,13974
|
|
94
94
|
machineconfig/profile/create_frontend.py,sha256=RlrQgsAzvJA3KQInPpavLJ6XIEayIjjq4it6dSLQR08,3352
|
|
95
95
|
machineconfig/profile/shell.py,sha256=B2dg2eV9fPMlMLlqjL3W91E0ZrEEg5qpJXmBax-Kx_M,10958
|
|
96
96
|
machineconfig/profile/records/generic/shares.toml,sha256=FduDztfyQtZcr5bfx-RSKhEEweweQSWfVXkKWnx8hCY,143
|
|
@@ -146,7 +146,7 @@ machineconfig/scripts/python/cloud_sync.py,sha256=RWGpAfJ9fnN18yNBSgN44dzA38Hmd4
|
|
|
146
146
|
machineconfig/scripts/python/count_lines.py,sha256=ZexMRsV70pe9fhLbGuens9EP5gCf078EwTDRHRZo5A0,15960
|
|
147
147
|
machineconfig/scripts/python/count_lines_frontend.py,sha256=HlzPLU9_oJYqPNbnoQ0Hm4CuYy1UUlkZPcE5tFBSEbo,545
|
|
148
148
|
machineconfig/scripts/python/croshell.py,sha256=zHUhOqWG81AOTeawZoDkpURnV1fAisY2lyZ0apvlmVY,6547
|
|
149
|
-
machineconfig/scripts/python/devops.py,sha256=
|
|
149
|
+
machineconfig/scripts/python/devops.py,sha256=GeHPeRvQGajtgWZZ4U1itF4bPh0tFTQ49YYg7iPnLCk,6767
|
|
150
150
|
machineconfig/scripts/python/devops_add_identity.py,sha256=wvjNgqsLmqD2SxbNCW_usqfp0LI-TDvcJJKGOWt2oFw,3775
|
|
151
151
|
machineconfig/scripts/python/devops_add_ssh_key.py,sha256=BXB-9RvuSZO0YTbnM2azeABW2ngLW4SKhhAGAieMzfw,6873
|
|
152
152
|
machineconfig/scripts/python/devops_backup_retrieve.py,sha256=JLJHmi8JmZ_qVTeMW-qBEAYGt1fmfWXzZ7Gm-Q-GDcU,5585
|
|
@@ -163,13 +163,13 @@ machineconfig/scripts/python/fire_jobs_streamlit_helper.py,sha256=47DEQpj8HBSa-_
|
|
|
163
163
|
machineconfig/scripts/python/ftpx.py,sha256=QfQTp-6jQP6yxfbLc5sKxiMtTgAgc8sjN7d17_uLiZc,9400
|
|
164
164
|
machineconfig/scripts/python/get_zellij_cmd.py,sha256=e35-18hoXM9N3PFbvbizfkNY_-63iMicieWE3TbGcCQ,576
|
|
165
165
|
machineconfig/scripts/python/gh_models.py,sha256=3BLfW25mBRiPO5VKtVm-nMlKLv-PaZDw7mObajq6F6M,5538
|
|
166
|
-
machineconfig/scripts/python/interactive.py,sha256=
|
|
166
|
+
machineconfig/scripts/python/interactive.py,sha256=FYb-bhn5GYDSER_l_w8YS_LwEI1aeczp3pF-3U2e3aQ,10038
|
|
167
167
|
machineconfig/scripts/python/mount_nfs.py,sha256=aECrL64j9g-9rF49sVJAjGmzaoGgcMnl3g9v17kQF4c,3239
|
|
168
168
|
machineconfig/scripts/python/mount_nw_drive.py,sha256=iru6AtnTyvyuk6WxlK5R4lDkuliVpPV5_uBTVVhXtjQ,1550
|
|
169
169
|
machineconfig/scripts/python/mount_ssh.py,sha256=k2fKq3f5dKq_7anrFOlqvJoI_3U4EWNHLRZ1o3Lsy6M,2268
|
|
170
170
|
machineconfig/scripts/python/onetimeshare.py,sha256=bmGsNnskym5OWfIhpOfZG5jq3m89FS0a6dF5Sb8LaZM,2539
|
|
171
171
|
machineconfig/scripts/python/pomodoro.py,sha256=SPkfeoZGv8rylGiOyzQ7UK3aXZ3G2FIOuGkSuBUggOI,2019
|
|
172
|
-
machineconfig/scripts/python/repos.py,sha256=
|
|
172
|
+
machineconfig/scripts/python/repos.py,sha256=7OwnQVedmLVgDnIY-OJ9epa1AcmRoUptu8ihyRNe_u4,5105
|
|
173
173
|
machineconfig/scripts/python/repos_helper.py,sha256=3jLdnNf1canpzi3JXiz5VA6UTUmLeNHuhjOWVl_thP0,3006
|
|
174
174
|
machineconfig/scripts/python/repos_helper_action.py,sha256=sXeOw5uHaK2GJixYW8qU_PD24mruGcQ59uf68ELC76A,14846
|
|
175
175
|
machineconfig/scripts/python/repos_helper_clone.py,sha256=9vGb9NCXT0lkerPzOJjmFfhU8LSzE-_1LDvjkhgnal0,5461
|
|
@@ -387,7 +387,7 @@ machineconfig/utils/accessories.py,sha256=W_9dLzjwNTW5JQk_pe3B2ijQ1nA2-8Kdg2r7VB
|
|
|
387
387
|
machineconfig/utils/code.py,sha256=CaDMxAoOKkjeMCr0Zw-yH0ghfz-4yvf3q7dPHKvHzrg,5634
|
|
388
388
|
machineconfig/utils/installer.py,sha256=FitnRR2xOekkAygXgptKWdHHGdbXImKR8PcbgPmiPXw,10738
|
|
389
389
|
machineconfig/utils/io.py,sha256=ZXB3aataS1IZ_0WMcCRSmoN1nbkvEO-bWYcs-TpngqU,2872
|
|
390
|
-
machineconfig/utils/links.py,sha256=
|
|
390
|
+
machineconfig/utils/links.py,sha256=GQExBsMoxewOhwIrNdERuzk9HVKcmWgNUGO-RzPMS6M,22588
|
|
391
391
|
machineconfig/utils/notifications.py,sha256=vvdsY5IX6XEiILTnt5lNyHxhCi0ljdGX2T_67VRfrG4,9009
|
|
392
392
|
machineconfig/utils/options.py,sha256=vUO4Kej-vDOv64wHr2HNDyu6PATURpjd7xp6N8OOoJg,7083
|
|
393
393
|
machineconfig/utils/path_extended.py,sha256=Xjdn2AVnB8p1jfNMNe2kJutVa5zGnFFJVGZbw-Bp_hg,53200
|
|
@@ -417,8 +417,8 @@ machineconfig/utils/schemas/fire_agents/fire_agents_input.py,sha256=pTxvLzIpD5RF
|
|
|
417
417
|
machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoSpdmTIdgS9LS-RvE-QZ-D260tD3o,1214
|
|
418
418
|
machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
|
|
419
419
|
machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
|
|
420
|
-
machineconfig-5.
|
|
421
|
-
machineconfig-5.
|
|
422
|
-
machineconfig-5.
|
|
423
|
-
machineconfig-5.
|
|
424
|
-
machineconfig-5.
|
|
420
|
+
machineconfig-5.20.dist-info/METADATA,sha256=1nL_AlCqeKOVeHhyQXWYzCUg6dXrutMRVaYqL6bbE2I,8030
|
|
421
|
+
machineconfig-5.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
422
|
+
machineconfig-5.20.dist-info/entry_points.txt,sha256=2afE1mw-o4MUlfxyX73SV02XaQI4SV_LdL2r6_CzhPU,1074
|
|
423
|
+
machineconfig-5.20.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
|
|
424
|
+
machineconfig-5.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|