machineconfig 7.38__py3-none-any.whl → 7.44__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/jobs/installer/installer_data.json +34 -0
- machineconfig/profile/create_links.py +2 -1
- machineconfig/profile/create_links_export.py +43 -11
- machineconfig/profile/create_shell_profile.py +64 -124
- machineconfig/profile/mapper.toml +4 -0
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/croshell.py +4 -4
- machineconfig/scripts/python/define.py +1 -1
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/explore.py +49 -0
- machineconfig/scripts/python/helpers_devops/cli_config.py +27 -31
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +12 -9
- machineconfig/scripts/python/helpers_devops/cli_nw.py +7 -6
- machineconfig/scripts/python/helpers_devops/cli_repos.py +11 -10
- machineconfig/scripts/python/helpers_devops/cli_self.py +5 -7
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +62 -166
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +34 -15
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/machineconfig.py +7 -0
- machineconfig/scripts/python/terminal.py +20 -3
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/wrap_mcfg.ps1 +5 -0
- machineconfig/settings/helix/config.toml +14 -0
- machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
- machineconfig/settings/lf/linux/exe/previewer.sh +3 -2
- machineconfig/settings/shells/bash/init.sh +2 -1
- machineconfig/settings/shells/nushell/config.nu +1 -31
- machineconfig/settings/shells/nushell/init.nu +100 -34
- machineconfig/settings/shells/wt/settings.json +10 -2
- machineconfig/settings/yazi/init.lua +36 -0
- machineconfig/settings/yazi/keymap.toml +52 -0
- machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
- machineconfig/settings/yazi/yazi.toml +8 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/utils/code.py +1 -1
- machineconfig/utils/links.py +3 -2
- machineconfig/utils/ssh.py +1 -1
- {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/METADATA +1 -1
- {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/RECORD +44 -41
- {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/entry_points.txt +1 -0
- machineconfig/scripts/python/helpers_repos/secure_repo.py +0 -15
- {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/WHEEL +0 -0
- {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
"""Like yadm and dotter."""
|
|
3
3
|
|
|
4
|
+
from machineconfig.profile.create_links_export import ON_CONFLICT_LOOSE, ON_CONFLICT_MAPPER
|
|
4
5
|
from typing import Annotated, Literal
|
|
5
6
|
import typer
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
def main(
|
|
9
11
|
file: Annotated[str, typer.Argument(help="file/folder path.")],
|
|
10
|
-
method: Annotated[Literal["symlink", "copy"], typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
|
|
11
|
-
on_conflict: Annotated[
|
|
12
|
-
sensitivity: Annotated[Literal["private", "public"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
|
|
12
|
+
method: Annotated[Literal["symlink", "s", "copy", "c"], typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
|
|
13
|
+
on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
|
|
14
|
+
sensitivity: Annotated[Literal["private", "v", "public", "b"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
|
|
13
15
|
destination: Annotated[str, typer.Option("--destination", "-d", help="destination folder (override the default, use at your own risk)")] = "",) -> None:
|
|
14
16
|
from rich.console import Console
|
|
15
17
|
from rich.panel import Panel
|
|
16
18
|
from machineconfig.utils.links import symlink_map, copy_map
|
|
17
19
|
from pathlib import Path
|
|
18
20
|
match sensitivity:
|
|
19
|
-
case "private":
|
|
21
|
+
case "private" | "v":
|
|
20
22
|
backup_root = Path.home().joinpath("dotfiles/mapper")
|
|
21
|
-
case "public":
|
|
23
|
+
case "public" | "b":
|
|
22
24
|
from machineconfig.utils.source_of_truth import CONFIG_ROOT
|
|
23
25
|
backup_root = Path(CONFIG_ROOT).joinpath("dotfiles/mapper")
|
|
24
26
|
|
|
@@ -32,18 +34,19 @@ def main(
|
|
|
32
34
|
dest_path.mkdir(parents=True, exist_ok=True)
|
|
33
35
|
new_path = dest_path.joinpath(orig_path.name)
|
|
34
36
|
|
|
37
|
+
|
|
35
38
|
from machineconfig.utils.path_extended import PathExtended
|
|
36
39
|
match method:
|
|
37
|
-
case "copy":
|
|
40
|
+
case "copy" | "c":
|
|
38
41
|
try:
|
|
39
|
-
copy_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=on_conflict)
|
|
42
|
+
copy_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=ON_CONFLICT_MAPPER[on_conflict])
|
|
40
43
|
except Exception as e:
|
|
41
44
|
typer.echo(f"[red]Error:[/] {e}")
|
|
42
45
|
typer.Exit(code=1)
|
|
43
46
|
return
|
|
44
|
-
case "symlink":
|
|
47
|
+
case "symlink" | "s":
|
|
45
48
|
try:
|
|
46
|
-
symlink_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=on_conflict)
|
|
49
|
+
symlink_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=ON_CONFLICT_MAPPER[on_conflict])
|
|
47
50
|
except Exception as e:
|
|
48
51
|
typer.echo(f"[red]Error:[/] {e}")
|
|
49
52
|
typer.Exit(code=1)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
import machineconfig.scripts.python.helpers_devops.cli_share_file
|
|
2
3
|
import machineconfig.scripts.python.helpers_devops.cli_terminal as cli_terminal
|
|
3
4
|
import machineconfig.scripts.python.helpers_devops.cli_share_server as cli_share_server
|
|
4
5
|
import typer
|
|
@@ -105,16 +106,16 @@ def get_app():
|
|
|
105
106
|
nw_apps.command(name="share-terminal", help="📡 [t] Share terminal via web browser")(cli_terminal.main)
|
|
106
107
|
nw_apps.command(name="t", help="Share terminal via web browser", hidden=True)(cli_terminal.main)
|
|
107
108
|
|
|
108
|
-
nw_apps.command(name="share-server", help="🌐 [s] Start local/global server to share files/folders via web browser", no_args_is_help=True)(cli_share_server.
|
|
109
|
-
nw_apps.command(name="s", help="Start local/global server to share files/folders via web browser", hidden=True, no_args_is_help=True)(cli_share_server.
|
|
109
|
+
nw_apps.command(name="share-server", help="🌐 [s] Start local/global server to share files/folders via web browser", no_args_is_help=True)(cli_share_server.web_file_explorer)
|
|
110
|
+
nw_apps.command(name="s", help="Start local/global server to share files/folders via web browser", hidden=True, no_args_is_help=True)(cli_share_server.web_file_explorer)
|
|
110
111
|
|
|
111
112
|
# app = cli_share_server.get_share_file_app()
|
|
112
113
|
# nw_apps.add_typer(app, name="share-file", help="📁 [f] Share a file via relay server", no_args_is_help=True)
|
|
113
114
|
# nw_apps.add_typer(app, name="f", help="Share a file via relay server", hidden=True, no_args_is_help=True)
|
|
114
|
-
nw_apps.command(name="send", no_args_is_help=True, hidden=False, help="📁 [sx] send files from here.")(
|
|
115
|
-
nw_apps.command(name="sx", no_args_is_help=True, hidden=True, help="📁 [sx] send files from here.")(
|
|
116
|
-
nw_apps.command(name="receive", no_args_is_help=True, hidden=False, help="📁 [rx] receive files to here.")(
|
|
117
|
-
nw_apps.command(name="rx", no_args_is_help=True, hidden=True, help="📁 [rx] receive files to here.")(
|
|
115
|
+
nw_apps.command(name="send", no_args_is_help=True, hidden=False, help="📁 [sx] send files from here.")(machineconfig.scripts.python.helpers_devops.cli_share_file.share_file_send)
|
|
116
|
+
nw_apps.command(name="sx", no_args_is_help=True, hidden=True, help="📁 [sx] send files from here.")(machineconfig.scripts.python.helpers_devops.cli_share_file.share_file_send)
|
|
117
|
+
nw_apps.command(name="receive", no_args_is_help=True, hidden=False, help="📁 [rx] receive files to here.")(machineconfig.scripts.python.helpers_devops.cli_share_file.share_file_receive)
|
|
118
|
+
nw_apps.command(name="rx", no_args_is_help=True, hidden=True, help="📁 [rx] receive files to here.")(machineconfig.scripts.python.helpers_devops.cli_share_file.share_file_receive)
|
|
118
119
|
|
|
119
120
|
nw_apps.command(name="install-ssh-server", help="📡 [i] Install SSH server")(install_ssh_server)
|
|
120
121
|
nw_apps.command(name="i", help="Install SSH server", hidden=True)(install_ssh_server)
|
|
@@ -8,38 +8,38 @@ in the event that username@github.com is not mentioned in the remote url.
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Annotated, Optional
|
|
10
10
|
import typer
|
|
11
|
-
from machineconfig.scripts.python.helpers_repos.
|
|
11
|
+
from machineconfig.scripts.python.helpers_repos.cloud_repo_sync import main as secure_repo_main
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Directory containing repo(s).")]
|
|
15
15
|
RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
|
|
16
|
-
|
|
16
|
+
UVsyncOption = Annotated[bool, typer.Option("--uv-sync/--no-uv-sync", "-u/-ns", help="Automatic uv sync after pulls.")]
|
|
17
17
|
CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False,
|
|
20
|
+
def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
|
|
21
21
|
"""🚀 Push changes across repositories."""
|
|
22
22
|
from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
|
|
23
|
-
git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, auto_uv_sync=
|
|
23
|
+
git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, auto_uv_sync=auto_uv_sync)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False,
|
|
26
|
+
def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
|
|
27
27
|
"""⬇️ Pull changes across repositories."""
|
|
28
28
|
from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
|
|
29
29
|
|
|
30
|
-
git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, auto_uv_sync=
|
|
30
|
+
git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, auto_uv_sync=auto_uv_sync)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False,
|
|
33
|
+
def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
|
|
34
34
|
"""💾 Commit changes across repositories."""
|
|
35
35
|
from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
|
|
36
|
-
git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, auto_uv_sync=
|
|
36
|
+
git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, auto_uv_sync=auto_uv_sync)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def sync(directory: DirectoryArgument = None, recursive: RecursiveOption = False,
|
|
39
|
+
def sync(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
|
|
40
40
|
"""🔄 Pull, commit, and push changes across repositories."""
|
|
41
41
|
from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
|
|
42
|
-
git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, auto_uv_sync=
|
|
42
|
+
git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, auto_uv_sync=auto_uv_sync)
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def capture(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
@@ -179,3 +179,4 @@ def get_app():
|
|
|
179
179
|
mirror_app.command(name="ctb", help="Check out to the main branch defined in the specification", hidden=True)(checkout_to_branch_command)
|
|
180
180
|
|
|
181
181
|
return repos_apps
|
|
182
|
+
|
|
@@ -9,10 +9,8 @@ def copy_both_assets():
|
|
|
9
9
|
create_helper.copy_assets_to_machine(which="settings")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def update(
|
|
12
|
+
def update(copy_assets: Annotated[bool, typer.Option("--assets-copy/--no-assets-copy", "-a/-na", help="Copy (overwrite) assets to the machine after the update")] = True):
|
|
13
13
|
"""🔄 UPDATE uv and machineconfig"""
|
|
14
|
-
# from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
15
|
-
# repo_root = LIBRARY_ROOT.parent.parent
|
|
16
14
|
from pathlib import Path
|
|
17
15
|
if Path.home().joinpath("code", "machineconfig").exists():
|
|
18
16
|
shell_script = """
|
|
@@ -36,7 +34,7 @@ uv tool install --upgrade machineconfig
|
|
|
36
34
|
else:
|
|
37
35
|
from machineconfig.utils.code import run_shell_script
|
|
38
36
|
run_shell_script(shell_script)
|
|
39
|
-
if
|
|
37
|
+
if copy_assets:
|
|
40
38
|
copy_both_assets()
|
|
41
39
|
|
|
42
40
|
def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-na", help="Copy (overwrite) assets to the machine after the update")] = False):
|
|
@@ -48,9 +46,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
|
|
|
48
46
|
else:
|
|
49
47
|
import platform
|
|
50
48
|
if platform.system() == "Windows":
|
|
51
|
-
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.
|
|
49
|
+
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.40" """)
|
|
52
50
|
else:
|
|
53
|
-
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.
|
|
51
|
+
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.40" """)
|
|
54
52
|
from machineconfig.profile.create_shell_profile import create_default_shell_profile
|
|
55
53
|
if not no_copy_assets:
|
|
56
54
|
create_default_shell_profile() # involves copying assets too
|
|
@@ -75,7 +73,7 @@ def navigate():
|
|
|
75
73
|
path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
|
|
76
74
|
from machineconfig.utils.code import run_shell_script
|
|
77
75
|
if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
|
|
78
|
-
else: executable = """--with "machineconfig>=7.
|
|
76
|
+
else: executable = """--with "machineconfig>=7.40,textual" """
|
|
79
77
|
run_shell_script(f"""uv run {executable} {path}""")
|
|
80
78
|
|
|
81
79
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
# import platform
|
|
3
|
+
# import sys
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def share_file_receive(code_args: Annotated[list[str], typer.Argument(help="Receive code or relay command. Examples: '7121-donor-olympic-bicycle' or '--relay 10.17.62.206:443 7121-donor-olympic-bicycle'")],
|
|
8
|
+
) -> None:
|
|
9
|
+
"""Receive a file using croc with relay server.
|
|
10
|
+
Usage examples:
|
|
11
|
+
devops network receive 7121-donor-olympic-bicycle
|
|
12
|
+
devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
13
|
+
devops network receive -- croc --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
14
|
+
"""
|
|
15
|
+
from machineconfig.utils.installer_utils.installer import install_if_missing
|
|
16
|
+
install_if_missing(which="croc")
|
|
17
|
+
import platform
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
is_windows = platform.system() == "Windows"
|
|
21
|
+
|
|
22
|
+
# If no args passed via typer, try to get them from sys.argv directly
|
|
23
|
+
# This handles the case where -- was used and arguments weren't parsed by typer
|
|
24
|
+
if not code_args or (len(code_args) == 1 and code_args[0] in ['--relay', 'croc']):
|
|
25
|
+
# Find the index of 'rx' or 'receive' in sys.argv and get everything after it
|
|
26
|
+
try:
|
|
27
|
+
for i, arg in enumerate(sys.argv):
|
|
28
|
+
if arg in ['rx', 'receive', 'r'] and i + 1 < len(sys.argv):
|
|
29
|
+
code_args = sys.argv[i + 1:]
|
|
30
|
+
break
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# Join all arguments
|
|
35
|
+
input_str = " ".join(code_args)
|
|
36
|
+
tokens = input_str.split()
|
|
37
|
+
|
|
38
|
+
# Parse input to extract relay server and secret code
|
|
39
|
+
relay_server: str | None = None
|
|
40
|
+
secret_code: str | None = None
|
|
41
|
+
|
|
42
|
+
# Remove 'croc' and 'export' from tokens if present
|
|
43
|
+
tokens = [t for t in tokens if t not in ['croc', 'export']]
|
|
44
|
+
|
|
45
|
+
# Look for --relay flag and capture next token
|
|
46
|
+
relay_idx = -1
|
|
47
|
+
for i, token in enumerate(tokens):
|
|
48
|
+
if token == '--relay' and i + 1 < len(tokens):
|
|
49
|
+
relay_server = tokens[i + 1]
|
|
50
|
+
relay_idx = i
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
# Look for CROC_SECRET= prefix in any token
|
|
54
|
+
for token in tokens:
|
|
55
|
+
if token.startswith('CROC_SECRET='):
|
|
56
|
+
secret_code = token.split('=', 1)[1].strip('"').strip("'")
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
# If no secret code found yet, look for tokens with dashes (typical pattern: number-word-word-word)
|
|
60
|
+
# Skip relay server and relay flag
|
|
61
|
+
if not secret_code:
|
|
62
|
+
for i, token in enumerate(tokens):
|
|
63
|
+
if '-' in token and not token.startswith('-') and token != relay_server:
|
|
64
|
+
if relay_idx >= 0 and (i == relay_idx or i == relay_idx + 1):
|
|
65
|
+
continue # Skip relay server parts
|
|
66
|
+
secret_code = token
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
if not secret_code and not relay_server:
|
|
70
|
+
typer.echo(f"❌ Error: Could not parse croc receive code from input: {input_str}", err=True)
|
|
71
|
+
typer.echo("Usage:", err=True)
|
|
72
|
+
typer.echo(" devops network receive 7121-donor-olympic-bicycle", err=True)
|
|
73
|
+
typer.echo(" devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle", err=True)
|
|
74
|
+
raise typer.Exit(code=1)
|
|
75
|
+
|
|
76
|
+
# Build the appropriate script for current OS
|
|
77
|
+
if is_windows:
|
|
78
|
+
# Windows PowerShell format: croc --relay server:port secret-code --yes
|
|
79
|
+
relay_arg = f"--relay {relay_server}" if relay_server else ""
|
|
80
|
+
code_arg = f"{secret_code}" if secret_code else ""
|
|
81
|
+
script = f"""croc {relay_arg} {code_arg} --yes""".strip()
|
|
82
|
+
else:
|
|
83
|
+
# Linux/macOS Bash format: CROC_SECRET="secret-code" croc --relay server:port --yes
|
|
84
|
+
relay_arg = f"--relay {relay_server}" if relay_server else ""
|
|
85
|
+
if secret_code:
|
|
86
|
+
script = f"""export CROC_SECRET="{secret_code}"
|
|
87
|
+
croc {relay_arg} --yes""".strip()
|
|
88
|
+
else:
|
|
89
|
+
script = f"""croc {relay_arg} --yes""".strip()
|
|
90
|
+
|
|
91
|
+
from machineconfig.utils.code import exit_then_run_shell_script, print_code
|
|
92
|
+
print_code(code=script, desc="🚀 Receiving file with croc", lexer="bash" if platform.system() != "Windows" else "powershell")
|
|
93
|
+
exit_then_run_shell_script(script=script, strict=False)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def share_file_send(path: Annotated[str, typer.Argument(help="Path to the file or directory to send")],
|
|
97
|
+
zip_folder: Annotated[bool, typer.Option("--zip", help="Zip folder before sending")] = False,
|
|
98
|
+
code: Annotated[str | None, typer.Option("--code", "-c", help="Codephrase used to connect to relay")] = None,
|
|
99
|
+
text: Annotated[str | None, typer.Option("--text", "-t", help="Send some text")] = None,
|
|
100
|
+
qrcode: Annotated[bool, typer.Option("--qrcode", "--qr", help="Show receive code as a qrcode")] = False,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Send a file using croc with relay server."""
|
|
103
|
+
from machineconfig.utils.installer_utils.installer import install_if_missing
|
|
104
|
+
install_if_missing(which="croc")
|
|
105
|
+
# Get relay server IP from environment or use default
|
|
106
|
+
import socket
|
|
107
|
+
import platform
|
|
108
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
109
|
+
s.connect(('8.8.8.8',80))
|
|
110
|
+
local_ip_v4 = s.getsockname()[0]
|
|
111
|
+
s.close()
|
|
112
|
+
relay_port = "443"
|
|
113
|
+
is_windows = platform.system() == "Windows"
|
|
114
|
+
|
|
115
|
+
# Build command parts
|
|
116
|
+
relay_arg = f"--relay {local_ip_v4}:{relay_port} --ip {local_ip_v4}:{relay_port}"
|
|
117
|
+
zip_arg = "--zip" if zip_folder else ""
|
|
118
|
+
text_arg = f"--text '{text}'" if text else ""
|
|
119
|
+
qrcode_arg = "--qrcode" if qrcode else ""
|
|
120
|
+
path_arg = f"{path}" if not text else ""
|
|
121
|
+
|
|
122
|
+
if is_windows:
|
|
123
|
+
# Windows PowerShell format
|
|
124
|
+
code_arg = f"--code {code}" if code else ""
|
|
125
|
+
script = f"""croc {relay_arg} send {zip_arg} {code_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
126
|
+
else:
|
|
127
|
+
# Linux/macOS Bash format
|
|
128
|
+
if code:
|
|
129
|
+
script = f"""export CROC_SECRET="{code}"
|
|
130
|
+
croc {relay_arg} send {zip_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
131
|
+
else:
|
|
132
|
+
script = f"""croc {relay_arg} send {zip_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
133
|
+
|
|
134
|
+
typer.echo(f"🚀 Sending file: {path}. Use: devops network receive")
|
|
135
|
+
from machineconfig.utils.code import exit_then_run_shell_script, print_code
|
|
136
|
+
print_code(code=script, desc="🚀 sending file with croc", lexer="bash" if platform.system() != "Windows" else "powershell")
|
|
137
|
+
exit_then_run_shell_script(script=script, strict=False)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Optional, Annotated
|
|
3
|
+
from machineconfig.scripts.python.helpers_devops.cli_share_file import share_file_receive, share_file_send
|
|
3
4
|
import typer
|
|
4
5
|
|
|
5
6
|
|
|
@@ -31,202 +32,97 @@ def display_share_url(local_ip_v4: str, port: int, protocol: str = "http") -> No
|
|
|
31
32
|
console.print(panel)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def web_file_explorer(
|
|
35
36
|
path: Annotated[str, typer.Argument(help="Path to the file or directory to share")],
|
|
36
37
|
port: Annotated[Optional[int], typer.Option("--port", "-p", help="Port to run the share server on (default: 8080)")] = None,
|
|
37
38
|
username: Annotated[Optional[str], typer.Option("--username", "-u", help="Username for share access (default: current user)")] = None,
|
|
38
39
|
password: Annotated[Optional[str], typer.Option("--password", "-w", help="Password for share access (default: from ~/dotfiles/creds/passwords/quick_password)")] = None,
|
|
39
|
-
over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the share server over the internet using ngrok")] = False
|
|
40
|
+
over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the share server over the internet using ngrok")] = False,
|
|
41
|
+
backend: Annotated[str, typer.Option("--backend", "-b", help="Backend to use: filebrowser (default), miniserve, or easy-sharing")] = "filebrowser"
|
|
40
42
|
) -> None:
|
|
41
43
|
from machineconfig.utils.installer_utils.installer import install_if_missing
|
|
42
|
-
|
|
43
|
-
if
|
|
44
|
+
|
|
45
|
+
if backend not in ["filebrowser", "miniserve", "easy-sharing"]:
|
|
46
|
+
typer.echo(f"❌ ERROR: Invalid backend '{backend}'. Must be one of: filebrowser, miniserve, easy-sharing", err=True)
|
|
47
|
+
raise typer.Exit(code=1)
|
|
48
|
+
|
|
49
|
+
install_if_missing(which=backend)
|
|
50
|
+
if over_internet:
|
|
51
|
+
install_if_missing(which="ngrok")
|
|
52
|
+
|
|
44
53
|
if username is None:
|
|
45
54
|
import getpass
|
|
46
55
|
username = getpass.getuser()
|
|
56
|
+
|
|
47
57
|
if password is None:
|
|
48
58
|
pwd_path = Path.home().joinpath("dotfiles/creds/passwords/quick_password")
|
|
49
59
|
if pwd_path.exists():
|
|
50
60
|
password = pwd_path.read_text(encoding="utf-8").strip()
|
|
51
61
|
else:
|
|
52
|
-
# raise ValueError("Password not provided and default password file does not exist.")
|
|
53
62
|
typer.echo(f"⚠️ WARNING: Password not provided and default password file does not exist.\nPath: {pwd_path}\nUsing default password: 'quick_password' (insecure!)", err=True)
|
|
54
|
-
typer.Exit(code=1)
|
|
63
|
+
raise typer.Exit(code=1)
|
|
55
64
|
|
|
56
65
|
if port is None:
|
|
57
|
-
port = 8080
|
|
66
|
+
port = 8080
|
|
58
67
|
|
|
59
68
|
import socket
|
|
60
69
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
61
|
-
s.connect(('8.8.8.8',80))
|
|
70
|
+
s.connect(('8.8.8.8', 80))
|
|
62
71
|
local_ip_v4 = s.getsockname()[0]
|
|
63
72
|
s.close()
|
|
64
73
|
|
|
65
|
-
# Display the flashy share announcement
|
|
66
74
|
protocol = "http"
|
|
67
75
|
display_share_url(local_ip_v4, port, protocol)
|
|
68
|
-
import subprocess
|
|
69
|
-
import time
|
|
70
|
-
# Build ezshare command
|
|
71
|
-
ezshare_cmd = f"""easy-sharing --port {port} --username {username} --password "{password}" {path}"""
|
|
72
|
-
ezshare_process = subprocess.Popen(ezshare_cmd, shell=True)
|
|
73
|
-
processes = [ezshare_process]
|
|
74
|
-
|
|
75
|
-
if over_internet:
|
|
76
|
-
ngrok_process = subprocess.Popen(f"ngrok http {port}", shell=True)
|
|
77
|
-
processes.append(ngrok_process)
|
|
78
|
-
time.sleep(3)
|
|
79
|
-
try:
|
|
80
|
-
import requests
|
|
81
|
-
response = requests.get("http://localhost:4040/api/tunnels")
|
|
82
|
-
data = response.json()
|
|
83
|
-
public_url = data['tunnels'][0]['public_url']
|
|
84
|
-
print(f"🌐 Ngrok tunnel ready: {public_url}")
|
|
85
|
-
except Exception as e:
|
|
86
|
-
print(f"Could not retrieve ngrok URL: {e}")
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
except KeyboardInterrupt:
|
|
93
|
-
print("\nTerminating processes...")
|
|
94
|
-
for p in processes:
|
|
95
|
-
p.terminate()
|
|
96
|
-
p.wait()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def share_file_send(path: Annotated[str, typer.Argument(help="Path to the file or directory to send")],
|
|
100
|
-
zip_folder: Annotated[bool, typer.Option("--zip", help="Zip folder before sending")] = False,
|
|
101
|
-
code: Annotated[str | None, typer.Option("--code", "-c", help="Codephrase used to connect to relay")] = None,
|
|
102
|
-
text: Annotated[str | None, typer.Option("--text", "-t", help="Send some text")] = None,
|
|
103
|
-
qrcode: Annotated[bool, typer.Option("--qrcode", "--qr", help="Show receive code as a qrcode")] = False,
|
|
104
|
-
) -> None:
|
|
105
|
-
"""Send a file using croc with relay server."""
|
|
106
|
-
from machineconfig.utils.installer_utils.installer import install_if_missing
|
|
107
|
-
install_if_missing(which="croc")
|
|
108
|
-
# Get relay server IP from environment or use default
|
|
109
|
-
import socket
|
|
110
|
-
import platform
|
|
111
|
-
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
112
|
-
s.connect(('8.8.8.8',80))
|
|
113
|
-
local_ip_v4 = s.getsockname()[0]
|
|
114
|
-
s.close()
|
|
115
|
-
relay_port = "443"
|
|
116
|
-
is_windows = platform.system() == "Windows"
|
|
117
|
-
|
|
118
|
-
# Build command parts
|
|
119
|
-
relay_arg = f"--relay {local_ip_v4}:{relay_port} --ip {local_ip_v4}:{relay_port}"
|
|
120
|
-
zip_arg = "--zip" if zip_folder else ""
|
|
121
|
-
text_arg = f"--text '{text}'" if text else ""
|
|
122
|
-
qrcode_arg = "--qrcode" if qrcode else ""
|
|
123
|
-
path_arg = f"{path}" if not text else ""
|
|
124
|
-
|
|
125
|
-
if is_windows:
|
|
126
|
-
# Windows PowerShell format
|
|
127
|
-
code_arg = f"--code {code}" if code else ""
|
|
128
|
-
script = f"""croc {relay_arg} send {zip_arg} {code_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
129
|
-
else:
|
|
130
|
-
# Linux/macOS Bash format
|
|
131
|
-
if code:
|
|
132
|
-
script = f"""export CROC_SECRET="{code}"
|
|
133
|
-
croc {relay_arg} send {zip_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
134
|
-
else:
|
|
135
|
-
script = f"""croc {relay_arg} send {zip_arg} {qrcode_arg} {text_arg} {path_arg}"""
|
|
77
|
+
path_obj = Path(path).resolve()
|
|
78
|
+
if not path_obj.exists():
|
|
79
|
+
typer.echo(f"❌ ERROR: Path does not exist: {path}", err=True)
|
|
80
|
+
raise typer.Exit(code=1)
|
|
136
81
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def share_file_receive(code_args: Annotated[list[str], typer.Argument(help="Receive code or relay command. Examples: '7121-donor-olympic-bicycle' or '--relay 10.17.62.206:443 7121-donor-olympic-bicycle'")],
|
|
144
|
-
) -> None:
|
|
145
|
-
"""Receive a file using croc with relay server.
|
|
146
|
-
Usage examples:
|
|
147
|
-
devops network receive 7121-donor-olympic-bicycle
|
|
148
|
-
devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
149
|
-
devops network receive -- croc --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
82
|
+
if backend == "filebrowser":
|
|
83
|
+
db_path = Path.home().joinpath(".config/filebrowser/filebrowser.db")
|
|
84
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
command = f"""
|
|
86
|
+
filebrowser users add {username} "{password}" --database {db_path}
|
|
87
|
+
filebrowser --address 0.0.0.0 --port {port} --root "{path_obj}" --database {db_path}
|
|
150
88
|
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# If no args passed via typer, try to get them from sys.argv directly
|
|
159
|
-
# This handles the case where -- was used and arguments weren't parsed by typer
|
|
160
|
-
if not code_args or (len(code_args) == 1 and code_args[0] in ['--relay', 'croc']):
|
|
161
|
-
# Find the index of 'rx' or 'receive' in sys.argv and get everything after it
|
|
162
|
-
try:
|
|
163
|
-
for i, arg in enumerate(sys.argv):
|
|
164
|
-
if arg in ['rx', 'receive', 'r'] and i + 1 < len(sys.argv):
|
|
165
|
-
code_args = sys.argv[i + 1:]
|
|
166
|
-
break
|
|
167
|
-
except Exception:
|
|
168
|
-
pass
|
|
169
|
-
|
|
170
|
-
# Join all arguments
|
|
171
|
-
input_str = " ".join(code_args)
|
|
172
|
-
tokens = input_str.split()
|
|
173
|
-
|
|
174
|
-
# Parse input to extract relay server and secret code
|
|
175
|
-
relay_server: str | None = None
|
|
176
|
-
secret_code: str | None = None
|
|
177
|
-
|
|
178
|
-
# Remove 'croc' and 'export' from tokens if present
|
|
179
|
-
tokens = [t for t in tokens if t not in ['croc', 'export']]
|
|
180
|
-
|
|
181
|
-
# Look for --relay flag and capture next token
|
|
182
|
-
relay_idx = -1
|
|
183
|
-
for i, token in enumerate(tokens):
|
|
184
|
-
if token == '--relay' and i + 1 < len(tokens):
|
|
185
|
-
relay_server = tokens[i + 1]
|
|
186
|
-
relay_idx = i
|
|
187
|
-
break
|
|
188
|
-
|
|
189
|
-
# Look for CROC_SECRET= prefix in any token
|
|
190
|
-
for token in tokens:
|
|
191
|
-
if token.startswith('CROC_SECRET='):
|
|
192
|
-
secret_code = token.split('=', 1)[1].strip('"').strip("'")
|
|
193
|
-
break
|
|
194
|
-
|
|
195
|
-
# If no secret code found yet, look for tokens with dashes (typical pattern: number-word-word-word)
|
|
196
|
-
# Skip relay server and relay flag
|
|
197
|
-
if not secret_code:
|
|
198
|
-
for i, token in enumerate(tokens):
|
|
199
|
-
if '-' in token and not token.startswith('-') and token != relay_server:
|
|
200
|
-
if relay_idx >= 0 and (i == relay_idx or i == relay_idx + 1):
|
|
201
|
-
continue # Skip relay server parts
|
|
202
|
-
secret_code = token
|
|
203
|
-
break
|
|
204
|
-
|
|
205
|
-
if not secret_code and not relay_server:
|
|
206
|
-
typer.echo(f"❌ Error: Could not parse croc receive code from input: {input_str}", err=True)
|
|
207
|
-
typer.echo("Usage:", err=True)
|
|
208
|
-
typer.echo(" devops network receive 7121-donor-olympic-bicycle", err=True)
|
|
209
|
-
typer.echo(" devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle", err=True)
|
|
89
|
+
elif backend == "miniserve":
|
|
90
|
+
command = f"""miniserve --port {port} --interfaces 0.0.0.0 --auth {username}:{password} --upload-files --mkdir --enable-tar --enable-tar-gz --enable-zip --qrcode "{path_obj}" """
|
|
91
|
+
elif backend == "easy-sharing":
|
|
92
|
+
command = f"""easy-sharing --port {port} --username {username} --password "{password}" "{path_obj}" """
|
|
93
|
+
else:
|
|
94
|
+
typer.echo(f"❌ ERROR: Unknown backend '{backend}'", err=True)
|
|
210
95
|
raise typer.Exit(code=1)
|
|
211
96
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
97
|
+
from machineconfig.utils.code import exit_then_run_shell_script
|
|
98
|
+
exit_then_run_shell_script(script=command, strict=False)
|
|
99
|
+
# import subprocess
|
|
100
|
+
# import time
|
|
101
|
+
# server_process: subprocess.Popen[bytes]
|
|
102
|
+
# server_process = subprocess.Popen(command, shell=True)
|
|
103
|
+
# processes = [server_process]
|
|
104
|
+
# if over_internet:
|
|
105
|
+
# ngrok_process = subprocess.Popen(f"ngrok http {port}", shell=True)
|
|
106
|
+
# processes.append(ngrok_process)
|
|
107
|
+
# time.sleep(3)
|
|
108
|
+
# try:
|
|
109
|
+
# import requests
|
|
110
|
+
# response = requests.get("http://localhost:4040/api/tunnels")
|
|
111
|
+
# data = response.json()
|
|
112
|
+
# public_url = data['tunnels'][0]['public_url']
|
|
113
|
+
# print(f"🌐 Ngrok tunnel ready: {public_url}")
|
|
114
|
+
# except Exception as e:
|
|
115
|
+
# print(f"Could not retrieve ngrok URL: {e}")
|
|
226
116
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
117
|
+
# try:
|
|
118
|
+
# while True:
|
|
119
|
+
# print(f"Share server ({backend}) is running. Press Ctrl+C to stop.")
|
|
120
|
+
# time.sleep(2)
|
|
121
|
+
# except KeyboardInterrupt:
|
|
122
|
+
# print("\nTerminating processes...")
|
|
123
|
+
# for p in processes:
|
|
124
|
+
# p.terminate()
|
|
125
|
+
# p.wait()
|
|
230
126
|
|
|
231
127
|
|
|
232
128
|
def get_share_file_app():
|
|
@@ -238,7 +134,7 @@ def get_share_file_app():
|
|
|
238
134
|
return app
|
|
239
135
|
|
|
240
136
|
def main_with_parser():
|
|
241
|
-
typer.run(
|
|
137
|
+
typer.run(web_file_explorer)
|
|
242
138
|
|
|
243
139
|
|
|
244
140
|
if __name__ == "__main__":
|