portacode 1.4.15.dev1__py3-none-any.whl → 1.4.15.dev2__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.
- portacode/_version.py +2 -2
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +3 -3
- portacode/connection/handlers/proxmox_infra.py +69 -24
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/METADATA +1 -1
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/RECORD +9 -9
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/WHEEL +0 -0
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.15.dev1.dist-info → portacode-1.4.15.dev2.dist-info}/top_level.txt +0 -0
portacode/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.4.15.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 15, '
|
|
31
|
+
__version__ = version = '1.4.15.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 15, 'dev2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -420,7 +420,7 @@ Creates or updates a Cloudflare tunnel in front of a managed container. The cont
|
|
|
420
420
|
|
|
421
421
|
* `ctid` (string, required): Identifier of the container.
|
|
422
422
|
* `container_port` (integer, required): Port inside the container to expose through Cloudflare.
|
|
423
|
-
* `cloudflare_url` (string,
|
|
423
|
+
* `cloudflare_url` (string, optional): Hostname (e.g., `app.example.com`) that the tunnel should serve. Leave blank to let Cloudflare create a quick tunnel (`*.cfargotunnel.com`) automatically.
|
|
424
424
|
* `protocol` (string, optional): One of `http`, `https`, or `tcp` (defaults to `http`).
|
|
425
425
|
|
|
426
426
|
**Responses:**
|
|
@@ -436,7 +436,7 @@ Refreshes the tunnel configuration for an existing tunnel (different port, URL,
|
|
|
436
436
|
|
|
437
437
|
* `ctid` (string, required): Identifier of the container.
|
|
438
438
|
* `container_port` (integer, optional): New container port.
|
|
439
|
-
* `cloudflare_url` (string, optional): New hostname.
|
|
439
|
+
* `cloudflare_url` (string, optional): New hostname (leave blank to keep the current hostname or let Cloudflare assign a quick tunnel).
|
|
440
440
|
* `protocol` (string, optional): New protocol (`http`, `https`, or `tcp`).
|
|
441
441
|
|
|
442
442
|
**Responses:**
|
|
@@ -1273,7 +1273,7 @@ Submitted after a successful `create_cloudflare_tunnel` action.
|
|
|
1273
1273
|
* `ctid` (string): Container ID associated with the tunnel.
|
|
1274
1274
|
* `success` (boolean): True when the tunnel is running.
|
|
1275
1275
|
* `message` (string): Summary text.
|
|
1276
|
-
* `tunnel` (object): Tunnel metadata matching the manager view.
|
|
1276
|
+
* `tunnel` (object): Tunnel metadata matching the manager view. When using Cloudflare Quick Tunnel (no hostname supplied), `tunnel.url` holds the autogenerated `*.cfargotunnel.com` hostname.
|
|
1277
1277
|
|
|
1278
1278
|
### `cloudflare_tunnel_updated`
|
|
1279
1279
|
|
|
@@ -8,6 +8,8 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import secrets
|
|
10
10
|
import shlex
|
|
11
|
+
import re
|
|
12
|
+
import select
|
|
11
13
|
import shutil
|
|
12
14
|
import stat
|
|
13
15
|
import subprocess
|
|
@@ -637,19 +639,19 @@ def _launch_container_tunnel(proxmox: Any, node: str, vmid: int, tunnel: Dict[st
|
|
|
637
639
|
port = int(tunnel.get("container_port") or 0)
|
|
638
640
|
if not port:
|
|
639
641
|
raise ValueError("container_port is required to create a tunnel.")
|
|
640
|
-
|
|
641
|
-
if not hostname:
|
|
642
|
-
raise ValueError("cloudflare_url is required to expose the tunnel.")
|
|
642
|
+
requested_hostname = tunnel.get("url") or None
|
|
643
643
|
protocol = (tunnel.get("protocol") or "http").lower()
|
|
644
644
|
ip_address = _resolve_container_ip(proxmox, node, vmid)
|
|
645
645
|
target_url = f"{protocol}://{ip_address}:{port}"
|
|
646
646
|
name = tunnel.get("name") or _build_tunnel_name(vmid, port)
|
|
647
647
|
_stop_cloudflare_process(name)
|
|
648
|
-
proc = _start_cloudflare_process(name,
|
|
648
|
+
proc, assigned_url = _start_cloudflare_process(name, target_url, hostname=requested_hostname)
|
|
649
|
+
if not assigned_url:
|
|
650
|
+
raise RuntimeError("Failed to determine Cloudflare hostname for the tunnel.")
|
|
649
651
|
updated = {
|
|
650
652
|
"name": name,
|
|
651
653
|
"container_port": port,
|
|
652
|
-
"url":
|
|
654
|
+
"url": assigned_url,
|
|
653
655
|
"protocol": protocol,
|
|
654
656
|
"status": "running",
|
|
655
657
|
"pid": proc.pid,
|
|
@@ -671,13 +673,14 @@ def _stop_container_tunnel(vmid: int) -> None:
|
|
|
671
673
|
return
|
|
672
674
|
name = tunnel.get("name") or _build_tunnel_name(vmid, int(tunnel.get("container_port") or 0))
|
|
673
675
|
stopped = _stop_cloudflare_process(name)
|
|
674
|
-
if stopped
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
676
|
+
if not stopped and tunnel.get("status") == "stopped":
|
|
677
|
+
return
|
|
678
|
+
tunnel_update = {
|
|
679
|
+
**tunnel,
|
|
680
|
+
"status": "stopped",
|
|
681
|
+
"pid": None,
|
|
682
|
+
"last_updated": datetime.utcnow().isoformat() + "Z",
|
|
683
|
+
}
|
|
681
684
|
_update_container_tunnel(vmid, tunnel_update)
|
|
682
685
|
|
|
683
686
|
|
|
@@ -827,25 +830,67 @@ def _get_cloudflared_binary() -> str:
|
|
|
827
830
|
return binary
|
|
828
831
|
|
|
829
832
|
|
|
830
|
-
def
|
|
833
|
+
def _drain_stream(stream: Optional[Any]) -> None:
|
|
834
|
+
if stream is None:
|
|
835
|
+
return
|
|
836
|
+
try:
|
|
837
|
+
for _ in iter(stream.readline, ""):
|
|
838
|
+
continue
|
|
839
|
+
except Exception:
|
|
840
|
+
pass
|
|
841
|
+
finally:
|
|
842
|
+
try:
|
|
843
|
+
stream.close()
|
|
844
|
+
except Exception:
|
|
845
|
+
pass
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def _await_quick_tunnel_url(proc: subprocess.Popen, timeout: float = 15.0) -> Optional[str]:
|
|
849
|
+
if not proc.stdout:
|
|
850
|
+
return None
|
|
851
|
+
cf_re = re.compile(r"https://[A-Za-z0-9\-.]+\.cfargotunnel\.com")
|
|
852
|
+
deadline = time.time() + timeout
|
|
853
|
+
while time.time() < deadline:
|
|
854
|
+
ready, _, _ = select.select([proc.stdout], [], [], 1)
|
|
855
|
+
if not ready:
|
|
856
|
+
continue
|
|
857
|
+
line = proc.stdout.readline()
|
|
858
|
+
if not line:
|
|
859
|
+
continue
|
|
860
|
+
match = cf_re.search(line)
|
|
861
|
+
if match:
|
|
862
|
+
return match.group(0)
|
|
863
|
+
return None
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _start_cloudflare_process(name: str, target_url: str, hostname: Optional[str] = None) -> Tuple[subprocess.Popen, Optional[str]]:
|
|
831
867
|
binary = _get_cloudflared_binary()
|
|
832
868
|
cmd = [
|
|
833
869
|
binary,
|
|
834
870
|
"tunnel",
|
|
835
|
-
"--hostname",
|
|
836
|
-
hostname,
|
|
837
871
|
"--url",
|
|
838
872
|
target_url,
|
|
839
873
|
"--no-autoupdate",
|
|
840
874
|
]
|
|
875
|
+
if hostname:
|
|
876
|
+
cmd.extend(["--hostname", hostname])
|
|
877
|
+
stdout_target = subprocess.DEVNULL
|
|
878
|
+
else:
|
|
879
|
+
stdout_target = subprocess.PIPE
|
|
841
880
|
proc = subprocess.Popen(
|
|
842
881
|
cmd,
|
|
843
|
-
stdout=
|
|
844
|
-
stderr=subprocess.
|
|
882
|
+
stdout=stdout_target,
|
|
883
|
+
stderr=subprocess.PIPE,
|
|
884
|
+
text=True,
|
|
845
885
|
)
|
|
846
886
|
with _CLOUDFLARE_TUNNELS_LOCK:
|
|
847
887
|
_CLOUDFLARE_TUNNEL_PROCESSES[name] = proc
|
|
848
|
-
|
|
888
|
+
assigned_url = hostname
|
|
889
|
+
if not hostname:
|
|
890
|
+
assigned_url = _await_quick_tunnel_url(proc)
|
|
891
|
+
threading.Thread(target=_drain_stream, args=(proc.stdout,), daemon=True).start()
|
|
892
|
+
threading.Thread(target=_drain_stream, args=(proc.stderr,), daemon=True).start()
|
|
893
|
+
return proc, assigned_url
|
|
849
894
|
|
|
850
895
|
|
|
851
896
|
def _stop_cloudflare_process(name: str) -> bool:
|
|
@@ -1884,8 +1929,7 @@ class CreateCloudflareTunnelHandler(SyncHandler):
|
|
|
1884
1929
|
if container_port <= 0:
|
|
1885
1930
|
raise ValueError("container_port is required and must be greater than zero.")
|
|
1886
1931
|
hostname = (message.get("cloudflare_url") or message.get("hostname") or "").strip()
|
|
1887
|
-
|
|
1888
|
-
raise ValueError("cloudflare_url is required.")
|
|
1932
|
+
hostname = hostname or None
|
|
1889
1933
|
protocol = (message.get("protocol") or "http").strip().lower()
|
|
1890
1934
|
if protocol not in {"http", "https", "tcp"}:
|
|
1891
1935
|
raise ValueError("protocol must be one of http, https, or tcp.")
|
|
@@ -1899,9 +1943,10 @@ class CreateCloudflareTunnelHandler(SyncHandler):
|
|
|
1899
1943
|
raise RuntimeError("Container must be running to create a tunnel.")
|
|
1900
1944
|
tunnel = {
|
|
1901
1945
|
"container_port": container_port,
|
|
1902
|
-
"url": hostname,
|
|
1903
1946
|
"protocol": protocol,
|
|
1904
1947
|
}
|
|
1948
|
+
if hostname:
|
|
1949
|
+
tunnel["url"] = hostname
|
|
1905
1950
|
created = _launch_container_tunnel(proxmox, node, vmid, tunnel)
|
|
1906
1951
|
return {
|
|
1907
1952
|
"event": "cloudflare_tunnel_created",
|
|
@@ -1934,16 +1979,16 @@ class UpdateCloudflareTunnelHandler(SyncHandler):
|
|
|
1934
1979
|
if container_port <= 0:
|
|
1935
1980
|
raise ValueError("container_port must be greater than zero.")
|
|
1936
1981
|
hostname = (message.get("cloudflare_url") or tunnel.get("url") or "").strip()
|
|
1937
|
-
|
|
1938
|
-
raise ValueError("cloudflare_url is required.")
|
|
1982
|
+
hostname = hostname or None
|
|
1939
1983
|
protocol = (message.get("protocol") or tunnel.get("protocol") or "http").strip().lower()
|
|
1940
1984
|
if protocol not in {"http", "https", "tcp"}:
|
|
1941
1985
|
raise ValueError("protocol must be one of http, https, or tcp.")
|
|
1942
1986
|
updated_tunnel = {
|
|
1943
1987
|
"container_port": container_port,
|
|
1944
|
-
"url": hostname,
|
|
1945
1988
|
"protocol": protocol,
|
|
1946
1989
|
}
|
|
1990
|
+
if hostname:
|
|
1991
|
+
updated_tunnel["url"] = hostname
|
|
1947
1992
|
result = _launch_container_tunnel(proxmox, node, vmid, updated_tunnel)
|
|
1948
1993
|
return {
|
|
1949
1994
|
"event": "cloudflare_tunnel_updated",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
|
|
2
2
|
portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
|
|
3
3
|
portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
|
|
4
|
-
portacode/_version.py,sha256=
|
|
4
|
+
portacode/_version.py,sha256=oWFHpNG7I0w-78ytqCCXMciE0ywZlQquvI0R-kyOX34,719
|
|
5
5
|
portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
|
|
6
6
|
portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
|
|
7
7
|
portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
|
|
@@ -14,7 +14,7 @@ portacode/connection/client.py,sha256=jtLb9_YufqPkzi9t8VQH3iz_JEMisbtY6a8L9U5wei
|
|
|
14
14
|
portacode/connection/multiplex.py,sha256=L-TxqJ_ZEbfNEfu1cwxgJ5vUdyRzZjsMy2Kx1diiZys,5237
|
|
15
15
|
portacode/connection/terminal.py,sha256=oyLPOVLPlUuN_eRvHPGazB51yi8W8JEF3oOEYxucGTE,45069
|
|
16
16
|
portacode/connection/handlers/README.md,sha256=HsLZG1QK1JNm67HsgL6WoDg9nxzKXxwkc5fJPFJdX5g,12169
|
|
17
|
-
portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=
|
|
17
|
+
portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=IZMuYF5wc23_ovEzXAvlLZLW1WXmqUUlrh_feTSsQfE,103110
|
|
18
18
|
portacode/connection/handlers/__init__.py,sha256=WSeBmi65GWFQPYt9M3E10rn0uZ_EPCJzNJOzSf2HZyw,2921
|
|
19
19
|
portacode/connection/handlers/base.py,sha256=oENFb-Fcfzwk99Qx8gJQriEMiwSxwygwjOiuCH36hM4,10231
|
|
20
20
|
portacode/connection/handlers/chunked_content.py,sha256=h6hXRmxSeOgnIxoU8CkmvEf2Odv-ajPrpHIe_W3GKcA,9251
|
|
@@ -22,7 +22,7 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
|
|
|
22
22
|
portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
|
|
23
23
|
portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
|
|
24
24
|
portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
|
|
25
|
-
portacode/connection/handlers/proxmox_infra.py,sha256=
|
|
25
|
+
portacode/connection/handlers/proxmox_infra.py,sha256=4XxGeHAppuvVdqTQI-UoYTunS78uI8_NSR5Rqtqexso,75073
|
|
26
26
|
portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
|
|
27
27
|
portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
|
|
28
28
|
portacode/connection/handlers/system_handlers.py,sha256=fr12QpOr_Z8KYGUU-AYrTQwRPAcrLK85hvj3SEq1Kw8,14757
|
|
@@ -65,7 +65,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
|
|
|
65
65
|
portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
|
|
66
66
|
portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
|
|
67
67
|
portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
|
|
68
|
-
portacode-1.4.15.
|
|
68
|
+
portacode-1.4.15.dev2.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
|
|
69
69
|
test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
|
|
70
70
|
test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
|
|
71
71
|
test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
|
|
@@ -91,8 +91,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
|
|
|
91
91
|
testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
|
|
92
92
|
testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
|
|
93
93
|
testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
|
|
94
|
-
portacode-1.4.15.
|
|
95
|
-
portacode-1.4.15.
|
|
96
|
-
portacode-1.4.15.
|
|
97
|
-
portacode-1.4.15.
|
|
98
|
-
portacode-1.4.15.
|
|
94
|
+
portacode-1.4.15.dev2.dist-info/METADATA,sha256=iZsuq2JwkRIK8b60HLPQ4kMUH5-oXoDaVMCFPGgfm6c,13051
|
|
95
|
+
portacode-1.4.15.dev2.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
96
|
+
portacode-1.4.15.dev2.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.15.dev2.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.15.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|