portacode 1.4.15.dev15__py3-none-any.whl → 1.4.15.dev23__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.dev15'
32
- __version_tuple__ = version_tuple = (1, 4, 15, 'dev15')
31
+ __version__ = version = '1.4.15.dev23'
32
+ __version_tuple__ = version_tuple = (1, 4, 15, 'dev23')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -496,6 +496,16 @@ def _friendly_step_label(step_name: str) -> str:
496
496
  return normalized.capitalize()
497
497
 
498
498
 
499
+ _NETWORK_WAIT_CMD = (
500
+ "count=0; "
501
+ "while [ \"$count\" -lt 20 ]; do "
502
+ " if command -v ip >/dev/null 2>&1 && ip route get 1.1.1.1 >/dev/null 2>&1; then break; fi; "
503
+ " if [ -f /proc/net/route ] && grep -q '^00000000' /proc/net/route >/dev/null 2>&1; then break; fi; "
504
+ " sleep 1; "
505
+ " count=$((count+1)); "
506
+ "done"
507
+ )
508
+
499
509
  _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
500
510
  "apt": {
501
511
  "update_cmd": "apt-get update -y",
@@ -524,7 +534,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
524
534
  "apk": {
525
535
  "update_cmd": "apk update",
526
536
  "update_step_name": "apk_update",
527
- "install_cmd": "apk add --no-cache python3 py3-pip sudo",
537
+ "install_cmd": "apk add --no-cache python3 py3-pip sudo shadow",
528
538
  "install_step_name": "install_deps",
529
539
  "update_retries": 3,
530
540
  "install_retries": 5,
@@ -570,7 +580,9 @@ def _build_bootstrap_steps(
570
580
  package_manager: str = "apt",
571
581
  ) -> List[Dict[str, Any]]:
572
582
  profile = _PACKAGE_MANAGER_PROFILES.get(package_manager, _PACKAGE_MANAGER_PROFILES["apt"])
573
- steps: List[Dict[str, Any]] = []
583
+ steps: List[Dict[str, Any]] = [
584
+ {"name": "wait_for_network", "cmd": _NETWORK_WAIT_CMD, "retries": 0},
585
+ ]
574
586
  update_cmd = profile.get("update_cmd")
575
587
  if update_cmd:
576
588
  steps.append(
@@ -596,7 +608,36 @@ def _build_bootstrap_steps(
596
608
  steps.extend(
597
609
  [
598
610
  {"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
599
- {"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
611
+ {
612
+ "name": "add_sudo",
613
+ "cmd": (
614
+ f"if command -v usermod >/dev/null 2>&1; then "
615
+ " if ! getent group sudo >/dev/null 2>&1; then "
616
+ " if command -v groupadd >/dev/null 2>&1; then "
617
+ " groupadd sudo >/dev/null 2>&1 || true; "
618
+ " fi; "
619
+ " fi; "
620
+ f" usermod -aG sudo {user}; "
621
+ "else "
622
+ " for grp in wheel sudo; do "
623
+ " if ! getent group \"$grp\" >/dev/null 2>&1 && command -v groupadd >/dev/null 2>&1; then "
624
+ " groupadd \"$grp\" >/dev/null 2>&1 || true; "
625
+ " fi; "
626
+ " addgroup \"$grp\" >/dev/null 2>&1 || true; "
627
+ f" addgroup {user} \"$grp\" >/dev/null 2>&1 || true; "
628
+ " done; "
629
+ "fi"
630
+ ),
631
+ "retries": 0,
632
+ },
633
+ {
634
+ "name": "add_sudoers",
635
+ "cmd": (
636
+ f"printf '%s ALL=(ALL) NOPASSWD:ALL\n' {shlex.quote(user)} >/etc/sudoers.d/portacode && "
637
+ "chmod 0440 /etc/sudoers.d/portacode"
638
+ ),
639
+ "retries": 0,
640
+ },
600
641
  ]
601
642
  )
602
643
  if password:
@@ -846,7 +887,8 @@ def _connect_proxmox(config: Dict[str, Any]) -> Any:
846
887
 
847
888
 
848
889
  def _run_pct(vmid: int, cmd: str, input_text: Optional[str] = None) -> Dict[str, Any]:
849
- full = ["pct", "exec", str(vmid), "--", "bash", "-lc", cmd]
890
+ shell = "/bin/sh"
891
+ full = ["pct", "exec", str(vmid), "--", shell, "-c", cmd]
850
892
  start = time.time()
851
893
  proc = subprocess.run(full, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input_text)
852
894
  return {
@@ -858,6 +900,10 @@ def _run_pct(vmid: int, cmd: str, input_text: Optional[str] = None) -> Dict[str,
858
900
  }
859
901
 
860
902
 
903
+ def _su_command(user: str, command: str) -> str:
904
+ return f"su - {user} -s /bin/sh -c {shlex.quote(command)}"
905
+
906
+
861
907
  def _run_pct_check(vmid: int, cmd: str) -> Dict[str, Any]:
862
908
  res = _run_pct(vmid, cmd)
863
909
  if res["returncode"] != 0:
@@ -917,7 +963,7 @@ def _push_bytes_to_container(
917
963
 
918
964
 
919
965
  def _resolve_portacode_key_dir(vmid: int, user: str) -> str:
920
- data_dir_cmd = f"su - {user} -c 'echo -n ${{XDG_DATA_HOME:-$HOME/.local/share}}'"
966
+ data_dir_cmd = _su_command(user, "echo -n ${XDG_DATA_HOME:-$HOME/.local/share}")
921
967
  data_home = _run_pct_check(vmid, data_dir_cmd)["stdout"].strip()
922
968
  portacode_dir = f"{data_home}/portacode"
923
969
  _run_pct_exec_check(vmid, ["mkdir", "-p", portacode_dir])
@@ -934,18 +980,19 @@ def _deploy_device_keypair(vmid: int, user: str, private_key: str, public_key: s
934
980
 
935
981
 
936
982
  def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -> Dict[str, Any]:
937
- cmd = ["pct", "exec", str(vmid), "--", "bash", "-lc", f"su - {user} -c 'portacode connect'"]
983
+ su_connect_cmd = _su_command(user, "portacode connect")
984
+ cmd = ["pct", "exec", str(vmid), "--", "/bin/sh", "-c", su_connect_cmd]
938
985
  proc = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
939
986
  start = time.time()
940
987
 
941
- data_dir_cmd = f"su - {user} -c 'echo -n ${{XDG_DATA_HOME:-$HOME/.local/share}}'"
988
+ data_dir_cmd = _su_command(user, "echo -n ${XDG_DATA_HOME:-$HOME/.local/share}")
942
989
  data_dir = _run_pct_check(vmid, data_dir_cmd)["stdout"].strip()
943
990
  key_dir = f"{data_dir}/portacode/keys"
944
991
  pub_path = f"{key_dir}/id_portacode.pub"
945
992
  priv_path = f"{key_dir}/id_portacode"
946
993
 
947
994
  def file_size(path: str) -> Optional[int]:
948
- stat_cmd = f"su - {user} -c 'test -s {path} && stat -c %s {path}'"
995
+ stat_cmd = _su_command(user, f"test -s {path} && stat -c %s {path}")
949
996
  res = _run_pct(vmid, stat_cmd)
950
997
  if res["returncode"] != 0:
951
998
  return None
@@ -1003,7 +1050,7 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
1003
1050
  final_pub = file_size(pub_path)
1004
1051
  final_priv = file_size(priv_path)
1005
1052
  if final_pub and final_priv:
1006
- key_res = _run_pct(vmid, f"su - {user} -c 'cat {pub_path}'")
1053
+ key_res = _run_pct(vmid, _su_command(user, f"cat {pub_path}"))
1007
1054
  if not process_exited:
1008
1055
  proc.terminate()
1009
1056
  try:
@@ -1043,7 +1090,7 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
1043
1090
  except subprocess.TimeoutExpired:
1044
1091
  proc.kill()
1045
1092
 
1046
- key_res = _run_pct(vmid, f"su - {user} -c 'cat {pub_path}'")
1093
+ key_res = _run_pct(vmid, _su_command(user, f"cat {pub_path}"))
1047
1094
  return {
1048
1095
  "ok": True,
1049
1096
  "public_key": key_res["stdout"].strip(),
@@ -1586,7 +1633,7 @@ class CreateProxmoxContainerHandler(SyncHandler):
1586
1633
  on_behalf_of_device=device_id,
1587
1634
  )
1588
1635
 
1589
- cmd = f"su - {payload['username']} -c 'sudo -S portacode service install'"
1636
+ cmd = _su_command(payload["username"], "sudo -S portacode service install")
1590
1637
  res = _run_pct(vmid, cmd, input_text=payload["password"] + "\n")
1591
1638
 
1592
1639
  if res["returncode"] != 0:
@@ -1718,7 +1765,7 @@ class StartPortacodeServiceHandler(SyncHandler):
1718
1765
  on_behalf_of_device=on_behalf_of_device,
1719
1766
  )
1720
1767
 
1721
- cmd = f"su - {user} -c 'sudo -S portacode service install'"
1768
+ cmd = _su_command(user, "sudo -S portacode service install")
1722
1769
  res = _run_pct(vmid, cmd, input_text=password + "\n")
1723
1770
 
1724
1771
  if res["returncode"] != 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.15.dev15
3
+ Version: 1.4.15.dev23
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=0X-Wp9VmwXeZPPl4KjWDu4REXY5P2jnbevaNhxIBWL0,721
4
+ portacode/_version.py,sha256=8YGMPC5_rqCpJnjJF3sLM5flS3H0d8do1siDivvmwVs,721
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=MIkDCRkc-Tkxm4FdvCBbiGcXKYKp6OUwtLtKHoOPF-I,69295
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=cAueQb_D3COmPI84LfI1bqHOcgt02BOgho_pCRGeLWE,71132
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.dev15.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.15.dev23.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.dev15.dist-info/METADATA,sha256=XJ2f3lsNMucCNlgkAJLKlnu8TYX1_eaSzKWCJnB-2Ik,13052
95
- portacode-1.4.15.dev15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.15.dev15.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.15.dev15.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.15.dev15.dist-info/RECORD,,
94
+ portacode-1.4.15.dev23.dist-info/METADATA,sha256=1pp2pJiX9YjuOBghWO4mYN7fkpK0dqJSv6CdFMgbwOQ,13052
95
+ portacode-1.4.15.dev23.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.15.dev23.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.15.dev23.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.15.dev23.dist-info/RECORD,,