portacode 1.4.15.dev5__py3-none-any.whl → 1.4.15.dev7__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.15.dev5'
32
- __version_tuple__ = version_tuple = (1, 4, 15, 'dev5')
31
+ __version__ = version = '1.4.15.dev7'
32
+ __version_tuple__ = version_tuple = (1, 4, 15, 'dev7')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import json
7
7
  import logging
8
+ import math
8
9
  import os
9
10
  import secrets
10
11
  import shlex
@@ -436,48 +437,119 @@ def _friendly_step_label(step_name: str) -> str:
436
437
  return normalized.capitalize()
437
438
 
438
439
 
440
+ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
441
+ "apt": {
442
+ "update_cmd": "apt-get update -y",
443
+ "update_step_name": "apt_update",
444
+ "install_cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
445
+ "install_step_name": "install_deps",
446
+ "update_retries": 4,
447
+ "install_retries": 5,
448
+ },
449
+ "dnf": {
450
+ "update_cmd": "dnf check-update || true",
451
+ "update_step_name": "dnf_update",
452
+ "install_cmd": "dnf install -y python3 python3-pip sudo",
453
+ "install_step_name": "install_deps",
454
+ "update_retries": 3,
455
+ "install_retries": 5,
456
+ },
457
+ "yum": {
458
+ "update_cmd": "yum makecache",
459
+ "update_step_name": "yum_update",
460
+ "install_cmd": "yum install -y python3 python3-pip sudo",
461
+ "install_step_name": "install_deps",
462
+ "update_retries": 3,
463
+ "install_retries": 5,
464
+ },
465
+ "apk": {
466
+ "update_cmd": "apk update",
467
+ "update_step_name": "apk_update",
468
+ "install_cmd": "apk add --no-cache python3 py3-pip sudo",
469
+ "install_step_name": "install_deps",
470
+ "update_retries": 3,
471
+ "install_retries": 5,
472
+ },
473
+ "pacman": {
474
+ "update_cmd": "pacman -Sy --noconfirm",
475
+ "update_step_name": "pacman_update",
476
+ "install_cmd": "pacman -S --noconfirm python python-pip sudo",
477
+ "install_step_name": "install_deps",
478
+ "update_retries": 3,
479
+ "install_retries": 5,
480
+ },
481
+ "zypper": {
482
+ "update_cmd": "zypper refresh",
483
+ "update_step_name": "zypper_update",
484
+ "install_cmd": "zypper install -y python3 python3-pip sudo",
485
+ "install_step_name": "install_deps",
486
+ "update_retries": 3,
487
+ "install_retries": 5,
488
+ },
489
+ }
490
+
491
+ _UPDATE_RETRY_ON = [
492
+ "Temporary failure resolving",
493
+ "Could not resolve",
494
+ "Failed to fetch",
495
+ ]
496
+
497
+ _INSTALL_RETRY_ON = [
498
+ "lock-frontend",
499
+ "Unable to acquire the dpkg frontend lock",
500
+ "Temporary failure resolving",
501
+ "Could not resolve",
502
+ "Failed to fetch",
503
+ ]
504
+
505
+
439
506
  def _build_bootstrap_steps(
440
507
  user: str,
441
508
  password: str,
442
509
  ssh_key: str,
443
510
  include_portacode_connect: bool = True,
511
+ package_manager: str = "apt",
444
512
  ) -> List[Dict[str, Any]]:
445
- steps = [
446
- {
447
- "name": "apt_update",
448
- "cmd": "apt-get update -y",
449
- "retries": 4,
450
- "retry_delay_s": 5,
451
- "retry_on": [
452
- "Temporary failure resolving",
453
- "Could not resolve",
454
- "Failed to fetch",
455
- ],
456
- },
457
- {
458
- "name": "install_deps",
459
- "cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
460
- "retries": 5,
461
- "retry_delay_s": 5,
462
- "retry_on": [
463
- "lock-frontend",
464
- "Unable to acquire the dpkg frontend lock",
465
- "Temporary failure resolving",
466
- "Could not resolve",
467
- "Failed to fetch",
468
- ],
469
- },
470
- {"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
471
- {"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
472
- ]
513
+ profile = _PACKAGE_MANAGER_PROFILES.get(package_manager, _PACKAGE_MANAGER_PROFILES["apt"])
514
+ steps: List[Dict[str, Any]] = []
515
+ update_cmd = profile.get("update_cmd")
516
+ if update_cmd:
517
+ steps.append(
518
+ {
519
+ "name": profile.get("update_step_name", "package_update"),
520
+ "cmd": update_cmd,
521
+ "retries": profile.get("update_retries", 3),
522
+ "retry_delay_s": 5,
523
+ "retry_on": _UPDATE_RETRY_ON,
524
+ }
525
+ )
526
+ install_cmd = profile.get("install_cmd")
527
+ if install_cmd:
528
+ steps.append(
529
+ {
530
+ "name": profile.get("install_step_name", "install_deps"),
531
+ "cmd": install_cmd,
532
+ "retries": profile.get("install_retries", 5),
533
+ "retry_delay_s": 5,
534
+ "retry_on": _INSTALL_RETRY_ON,
535
+ }
536
+ )
537
+ steps.extend(
538
+ [
539
+ {"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
540
+ {"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
541
+ ]
542
+ )
473
543
  if password:
474
544
  steps.append({"name": "set_password", "cmd": f"echo '{user}:{password}' | chpasswd", "retries": 0})
475
545
  if ssh_key:
476
- steps.append({
477
- "name": "add_ssh_key",
478
- "cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
479
- "retries": 0,
480
- })
546
+ steps.append(
547
+ {
548
+ "name": "add_ssh_key",
549
+ "cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
550
+ "retries": 0,
551
+ }
552
+ )
481
553
  steps.extend(
482
554
  [
483
555
  {"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
@@ -489,6 +561,24 @@ def _build_bootstrap_steps(
489
561
  return steps
490
562
 
491
563
 
564
+ def _detect_package_manager(vmid: int) -> str:
565
+ candidates = [
566
+ ("apt", "apt-get"),
567
+ ("dnf", "dnf"),
568
+ ("yum", "yum"),
569
+ ("apk", "apk"),
570
+ ("pacman", "pacman"),
571
+ ("zypper", "zypper"),
572
+ ]
573
+ for name, binary in candidates:
574
+ res = _run_pct(vmid, f"command -v {binary} >/dev/null 2>&1")
575
+ if res.get("returncode") == 0:
576
+ logger.debug("Detected package manager %s inside container %s", name, vmid)
577
+ return name
578
+ logger.warning("Unable to detect package manager inside container %s; defaulting to apt", vmid)
579
+ return "apt"
580
+
581
+
492
582
  def _get_storage_type(storages: Iterable[Dict[str, Any]], storage_name: str) -> str:
493
583
  for entry in storages:
494
584
  if entry.get("storage") == storage_name:
@@ -966,7 +1056,16 @@ def _bootstrap_portacode(
966
1056
  total_steps: Optional[int] = None,
967
1057
  default_public_key: Optional[str] = None,
968
1058
  ) -> Tuple[str, List[Dict[str, Any]]]:
969
- actual_steps = steps if steps is not None else _build_bootstrap_steps(user, password, ssh_key)
1059
+ if steps is not None:
1060
+ actual_steps = steps
1061
+ else:
1062
+ package_manager = _detect_package_manager(vmid)
1063
+ actual_steps = _build_bootstrap_steps(
1064
+ user,
1065
+ password,
1066
+ ssh_key,
1067
+ package_manager=package_manager,
1068
+ )
970
1069
  results, ok = _run_setup_steps(
971
1070
  vmid,
972
1071
  actual_steps,
@@ -1121,7 +1220,7 @@ def _instantiate_container(proxmox: Any, node: str, payload: Dict[str, Any]) ->
1121
1220
  memory=int(payload["ram_mib"]),
1122
1221
  swap=int(payload.get("swap_mb", 0)),
1123
1222
  cores=max(int(payload.get("cores", 1)), 1),
1124
- cpuunits=int(payload.get("cpuunits", 256)),
1223
+ cpulimit=float(payload.get("cpulimit", payload.get("cpus", 1))),
1125
1224
  net0=payload["net0"],
1126
1225
  unprivileged=int(payload.get("unprivileged", 1)),
1127
1226
  description=payload.get("description", MANAGED_MARKER),
@@ -1144,7 +1243,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
1144
1243
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1145
1244
  logger.info("create_proxmox_container command received")
1146
1245
  request_id = message.get("request_id")
1147
- device_id = (message.get("device_id") or "").strip()
1246
+ raw_device_id = message.get("device_id")
1247
+ device_id = str(raw_device_id or "").strip()
1148
1248
  if not device_id:
1149
1249
  raise ValueError("device_id is required to create a container")
1150
1250
  device_public_key = (message.get("device_public_key") or "").strip()
@@ -1234,7 +1334,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
1234
1334
  proxmox = _connect_proxmox(config)
1235
1335
  node = config.get("node") or DEFAULT_NODE_NAME
1236
1336
  payload = _build_container_payload(message, config)
1237
- payload["cpuunits"] = max(int(payload["cpus"] * 1024), 10)
1337
+ payload["cpulimit"] = float(payload["cpus"])
1338
+ payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1238
1339
  payload["memory"] = int(payload["ram_mib"])
1239
1340
  payload["node"] = node
1240
1341
  logger.debug(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.15.dev5
3
+ Version: 1.4.15.dev7
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=Ni1Cvzx1sHOC7jxKrut8PDiKVmTovT_jEyNZskRL3bA,719
4
+ portacode/_version.py,sha256=Xx0Po1xBg9CoeugBu19tIDntOjkrKAyq9NOKq8YEY_w,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
@@ -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=Zh_m4YKsup2kQRvb5dEOm1GHKmwK7o5Bnp21d-Xx8Kc,62329
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=GXPyDcdwgtTfQyw7msAARAmHGun-p8GJOdtV0LL5iuQ,65513
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.dev5.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.15.dev7.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.dev5.dist-info/METADATA,sha256=VZMdZRbgpr73l7eLNkUoOnobz1IAK7Vd-3_cR8u-JPA,13051
95
- portacode-1.4.15.dev5.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
- portacode-1.4.15.dev5.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.15.dev5.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.15.dev5.dist-info/RECORD,,
94
+ portacode-1.4.15.dev7.dist-info/METADATA,sha256=YHefdl_WhByeUv5ELxOcmpCdhID0lj04wJTl57e3O8A,13051
95
+ portacode-1.4.15.dev7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.15.dev7.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.15.dev7.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.15.dev7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5