portacode 1.4.17.dev7__py3-none-any.whl → 1.4.17.dev9__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/proxmox_infra.py +246 -234
- portacode/service.py +109 -2
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.dist-info}/METADATA +1 -1
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.dist-info}/RECORD +9 -9
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.dist-info}/WHEEL +0 -0
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.17.dev7.dist-info → portacode-1.4.17.dev9.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.17.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 17, '
|
|
31
|
+
__version__ = version = '1.4.17.dev9'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 17, 'dev9')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -2058,266 +2058,278 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
2058
2058
|
_validate_environment,
|
|
2059
2059
|
)
|
|
2060
2060
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
payload["cpulimit"] = float(payload["cpus"])
|
|
2074
|
-
payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
|
|
2075
|
-
payload["memory"] = int(payload["ram_mib"])
|
|
2076
|
-
payload["node"] = node
|
|
2077
|
-
reservation_id = _reserve_container_resources(
|
|
2078
|
-
payload, device_id=device_id, request_id=request_id
|
|
2079
|
-
)
|
|
2080
|
-
_emit_host_event(
|
|
2081
|
-
self,
|
|
2082
|
-
{
|
|
2083
|
-
"event": "proxmox_container_accepted",
|
|
2084
|
-
"success": True,
|
|
2085
|
-
"message": "Provisioning accepted; resources reserved.",
|
|
2086
|
-
"device_id": device_id,
|
|
2087
|
-
"request_id": request_id,
|
|
2088
|
-
},
|
|
2089
|
-
)
|
|
2090
|
-
provisioning_id = secrets.token_hex(6)
|
|
2091
|
-
payload["description"] = f"{payload.get('description', MANAGED_MARKER)};provisioning_id={provisioning_id}"
|
|
2092
|
-
logger.debug(
|
|
2093
|
-
"Provisioning container node=%s template=%s ram=%s cpu=%s storage=%s",
|
|
2094
|
-
node,
|
|
2095
|
-
payload["template"],
|
|
2096
|
-
payload["ram_mib"],
|
|
2097
|
-
payload["cpus"],
|
|
2098
|
-
payload["storage"],
|
|
2099
|
-
)
|
|
2100
|
-
try:
|
|
2101
|
-
vmid = _allocate_vmid(proxmox)
|
|
2102
|
-
vmid, _ = _instantiate_container(proxmox, node, payload, vmid=vmid)
|
|
2103
|
-
except Exception:
|
|
2104
|
-
_release_container_reservation(reservation_id)
|
|
2105
|
-
if vmid is not None:
|
|
2106
|
-
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2107
|
-
raise
|
|
2108
|
-
payload["vmid"] = vmid
|
|
2109
|
-
payload["created_at"] = datetime.utcnow().isoformat() + "Z"
|
|
2110
|
-
payload["status"] = "creating"
|
|
2111
|
-
payload["device_id"] = device_id
|
|
2112
|
-
try:
|
|
2113
|
-
_write_container_record(vmid, payload, reservation_id=reservation_id)
|
|
2114
|
-
created_record = True
|
|
2115
|
-
except Exception:
|
|
2116
|
-
_release_container_reservation(reservation_id)
|
|
2117
|
-
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2118
|
-
raise
|
|
2119
|
-
return proxmox, node, vmid, payload
|
|
2120
|
-
|
|
2121
|
-
try:
|
|
2122
|
-
proxmox, node, vmid, payload = _run_lifecycle_step(
|
|
2123
|
-
"create_container",
|
|
2124
|
-
"Creating container",
|
|
2125
|
-
"Provisioning the LXC container…",
|
|
2126
|
-
"Container created.",
|
|
2127
|
-
_create_container,
|
|
2128
|
-
)
|
|
2129
|
-
|
|
2130
|
-
def _start_container_step():
|
|
2131
|
-
_start_container(proxmox, node, vmid)
|
|
2061
|
+
node = config.get("node") or DEFAULT_NODE_NAME
|
|
2062
|
+
payload = _build_container_payload(message, config)
|
|
2063
|
+
payload["cpulimit"] = float(payload["cpus"])
|
|
2064
|
+
payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
|
|
2065
|
+
payload["memory"] = int(payload["ram_mib"])
|
|
2066
|
+
payload["node"] = node
|
|
2067
|
+
|
|
2068
|
+
reservation_id = _reserve_container_resources(
|
|
2069
|
+
payload, device_id=device_id, request_id=request_id
|
|
2070
|
+
)
|
|
2071
|
+
provisioning_id = secrets.token_hex(6)
|
|
2072
|
+
payload["description"] = f"{payload.get('description', MANAGED_MARKER)};provisioning_id={provisioning_id}"
|
|
2132
2073
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
request_id=request_id,
|
|
2178
|
-
details=details or None,
|
|
2179
|
-
on_behalf_of_device=device_id,
|
|
2074
|
+
def _provision_background() -> None:
|
|
2075
|
+
nonlocal current_step_index
|
|
2076
|
+
proxmox: Any = None
|
|
2077
|
+
vmid: Optional[int] = None
|
|
2078
|
+
created_record = False
|
|
2079
|
+
try:
|
|
2080
|
+
def _create_container():
|
|
2081
|
+
nonlocal proxmox, vmid, created_record
|
|
2082
|
+
proxmox = _connect_proxmox(config)
|
|
2083
|
+
logger.debug(
|
|
2084
|
+
"Provisioning container node=%s template=%s ram=%s cpu=%s storage=%s",
|
|
2085
|
+
node,
|
|
2086
|
+
payload["template"],
|
|
2087
|
+
payload["ram_mib"],
|
|
2088
|
+
payload["cpus"],
|
|
2089
|
+
payload["storage"],
|
|
2090
|
+
)
|
|
2091
|
+
try:
|
|
2092
|
+
vmid = _allocate_vmid(proxmox)
|
|
2093
|
+
vmid, _ = _instantiate_container(proxmox, node, payload, vmid=vmid)
|
|
2094
|
+
except Exception:
|
|
2095
|
+
_release_container_reservation(reservation_id)
|
|
2096
|
+
if vmid is not None:
|
|
2097
|
+
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2098
|
+
raise
|
|
2099
|
+
payload["vmid"] = vmid
|
|
2100
|
+
payload["created_at"] = datetime.utcnow().isoformat() + "Z"
|
|
2101
|
+
payload["status"] = "creating"
|
|
2102
|
+
payload["device_id"] = device_id
|
|
2103
|
+
try:
|
|
2104
|
+
_write_container_record(vmid, payload, reservation_id=reservation_id)
|
|
2105
|
+
created_record = True
|
|
2106
|
+
except Exception:
|
|
2107
|
+
_release_container_reservation(reservation_id)
|
|
2108
|
+
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2109
|
+
raise
|
|
2110
|
+
return proxmox, node, vmid, payload
|
|
2111
|
+
|
|
2112
|
+
proxmox, _, vmid, payload_local = _run_lifecycle_step(
|
|
2113
|
+
"create_container",
|
|
2114
|
+
"Creating container",
|
|
2115
|
+
"Provisioning the LXC container…",
|
|
2116
|
+
"Container created.",
|
|
2117
|
+
_create_container,
|
|
2180
2118
|
)
|
|
2181
2119
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
payload["username"],
|
|
2185
|
-
payload["password"],
|
|
2186
|
-
payload["ssh_public_key"],
|
|
2187
|
-
steps=bootstrap_steps,
|
|
2188
|
-
progress_callback=_bootstrap_progress_callback,
|
|
2189
|
-
start_index=current_step_index,
|
|
2190
|
-
total_steps=total_steps,
|
|
2191
|
-
default_public_key=device_public_key if has_device_keypair else None,
|
|
2192
|
-
)
|
|
2193
|
-
current_step_index += len(bootstrap_steps)
|
|
2120
|
+
def _start_container_step():
|
|
2121
|
+
_start_container(proxmox, node, vmid)
|
|
2194
2122
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
"
|
|
2199
|
-
|
|
2200
|
-
|
|
2123
|
+
_run_lifecycle_step(
|
|
2124
|
+
"start_container",
|
|
2125
|
+
"Starting container",
|
|
2126
|
+
"Booting the container…",
|
|
2127
|
+
"Container startup completed.",
|
|
2128
|
+
_start_container_step,
|
|
2201
2129
|
)
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2130
|
+
_update_container_record(vmid, {"status": "running"})
|
|
2131
|
+
|
|
2132
|
+
def _bootstrap_progress_callback(
|
|
2133
|
+
step_index: int,
|
|
2134
|
+
total: int,
|
|
2135
|
+
step: Dict[str, Any],
|
|
2136
|
+
status: str,
|
|
2137
|
+
result: Optional[Dict[str, Any]],
|
|
2138
|
+
):
|
|
2139
|
+
label = step.get("display_name") or _friendly_step_label(step.get("name", "bootstrap"))
|
|
2140
|
+
error_summary = (result or {}).get("error_summary") or (result or {}).get("error")
|
|
2141
|
+
attempt = (result or {}).get("attempt")
|
|
2142
|
+
if status == "in_progress":
|
|
2143
|
+
message_text = f"{label} is running…"
|
|
2144
|
+
elif status == "completed":
|
|
2145
|
+
message_text = f"{label} completed."
|
|
2146
|
+
elif status == "retrying":
|
|
2147
|
+
attempt_desc = f" (attempt {attempt})" if attempt else ""
|
|
2148
|
+
message_text = f"{label} failed{attempt_desc}; retrying…"
|
|
2149
|
+
else:
|
|
2150
|
+
message_text = f"{label} failed"
|
|
2151
|
+
if error_summary:
|
|
2152
|
+
message_text += f": {error_summary}"
|
|
2153
|
+
details: Dict[str, Any] = {}
|
|
2154
|
+
if attempt:
|
|
2155
|
+
details["attempt"] = attempt
|
|
2156
|
+
if error_summary:
|
|
2157
|
+
details["error_summary"] = error_summary
|
|
2158
|
+
_emit_progress_event(
|
|
2159
|
+
self,
|
|
2160
|
+
step_index=step_index,
|
|
2161
|
+
total_steps=total,
|
|
2162
|
+
step_name=step.get("name", "bootstrap"),
|
|
2163
|
+
step_label=label,
|
|
2164
|
+
status=status,
|
|
2165
|
+
message=message_text,
|
|
2166
|
+
phase="bootstrap",
|
|
2167
|
+
request_id=request_id,
|
|
2168
|
+
details=details or None,
|
|
2169
|
+
on_behalf_of_device=device_id,
|
|
2170
|
+
)
|
|
2210
2171
|
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2172
|
+
public_key, steps = _bootstrap_portacode(
|
|
2173
|
+
vmid,
|
|
2174
|
+
payload_local["username"],
|
|
2175
|
+
payload_local["password"],
|
|
2176
|
+
payload_local["ssh_public_key"],
|
|
2177
|
+
steps=bootstrap_steps,
|
|
2178
|
+
progress_callback=_bootstrap_progress_callback,
|
|
2179
|
+
start_index=current_step_index,
|
|
2216
2180
|
total_steps=total_steps,
|
|
2217
|
-
|
|
2218
|
-
step_label=auth_label,
|
|
2219
|
-
status="in_progress",
|
|
2220
|
-
message="Notifying the server of the new device…",
|
|
2221
|
-
phase="service",
|
|
2222
|
-
request_id=request_id,
|
|
2223
|
-
on_behalf_of_device=device_id,
|
|
2224
|
-
)
|
|
2225
|
-
_emit_progress_event(
|
|
2226
|
-
self,
|
|
2227
|
-
step_index=service_start_index,
|
|
2228
|
-
total_steps=total_steps,
|
|
2229
|
-
step_name=auth_step_name,
|
|
2230
|
-
step_label=auth_label,
|
|
2231
|
-
status="completed",
|
|
2232
|
-
message="Authentication metadata recorded.",
|
|
2233
|
-
phase="service",
|
|
2234
|
-
request_id=request_id,
|
|
2235
|
-
on_behalf_of_device=device_id,
|
|
2181
|
+
default_public_key=device_public_key if has_device_keypair else None,
|
|
2236
2182
|
)
|
|
2183
|
+
current_step_index += len(bootstrap_steps)
|
|
2184
|
+
|
|
2185
|
+
service_installed = False
|
|
2186
|
+
if has_device_keypair:
|
|
2187
|
+
logger.info(
|
|
2188
|
+
"deploying dashboard-provided Portacode keypair (device_id=%s) into container %s",
|
|
2189
|
+
device_id,
|
|
2190
|
+
vmid,
|
|
2191
|
+
)
|
|
2192
|
+
_deploy_device_keypair(
|
|
2193
|
+
vmid,
|
|
2194
|
+
payload_local["username"],
|
|
2195
|
+
device_private_key,
|
|
2196
|
+
device_public_key,
|
|
2197
|
+
)
|
|
2198
|
+
service_installed = True
|
|
2199
|
+
service_start_index = current_step_index
|
|
2237
2200
|
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2201
|
+
auth_step_name = "setup_device_authentication"
|
|
2202
|
+
auth_label = "Setting up device authentication"
|
|
2203
|
+
_emit_progress_event(
|
|
2204
|
+
self,
|
|
2205
|
+
step_index=service_start_index,
|
|
2206
|
+
total_steps=total_steps,
|
|
2207
|
+
step_name=auth_step_name,
|
|
2208
|
+
step_label=auth_label,
|
|
2209
|
+
status="in_progress",
|
|
2210
|
+
message="Notifying the server of the new device…",
|
|
2211
|
+
phase="service",
|
|
2212
|
+
request_id=request_id,
|
|
2213
|
+
on_behalf_of_device=device_id,
|
|
2214
|
+
)
|
|
2215
|
+
_emit_progress_event(
|
|
2216
|
+
self,
|
|
2217
|
+
step_index=service_start_index,
|
|
2218
|
+
total_steps=total_steps,
|
|
2219
|
+
step_name=auth_step_name,
|
|
2220
|
+
step_label=auth_label,
|
|
2221
|
+
status="completed",
|
|
2222
|
+
message="Authentication metadata recorded.",
|
|
2223
|
+
phase="service",
|
|
2224
|
+
request_id=request_id,
|
|
2225
|
+
on_behalf_of_device=device_id,
|
|
2226
|
+
)
|
|
2252
2227
|
|
|
2253
|
-
|
|
2254
|
-
|
|
2228
|
+
install_step = service_start_index + 1
|
|
2229
|
+
install_label = "Launching Portacode service"
|
|
2230
|
+
_emit_progress_event(
|
|
2231
|
+
self,
|
|
2232
|
+
step_index=install_step,
|
|
2233
|
+
total_steps=total_steps,
|
|
2234
|
+
step_name="launch_portacode_service",
|
|
2235
|
+
step_label=install_label,
|
|
2236
|
+
status="in_progress",
|
|
2237
|
+
message="Running sudo portacode service install…",
|
|
2238
|
+
phase="service",
|
|
2239
|
+
request_id=request_id,
|
|
2240
|
+
on_behalf_of_device=device_id,
|
|
2241
|
+
)
|
|
2242
|
+
|
|
2243
|
+
cmd = _su_command(payload_local["username"], "sudo -S portacode service install")
|
|
2244
|
+
res = _run_pct(vmid, cmd, input_text=payload_local["password"] + "\n")
|
|
2245
|
+
|
|
2246
|
+
if res["returncode"] != 0:
|
|
2247
|
+
_emit_progress_event(
|
|
2248
|
+
self,
|
|
2249
|
+
step_index=install_step,
|
|
2250
|
+
total_steps=total_steps,
|
|
2251
|
+
step_name="launch_portacode_service",
|
|
2252
|
+
step_label=install_label,
|
|
2253
|
+
status="failed",
|
|
2254
|
+
message=f"{install_label} failed: {res.get('stderr') or res.get('stdout')}",
|
|
2255
|
+
phase="service",
|
|
2256
|
+
request_id=request_id,
|
|
2257
|
+
details={
|
|
2258
|
+
"stderr": res.get("stderr"),
|
|
2259
|
+
"stdout": res.get("stdout"),
|
|
2260
|
+
},
|
|
2261
|
+
on_behalf_of_device=device_id,
|
|
2262
|
+
)
|
|
2263
|
+
raise RuntimeError(res.get("stderr") or res.get("stdout") or "Service install failed")
|
|
2255
2264
|
|
|
2256
|
-
if res["returncode"] != 0:
|
|
2257
2265
|
_emit_progress_event(
|
|
2258
2266
|
self,
|
|
2259
2267
|
step_index=install_step,
|
|
2260
2268
|
total_steps=total_steps,
|
|
2261
2269
|
step_name="launch_portacode_service",
|
|
2262
2270
|
step_label=install_label,
|
|
2263
|
-
status="
|
|
2264
|
-
message=
|
|
2271
|
+
status="completed",
|
|
2272
|
+
message="Portacode service install finished.",
|
|
2265
2273
|
phase="service",
|
|
2266
2274
|
request_id=request_id,
|
|
2267
|
-
details={
|
|
2268
|
-
"stderr": res.get("stderr"),
|
|
2269
|
-
"stdout": res.get("stdout"),
|
|
2270
|
-
},
|
|
2271
2275
|
on_behalf_of_device=device_id,
|
|
2272
2276
|
)
|
|
2273
|
-
raise RuntimeError(res.get("stderr") or res.get("stdout") or "Service install failed")
|
|
2274
2277
|
|
|
2275
|
-
|
|
2278
|
+
logger.info(
|
|
2279
|
+
"create_proxmox_container: portacode service install completed inside ct %s", vmid
|
|
2280
|
+
)
|
|
2281
|
+
|
|
2282
|
+
current_step_index += 2
|
|
2283
|
+
|
|
2284
|
+
_emit_host_event(
|
|
2276
2285
|
self,
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
+
{
|
|
2287
|
+
"event": "proxmox_container_created",
|
|
2288
|
+
"success": True,
|
|
2289
|
+
"message": f"Container {vmid} is ready and Portacode key captured.",
|
|
2290
|
+
"ctid": str(vmid),
|
|
2291
|
+
"public_key": public_key,
|
|
2292
|
+
"container": {
|
|
2293
|
+
"vmid": vmid,
|
|
2294
|
+
"hostname": payload_local["hostname"],
|
|
2295
|
+
"template": payload_local["template"],
|
|
2296
|
+
"storage": payload_local["storage"],
|
|
2297
|
+
"disk_gib": payload_local["disk_gib"],
|
|
2298
|
+
"ram_mib": payload_local["ram_mib"],
|
|
2299
|
+
"cpus": payload_local["cpus"],
|
|
2300
|
+
},
|
|
2301
|
+
"setup_steps": steps,
|
|
2302
|
+
"device_id": device_id,
|
|
2303
|
+
"on_behalf_of_device": device_id,
|
|
2304
|
+
"service_installed": service_installed,
|
|
2305
|
+
"request_id": request_id,
|
|
2306
|
+
},
|
|
2286
2307
|
)
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2308
|
+
except Exception as exc:
|
|
2309
|
+
if reservation_id and not created_record:
|
|
2310
|
+
_release_container_reservation(reservation_id)
|
|
2311
|
+
if vmid is not None and proxmox and node:
|
|
2312
|
+
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2313
|
+
_remove_container_record(vmid)
|
|
2314
|
+
_emit_host_event(
|
|
2315
|
+
self,
|
|
2316
|
+
{
|
|
2317
|
+
"event": "error",
|
|
2318
|
+
"message": str(exc),
|
|
2319
|
+
"device_id": device_id,
|
|
2320
|
+
"request_id": request_id,
|
|
2321
|
+
},
|
|
2290
2322
|
)
|
|
2291
2323
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
"vmid": vmid,
|
|
2302
|
-
"hostname": payload["hostname"],
|
|
2303
|
-
"template": payload["template"],
|
|
2304
|
-
"storage": payload["storage"],
|
|
2305
|
-
"disk_gib": payload["disk_gib"],
|
|
2306
|
-
"ram_mib": payload["ram_mib"],
|
|
2307
|
-
"cpus": payload["cpus"],
|
|
2308
|
-
},
|
|
2309
|
-
"setup_steps": steps,
|
|
2310
|
-
"device_id": device_id,
|
|
2311
|
-
"on_behalf_of_device": device_id,
|
|
2312
|
-
"service_installed": service_installed,
|
|
2313
|
-
}
|
|
2314
|
-
except Exception:
|
|
2315
|
-
if reservation_id and not created_record:
|
|
2316
|
-
_release_container_reservation(reservation_id)
|
|
2317
|
-
if vmid is not None and proxmox and node:
|
|
2318
|
-
_cleanup_failed_container(proxmox, node, vmid, provisioning_id)
|
|
2319
|
-
_remove_container_record(vmid)
|
|
2320
|
-
raise
|
|
2324
|
+
threading.Thread(target=_provision_background, daemon=True).start()
|
|
2325
|
+
|
|
2326
|
+
return {
|
|
2327
|
+
"event": "proxmox_container_accepted",
|
|
2328
|
+
"success": True,
|
|
2329
|
+
"message": "Provisioning accepted; resources reserved.",
|
|
2330
|
+
"device_id": device_id,
|
|
2331
|
+
"request_id": request_id,
|
|
2332
|
+
}
|
|
2321
2333
|
|
|
2322
2334
|
|
|
2323
2335
|
class StartPortacodeServiceHandler(SyncHandler):
|
portacode/service.py
CHANGED
|
@@ -6,6 +6,7 @@ runs ``portacode connect`` automatically at login / boot.
|
|
|
6
6
|
Platforms implemented:
|
|
7
7
|
|
|
8
8
|
• Linux (systemd **user** service) – no root privileges required
|
|
9
|
+
• Linux (OpenRC service) – system-wide (e.g., Alpine)
|
|
9
10
|
• macOS (launchd LaunchAgent plist) – per-user
|
|
10
11
|
• Windows (Task Scheduler *ONLOGON* task) – highest privilege, current user
|
|
11
12
|
|
|
@@ -26,6 +27,7 @@ import os
|
|
|
26
27
|
from typing import Protocol
|
|
27
28
|
import shutil
|
|
28
29
|
import pwd
|
|
30
|
+
import tempfile
|
|
29
31
|
|
|
30
32
|
__all__ = [
|
|
31
33
|
"ServiceManager",
|
|
@@ -199,6 +201,107 @@ class _SystemdUserService:
|
|
|
199
201
|
return (status or "") + "\n--- recent logs ---\n" + (journal or "<no logs>")
|
|
200
202
|
|
|
201
203
|
|
|
204
|
+
# ---------------------------------------------------------------------------
|
|
205
|
+
# Linux – OpenRC implementation (e.g., Alpine)
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
class _OpenRCService:
|
|
208
|
+
NAME = "portacode"
|
|
209
|
+
|
|
210
|
+
def __init__(self) -> None:
|
|
211
|
+
self.init_path = Path("/etc/init.d") / self.NAME
|
|
212
|
+
self.user = os.environ.get("SUDO_USER") or os.environ.get("USER") or os.getlogin()
|
|
213
|
+
try:
|
|
214
|
+
self.home = Path(pwd.getpwnam(self.user).pw_dir)
|
|
215
|
+
except KeyError:
|
|
216
|
+
self.home = Path("/root") if self.user == "root" else Path(f"/home/{self.user}")
|
|
217
|
+
self.python = shutil.which("python3") or sys.executable
|
|
218
|
+
self.log_dir = Path("/var/log/portacode")
|
|
219
|
+
self.log_path = self.log_dir / "connect.log"
|
|
220
|
+
|
|
221
|
+
def _run(self, *args: str) -> subprocess.CompletedProcess[str]:
|
|
222
|
+
prefix = ["sudo"] if os.geteuid() != 0 else []
|
|
223
|
+
cmd = [*prefix, *args]
|
|
224
|
+
return subprocess.run(cmd, text=True, capture_output=True)
|
|
225
|
+
|
|
226
|
+
def _write_init_script(self) -> None:
|
|
227
|
+
script = textwrap.dedent(f"""
|
|
228
|
+
#!/sbin/openrc-run
|
|
229
|
+
description="Portacode persistent connection"
|
|
230
|
+
|
|
231
|
+
command="{self.python}"
|
|
232
|
+
command_args="-m portacode connect --non-interactive"
|
|
233
|
+
command_user="{self.user}"
|
|
234
|
+
command_background="yes"
|
|
235
|
+
pidfile="/run/portacode.pid"
|
|
236
|
+
directory="{self.home}"
|
|
237
|
+
output_log="{self.log_path}"
|
|
238
|
+
error_log="{self.log_path}"
|
|
239
|
+
|
|
240
|
+
depend() {{
|
|
241
|
+
need net
|
|
242
|
+
}}
|
|
243
|
+
|
|
244
|
+
start_pre() {{
|
|
245
|
+
checkpath --directory --mode 0755 /var/log/portacode
|
|
246
|
+
}}
|
|
247
|
+
""").lstrip()
|
|
248
|
+
|
|
249
|
+
tmp_path = Path(tempfile.gettempdir()) / f"portacode-init-{os.getpid()}"
|
|
250
|
+
tmp_path.write_text(script)
|
|
251
|
+
if os.geteuid() != 0:
|
|
252
|
+
self._run("install", "-m", "755", str(tmp_path), str(self.init_path))
|
|
253
|
+
else:
|
|
254
|
+
self.init_path.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
shutil.copyfile(tmp_path, self.init_path)
|
|
256
|
+
self.init_path.chmod(0o755)
|
|
257
|
+
try:
|
|
258
|
+
tmp_path.unlink()
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
def install(self) -> None:
|
|
263
|
+
self._write_init_script()
|
|
264
|
+
self._run("rc-update", "add", self.NAME, "default")
|
|
265
|
+
self._run("rc-service", self.NAME, "start")
|
|
266
|
+
|
|
267
|
+
def uninstall(self) -> None:
|
|
268
|
+
self._run("rc-service", self.NAME, "stop")
|
|
269
|
+
self._run("rc-update", "del", self.NAME, "default")
|
|
270
|
+
if self.init_path.exists():
|
|
271
|
+
if os.geteuid() != 0:
|
|
272
|
+
self._run("rm", "-f", str(self.init_path))
|
|
273
|
+
else:
|
|
274
|
+
self.init_path.unlink()
|
|
275
|
+
|
|
276
|
+
def start(self) -> None:
|
|
277
|
+
self._run("rc-service", self.NAME, "start")
|
|
278
|
+
|
|
279
|
+
def stop(self) -> None:
|
|
280
|
+
self._run("rc-service", self.NAME, "stop")
|
|
281
|
+
|
|
282
|
+
def status(self) -> str:
|
|
283
|
+
res = self._run("rc-service", self.NAME, "status")
|
|
284
|
+
out = (res.stdout or res.stderr or "").strip().lower()
|
|
285
|
+
if "started" in out or "running" in out:
|
|
286
|
+
return "running"
|
|
287
|
+
if "stopped" in out:
|
|
288
|
+
return "stopped"
|
|
289
|
+
return out or "unknown"
|
|
290
|
+
|
|
291
|
+
def status_verbose(self) -> str:
|
|
292
|
+
res = self._run("rc-service", self.NAME, "status")
|
|
293
|
+
status = res.stdout or res.stderr or ""
|
|
294
|
+
log_tail = "<no logs>"
|
|
295
|
+
try:
|
|
296
|
+
if self.log_path.exists():
|
|
297
|
+
with self.log_path.open("r", encoding="utf-8", errors="ignore") as fh:
|
|
298
|
+
lines = fh.readlines()
|
|
299
|
+
log_tail = "".join(lines[-20:]) or "<no logs>"
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
return (status or "").rstrip() + "\n--- recent logs ---\n" + log_tail
|
|
303
|
+
|
|
304
|
+
|
|
202
305
|
# ---------------------------------------------------------------------------
|
|
203
306
|
# macOS – launchd (LaunchAgent) implementation
|
|
204
307
|
# ---------------------------------------------------------------------------
|
|
@@ -422,9 +525,13 @@ class _WindowsTask:
|
|
|
422
525
|
def get_manager(system_mode: bool = False) -> ServiceManager:
|
|
423
526
|
system = platform.system().lower()
|
|
424
527
|
if system == "linux":
|
|
425
|
-
|
|
528
|
+
if shutil.which("systemctl"):
|
|
529
|
+
return _SystemdUserService(system_mode=system_mode) # type: ignore[return-value]
|
|
530
|
+
if shutil.which("rc-service") or Path("/sbin/openrc").exists():
|
|
531
|
+
return _OpenRCService() # type: ignore[return-value]
|
|
532
|
+
raise RuntimeError("Unsupported Linux init system (no systemctl or rc-service found)")
|
|
426
533
|
if system == "darwin":
|
|
427
534
|
return _LaunchdService() # type: ignore[return-value]
|
|
428
535
|
if system.startswith("windows") or system == "windows":
|
|
429
536
|
return _WindowsTask() # type: ignore[return-value]
|
|
430
|
-
raise RuntimeError(f"Unsupported platform: {system}")
|
|
537
|
+
raise RuntimeError(f"Unsupported platform: {system}")
|
|
@@ -1,13 +1,13 @@
|
|
|
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=vNmWumwOSakJSXsv8x_EdHyiL23srCSUrbOcexyXbKc,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
|
|
8
8
|
portacode/logging_categories.py,sha256=9m-BYrjyHh1vjZYBQT4JhAh6b_oYUhIWayO-noH1cSE,5063
|
|
9
9
|
portacode/pairing.py,sha256=OzSuc0GhrknrDrny4aBU6IUnmKzRDTtocuDpyaVnyrs,3116
|
|
10
|
-
portacode/service.py,sha256=
|
|
10
|
+
portacode/service.py,sha256=RkXWeTDT0t3rJ3szt_qlKR2P_SXIMUcFSl70BfIJpjo,20651
|
|
11
11
|
portacode/connection/README.md,sha256=f9rbuIEKa7cTm9C98rCiBbEtbiIXQU11esGSNhSMiJg,883
|
|
12
12
|
portacode/connection/__init__.py,sha256=atqcVGkViIEd7pRa6cP2do07RJOM0UWpbnz5zXjGktU,250
|
|
13
13
|
portacode/connection/client.py,sha256=jtLb9_YufqPkzi9t8VQH3iz_JEMisbtY6a8L9U5weiU,14181
|
|
@@ -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=dxn6m7buw_GHbhkhWX6J7gDpqCpXmmBxfqSb-6aQiPg,98542
|
|
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.17.
|
|
68
|
+
portacode-1.4.17.dev9.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.17.
|
|
95
|
-
portacode-1.4.17.
|
|
96
|
-
portacode-1.4.17.
|
|
97
|
-
portacode-1.4.17.
|
|
98
|
-
portacode-1.4.17.
|
|
94
|
+
portacode-1.4.17.dev9.dist-info/METADATA,sha256=unCc_gEcNxw9Xt0mzn9Q7uPd9ENflwl8_o_LBb_uH4E,13051
|
|
95
|
+
portacode-1.4.17.dev9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
portacode-1.4.17.dev9.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.17.dev9.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.17.dev9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|