machineconfig 8.14__py3-none-any.whl → 8.50__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/remote/run_cluster.py +1 -1
- machineconfig/cluster/remote/run_remote.py +1 -1
- machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
- machineconfig/cluster/sessions_managers/wt_local.py +1 -1
- machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
- machineconfig/jobs/installer/checks/check_installations.py +133 -0
- machineconfig/jobs/installer/checks/install_utils.py +132 -0
- machineconfig/jobs/installer/checks/report_utils.py +39 -0
- machineconfig/jobs/installer/checks/vt_utils.py +89 -0
- machineconfig/jobs/installer/installer_data.json +225 -140
- machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
- machineconfig/jobs/installer/package_groups.py +10 -9
- machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
- machineconfig/jobs/installer/python_scripts/code.py +10 -8
- machineconfig/jobs/installer/python_scripts/hx.py +30 -13
- machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
- machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
- machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
- machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
- machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
- machineconfig/jobs/scripts_dynamic/a.py +413 -10
- machineconfig/profile/create_links.py +77 -20
- machineconfig/profile/create_links_export.py +63 -58
- machineconfig/profile/mapper_data.toml +30 -0
- machineconfig/profile/mapper_dotfiles.toml +253 -0
- machineconfig/scripts/python/agents.py +70 -172
- machineconfig/scripts/python/ai/initai.py +3 -1
- machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
- machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
- machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -0
- machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
- machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
- machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
- machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
- machineconfig/scripts/python/cloud.py +58 -11
- machineconfig/scripts/python/croshell.py +4 -156
- machineconfig/scripts/python/devops.py +57 -40
- machineconfig/scripts/python/devops_navigator.py +17 -3
- machineconfig/scripts/python/fire_jobs.py +8 -207
- machineconfig/scripts/python/ftpx.py +5 -225
- machineconfig/scripts/python/graph/cli_graph.json +8743 -0
- machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
- machineconfig/scripts/python/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
- machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +7 -6
- machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +76 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
- machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +274 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +40 -23
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
- machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +3 -3
- machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
- machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +3 -3
- machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
- machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
- machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
- machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
- machineconfig/scripts/python/helpers/helpers_network/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
- machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +117 -33
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
- machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
- machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +186 -0
- machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +1 -1
- machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
- machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/download.py +1 -1
- machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +47 -26
- machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
- machineconfig/scripts/python/mcfg_entry.py +133 -48
- machineconfig/scripts/python/msearch.py +15 -61
- machineconfig/scripts/python/sessions.py +59 -194
- machineconfig/scripts/python/terminal.py +18 -96
- machineconfig/scripts/python/utils.py +101 -20
- machineconfig/settings/atuin/config.toml +294 -0
- machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
- machineconfig/settings/linters/.ruff.toml +1 -0
- machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
- machineconfig/settings/shells/bash/init.sh +6 -3
- machineconfig/settings/shells/pwsh/init.ps1 +69 -1
- machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
- machineconfig/settings/shells/wt/settings.json +20 -7
- machineconfig/settings/shells/zsh/init.sh +25 -4
- machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
- machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
- machineconfig/settings/tv/config.toml +234 -0
- machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
- machineconfig/settings/wsl/.wslconfig +5 -30
- machineconfig/settings/yazi/yazi_linux.toml +18 -8
- machineconfig/settings/zellij/layouts/st.kdl +2 -2
- machineconfig/settings/zellij/layouts/st2.kdl +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
- machineconfig/setup_mac/__init__.py +0 -2
- machineconfig/setup_windows/__init__.py +0 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
- machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
- machineconfig/type_hinting/sql/__init__.py +1 -0
- machineconfig/type_hinting/sql/base.py +216 -0
- machineconfig/type_hinting/sql/core_schema.py +64 -0
- machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
- machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
- machineconfig/type_hinting/typedict/__init__.py +1 -0
- machineconfig/type_hinting/typedict/ast_utils.py +130 -0
- machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
- machineconfig/type_hinting/typedict/generators.py +231 -0
- machineconfig/type_hinting/typedict/polars_schema.py +24 -0
- machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
- machineconfig/utils/accessories.py +24 -0
- machineconfig/utils/code.py +41 -13
- machineconfig/utils/files/ascii_art.py +10 -14
- machineconfig/utils/files/headers.py +3 -5
- machineconfig/utils/files/read.py +8 -1
- machineconfig/utils/installer_utils/github_release_bulk.py +11 -91
- machineconfig/utils/installer_utils/github_release_scraper.py +99 -0
- machineconfig/utils/installer_utils/install_from_url.py +1 -1
- machineconfig/utils/installer_utils/installer_class.py +12 -4
- machineconfig/utils/installer_utils/installer_cli.py +1 -15
- machineconfig/utils/installer_utils/installer_helper.py +2 -2
- machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
- machineconfig/utils/installer_utils/installer_runner.py +4 -4
- machineconfig/utils/io.py +25 -8
- machineconfig/utils/meta.py +6 -4
- machineconfig/utils/options.py +49 -19
- machineconfig/utils/options_utils/__init__.py +0 -0
- machineconfig/utils/options_utils/options_tv_linux.py +211 -0
- machineconfig/utils/options_utils/options_tv_windows.py +88 -0
- machineconfig/utils/options_utils/tv_options.py +37 -0
- machineconfig/utils/path_extended.py +6 -6
- machineconfig/utils/scheduler.py +8 -2
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/source_of_truth.py +6 -1
- machineconfig/utils/ssh.py +69 -18
- machineconfig/utils/ssh_utils/abc.py +1 -1
- machineconfig/utils/ssh_utils/copy_from_here.py +17 -12
- machineconfig/utils/ssh_utils/utils.py +21 -5
- machineconfig/utils/ssh_utils/wsl.py +107 -170
- machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
- machineconfig/utils/upgrade_packages.py +4 -8
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/METADATA +29 -22
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/RECORD +251 -211
- machineconfig/jobs/installer/check_installations.py +0 -248
- machineconfig/profile/backup.toml +0 -49
- machineconfig/profile/mapper.toml +0 -263
- machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
- machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
- machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
- machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
- machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
- machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
- machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
- machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
- machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
- machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
- machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
- machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
- machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
- machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
- machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
- machineconfig/utils/options_tv.py +0 -119
- machineconfig/utils/tst.py +0 -20
- /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
- /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
- /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers}/__init__.py +0 -0
- /machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
- /machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_agents}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_devops/themes}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/f.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
- /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
- /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
- /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
- /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
- /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/WHEEL +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/entry_points.txt +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""BR: Backup and Retrieve"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from platform import system
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
from machineconfig.utils.io import read_ini
|
|
12
|
+
from machineconfig.utils.source_of_truth import DEFAULTS_PATH
|
|
13
|
+
from machineconfig.utils.code import print_code
|
|
14
|
+
from machineconfig.utils.options import choose_cloud_interactively
|
|
15
|
+
from machineconfig.scripts.python.helpers.helpers_cloud.helpers2 import ES
|
|
16
|
+
from machineconfig.scripts.python.helpers.helpers_devops.backup_config import (
|
|
17
|
+
BackupConfig, BackupGroup, VALID_OS, USER_BACKUP_PATH, DEFAULT_BACKUP_HEADER,
|
|
18
|
+
normalize_os_name, os_applies, read_backup_config,
|
|
19
|
+
)
|
|
20
|
+
from machineconfig.profile.create_links_export import REPO_LOOSE
|
|
21
|
+
|
|
22
|
+
DIRECTION = Literal["BACKUP", "RETRIEVE"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _sanitize_entry_name(value: str) -> str:
|
|
26
|
+
token = value.strip().replace(".", "_").replace("-", "_")
|
|
27
|
+
token = re.sub(r"\s+", "_", token)
|
|
28
|
+
token = re.sub(r"[^A-Za-z0-9_]", "_", token)
|
|
29
|
+
return token or "backup_item"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _format_backup_entry_block(
|
|
33
|
+
entry_name: str,
|
|
34
|
+
path_local: str,
|
|
35
|
+
path_cloud: str,
|
|
36
|
+
zip: bool,
|
|
37
|
+
encrypt: bool,
|
|
38
|
+
rel2home: bool,
|
|
39
|
+
os_value: str,
|
|
40
|
+
) -> str:
|
|
41
|
+
parts = [
|
|
42
|
+
f"path_local = '{path_local}'",
|
|
43
|
+
f"path_cloud = '{path_cloud}'",
|
|
44
|
+
f"encrypt = {str(encrypt).lower()}",
|
|
45
|
+
f"zip = {str(zip).lower()}",
|
|
46
|
+
f"rel2home = {str(rel2home).lower()}",
|
|
47
|
+
f"os = '{os_value}'",
|
|
48
|
+
]
|
|
49
|
+
return f"{entry_name} = {{ {', '.join(parts)} }}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _upsert_backup_entry(content: str, group_name: str, entry_name: str, entry_line: str) -> tuple[str, bool]:
|
|
53
|
+
header = f"[{group_name}]"
|
|
54
|
+
lines = content.splitlines()
|
|
55
|
+
new_lines: list[str] = []
|
|
56
|
+
in_target = False
|
|
57
|
+
entry_written = False
|
|
58
|
+
replaced = False
|
|
59
|
+
for line in lines:
|
|
60
|
+
stripped = line.strip()
|
|
61
|
+
if stripped.startswith("[") and stripped.endswith("]"):
|
|
62
|
+
if in_target and not entry_written:
|
|
63
|
+
new_lines.append(entry_line)
|
|
64
|
+
entry_written = True
|
|
65
|
+
in_target = False
|
|
66
|
+
if stripped == header:
|
|
67
|
+
in_target = True
|
|
68
|
+
new_lines.append(line)
|
|
69
|
+
continue
|
|
70
|
+
if in_target:
|
|
71
|
+
if stripped.startswith(f"{entry_name} ="):
|
|
72
|
+
new_lines.append(entry_line)
|
|
73
|
+
replaced = True
|
|
74
|
+
entry_written = True
|
|
75
|
+
continue
|
|
76
|
+
new_lines.append(line)
|
|
77
|
+
if not entry_written:
|
|
78
|
+
if header not in content:
|
|
79
|
+
if new_lines and new_lines[-1].strip():
|
|
80
|
+
new_lines.append("")
|
|
81
|
+
new_lines.append(header)
|
|
82
|
+
new_lines.append(entry_line)
|
|
83
|
+
updated = "\n".join(new_lines).rstrip() + "\n"
|
|
84
|
+
return updated, replaced
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def register_backup_entry(
|
|
88
|
+
path_local: str,
|
|
89
|
+
group: str,
|
|
90
|
+
entry_name: Optional[str] = None,
|
|
91
|
+
path_cloud: Optional[str] = None,
|
|
92
|
+
zip: bool = True,
|
|
93
|
+
encrypt: bool = True,
|
|
94
|
+
rel2home: Optional[bool] = None,
|
|
95
|
+
os: str = "any",
|
|
96
|
+
) -> tuple[Path, str, bool]:
|
|
97
|
+
local_path = Path(path_local).expanduser().absolute()
|
|
98
|
+
if not local_path.exists():
|
|
99
|
+
raise ValueError(f"Local path does not exist: {local_path}")
|
|
100
|
+
os_parts = [part.strip() for part in os.split(",")]
|
|
101
|
+
os_tokens: list[str] = []
|
|
102
|
+
for part in os_parts:
|
|
103
|
+
if not part:
|
|
104
|
+
continue
|
|
105
|
+
token = normalize_os_name(part)
|
|
106
|
+
if token in {"any", "all", "*"}:
|
|
107
|
+
os_tokens = ["any"]
|
|
108
|
+
break
|
|
109
|
+
if token not in VALID_OS:
|
|
110
|
+
raise ValueError(f"Invalid os value: {os!r}. Expected one of: {sorted(VALID_OS)}")
|
|
111
|
+
os_tokens.append(token)
|
|
112
|
+
if not os_tokens:
|
|
113
|
+
raise ValueError(f"Invalid os value: {os!r}. Expected one of: {sorted(VALID_OS)}")
|
|
114
|
+
home = Path.home()
|
|
115
|
+
in_home = local_path.is_relative_to(home)
|
|
116
|
+
if rel2home is None:
|
|
117
|
+
rel2home = in_home
|
|
118
|
+
if rel2home and not in_home:
|
|
119
|
+
raise ValueError("rel2home is true, but the local path is not under the home directory.")
|
|
120
|
+
group_name = _sanitize_entry_name(group) if group and group.strip() else "default"
|
|
121
|
+
if entry_name is None or not entry_name.strip():
|
|
122
|
+
base_name = _sanitize_entry_name(local_path.stem)
|
|
123
|
+
os_tag = "any" if "any" in os_tokens else "_".join(os_tokens)
|
|
124
|
+
entry_name = f"{base_name}_{os_tag}" if os_tag != "any" else base_name
|
|
125
|
+
else:
|
|
126
|
+
entry_name = _sanitize_entry_name(entry_name)
|
|
127
|
+
local_display = f"~/{local_path.relative_to(home)}" if rel2home and in_home else local_path.as_posix()
|
|
128
|
+
cloud_value = path_cloud.strip() if path_cloud and path_cloud.strip() else ES
|
|
129
|
+
os_value = "any" if "any" in os_tokens else ",".join(os_tokens)
|
|
130
|
+
entry_block = _format_backup_entry_block(
|
|
131
|
+
entry_name=entry_name,
|
|
132
|
+
path_local=local_display,
|
|
133
|
+
path_cloud=cloud_value,
|
|
134
|
+
zip=zip,
|
|
135
|
+
encrypt=encrypt,
|
|
136
|
+
rel2home=rel2home,
|
|
137
|
+
os_value=os_value,
|
|
138
|
+
)
|
|
139
|
+
USER_BACKUP_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
if USER_BACKUP_PATH.exists():
|
|
141
|
+
content = USER_BACKUP_PATH.read_text(encoding="utf-8")
|
|
142
|
+
else:
|
|
143
|
+
content = DEFAULT_BACKUP_HEADER
|
|
144
|
+
updated, replaced = _upsert_backup_entry(content=content, group_name=group_name, entry_name=entry_name, entry_line=entry_block)
|
|
145
|
+
USER_BACKUP_PATH.write_text(updated, encoding="utf-8")
|
|
146
|
+
return USER_BACKUP_PATH, entry_name, replaced
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main_backup_retrieve(direction: DIRECTION, which: Optional[str], cloud: Optional[str], repo: REPO_LOOSE) -> None:
|
|
150
|
+
console = Console()
|
|
151
|
+
if cloud is None or not cloud.strip():
|
|
152
|
+
try:
|
|
153
|
+
cloud = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"].strip()
|
|
154
|
+
console.print(Panel(f"⚠️ DEFAULT CLOUD CONFIGURATION\n🌥️ Using default cloud: {cloud}", title="[bold blue]Cloud Configuration[/bold blue]", border_style="blue"))
|
|
155
|
+
except (FileNotFoundError, KeyError, IndexError):
|
|
156
|
+
console.print(Panel("🔍 DEFAULT CLOUD NOT FOUND\n🔄 Please select a cloud configuration from the options below", title="[bold red]Error: Cloud Not Found[/bold red]", border_style="red"))
|
|
157
|
+
cloud = choose_cloud_interactively().strip()
|
|
158
|
+
else:
|
|
159
|
+
cloud = cloud.strip()
|
|
160
|
+
console.print(Panel(f"🌥️ Using provided cloud: {cloud}", title="[bold blue]Cloud Configuration[/bold blue]", border_style="blue"))
|
|
161
|
+
assert cloud is not None
|
|
162
|
+
bu_file: BackupConfig = read_backup_config(repo=repo)
|
|
163
|
+
system_raw = system()
|
|
164
|
+
normalized_system = normalize_os_name(system_raw)
|
|
165
|
+
filtered: BackupConfig = {}
|
|
166
|
+
for group_name, group_items in bu_file.items():
|
|
167
|
+
matched: BackupGroup = {}
|
|
168
|
+
for key, val in group_items.items():
|
|
169
|
+
if os_applies(val["os"], system_name=normalized_system):
|
|
170
|
+
matched[key] = val
|
|
171
|
+
if matched:
|
|
172
|
+
filtered[group_name] = matched
|
|
173
|
+
bu_file = filtered
|
|
174
|
+
console.print(Panel(
|
|
175
|
+
f"🖥️ {system_raw} ENVIRONMENT DETECTED\n"
|
|
176
|
+
"🔍 Filtering entries by os field\n"
|
|
177
|
+
f"✅ Found {sum(len(item) for item in bu_file.values())} applicable backup configuration entries",
|
|
178
|
+
title="[bold blue]Environment[/bold blue]",
|
|
179
|
+
border_style="blue",
|
|
180
|
+
))
|
|
181
|
+
|
|
182
|
+
if which is None:
|
|
183
|
+
# import platform
|
|
184
|
+
# if platform.system() not in {"Linux", "Darwin"}:
|
|
185
|
+
# console.print(Panel(f"🔍 SELECT {direction} ITEMS\n📋 Choose which configuration entries to process", title="[bold blue]Select Items[/bold blue]", border_style="blue"))
|
|
186
|
+
# choices = choose_from_options(multi=True, msg=f"WHICH FILE of the following do you want to {direction}?", options=["all"] + list(bu_file.keys()), tv=True)
|
|
187
|
+
# else:
|
|
188
|
+
from machineconfig.utils.options_utils.options_tv_linux import select_from_options
|
|
189
|
+
choice = select_from_options(
|
|
190
|
+
options_to_preview_mapping=bu_file, extension="toml", multi=False, preview_size_percent=75.0,
|
|
191
|
+
)
|
|
192
|
+
if choice is None:
|
|
193
|
+
console.print(Panel("❌ NO ITEMS SELECTED\n⚠️ Exiting without processing any items", title="[bold red]No Items Selected[/bold red]", border_style="red"))
|
|
194
|
+
return
|
|
195
|
+
choices = [choice]
|
|
196
|
+
else:
|
|
197
|
+
choices = [token.strip() for token in which.split(",")] if which else []
|
|
198
|
+
console.print(Panel(f"🔖 PRE-SELECTED ITEMS\n📝 Using: {', '.join(choices)}", title="[bold blue]Pre-selected Items[/bold blue]", border_style="blue"))
|
|
199
|
+
|
|
200
|
+
items: BackupConfig
|
|
201
|
+
if "all" in choices:
|
|
202
|
+
items = bu_file
|
|
203
|
+
console.print(Panel(f"📋 PROCESSING ALL ENTRIES\n🔢 Total entries to process: {sum(len(item) for item in bu_file.values())}", title="[bold blue]Process All Entries[/bold blue]", border_style="blue"))
|
|
204
|
+
else:
|
|
205
|
+
items = {}
|
|
206
|
+
unknown: list[str] = []
|
|
207
|
+
for choice in choices:
|
|
208
|
+
if not choice:
|
|
209
|
+
continue
|
|
210
|
+
if choice in bu_file:
|
|
211
|
+
items[choice] = bu_file[choice]
|
|
212
|
+
continue
|
|
213
|
+
if "." in choice:
|
|
214
|
+
group_name, item_name = choice.split(".", 1)
|
|
215
|
+
if group_name in bu_file and item_name in bu_file[group_name]:
|
|
216
|
+
items.setdefault(group_name, {})[item_name] = bu_file[group_name][item_name]
|
|
217
|
+
continue
|
|
218
|
+
unknown.append(choice)
|
|
219
|
+
if unknown:
|
|
220
|
+
raise ValueError(f"Unknown backup entries: {', '.join(unknown)}")
|
|
221
|
+
console.print(Panel(f"📋 PROCESSING SELECTED ENTRIES\n🔢 Total entries to process: {sum(len(item) for item in items.values())}", title="[bold blue]Process Selected Entries[/bold blue]", border_style="blue"))
|
|
222
|
+
program = ""
|
|
223
|
+
console.print(Panel(f"🚀 GENERATING {direction} SCRIPT\n🌥️ Cloud: {cloud}\n🗂️ Items: {sum(len(item) for item in items.values())}", title="[bold blue]Script Generation[/bold blue]", border_style="blue"))
|
|
224
|
+
for group_name, group_items in items.items():
|
|
225
|
+
for item_name, item in group_items.items():
|
|
226
|
+
display_name = f"{group_name}.{item_name}"
|
|
227
|
+
flags = ""
|
|
228
|
+
flags += "z" if item["zip"] else ""
|
|
229
|
+
flags += "e" if item["encrypt"] else ""
|
|
230
|
+
flags += "r" if item["rel2home"] else ""
|
|
231
|
+
flags += "o" if "any" not in item["os"] else ""
|
|
232
|
+
local_path = Path(item["path_local"]).as_posix()
|
|
233
|
+
if item["path_cloud"] in (None, ES):
|
|
234
|
+
remote_path = ES
|
|
235
|
+
remote_display = f"{ES} (deduced)"
|
|
236
|
+
else:
|
|
237
|
+
remote_path = Path(item["path_cloud"]).as_posix()
|
|
238
|
+
remote_display = remote_path
|
|
239
|
+
remote_spec = f"{cloud}:{remote_path}"
|
|
240
|
+
console.print(Panel(
|
|
241
|
+
f"📦 PROCESSING: {display_name}\n"
|
|
242
|
+
f"📂 Local path: {local_path}\n"
|
|
243
|
+
f"☁️ Remote path: {remote_display}\n"
|
|
244
|
+
f"🏳️ Flags: {flags or 'None'}",
|
|
245
|
+
title=f"[bold blue]Processing Item: {display_name}[/bold blue]",
|
|
246
|
+
border_style="blue",
|
|
247
|
+
))
|
|
248
|
+
flag_arg = f" -{flags}" if flags else ""
|
|
249
|
+
if direction == "BACKUP":
|
|
250
|
+
program += f"""\ncloud copy "{local_path}" "{remote_spec}"{flag_arg}\n"""
|
|
251
|
+
elif direction == "RETRIEVE":
|
|
252
|
+
program += f"""\ncloud copy "{remote_spec}" "{local_path}"{flag_arg}\n"""
|
|
253
|
+
else:
|
|
254
|
+
console.print(Panel('❌ ERROR: INVALID DIRECTION\n⚠️ Direction must be either "BACKUP" or "RETRIEVE"', title="[bold red]Error: Invalid Direction[/bold red]", border_style="red"))
|
|
255
|
+
raise RuntimeError(f"Unknown direction: {direction}")
|
|
256
|
+
if group_name == "dotfiles" and system_raw == "Linux":
|
|
257
|
+
program += """\nchmod 700 ~/.ssh/*\n"""
|
|
258
|
+
console.print(Panel("🔒 SPECIAL HANDLING: SSH PERMISSIONS\n🛠️ Setting secure permissions for SSH files\n📝 Command: chmod 700 ~/.ssh/*", title="[bold blue]Special Handling: SSH Permissions[/bold blue]", border_style="blue"))
|
|
259
|
+
print_code(program, lexer="shell", desc=f"{direction} script")
|
|
260
|
+
console.print(Panel(f"✅ {direction} SCRIPT GENERATION COMPLETE\n🚀 Ready to execute the operations", title="[bold green]Script Generation Complete[/bold green]", border_style="green"))
|
|
261
|
+
import subprocess
|
|
262
|
+
|
|
263
|
+
subprocess.run(program, shell=True, check=True)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
pass
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Annotated
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import typer
|
|
6
|
+
import machineconfig.scripts.python.helpers.helpers_devops.cli_config_dotfile as dotfile_module
|
|
7
|
+
import machineconfig.profile.create_links_export as create_links_export
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def configure_shell_profile(which: Annotated[Literal["default", "d", "nushell", "n"], typer.Option(..., "--which", "-w", help="Which shell profile to create/configure")]="default"):
|
|
11
|
+
"""🔗 Configure your shell profile."""
|
|
12
|
+
from machineconfig.profile.create_shell_profile import create_default_shell_profile, create_nu_shell_profile
|
|
13
|
+
match which:
|
|
14
|
+
case "nushell" | "n":
|
|
15
|
+
create_nu_shell_profile()
|
|
16
|
+
return
|
|
17
|
+
case "default" | "d":
|
|
18
|
+
create_default_shell_profile()
|
|
19
|
+
return
|
|
20
|
+
msg = typer.style("Error: ", fg=typer.colors.RED) + f"Unknown shell profile type: {which}"
|
|
21
|
+
typer.echo(msg)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pwsh_theme():
|
|
26
|
+
"""🔗 Select powershell prompt theme."""
|
|
27
|
+
import machineconfig.scripts.python.helpers.helpers_devops.themes as themes
|
|
28
|
+
file = Path(themes.__file__).parent / "choose_pwsh_theme.ps1"
|
|
29
|
+
import subprocess
|
|
30
|
+
subprocess.run(["pwsh", "-File", str(file)])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def starship_theme():
|
|
34
|
+
"""🔗 Select starship prompt theme."""
|
|
35
|
+
import subprocess
|
|
36
|
+
import platform
|
|
37
|
+
import os
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
current_dir = Path(__file__).parent.joinpath("themes")
|
|
41
|
+
|
|
42
|
+
if platform.system() == "Windows":
|
|
43
|
+
script_path = current_dir / "choose_starship_theme.ps1"
|
|
44
|
+
try:
|
|
45
|
+
subprocess.run(["pwsh", "-File", str(script_path)], check=True)
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
# Fallback to powershell if pwsh is not available
|
|
48
|
+
subprocess.run(["powershell", "-File", str(script_path)], check=True)
|
|
49
|
+
else:
|
|
50
|
+
script_path = current_dir / "choose_starship_theme.sh"
|
|
51
|
+
# Ensure executable
|
|
52
|
+
os.chmod(script_path, 0o755)
|
|
53
|
+
subprocess.run(["bash", str(script_path)])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def copy_assets(which: Annotated[Literal["scripts", "s", "settings", "t", "both", "b"], typer.Argument(..., help="Which assets to copy")]):
|
|
57
|
+
"""🔗 Copy asset files from library to machine."""
|
|
58
|
+
import machineconfig.profile.create_helper as create_helper
|
|
59
|
+
match which:
|
|
60
|
+
case "both" | "b":
|
|
61
|
+
create_helper.copy_assets_to_machine(which="scripts")
|
|
62
|
+
create_helper.copy_assets_to_machine(which="settings")
|
|
63
|
+
return
|
|
64
|
+
case "scripts" | "s":
|
|
65
|
+
create_helper.copy_assets_to_machine(which="scripts")
|
|
66
|
+
return
|
|
67
|
+
case "settings" | "t":
|
|
68
|
+
create_helper.copy_assets_to_machine(which="settings")
|
|
69
|
+
return
|
|
70
|
+
msg = typer.style("Error: ", fg=typer.colors.RED) + f"Unknown asset type: {which}"
|
|
71
|
+
typer.echo(msg)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_app():
|
|
75
|
+
config_apps = typer.Typer(help="⚙️ [c] configuration subcommands", no_args_is_help=True, add_help_option=True, add_completion=False)
|
|
76
|
+
config_apps.command("sync", no_args_is_help=True, help="🔗 [s] Sync dotfiles.")(create_links_export.main_from_parser)
|
|
77
|
+
config_apps.command("s", no_args_is_help=True, help="Sync dotfiles.", hidden=True)(create_links_export.main_from_parser)
|
|
78
|
+
|
|
79
|
+
config_apps.command("register", no_args_is_help=True, help="🔗 [r] Register dotfiles agains user mapper.toml")(dotfile_module.register_dotfile)
|
|
80
|
+
config_apps.command("r", no_args_is_help=True, hidden=True)(dotfile_module.register_dotfile)
|
|
81
|
+
|
|
82
|
+
config_apps.command("export-dotfiles", no_args_is_help=True, help="🔗 [e] Export dotfiles for migration to new machine.")(dotfile_module.export_dotfiles)
|
|
83
|
+
config_apps.command("e", no_args_is_help=True, help="Export dotfiles for migration to new machine.", hidden=True)(dotfile_module.export_dotfiles)
|
|
84
|
+
config_apps.command("import-dotfiles", no_args_is_help=False, help="🔗 [i] Import dotfiles from exported archive.")(dotfile_module.import_dotfiles)
|
|
85
|
+
config_apps.command("i", no_args_is_help=False, help="Import dotfiles from exported archive.", hidden=True)(dotfile_module.import_dotfiles)
|
|
86
|
+
|
|
87
|
+
config_apps.command("shell", no_args_is_help=False, help="🔗 [S] Configure your shell profile.")(configure_shell_profile)
|
|
88
|
+
config_apps.command("S", no_args_is_help=False, help="Configure your shell profile.", hidden=True)(configure_shell_profile)
|
|
89
|
+
config_apps.command("starship-theme", no_args_is_help=False, help="🔗 [t] Select starship prompt theme.")(starship_theme)
|
|
90
|
+
config_apps.command("t", no_args_is_help=False, help="Select starship prompt theme.", hidden=True)(starship_theme)
|
|
91
|
+
config_apps.command("pwsh-theme", no_args_is_help=False, help="🔗 [T] Select powershell prompt theme.")(pwsh_theme)
|
|
92
|
+
config_apps.command("T", no_args_is_help=False, help="Select powershell prompt theme.", hidden=True)(pwsh_theme)
|
|
93
|
+
|
|
94
|
+
config_apps.command("copy-assets", no_args_is_help=True, help="🔗 [c] Copy asset files from library to machine.", hidden=False)(copy_assets)
|
|
95
|
+
config_apps.command("c", no_args_is_help=True, help="Copy asset files from library to machine.", hidden=True)(copy_assets)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
return config_apps
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
|
|
2
|
+
"""Like yadm and dotter."""
|
|
3
|
+
|
|
4
|
+
from git import Optional
|
|
5
|
+
from machineconfig.profile.create_links_export import ON_CONFLICT_LOOSE, ON_CONFLICT_MAPPER, METHOD_LOOSE, METHOD_MAP
|
|
6
|
+
from typing import Annotated, Literal
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from machineconfig.utils.source_of_truth import CONFIG_ROOT
|
|
11
|
+
BACKUP_ROOT_PRIVATE = Path.home().joinpath("dotfiles/machineconfig/mapper/files")
|
|
12
|
+
BACKUP_ROOT_PUBLIC = Path(CONFIG_ROOT).joinpath("dotfiles/mapper")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _write_to_user_mapper(section: str, entry_name: str, original_path: Path, self_managed_path: Path, method: Literal["symlink", "copy"], is_contents: bool, os_filter: str) -> Path:
|
|
17
|
+
from machineconfig.profile.create_links import USER_MAPPER_PATH
|
|
18
|
+
mapper_path = USER_MAPPER_PATH
|
|
19
|
+
mapper_path.parent.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
if mapper_path.exists():
|
|
21
|
+
content = mapper_path.read_text(encoding="utf-8")
|
|
22
|
+
else:
|
|
23
|
+
content = "# User-defined config file mappings\n# Created by `d c` CLI tool\n\n"
|
|
24
|
+
home = Path.home()
|
|
25
|
+
original_str = f"~/{original_path.relative_to(home)}" if original_path.is_relative_to(home) else original_path.as_posix()
|
|
26
|
+
self_managed_str = f"~/{self_managed_path.relative_to(home)}" if self_managed_path.is_relative_to(home) else self_managed_path.as_posix()
|
|
27
|
+
entry_dict_parts: list[str] = [f"original = '{original_str}'", f"self_managed = '{self_managed_str}'"]
|
|
28
|
+
if is_contents:
|
|
29
|
+
entry_dict_parts.append("contents = true")
|
|
30
|
+
if method == "copy":
|
|
31
|
+
entry_dict_parts.append("copy = true")
|
|
32
|
+
entry_dict_parts.append(f"os = '{os_filter}'")
|
|
33
|
+
entry_line = f"{entry_name} = {{ {', '.join(entry_dict_parts)} }}"
|
|
34
|
+
section_header = f"[{section}]"
|
|
35
|
+
if section_header in content:
|
|
36
|
+
lines = content.split("\n")
|
|
37
|
+
new_lines: list[str] = []
|
|
38
|
+
in_section = False
|
|
39
|
+
entry_updated = False
|
|
40
|
+
for line in lines:
|
|
41
|
+
if line.strip().startswith("[") and line.strip().endswith("]"):
|
|
42
|
+
if in_section and not entry_updated:
|
|
43
|
+
new_lines.append(entry_line)
|
|
44
|
+
entry_updated = True
|
|
45
|
+
in_section = line.strip() == section_header
|
|
46
|
+
if in_section and line.strip().startswith(f"{entry_name} ="):
|
|
47
|
+
new_lines.append(entry_line)
|
|
48
|
+
entry_updated = True
|
|
49
|
+
continue
|
|
50
|
+
new_lines.append(line)
|
|
51
|
+
if in_section and not entry_updated:
|
|
52
|
+
new_lines.append(entry_line)
|
|
53
|
+
content = "\n".join(new_lines)
|
|
54
|
+
else:
|
|
55
|
+
content = content.rstrip() + f"\n\n{section_header}\n{entry_line}\n"
|
|
56
|
+
mapper_path.write_text(content, encoding="utf-8")
|
|
57
|
+
return mapper_path
|
|
58
|
+
|
|
59
|
+
def record_mapping(orig_path: Path, new_path: Path, method: METHOD_LOOSE, section: str, os_filter: str) -> None:
|
|
60
|
+
entry_name = orig_path.stem.replace(".", "_").replace("-", "_")
|
|
61
|
+
method_resolved = METHOD_MAP[method]
|
|
62
|
+
mapper_file = _write_to_user_mapper(section=section, entry_name=entry_name, original_path=orig_path, self_managed_path=new_path, method=method_resolved, is_contents=False, os_filter=os_filter)
|
|
63
|
+
home = Path.home()
|
|
64
|
+
orig_display = f"~/{orig_path.relative_to(home)}" if orig_path.is_relative_to(home) else orig_path.as_posix()
|
|
65
|
+
new_display = f"~/{new_path.relative_to(home)}" if new_path.is_relative_to(home) else new_path.as_posix()
|
|
66
|
+
from rich.console import Console
|
|
67
|
+
from rich.panel import Panel
|
|
68
|
+
console = Console()
|
|
69
|
+
console.print(Panel(f"📝 Mapping recorded in: [cyan]{mapper_file}[/cyan]\n[{section}]\n{entry_name} = {{ original = '{orig_display}', self_managed = '{new_display}', os = '{os_filter}' }}", title="Mapper Entry Saved", border_style="cyan", padding=(1, 2),))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_backup_path(orig_path: Path, sensitivity: Literal["private", "v", "public", "b"], destination: Optional[str], shared: bool) -> Path:
|
|
73
|
+
match sensitivity:
|
|
74
|
+
case "private" | "v":
|
|
75
|
+
backup_root = BACKUP_ROOT_PRIVATE
|
|
76
|
+
case "public" | "b":
|
|
77
|
+
backup_root = BACKUP_ROOT_PUBLIC
|
|
78
|
+
if destination is None:
|
|
79
|
+
if shared:
|
|
80
|
+
new_path = backup_root.joinpath("shared").joinpath(orig_path.name)
|
|
81
|
+
else:
|
|
82
|
+
new_path = backup_root.joinpath(orig_path.relative_to(Path.home()))
|
|
83
|
+
else:
|
|
84
|
+
if shared:
|
|
85
|
+
dest_path = Path(destination).expanduser().absolute()
|
|
86
|
+
new_path = dest_path.joinpath("shared").joinpath(orig_path.name)
|
|
87
|
+
else:
|
|
88
|
+
dest_path = Path(destination).expanduser().absolute()
|
|
89
|
+
new_path = dest_path.joinpath(orig_path.name)
|
|
90
|
+
return new_path
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_original_path_from_backup_path(backup_path: Path, sensitivity: Literal["private", "v", "public", "b"], destination: Optional[str], shared: bool) -> Path:
|
|
94
|
+
match sensitivity:
|
|
95
|
+
case "private" | "v":
|
|
96
|
+
backup_root = BACKUP_ROOT_PRIVATE
|
|
97
|
+
case "public" | "b":
|
|
98
|
+
backup_root = BACKUP_ROOT_PUBLIC
|
|
99
|
+
if destination is None:
|
|
100
|
+
if shared:
|
|
101
|
+
relative_part = backup_path.relative_to(backup_root.joinpath("shared"))
|
|
102
|
+
else:
|
|
103
|
+
relative_part = backup_path.relative_to(backup_root)
|
|
104
|
+
original_path = Path.home().joinpath(relative_part)
|
|
105
|
+
else:
|
|
106
|
+
dest_path = Path(destination).expanduser().absolute()
|
|
107
|
+
if shared:
|
|
108
|
+
relative_part = backup_path.relative_to(dest_path.joinpath("shared"))
|
|
109
|
+
else:
|
|
110
|
+
relative_part = backup_path.relative_to(dest_path)
|
|
111
|
+
original_path = Path.home().joinpath(relative_part)
|
|
112
|
+
return original_path
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def register_dotfile(
|
|
116
|
+
file: Annotated[str, typer.Argument(help="file/folder path.")],
|
|
117
|
+
method: Annotated[METHOD_LOOSE, typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
|
|
118
|
+
on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
|
|
119
|
+
sensitivity: Annotated[Literal["private", "v", "public", "b"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
|
|
120
|
+
destination: Annotated[Optional[str], typer.Option("--destination", "-d", help="destination folder (override the default, use at your own risk)")] = None,
|
|
121
|
+
section: Annotated[str, typer.Option("--section", "-se", help="Section name in mapper_dotfiles.toml to record this mapping.")] = "default",
|
|
122
|
+
os_filter: Annotated[str, typer.Option("--os", help="Comma-separated OS list or 'any' to apply everywhere.")] = "any",
|
|
123
|
+
shared: Annotated[bool, typer.Option("--shared", "-sh", help="Whether the config file is shared across destinations directory.")] = False,
|
|
124
|
+
record: Annotated[bool, typer.Option("--record", "-r", help="Record the mapping in user's mapper.toml")] = True,
|
|
125
|
+
) -> None:
|
|
126
|
+
from rich.console import Console
|
|
127
|
+
from rich.panel import Panel
|
|
128
|
+
from machineconfig.utils.links import symlink_map, copy_map
|
|
129
|
+
console = Console()
|
|
130
|
+
orig_path = Path(file).expanduser().absolute()
|
|
131
|
+
new_path = get_backup_path(orig_path=orig_path, sensitivity=sensitivity, destination=destination, shared=shared)
|
|
132
|
+
if not orig_path.exists() and not new_path.exists():
|
|
133
|
+
console.print(f"[red]Error:[/] Neither original file nor self-managed file exists:\n Original: {orig_path}\n Self-managed: {new_path}")
|
|
134
|
+
raise typer.Exit(code=1)
|
|
135
|
+
new_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
match method:
|
|
137
|
+
case "copy" | "c":
|
|
138
|
+
try:
|
|
139
|
+
copy_map(config_file_default_path=orig_path, self_managed_config_file_path=new_path, on_conflict=ON_CONFLICT_MAPPER[on_conflict]) # type: ignore[arg-type]
|
|
140
|
+
except Exception as e:
|
|
141
|
+
msg = typer.style("Error: ", fg=typer.colors.RED) + str(e)
|
|
142
|
+
typer.echo(msg)
|
|
143
|
+
typer.Exit(code=1)
|
|
144
|
+
return
|
|
145
|
+
case "symlink" | "s":
|
|
146
|
+
try:
|
|
147
|
+
symlink_map(config_file_default_path=orig_path, self_managed_config_file_path=new_path, on_conflict=ON_CONFLICT_MAPPER[on_conflict]) # type: ignore[arg-type]
|
|
148
|
+
except Exception as e:
|
|
149
|
+
msg = typer.style("Error: ", fg=typer.colors.RED) + str(e)
|
|
150
|
+
typer.echo(msg)
|
|
151
|
+
typer.Exit(code=1)
|
|
152
|
+
return
|
|
153
|
+
case _:
|
|
154
|
+
raise ValueError(f"Unknown method: {method}")
|
|
155
|
+
console.print(Panel("\n".join(["✅ Symbolic link created successfully!", "🔄 Add the following snippet to mapper.toml to persist this mapping:",]), title="Symlink Created", border_style="green", padding=(1, 2),))
|
|
156
|
+
if record:
|
|
157
|
+
record_mapping(orig_path=orig_path, new_path=new_path, method=method, section=section, os_filter=os_filter)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def export_dotfiles(
|
|
161
|
+
pwd: Annotated[str, typer.Argument(..., help="Password for zip encryption")],
|
|
162
|
+
over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Use internet-based transfer (wormhole-magic)")] = False,
|
|
163
|
+
over_ssh: Annotated[bool, typer.Option("--over-ssh", "-s", help="Use SSH-based transfer (scp) to a remote machine")] = False,
|
|
164
|
+
):
|
|
165
|
+
"""🔗 Export dotfiles for migration to new machine."""
|
|
166
|
+
if over_ssh:
|
|
167
|
+
code_sample = """ftpx ~/dotfiles user@remote_host:^ -z"""
|
|
168
|
+
print("🔗 Exporting dotfiles via SSH-based transfer (scp).")
|
|
169
|
+
print(f"💡 Run the following command on your local machine to copy dotfiles to the remote machine:\n{code_sample}")
|
|
170
|
+
remote_address = typer.prompt("Enter the remote machine address (user@host) to copy dotfiles to ")
|
|
171
|
+
code_concrete = f"fptx ~/dotfiles {remote_address}:^ -z"
|
|
172
|
+
from machineconfig.utils.code import run_shell_script
|
|
173
|
+
run_shell_script(code_concrete)
|
|
174
|
+
dotfiles_dir = Path.home().joinpath("dotfiles")
|
|
175
|
+
if not dotfiles_dir.exists() or not dotfiles_dir.is_dir():
|
|
176
|
+
print(f"❌ Dotfiles directory does not exist: {dotfiles_dir}")
|
|
177
|
+
raise typer.Exit(code=1)
|
|
178
|
+
dotfiles_zip = Path.home().joinpath("dotfiles.zip")
|
|
179
|
+
if dotfiles_zip.exists():
|
|
180
|
+
dotfiles_zip.unlink()
|
|
181
|
+
import shutil
|
|
182
|
+
zipfile = shutil.make_archive(base_name=str(dotfiles_zip)[:-4], format="zip", root_dir=str(dotfiles_dir), base_dir=".", verbose=False)
|
|
183
|
+
from machineconfig.utils.io import encrypt
|
|
184
|
+
zipfile_enc_bytes = encrypt(
|
|
185
|
+
msg=Path(zipfile).read_bytes(),
|
|
186
|
+
pwd=pwd,
|
|
187
|
+
)
|
|
188
|
+
Path(zipfile).unlink()
|
|
189
|
+
zipfile_enc_path = Path(f"{zipfile}.enc")
|
|
190
|
+
if zipfile_enc_path.exists():
|
|
191
|
+
zipfile_enc_path.unlink()
|
|
192
|
+
zipfile_enc_path.write_bytes(zipfile_enc_bytes)
|
|
193
|
+
print(f"✅ Dotfiles exported to: {zipfile_enc_path}")
|
|
194
|
+
if over_internet:
|
|
195
|
+
# rm ~/dotfiles.zip || true
|
|
196
|
+
# ouch c ~/dotfiles dotfiles.zip
|
|
197
|
+
# # INSECURE OVER INTERNET: uvx wormhole-magic send ~/dotfiles.zip
|
|
198
|
+
raise NotImplementedError("Internet-based transfer not yet implemented.")
|
|
199
|
+
else:
|
|
200
|
+
# devops network share-server --no-auth ./dotfiles.zip
|
|
201
|
+
import machineconfig.scripts.python.helpers.helpers_devops.cli_share_server as cli_share_server
|
|
202
|
+
from machineconfig.scripts.python.helpers.helpers_network.address import select_lan_ipv4
|
|
203
|
+
localipv4 = select_lan_ipv4(prefer_vpn=False)
|
|
204
|
+
port = 8888
|
|
205
|
+
msg = f"""On the remote machine, run the following:
|
|
206
|
+
d c i -u http://{localipv4}:{port} -p {pwd}
|
|
207
|
+
"""
|
|
208
|
+
from machineconfig.utils.accessories import display_with_flashy_style
|
|
209
|
+
display_with_flashy_style(msg=msg, title="Remote Machine Instructions",)
|
|
210
|
+
cli_share_server.web_file_explorer(
|
|
211
|
+
path=str(zipfile_enc_path),
|
|
212
|
+
no_auth=True,
|
|
213
|
+
port=port,
|
|
214
|
+
# bind_address="
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def import_dotfiles(
|
|
219
|
+
url: Annotated[Optional[str], typer.Option(..., "--url", "-u", help="URL or local path to the encrypted dotfiles zip")] = None,
|
|
220
|
+
pwd: Annotated[Optional[str], typer.Option(..., "--pwd", "-p", help="Password for zip decryption")] = None,
|
|
221
|
+
use_ssh: Annotated[bool, typer.Option("--use-ssh", "-s", help="Use SSH-based transfer (scp) from a remote machine that has dotfiles.")]=False,
|
|
222
|
+
):
|
|
223
|
+
# # INSECURE cd $HOME; uvx wormhole-magic receive dotfiles.zip.enc --accept-file
|
|
224
|
+
# ☁️ [bold blue]Method 3: USING INTERNET SECURE SHARE[/bold blue]
|
|
225
|
+
# [dim]cd ~
|
|
226
|
+
# cloud copy SHARE_URL . --config ss[/dim]
|
|
227
|
+
if use_ssh:
|
|
228
|
+
print("🔗 Importing dotfiles via SSH-based transfer (scp).")
|
|
229
|
+
code = """fptx $USER@$(hostname):^ ~/dotfiles -z"""
|
|
230
|
+
print(f"💡 Run the following command on the remote machine that has the dotfiles:\n{code}")
|
|
231
|
+
url = typer.prompt("Enter the remote machine address (user@host) to copy dotfiles from ")
|
|
232
|
+
code_concrete = f"fptx {url}:^ ~/dotfiles -z"
|
|
233
|
+
from machineconfig.utils.code import run_shell_script
|
|
234
|
+
run_shell_script(code_concrete)
|
|
235
|
+
print("✅ Dotfiles copied via SSH.")
|
|
236
|
+
return
|
|
237
|
+
if url is None:
|
|
238
|
+
url = typer.prompt("Enter the URL or local path to the encrypted dotfiles zip (e..g 192.168.20.4:8888) ")
|
|
239
|
+
if pwd is None:
|
|
240
|
+
pwd = typer.prompt("Enter the password for zip decryption", hide_input=True)
|
|
241
|
+
from machineconfig.scripts.python.helpers.helpers_utils.download import download
|
|
242
|
+
downloaded_file = download(url=url, decompress=False, output_dir=str(Path.home()))
|
|
243
|
+
if downloaded_file is None or not downloaded_file.exists():
|
|
244
|
+
print(f"❌ Failed to download file from URL: {url}")
|
|
245
|
+
raise typer.Exit(code=1)
|
|
246
|
+
zipfile_enc_path = downloaded_file
|
|
247
|
+
from machineconfig.utils.io import decrypt
|
|
248
|
+
zipfile_bytes = decrypt(
|
|
249
|
+
token=zipfile_enc_path.read_bytes(),
|
|
250
|
+
pwd=pwd,
|
|
251
|
+
)
|
|
252
|
+
zipfile_path = Path(str(zipfile_enc_path)[:-4])
|
|
253
|
+
if zipfile_path.exists():
|
|
254
|
+
print(f"⚠️ WARNING: Overwriting existing file: {zipfile_path}")
|
|
255
|
+
zipfile_path.unlink()
|
|
256
|
+
zipfile_path.write_bytes(zipfile_bytes)
|
|
257
|
+
print(f"✅ Decrypted zip file saved to: {zipfile_path}")
|
|
258
|
+
import zipfile
|
|
259
|
+
if Path.home().joinpath("dotfiles").exists():
|
|
260
|
+
print(f"⚠️ WARNING: Overwriting existing directory: {Path.home().joinpath('dotfiles')}")
|
|
261
|
+
import shutil
|
|
262
|
+
shutil.rmtree(Path.home().joinpath("dotfiles"))
|
|
263
|
+
with zipfile.ZipFile(zipfile_path, 'r') as zip_ref:
|
|
264
|
+
zip_ref.extractall(Path.home().joinpath("dotfiles"))
|
|
265
|
+
print(f"✅ Dotfiles extracted to: {Path.home().joinpath('dotfiles')}")
|
|
266
|
+
zipfile_path.unlink()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def arg_parser() -> None:
|
|
270
|
+
typer.run(register_dotfile)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
arg_parser()
|