portacode 1.4.14__py3-none-any.whl → 1.4.15.dev0__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 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.14'
32
- __version_tuple__ = version_tuple = (1, 4, 14)
31
+ __version__ = version = '1.4.15.dev0'
32
+ __version_tuple__ = version_tuple = (1, 4, 15, 'dev0')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -326,9 +326,10 @@ Configures a Proxmox node for Portacode infrastructure usage (API token validati
326
326
 
327
327
  **Payload Fields:**
328
328
 
329
- * `token_identifier` (string, required): API token identifier in the form `user@realm!tokenid`.
330
- * `token_value` (string, required): Secret value associated with the token.
331
- * `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`.
329
+ * `token_identifier` (string, optional when reconfiguring): API token identifier in the form `user@realm!tokenid`. Required on first configuration or when replacing the stored token.
330
+ * `token_value` (string, optional when reconfiguring): Secret value associated with the token. Required when `token_identifier` is supplied.
331
+ * `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`. When omitted, the last configured value is preserved.
332
+ * `cloudflare_api_token` (string, optional): Cloudflare API token the host can reuse later to provision tunnels.
332
333
 
333
334
  **Responses:**
334
335
 
@@ -1126,6 +1127,8 @@ Provides system information in response to a `system_info` action. Handled by [`
1126
1127
  * `message` (string|null): Informational text about the network setup attempt.
1127
1128
  * `bridge` (string): The bridge interface configured (typically `vmbr1`).
1128
1129
  * `health` (string|null): `"healthy"` when the connectivity verification succeeded.
1130
+ * `cloudflare` (object): Optional Cloudflare metadata collected for future tunnels:
1131
+ * `configured` (boolean): True when a Cloudflare API token is stored.
1129
1132
  * `node_status` (object|null): Status response returned by the Proxmox API when validating the token.
1130
1133
  * `managed_containers` (object): Cached summary of the Portacode-managed containers:
1131
1134
  * `updated_at` (string): ISO timestamp when this snapshot was last refreshed.
@@ -1005,6 +1005,12 @@ def _bootstrap_portacode(
1005
1005
  return public_key, results
1006
1006
 
1007
1007
 
1008
+ def _build_cloudflare_snapshot(cloudflare_config: Dict[str, Any] | None) -> Dict[str, Any]:
1009
+ if not cloudflare_config:
1010
+ return {"configured": False}
1011
+ return {"configured": bool(cloudflare_config.get("api_token"))}
1012
+
1013
+
1008
1014
  def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
1009
1015
  network = config.get("network", {})
1010
1016
  base_network = {
@@ -1012,8 +1018,13 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
1012
1018
  "message": network.get("message"),
1013
1019
  "bridge": network.get("bridge", DEFAULT_BRIDGE),
1014
1020
  }
1021
+ cloudflare_snapshot = _build_cloudflare_snapshot(config.get("cloudflare"))
1015
1022
  if not config:
1016
- return {"configured": False, "network": base_network}
1023
+ return {
1024
+ "configured": False,
1025
+ "network": base_network,
1026
+ "cloudflare": cloudflare_snapshot,
1027
+ }
1017
1028
  return {
1018
1029
  "configured": True,
1019
1030
  "host": config.get("host"),
@@ -1024,18 +1035,54 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
1024
1035
  "templates": config.get("templates") or [],
1025
1036
  "last_verified": config.get("last_verified"),
1026
1037
  "network": base_network,
1038
+ "cloudflare": cloudflare_snapshot,
1027
1039
  }
1028
1040
 
1029
1041
 
1030
- def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl: bool = False) -> Dict[str, Any]:
1042
+ def _resolve_proxmox_credentials(
1043
+ token_identifier: Optional[str],
1044
+ token_value: Optional[str],
1045
+ existing: Dict[str, Any],
1046
+ ) -> Tuple[str, str, str]:
1047
+ if token_identifier:
1048
+ if not token_value:
1049
+ raise ValueError("token_value is required when providing a new token_identifier")
1050
+ user, token_name = _parse_token(token_identifier)
1051
+ return user, token_name, token_value
1052
+ if existing and existing.get("user") and existing.get("token_name") and existing.get("token_value"):
1053
+ return existing["user"], existing["token_name"], existing["token_value"]
1054
+ raise ValueError("Proxmox token identifier and value are required when no existing configuration is available")
1055
+
1056
+
1057
+ def _build_cloudflare_config(existing: Dict[str, Any], api_token: Optional[str]) -> Dict[str, Any]:
1058
+ cloudflare: Dict[str, Any] = dict(existing.get("cloudflare", {}) or {})
1059
+ if api_token:
1060
+ cloudflare["api_token"] = api_token
1061
+ if cloudflare.get("api_token"):
1062
+ cloudflare["configured"] = True
1063
+ elif "configured" in cloudflare:
1064
+ cloudflare.pop("configured", None)
1065
+ return cloudflare
1066
+
1067
+
1068
+ def configure_infrastructure(
1069
+ token_identifier: Optional[str] = None,
1070
+ token_value: Optional[str] = None,
1071
+ verify_ssl: Optional[bool] = None,
1072
+ cloudflare_api_token: Optional[str] = None,
1073
+ ) -> Dict[str, Any]:
1031
1074
  ProxmoxAPI = _ensure_proxmoxer()
1032
- user, token_name = _parse_token(token_identifier)
1075
+ existing = _load_config()
1076
+ user, token_name, resolved_token_value = _resolve_proxmox_credentials(
1077
+ token_identifier, token_value, existing
1078
+ )
1079
+ actual_verify_ssl = verify_ssl if verify_ssl is not None else existing.get("verify_ssl", False)
1033
1080
  client = ProxmoxAPI(
1034
1081
  DEFAULT_HOST,
1035
1082
  user=user,
1036
1083
  token_name=token_name,
1037
- token_value=token_value,
1038
- verify_ssl=verify_ssl,
1084
+ token_value=resolved_token_value,
1085
+ verify_ssl=actual_verify_ssl,
1039
1086
  timeout=30,
1040
1087
  )
1041
1088
  node = _pick_node(client)
@@ -1043,31 +1090,35 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
1043
1090
  storages = client.nodes(node).storage.get()
1044
1091
  default_storage = _pick_storage(storages)
1045
1092
  templates = _list_templates(client, node, storages)
1046
- network: Dict[str, Any] = {}
1047
- try:
1048
- network = _ensure_bridge()
1049
- # Wait for network convergence before validating connectivity
1050
- time.sleep(2)
1051
- if not _verify_connectivity():
1052
- raise RuntimeError("Connectivity check failed; bridge reverted")
1053
- network["health"] = "healthy"
1054
- except Exception as exc:
1055
- logger.warning("Bridge setup failed; reverting previous changes: %s", exc)
1056
- _revert_bridge()
1057
- raise
1093
+ network = dict(existing.get("network", {}) or {})
1094
+ if not network.get("applied"):
1095
+ try:
1096
+ network = _ensure_bridge()
1097
+ # Wait for network convergence before validating connectivity
1098
+ time.sleep(2)
1099
+ if not _verify_connectivity():
1100
+ raise RuntimeError("Connectivity check failed; bridge reverted")
1101
+ network["health"] = "healthy"
1102
+ except Exception as exc:
1103
+ logger.warning("Bridge setup failed; reverting previous changes: %s", exc)
1104
+ _revert_bridge()
1105
+ raise
1058
1106
  config = {
1059
1107
  "host": DEFAULT_HOST,
1060
1108
  "node": node,
1061
1109
  "user": user,
1062
1110
  "token_name": token_name,
1063
- "token_value": token_value,
1064
- "verify_ssl": verify_ssl,
1111
+ "token_value": resolved_token_value,
1112
+ "verify_ssl": actual_verify_ssl,
1065
1113
  "default_storage": default_storage,
1066
1114
  "templates": templates,
1067
1115
  "last_verified": datetime.utcnow().isoformat() + "Z",
1068
1116
  "network": network,
1069
1117
  "node_status": status,
1070
1118
  }
1119
+ cloudflare = _build_cloudflare_config(existing, cloudflare_api_token)
1120
+ if cloudflare:
1121
+ config["cloudflare"] = cloudflare
1071
1122
  _save_config(config)
1072
1123
  snapshot = build_snapshot(config)
1073
1124
  snapshot["node_status"] = status
@@ -1643,10 +1694,13 @@ class ConfigureProxmoxInfraHandler(SyncHandler):
1643
1694
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1644
1695
  token_identifier = message.get("token_identifier")
1645
1696
  token_value = message.get("token_value")
1646
- verify_ssl = bool(message.get("verify_ssl"))
1647
- if not token_identifier or not token_value:
1648
- raise ValueError("token_identifier and token_value are required")
1649
- snapshot = configure_infrastructure(token_identifier, token_value, verify_ssl=verify_ssl)
1697
+ verify_ssl = message.get("verify_ssl")
1698
+ snapshot = configure_infrastructure(
1699
+ token_identifier=token_identifier,
1700
+ token_value=token_value,
1701
+ verify_ssl=verify_ssl,
1702
+ cloudflare_api_token=message.get("cloudflare_api_token"),
1703
+ )
1650
1704
  return {
1651
1705
  "event": "proxmox_infra_configured",
1652
1706
  "success": True,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.14
3
+ Version: 1.4.15.dev0
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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=kpfD1bHnQkFa0-DFuQayl0P6Mvgylvye8ENBeTdr_CY,706
4
+ portacode/_version.py,sha256=ZC_kn1NEQ9Aet-DPiqGe5ZTNbDk0ZOpeb6OhHRUx77M,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=--NNK-qrqQhZ9O4RHuAQfDQb6QxKtl_No101c1Sy_cQ,98994
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=bW3coRPyolXX8GIFQXfOyonxg2ad7UOfmMenN23_5FQ,99509
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=v2IftCXnNO8ta8dWfdXf_eRBAeFO6YxkFTCSuTrp5ss,61134
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=K9s0e9lb97I9_kNmnpoJMpoO1uTrl2ef9KTUwIbiF-g,63287
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.14.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.15.dev0.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.14.dist-info/METADATA,sha256=NfhCGg_ZISg6wvISMe9SRNlRcIqfKoUvDZt2ac0Dg8c,13046
95
- portacode-1.4.14.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
- portacode-1.4.14.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.14.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.14.dist-info/RECORD,,
94
+ portacode-1.4.15.dev0.dist-info/METADATA,sha256=R7FEzw07kw5YY1i3Sj0-W88K5pOVHVwMYGPE1kVDduA,13051
95
+ portacode-1.4.15.dev0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
+ portacode-1.4.15.dev0.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.15.dev0.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.15.dev0.dist-info/RECORD,,