machineconfig 7.57__py3-none-any.whl → 7.79__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/utils/maker.py +21 -9
- machineconfig/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +3 -3
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
- machineconfig/jobs/installer/custom_dev/sysabc.py +36 -28
- machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
- machineconfig/jobs/installer/installer_data.json +127 -25
- machineconfig/jobs/installer/package_groups.py +20 -13
- machineconfig/profile/create_links_export.py +2 -2
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/agents.py +22 -17
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +3 -4
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +19 -18
- machineconfig/scripts/python/ftpx.py +36 -12
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
- machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
- machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
- machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
- machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +4 -5
- machineconfig/scripts/python/helpers_devops/cli_nw.py +88 -7
- machineconfig/scripts/python/helpers_devops/cli_self.py +7 -6
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +9 -9
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +13 -12
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +7 -6
- machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -73
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
- machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
- machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +23 -13
- machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +7 -4
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
- machineconfig/scripts/python/helpers_repos/record.py +2 -1
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
- machineconfig/scripts/python/helpers_utils/download.py +152 -0
- machineconfig/scripts/python/helpers_utils/path.py +81 -31
- machineconfig/scripts/python/interactive.py +2 -2
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +21 -2
- machineconfig/scripts/python/nw/address.py +132 -0
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
- machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
- machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
- machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
- machineconfig/scripts/python/sessions.py +35 -20
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -10
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/nushell/config.nu +2 -2
- machineconfig/settings/shells/nushell/env.nu +45 -6
- machineconfig/settings/shells/nushell/init.nu +282 -95
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/shells/zsh/init.sh +0 -7
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +6 -4
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/install_from_url.py +180 -0
- machineconfig/utils/installer_utils/installer_class.py +53 -47
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +71 -65
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
- machineconfig/utils/links.py +2 -2
- machineconfig/utils/meta.py +30 -16
- machineconfig/utils/options.py +4 -4
- machineconfig/utils/path_extended.py +3 -3
- machineconfig/utils/path_helper.py +33 -31
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/ssh.py +143 -409
- machineconfig/utils/ssh_utils/abc.py +8 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
- machineconfig/utils/ssh_utils/utils.py +141 -0
- machineconfig/utils/ssh_utils/wsl.py +168 -0
- machineconfig/utils/upgrade_packages.py +2 -1
- machineconfig/utils/ve.py +11 -4
- {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/METADATA +1 -1
- {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/RECORD +102 -92
- {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/entry_points.txt +2 -2
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/scripts/python/explore.py +0 -49
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
- {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/WHEEL +0 -0
- {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/top_level.txt +0 -0
|
@@ -2,16 +2,9 @@
|
|
|
2
2
|
TODO: use typer or typed-argument-parser to parse args
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from machineconfig.scripts.python.helpers_cloud.helpers2 import parse_cloud_source_target
|
|
6
|
-
from machineconfig.scripts.python.helpers_cloud.cloud_helpers import Args
|
|
7
|
-
from machineconfig.scripts.python.helpers_cloud.cloud_mount import get_mprocs_mount_txt
|
|
8
5
|
|
|
9
6
|
from typing import Annotated, Optional
|
|
10
7
|
import typer
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
|
|
14
|
-
console = Console()
|
|
15
8
|
|
|
16
9
|
|
|
17
10
|
def main(
|
|
@@ -27,6 +20,14 @@ def main(
|
|
|
27
20
|
delete: Annotated[bool, typer.Option("--delete", "-D", help="Delete files in remote that are not in local.")] = False,
|
|
28
21
|
verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Verbosity of mprocs to show details of syncing.")] = False,
|
|
29
22
|
) -> None:
|
|
23
|
+
|
|
24
|
+
from machineconfig.scripts.python.helpers_cloud.helpers2 import parse_cloud_source_target
|
|
25
|
+
from machineconfig.scripts.python.helpers_cloud.cloud_helpers import Args
|
|
26
|
+
from machineconfig.scripts.python.helpers_cloud.cloud_mount import get_mprocs_mount_txt
|
|
27
|
+
from rich.console import Console
|
|
28
|
+
from rich.panel import Panel
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
30
31
|
title = "☁️ Cloud Sync Utility"
|
|
31
32
|
console.print(Panel(title, title_align="left", border_style="blue"))
|
|
32
33
|
|
|
@@ -20,9 +20,9 @@ def get_read_data_pycode(path: str):
|
|
|
20
20
|
from rich.panel import Panel
|
|
21
21
|
from rich.text import Text
|
|
22
22
|
from rich.console import Console
|
|
23
|
-
from
|
|
23
|
+
from pathlib import Path
|
|
24
24
|
console = Console()
|
|
25
|
-
p =
|
|
25
|
+
p = Path(path).absolute()
|
|
26
26
|
try:
|
|
27
27
|
from machineconfig.utils.files.read import Read
|
|
28
28
|
from machineconfig.utils.accessories import pprint
|
|
@@ -95,13 +95,12 @@ def main(
|
|
|
95
95
|
if md_file.name != "slides.md":
|
|
96
96
|
SLIDEV_REPO.joinpath(md_file.name).with_name(name="slides.md", inplace=True, overwrite=True)
|
|
97
97
|
|
|
98
|
-
import
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
local_ip_v4 = socket.gethostbyname(socket.gethostname())
|
|
98
|
+
import machineconfig.scripts.python.nw.address as helper
|
|
99
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
100
|
+
if res is None:
|
|
101
|
+
print("❌ Error: Could not determine local LAN IPv4 address for presentation.")
|
|
102
|
+
raise typer.Exit(code=1)
|
|
103
|
+
local_ip_v4 = res
|
|
105
104
|
|
|
106
105
|
print("🌐 Presentation will be served at:")
|
|
107
106
|
print(f" - http://{platform.node()}:{port}")
|
|
@@ -45,18 +45,17 @@ def main(
|
|
|
45
45
|
dest_path = Path(destination).expanduser().absolute()
|
|
46
46
|
dest_path.mkdir(parents=True, exist_ok=True)
|
|
47
47
|
new_path = dest_path.joinpath(orig_path.name)
|
|
48
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
49
48
|
match method:
|
|
50
49
|
case "copy" | "c":
|
|
51
50
|
try:
|
|
52
|
-
copy_map(config_file_default_path=
|
|
51
|
+
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]
|
|
53
52
|
except Exception as e:
|
|
54
53
|
typer.echo(f"[red]Error:[/] {e}")
|
|
55
54
|
typer.Exit(code=1)
|
|
56
55
|
return
|
|
57
56
|
case "symlink" | "s":
|
|
58
57
|
try:
|
|
59
|
-
symlink_map(config_file_default_path=
|
|
58
|
+
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]
|
|
60
59
|
except Exception as e:
|
|
61
60
|
typer.echo(f"[red]Error:[/] {e}")
|
|
62
61
|
typer.Exit(code=1)
|
|
@@ -66,10 +65,10 @@ def main(
|
|
|
66
65
|
|
|
67
66
|
# mapper_snippet = "\n".join(
|
|
68
67
|
# [
|
|
69
|
-
# f"[bold]📝 Edit configuration file:[/] [cyan]nano {
|
|
68
|
+
# f"[bold]📝 Edit configuration file:[/] [cyan]nano {Path(CONFIG_ROOT)}/symlinks/mapper.toml[/cyan]",
|
|
70
69
|
# "",
|
|
71
70
|
# f"[{new_path.parent.name}]",
|
|
72
|
-
# f"{orig_path.name.split('.')[0]} = {{ this = '{orig_path.
|
|
71
|
+
# f"{orig_path.name.split('.')[0]} = {{ this = '{orig_path.as_posix()}', to_this = '{new_path.as_posix()}' }}",
|
|
73
72
|
# ]
|
|
74
73
|
# )
|
|
75
74
|
# console.print(
|
|
@@ -35,12 +35,63 @@ def add_ssh_identity():
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def show_address():
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
"""📌 Show this computer addresses on network"""
|
|
39
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from machineconfig.utils.accessories import randstr
|
|
42
|
+
tmp_file = Path.home().joinpath("tmp_results/tmp_files/ipinfo_result_" + randstr(8) + ".json")
|
|
43
|
+
tmp_file.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
install_if_missing("ipinfo")
|
|
45
|
+
script = f"""
|
|
46
|
+
ipinfo myip --json > "{tmp_file.as_posix()}"
|
|
47
|
+
"""
|
|
48
|
+
from machineconfig.utils.code import run_shell_script
|
|
49
|
+
run_shell_script(script=script)
|
|
50
|
+
import json
|
|
51
|
+
loaded_json = json.loads(tmp_file.read_text(encoding="utf-8"))
|
|
52
|
+
from rich import print_json
|
|
53
|
+
print_json(data=loaded_json)
|
|
54
|
+
|
|
55
|
+
import machineconfig.scripts.python.nw.address as helper
|
|
56
|
+
from rich.table import Table
|
|
57
|
+
from rich.console import Console
|
|
58
|
+
res = helper.get_all_ipv4_addresses()
|
|
59
|
+
res.append( ("Public IP", loaded_json.get("ip", "N/A")))
|
|
60
|
+
|
|
61
|
+
# loc = loaded_json["loc"]
|
|
62
|
+
# cmd = f"""curl "https://maps.geoapify.com/v1/staticmap?style=osm-bright&width=600&height=300¢er=lonlat:{loc}&zoom=6&marker=lonlat:{loc};color:%23ff0000;size:medium&apiKey=$GEOAPIFY_API_KEY" -o map.png && chafa map.png"""
|
|
63
|
+
# from machineconfig.utils.code import run_shell_script
|
|
64
|
+
# run_shell_script(script=cmd)
|
|
65
|
+
|
|
66
|
+
table = Table(title="Network Interfaces")
|
|
67
|
+
table.add_column("Interface", style="cyan")
|
|
68
|
+
table.add_column("IP Address", style="green")
|
|
69
|
+
|
|
70
|
+
for iface, ip in res:
|
|
71
|
+
table.add_row(iface, ip)
|
|
72
|
+
|
|
73
|
+
console = Console()
|
|
74
|
+
console.print(table)
|
|
75
|
+
|
|
76
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
77
|
+
if res is not None:
|
|
78
|
+
# ip, iface = res
|
|
79
|
+
# print(f"Selected IP: {ip} on interface: {iface}")
|
|
80
|
+
print(f"LAN IPv4: {res}")
|
|
81
|
+
else:
|
|
82
|
+
print("No network interfaces found.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def bind_wsl_port(port: Annotated[int, typer.Option(..., "--port", "-p", help="Port number to bind")]):
|
|
87
|
+
code = f"""
|
|
88
|
+
|
|
89
|
+
$wsl_ip = (wsl.exe hostname -I).Trim().Split(' ')[0]
|
|
90
|
+
netsh interface portproxy add v4tov4 listenport={port} listenaddress=0.0.0.0 connectport={port} connectaddress=$wsl_ip
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
from machineconfig.utils.code import exit_then_run_shell_script
|
|
94
|
+
exit_then_run_shell_script(code)
|
|
44
95
|
|
|
45
96
|
|
|
46
97
|
def debug_ssh():
|
|
@@ -101,6 +152,28 @@ def wifi_select(
|
|
|
101
152
|
console.print("[blue]👋 Goodbye![/blue]")
|
|
102
153
|
|
|
103
154
|
|
|
155
|
+
|
|
156
|
+
def reset_cloudflare_tunnel():
|
|
157
|
+
code = """
|
|
158
|
+
# cloudflared tunnel route dns glenn # creates CNAMES in Cloudflare dashboard
|
|
159
|
+
# sudo systemctl stop cloudflared
|
|
160
|
+
# test: cloudflared tunnel run glenn
|
|
161
|
+
home_dir=$HOME
|
|
162
|
+
cloudflared_path="$home_dir/.local/bin/cloudflared"
|
|
163
|
+
sudo $cloudflared_path service uninstall
|
|
164
|
+
sudo rm /etc/cloudflared/config.yml || true
|
|
165
|
+
sudo $cloudflared_path --config $home_dir/.cloudflared/config.yml service install
|
|
166
|
+
"""
|
|
167
|
+
print(code)
|
|
168
|
+
def add_ip_exclusion_to_warp(ip: Annotated[str, typer.Option(..., "--ip", help="IP address to exclude from WARP")]):
|
|
169
|
+
code = f"""
|
|
170
|
+
sudo warp-cli tunnel ip add {ip}
|
|
171
|
+
sudo warp-cli disconnect
|
|
172
|
+
sudo warp-cli connect
|
|
173
|
+
"""
|
|
174
|
+
print(code)
|
|
175
|
+
|
|
176
|
+
|
|
104
177
|
def get_app():
|
|
105
178
|
nw_apps = typer.Typer(help="🔐 [n] Network subcommands", no_args_is_help=True, add_help_option=False, add_completion=False)
|
|
106
179
|
nw_apps.command(name="share-terminal", help="📡 [t] Share terminal via web browser")(cli_terminal.main)
|
|
@@ -128,7 +201,15 @@ def get_app():
|
|
|
128
201
|
nw_apps.command(name="debug-ssh", help="🐛 [d] Debug SSH connection")(debug_ssh)
|
|
129
202
|
nw_apps.command(name="d", help="Debug SSH connection", hidden=True)(debug_ssh)
|
|
130
203
|
|
|
131
|
-
nw_apps.command(name="wifi-select", no_args_is_help=True, help="📶 WiFi connection utility.")(wifi_select)
|
|
204
|
+
nw_apps.command(name="wifi-select", no_args_is_help=True, help="📶 [w] WiFi connection utility.")(wifi_select)
|
|
132
205
|
nw_apps.command(name="w", no_args_is_help=True, hidden=True)(wifi_select)
|
|
133
206
|
|
|
207
|
+
nw_apps.command(name="bind-wsl-port", help="🔌 [b] Bind WSL port to Windows host", no_args_is_help=True)(bind_wsl_port)
|
|
208
|
+
nw_apps.command(name="b", help="Bind WSL port to Windows host", hidden=True, no_args_is_help=True)(bind_wsl_port)
|
|
209
|
+
|
|
210
|
+
nw_apps.command(name="reset-cloudflare-tunnel", help="☁️ [r] Reset Cloudflare tunnel service")(reset_cloudflare_tunnel)
|
|
211
|
+
nw_apps.command(name="r", help="Reset Cloudflare tunnel service", hidden=True)(reset_cloudflare_tunnel)
|
|
212
|
+
nw_apps.command(name="add-ip-exclusion-to-warp", help="🚫 [p] Add IP exclusion to WARP")(add_ip_exclusion_to_warp)
|
|
213
|
+
nw_apps.command(name="p", help="Add IP exclusion to WARP", hidden=True)(add_ip_exclusion_to_warp)
|
|
214
|
+
|
|
134
215
|
return nw_apps
|
|
@@ -30,7 +30,8 @@ uv tool install --upgrade machineconfig
|
|
|
30
30
|
if platform.system() == "Windows":
|
|
31
31
|
from machineconfig.utils.code import exit_then_run_shell_script, get_uv_command_executing_python_script
|
|
32
32
|
from machineconfig.utils.meta import lambda_to_python_script
|
|
33
|
-
python_script = lambda_to_python_script(lambda: copy_both_assets(),
|
|
33
|
+
python_script = lambda_to_python_script(lambda: copy_both_assets(),
|
|
34
|
+
in_global=True, import_module=False)
|
|
34
35
|
uv_command, _py_file = get_uv_command_executing_python_script(python_script=python_script, uv_with=["machineconfig"], uv_project_dir=None)
|
|
35
36
|
exit_then_run_shell_script(shell_script + "\n" + uv_command, strict=True)
|
|
36
37
|
else:
|
|
@@ -52,9 +53,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
|
|
|
52
53
|
else:
|
|
53
54
|
import platform
|
|
54
55
|
if platform.system() == "Windows":
|
|
55
|
-
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.
|
|
56
|
+
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.79" """)
|
|
56
57
|
else:
|
|
57
|
-
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.
|
|
58
|
+
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.79" """)
|
|
58
59
|
from machineconfig.profile.create_shell_profile import create_default_shell_profile
|
|
59
60
|
if not no_copy_assets:
|
|
60
61
|
create_default_shell_profile() # involves copying assets too
|
|
@@ -77,10 +78,10 @@ def navigate():
|
|
|
77
78
|
import machineconfig.scripts.python as navigator
|
|
78
79
|
from pathlib import Path
|
|
79
80
|
path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
|
|
80
|
-
from machineconfig.utils.code import
|
|
81
|
+
from machineconfig.utils.code import exit_then_run_shell_script
|
|
81
82
|
if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
|
|
82
|
-
else: executable = """--with "machineconfig>=7.
|
|
83
|
-
|
|
83
|
+
else: executable = """--with "machineconfig>=7.79,textual" """
|
|
84
|
+
exit_then_run_shell_script(f"""uv run {executable} {path}""")
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
def run_python(ip: Annotated[str, typer.Argument(..., help="Python command to run in the machineconfig environment")],
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
+
|
|
1
2
|
import typer
|
|
2
|
-
# import platform
|
|
3
|
-
# import sys
|
|
4
3
|
from typing import Annotated
|
|
5
4
|
|
|
6
5
|
|
|
@@ -12,7 +11,7 @@ Usage examples:
|
|
|
12
11
|
devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
13
12
|
devops network receive -- croc --relay 10.17.62.206:443 7121-donor-olympic-bicycle
|
|
14
13
|
"""
|
|
15
|
-
from machineconfig.utils.installer_utils.
|
|
14
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
16
15
|
install_if_missing(which="croc")
|
|
17
16
|
import platform
|
|
18
17
|
import sys
|
|
@@ -100,15 +99,16 @@ def share_file_send(path: Annotated[str, typer.Argument(help="Path to the file o
|
|
|
100
99
|
qrcode: Annotated[bool, typer.Option("--qrcode", "--qr", help="Show receive code as a qrcode")] = False,
|
|
101
100
|
) -> None:
|
|
102
101
|
"""Send a file using croc with relay server."""
|
|
103
|
-
from machineconfig.utils.installer_utils.
|
|
102
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
104
103
|
install_if_missing(which="croc")
|
|
105
104
|
# Get relay server IP from environment or use default
|
|
106
|
-
import
|
|
105
|
+
import machineconfig.scripts.python.nw.address as helper
|
|
106
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
107
|
+
if res is None:
|
|
108
|
+
typer.echo("❌ Error: Could not determine local LAN IPv4 address for relay.", err=True)
|
|
109
|
+
raise typer.Exit(code=1)
|
|
110
|
+
local_ip_v4 = res
|
|
107
111
|
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
112
|
relay_port = "443"
|
|
113
113
|
is_windows = platform.system() == "Windows"
|
|
114
114
|
|
|
@@ -38,18 +38,16 @@ def web_file_explorer(
|
|
|
38
38
|
username: Annotated[Optional[str], typer.Option("--username", "-u", help="Username for share access (default: current user)")] = None,
|
|
39
39
|
password: Annotated[Optional[str], typer.Option("--password", "-w", help="Password for share access (default: from ~/dotfiles/creds/passwords/quick_password)")] = None,
|
|
40
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")] = "
|
|
41
|
+
backend: Annotated[str, typer.Option("--backend", "-b", help="Backend to use: filebrowser (default), miniserve, qrcp, or easy-sharing")] = "miniserve",
|
|
42
42
|
) -> None:
|
|
43
|
-
from machineconfig.utils.installer_utils.
|
|
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)
|
|
43
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
44
|
+
|
|
45
|
+
if backend not in ["filebrowser", "miniserve", "qrcp", "easy-sharing"]:
|
|
46
|
+
typer.echo(f"❌ ERROR: Invalid backend '{backend}'. Must be one of: filebrowser, miniserve, qrcp, easy-sharing", err=True)
|
|
47
47
|
raise typer.Exit(code=1)
|
|
48
|
-
|
|
49
48
|
install_if_missing(which=backend)
|
|
50
49
|
if over_internet:
|
|
51
50
|
install_if_missing(which="ngrok")
|
|
52
|
-
|
|
53
51
|
if username is None:
|
|
54
52
|
import getpass
|
|
55
53
|
username = getpass.getuser()
|
|
@@ -65,11 +63,12 @@ def web_file_explorer(
|
|
|
65
63
|
if port is None:
|
|
66
64
|
port = 8080
|
|
67
65
|
|
|
68
|
-
import
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
import machineconfig.scripts.python.nw.address as helper
|
|
67
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
68
|
+
if res is None:
|
|
69
|
+
typer.echo("❌ ERROR: Could not determine local LAN IPv4 address for share server.", err=True)
|
|
70
|
+
raise typer.Exit(code=1)
|
|
71
|
+
local_ip_v4 = res
|
|
73
72
|
|
|
74
73
|
protocol = "http"
|
|
75
74
|
display_share_url(local_ip_v4, port, protocol)
|
|
@@ -90,6 +89,8 @@ filebrowser --address 0.0.0.0 --port {port} --root "{path_obj}" --database {db_p
|
|
|
90
89
|
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
90
|
elif backend == "easy-sharing":
|
|
92
91
|
command = f"""easy-sharing --port {port} --username {username} --password "{password}" "{path_obj}" """
|
|
92
|
+
elif backend == "qrcp":
|
|
93
|
+
command = f"""qrcp "{path_obj}" """
|
|
93
94
|
else:
|
|
94
95
|
typer.echo(f"❌ ERROR: Unknown backend '{backend}'", err=True)
|
|
95
96
|
raise typer.Exit(code=1)
|
|
@@ -60,7 +60,7 @@ def main(
|
|
|
60
60
|
ssl_ca: Annotated[Optional[str], typer.Option("--ssl-ca", "-A", help="SSL CA file path for client certificate verification")] = None,
|
|
61
61
|
over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the terminal over the internet using ngrok")] = False
|
|
62
62
|
) -> None:
|
|
63
|
-
from machineconfig.utils.installer_utils.
|
|
63
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
64
64
|
install_if_missing("ttyd")
|
|
65
65
|
if over_internet: install_if_missing("ngrok")
|
|
66
66
|
|
|
@@ -96,11 +96,12 @@ def main(
|
|
|
96
96
|
if ssl_ca and not Path(ssl_ca).exists():
|
|
97
97
|
raise FileNotFoundError(f"SSL CA file not found: {ssl_ca}")
|
|
98
98
|
|
|
99
|
-
import
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
import machineconfig.scripts.python.nw.address as helper
|
|
100
|
+
res = helper.select_lan_ipv4(prefer_vpn=False)
|
|
101
|
+
if res is None:
|
|
102
|
+
print("❌ Error: Could not determine local LAN IPv4 address for terminal.")
|
|
103
|
+
raise typer.Exit(code=1)
|
|
104
|
+
local_ip_v4 = res
|
|
104
105
|
|
|
105
106
|
# Display the flashy terminal announcement
|
|
106
107
|
protocol = "https" if ssl else "http"
|
|
@@ -2,78 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
from typing import Annotated, Optional
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
def download(
|
|
8
|
-
url: Annotated[Optional[str], typer.Argument(..., help="The URL to download the file from.")] = None,
|
|
9
|
-
decompress: Annotated[bool, typer.Option("--decompress", "-d", help="Decompress the file if it's an archive.")] = False,
|
|
10
|
-
output: Annotated[Optional[str], typer.Option("--output", "-o", help="The output file path.")] = None,
|
|
11
|
-
) -> None:
|
|
12
|
-
if url is None:
|
|
13
|
-
typer.echo("❌ Error: URL is required.", err=True)
|
|
14
|
-
raise typer.Exit(code=1)
|
|
15
|
-
typer.echo(f"📥 Downloading from: {url}")
|
|
16
|
-
download_path = Path(output) if output else Path(url.split("/")[-1])
|
|
17
|
-
import requests
|
|
18
|
-
import subprocess
|
|
19
|
-
try:
|
|
20
|
-
response = requests.get(url, allow_redirects=True, stream=True, timeout=60)
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
|
|
23
|
-
total_size = int(response.headers.get('content-length', 0))
|
|
24
|
-
|
|
25
|
-
with open(download_path, 'wb') as f:
|
|
26
|
-
if total_size == 0:
|
|
27
|
-
f.write(response.content)
|
|
28
|
-
else:
|
|
29
|
-
downloaded = 0
|
|
30
|
-
chunk_size = 8192
|
|
31
|
-
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
32
|
-
if chunk:
|
|
33
|
-
f.write(chunk)
|
|
34
|
-
downloaded += len(chunk)
|
|
35
|
-
progress = (downloaded / total_size) * 100
|
|
36
|
-
typer.echo(f"\r⏬ Progress: {progress:.1f}% ({downloaded}/{total_size} bytes)", nl=False)
|
|
37
|
-
typer.echo()
|
|
38
|
-
|
|
39
|
-
typer.echo(f"✅ Downloaded to: {download_path}")
|
|
40
|
-
except requests.exceptions.RequestException as e:
|
|
41
|
-
typer.echo(f"❌ Download failed: {e}", err=True)
|
|
42
|
-
raise typer.Exit(code=1)
|
|
43
|
-
except OSError as e:
|
|
44
|
-
typer.echo(f"❌ File write error: {e}", err=True)
|
|
45
|
-
raise typer.Exit(code=1)
|
|
46
|
-
|
|
47
|
-
if decompress:
|
|
48
|
-
typer.echo(f"📦 Decompressing: {download_path}")
|
|
49
|
-
|
|
50
|
-
base_name = download_path.name
|
|
51
|
-
parts = base_name.split('.')
|
|
52
|
-
base_name = parts[0] if parts else download_path.stem
|
|
53
|
-
|
|
54
|
-
extract_dir = download_path.parent / base_name
|
|
55
|
-
extract_dir.mkdir(parents=True, exist_ok=True)
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
subprocess.run(
|
|
59
|
-
["ouch", "decompress", str(download_path), "--dir", str(extract_dir)],
|
|
60
|
-
check=True,
|
|
61
|
-
capture_output=True,
|
|
62
|
-
text=True
|
|
63
|
-
)
|
|
64
|
-
typer.echo(f"✅ Decompressed to: {extract_dir}")
|
|
65
|
-
|
|
66
|
-
if download_path.exists():
|
|
67
|
-
download_path.unlink()
|
|
68
|
-
typer.echo(f"🗑️ Removed archive: {download_path}")
|
|
69
|
-
|
|
70
|
-
except subprocess.CalledProcessError as e:
|
|
71
|
-
typer.echo(f"❌ Decompression failed: {e.stderr}", err=True)
|
|
72
|
-
raise typer.Exit(code=1)
|
|
73
|
-
except FileNotFoundError:
|
|
74
|
-
typer.echo("❌ Error: ouch command not found. Please install ouch.", err=True)
|
|
75
|
-
typer.echo("💡 Install with: cargo install ouch", err=True)
|
|
76
|
-
raise typer.Exit(code=1)
|
|
77
5
|
|
|
78
6
|
|
|
79
7
|
def merge_pdfs(
|
|
@@ -108,7 +36,8 @@ def merge_pdfs(
|
|
|
108
36
|
writer.write(output_path)
|
|
109
37
|
print(f"✅ Merged PDF saved to: {output_path}")
|
|
110
38
|
from machineconfig.utils.meta import lambda_to_python_script
|
|
111
|
-
code = lambda_to_python_script(lambda : merge_pdfs_internal(pdfs=pdfs, output=output, compress=compress),
|
|
39
|
+
code = lambda_to_python_script(lambda : merge_pdfs_internal(pdfs=pdfs, output=output, compress=compress),
|
|
40
|
+
in_global=True, import_module=False)
|
|
112
41
|
from machineconfig.utils.code import run_shell_script, get_uv_command_executing_python_script
|
|
113
42
|
uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=["pypdf"], uv_project_dir=None)
|
|
114
43
|
run_shell_script(uv_command)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# import subprocess
|
|
4
4
|
from machineconfig.utils.io import read_ini
|
|
5
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
6
5
|
from machineconfig.utils.source_of_truth import LIBRARY_ROOT, DEFAULTS_PATH
|
|
7
6
|
from machineconfig.utils.code import print_code
|
|
8
7
|
from machineconfig.utils.options import choose_cloud_interactively, choose_from_options
|
|
@@ -11,6 +10,7 @@ from platform import system
|
|
|
11
10
|
from typing import Any, Literal, Optional
|
|
12
11
|
from rich.console import Console
|
|
13
12
|
from rich.panel import Panel
|
|
13
|
+
from pathlib import Path
|
|
14
14
|
import tomllib
|
|
15
15
|
|
|
16
16
|
|
|
@@ -56,13 +56,13 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str], cloud: Option
|
|
|
56
56
|
flags += "e" if item["encrypt"] == "True" else ""
|
|
57
57
|
flags += "r" if item["rel2home"] == "True" else ""
|
|
58
58
|
flags += "o" if system().lower() in item_name else ""
|
|
59
|
-
console.print(Panel(f"📦 PROCESSING: {item_name}\n📂 Path: {
|
|
59
|
+
console.print(Panel(f"📦 PROCESSING: {item_name}\n📂 Path: {Path(item['path']).as_posix()}\n🏳️ Flags: {flags or 'None'}", title=f"[bold blue]Processing Item: {item_name}[/bold blue]", border_style="blue"))
|
|
60
60
|
if flags:
|
|
61
61
|
flags = "-" + flags
|
|
62
62
|
if direction == "BACKUP":
|
|
63
|
-
program += f"""\ncloud_copy "{
|
|
63
|
+
program += f"""\ncloud_copy "{Path(item["path"]).as_posix()}" $cloud {flags}\n"""
|
|
64
64
|
elif direction == "RETRIEVE":
|
|
65
|
-
program += f"""\ncloud_copy $cloud "{
|
|
65
|
+
program += f"""\ncloud_copy $cloud "{Path(item["path"]).as_posix()}" {flags}\n"""
|
|
66
66
|
else:
|
|
67
67
|
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"))
|
|
68
68
|
raise RuntimeError(f"Unknown direction: {direction}")
|
|
@@ -17,22 +17,6 @@ from machineconfig.utils.source_of_truth import CONFIG_ROOT, DEFAULTS_PATH, LIBR
|
|
|
17
17
|
console = Console()
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def _check_system_info() -> dict[str, str]:
|
|
21
|
-
"""Gather basic system information."""
|
|
22
|
-
import socket
|
|
23
|
-
import os
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
"hostname": socket.gethostname(),
|
|
27
|
-
"system": platform.system(),
|
|
28
|
-
"release": platform.release(),
|
|
29
|
-
"version": platform.version(),
|
|
30
|
-
"machine": platform.machine(),
|
|
31
|
-
"processor": platform.processor() or "Unknown",
|
|
32
|
-
"python_version": platform.python_version(),
|
|
33
|
-
"user": os.getenv("USER") or os.getenv("USERNAME") or "Unknown",
|
|
34
|
-
}
|
|
35
|
-
|
|
36
20
|
|
|
37
21
|
def _check_shell_profile_status() -> dict[str, Any]:
|
|
38
22
|
"""Check shell profile configuration status."""
|
|
@@ -480,10 +464,14 @@ def main() -> None:
|
|
|
480
464
|
console.print("\n")
|
|
481
465
|
console.print(Panel(Text("📊 Machine Status Report", justify="center", style="bold white"), style="bold blue", padding=(1, 2)))
|
|
482
466
|
console.print("\n")
|
|
483
|
-
|
|
484
|
-
system_info = _check_system_info()
|
|
467
|
+
|
|
468
|
+
# system_info = _check_system_info()
|
|
469
|
+
# from machineconfig.scripts.python.helpers_devops.devops_system_info import _check_system_info
|
|
470
|
+
from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
|
|
471
|
+
system_info = get_machine_specs()
|
|
472
|
+
from typing import cast
|
|
473
|
+
system_info = cast(dict[str, str], system_info)
|
|
485
474
|
_display_system_info(system_info)
|
|
486
|
-
|
|
487
475
|
shell_status = _check_shell_profile_status()
|
|
488
476
|
_display_shell_status(shell_status)
|
|
489
477
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
import os
|
|
3
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
4
|
import platform
|
|
5
5
|
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ def parse_pyfile(file_path: str):
|
|
|
12
12
|
func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
|
|
13
13
|
import ast
|
|
14
14
|
|
|
15
|
-
parsed_ast = ast.parse(
|
|
15
|
+
parsed_ast = ast.parse(Path(file_path).read_text(encoding="utf-8"))
|
|
16
16
|
functions = [node for node in ast.walk(parsed_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
|
|
17
17
|
module__doc__ = ast.get_docstring(parsed_ast)
|
|
18
18
|
main_option = f"RUN AS MAIN -- {module__doc__ if module__doc__ is not None else 'NoDocs'}"
|
|
@@ -102,7 +102,6 @@ def wrap_import_in_try_except(import_line: str, pyfile: str, repo_root: Optional
|
|
|
102
102
|
print(fr"❌ Failed to import `{pyfile}` as a module: {ex} ")
|
|
103
103
|
print("⚠️ Attempting import with ad-hoc `$PATH` manipulation. DO NOT pickle any objects in this session as correct deserialization cannot be guaranteed.")
|
|
104
104
|
import sys
|
|
105
|
-
from pathlib import Path
|
|
106
105
|
sys.path.append(str(Path(pyfile).parent))
|
|
107
106
|
if repo_root is not None:
|
|
108
107
|
sys.path.append(repo_root)
|
|
@@ -5,11 +5,10 @@ from typing import Optional
|
|
|
5
5
|
import tomllib
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from machineconfig.utils.accessories import randstr
|
|
8
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
9
8
|
from machineconfig.utils.options import choose_from_options
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
def choose_function_or_lines(choice_file:
|
|
11
|
+
def choose_function_or_lines(choice_file: Path, kwargs_dict: dict[str, object]) -> tuple[Optional[str], Path, dict[str, object]]:
|
|
13
12
|
"""
|
|
14
13
|
Choose a function to run from a Python file or lines from a shell script.
|
|
15
14
|
|
|
@@ -46,7 +45,7 @@ def choose_function_or_lines(choice_file: PathExtended, kwargs_dict: dict[str, o
|
|
|
46
45
|
continue
|
|
47
46
|
options.append(line)
|
|
48
47
|
chosen_lines = choose_from_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
|
|
49
|
-
choice_file =
|
|
48
|
+
choice_file = Path.home().joinpath(f"tmp_results/tmp_scripts/shell/{randstr(10)}.sh")
|
|
50
49
|
choice_file.parent.mkdir(parents=True, exist_ok=True)
|
|
51
50
|
choice_file.write_text("\n".join(chosen_lines), encoding="utf-8")
|
|
52
51
|
choice_function = None
|
|
@@ -55,15 +54,13 @@ def choose_function_or_lines(choice_file: PathExtended, kwargs_dict: dict[str, o
|
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
def get_command_streamlit(choice_file: Path, environment: str, repo_root: Optional[Path]) -> str:
|
|
58
|
-
import
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
finally:
|
|
66
|
-
s.close()
|
|
57
|
+
# from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
|
|
58
|
+
from machineconfig.scripts.python.nw.address import select_lan_ipv4
|
|
59
|
+
res = select_lan_ipv4(prefer_vpn=False)
|
|
60
|
+
if res is None:
|
|
61
|
+
raise RuntimeError("Could not determine local LAN IPv4 address for streamlit app.")
|
|
62
|
+
local_ip_v4 = res
|
|
63
|
+
|
|
67
64
|
computer_name = platform.node()
|
|
68
65
|
port = 8501
|
|
69
66
|
toml_path: Optional[Path] = None
|
|
@@ -82,7 +79,7 @@ def get_command_streamlit(choice_file: Path, environment: str, repo_root: Option
|
|
|
82
79
|
port = config["server"]["port"]
|
|
83
80
|
secrets_path = toml_path.with_name("secrets.toml")
|
|
84
81
|
if repo_root is not None:
|
|
85
|
-
secrets_template_path = Path.home().joinpath(f"dotfiles/creds/streamlit/{
|
|
82
|
+
secrets_template_path = Path.home().joinpath(f"dotfiles/creds/streamlit/{Path(repo_root).name}/{choice_file.name}/secrets.toml")
|
|
86
83
|
if environment != "" and not secrets_path.exists() and secrets_template_path.exists():
|
|
87
84
|
secrets_template = tomllib.loads(secrets_template_path.read_text(encoding="utf-8"))
|
|
88
85
|
if environment == "ip":
|
|
@@ -101,6 +98,19 @@ def get_command_streamlit(choice_file: Path, environment: str, repo_root: Option
|
|
|
101
98
|
except Exception as ex:
|
|
102
99
|
print(ex)
|
|
103
100
|
raise ex
|
|
101
|
+
from machineconfig.utils.installer_utils.installer_cli import install_if_missing
|
|
102
|
+
install_if_missing("qrterminal")
|
|
103
|
+
script = f"""
|
|
104
|
+
qrterminal "http://{local_ip_v4}:{port}"
|
|
105
|
+
echo "http://{local_ip_v4}:{port}"
|
|
106
|
+
qrterminal "http://{computer_name}:{port}"
|
|
107
|
+
echo "http://{computer_name}:{port}"
|
|
108
|
+
"""
|
|
109
|
+
# from machineconfig.utils.code import run_shell_script
|
|
110
|
+
# run_shell_script(script)
|
|
111
|
+
from machineconfig.utils.code import print_code
|
|
112
|
+
print_code(code=script, lexer="shell", desc="Streamlit QR Codes and URLs")
|
|
113
|
+
|
|
104
114
|
message = f"🚀 Streamlit app is running @:\n1- http://{local_ip_v4}:{port}\n2- http://{computer_name}:{port}\n3- http://localhost:{port}"
|
|
105
115
|
from rich.panel import Panel
|
|
106
116
|
from rich import print as rprint
|