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.

Files changed (45) hide show
  1. machineconfig/jobs/installer/installer_data.json +34 -0
  2. machineconfig/profile/create_links.py +2 -1
  3. machineconfig/profile/create_links_export.py +43 -11
  4. machineconfig/profile/create_shell_profile.py +64 -124
  5. machineconfig/profile/mapper.toml +4 -0
  6. machineconfig/scripts/linux/wrap_mcfg +1 -1
  7. machineconfig/scripts/python/croshell.py +4 -4
  8. machineconfig/scripts/python/define.py +1 -1
  9. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  10. machineconfig/scripts/python/explore.py +49 -0
  11. machineconfig/scripts/python/helpers_devops/cli_config.py +27 -31
  12. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +12 -9
  13. machineconfig/scripts/python/helpers_devops/cli_nw.py +7 -6
  14. machineconfig/scripts/python/helpers_devops/cli_repos.py +11 -10
  15. machineconfig/scripts/python/helpers_devops/cli_self.py +5 -7
  16. machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
  17. machineconfig/scripts/python/helpers_devops/cli_share_server.py +62 -166
  18. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +34 -15
  19. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  20. machineconfig/scripts/python/machineconfig.py +7 -0
  21. machineconfig/scripts/python/terminal.py +20 -3
  22. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  23. machineconfig/scripts/windows/wrap_mcfg.ps1 +5 -0
  24. machineconfig/settings/helix/config.toml +14 -0
  25. machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
  26. machineconfig/settings/lf/linux/exe/previewer.sh +3 -2
  27. machineconfig/settings/shells/bash/init.sh +2 -1
  28. machineconfig/settings/shells/nushell/config.nu +1 -31
  29. machineconfig/settings/shells/nushell/init.nu +100 -34
  30. machineconfig/settings/shells/wt/settings.json +10 -2
  31. machineconfig/settings/yazi/init.lua +36 -0
  32. machineconfig/settings/yazi/keymap.toml +52 -0
  33. machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
  34. machineconfig/settings/yazi/yazi.toml +8 -0
  35. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  36. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  37. machineconfig/utils/code.py +1 -1
  38. machineconfig/utils/links.py +3 -2
  39. machineconfig/utils/ssh.py +1 -1
  40. {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/METADATA +1 -1
  41. {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/RECORD +44 -41
  42. {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/entry_points.txt +1 -0
  43. machineconfig/scripts/python/helpers_repos/secure_repo.py +0 -15
  44. {machineconfig-7.38.dist-info → machineconfig-7.44.dist-info}/WHEEL +0 -0
  45. {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[Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"], typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
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.main)
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.main)
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.")(cli_share_server.share_file_send)
115
- nw_apps.command(name="sx", no_args_is_help=True, hidden=True, help="📁 [sx] send files from here.")(cli_share_server.share_file_send)
116
- nw_apps.command(name="receive", no_args_is_help=True, hidden=False, help="📁 [rx] receive files to here.")(cli_share_server.share_file_receive)
117
- nw_apps.command(name="rx", no_args_is_help=True, hidden=True, help="📁 [rx] receive files to here.")(cli_share_server.share_file_receive)
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.secure_repo import main as secure_repo_main
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
- NO_UVsyncOption = Annotated[bool, typer.Option("--no-uv-sync", "-ns", help="🚫 Disable automatic uv sync after pulls.")]
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, no_uv_sync: NO_UVsyncOption = False) -> None:
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=not no_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, no_uv_sync: NO_UVsyncOption = False) -> None:
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=not no_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, no_uv_sync: NO_UVsyncOption = False) -> None:
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=not no_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, no_uv_sync: NO_UVsyncOption = False) -> None:
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=not no_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(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-na", help="Copy (overwrite) assets to the machine after the update")] = False):
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 not no_copy_assets:
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.38" """)
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.38" """)
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.38,textual" """
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 main(
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
- install_if_missing(which="easy-sharing")
43
- if over_internet: install_if_missing(which="ngrok", )
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 # Default port for ezshare
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
- try:
89
- while True:
90
- print("Share server is running. Press Ctrl+C to stop.")
91
- time.sleep(2)
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
- typer.echo(f"🚀 Sending file: {path}. Use: devops network receive")
138
- from machineconfig.utils.code import exit_then_run_shell_script, print_code
139
- print_code(code=script, desc="🚀 sending file with croc", lexer="bash" if platform.system() != "Windows" else "powershell")
140
- exit_then_run_shell_script(script=script, strict=False)
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
- from machineconfig.utils.installer_utils.installer import install_if_missing
152
- install_if_missing(which="croc")
153
- import platform
154
- import sys
155
-
156
- is_windows = platform.system() == "Windows"
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
- # Build the appropriate script for current OS
213
- if is_windows:
214
- # Windows PowerShell format: croc --relay server:port secret-code --yes
215
- relay_arg = f"--relay {relay_server}" if relay_server else ""
216
- code_arg = f"{secret_code}" if secret_code else ""
217
- script = f"""croc {relay_arg} {code_arg} --yes""".strip()
218
- else:
219
- # Linux/macOS Bash format: CROC_SECRET="secret-code" croc --relay server:port --yes
220
- relay_arg = f"--relay {relay_server}" if relay_server else ""
221
- if secret_code:
222
- script = f"""export CROC_SECRET="{secret_code}"
223
- croc {relay_arg} --yes""".strip()
224
- else:
225
- script = f"""croc {relay_arg} --yes""".strip()
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
- from machineconfig.utils.code import exit_then_run_shell_script, print_code
228
- print_code(code=script, desc="🚀 Receiving file with croc", lexer="bash" if platform.system() != "Windows" else "powershell")
229
- exit_then_run_shell_script(script=script, strict=False)
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(main)
137
+ typer.run(web_file_explorer)
242
138
 
243
139
 
244
140
  if __name__ == "__main__":