portacode 1.4.17.dev8__py3-none-any.whl → 1.4.17.dev10__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.17.dev8'
32
- __version_tuple__ = version_tuple = (1, 4, 17, 'dev8')
31
+ __version__ = version = '1.4.17.dev10'
32
+ __version_tuple__ = version_tuple = (1, 4, 17, 'dev10')
33
33
 
34
34
  __commit_id__ = commit_id = None
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,129 @@ 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.wrapper_path = Path("/usr/local/share/portacode/connect_service.sh")
213
+ self.user = os.environ.get("SUDO_USER") or os.environ.get("USER") or os.getlogin()
214
+ try:
215
+ self.home = Path(pwd.getpwnam(self.user).pw_dir)
216
+ except KeyError:
217
+ self.home = Path("/root") if self.user == "root" else Path(f"/home/{self.user}")
218
+ self.python = shutil.which("python3") or sys.executable
219
+ self.log_dir = Path("/var/log/portacode")
220
+ self.log_path = self.log_dir / "connect.log"
221
+
222
+ def _run(self, *args: str) -> subprocess.CompletedProcess[str]:
223
+ prefix = ["sudo"] if os.geteuid() != 0 else []
224
+ cmd = [*prefix, *args]
225
+ return subprocess.run(cmd, text=True, capture_output=True)
226
+
227
+ def _write_init_script(self) -> None:
228
+ self._write_wrapper_script()
229
+ script = textwrap.dedent(f"""
230
+ #!/sbin/openrc-run
231
+ description="Portacode persistent connection"
232
+
233
+ command="{self.wrapper_path}"
234
+ command_user="{self.user}"
235
+ command_background="yes"
236
+ pidfile="/run/portacode.pid"
237
+ directory="{self.home}"
238
+
239
+ depend() {{
240
+ need net
241
+ }}
242
+
243
+ start_pre() {{
244
+ checkpath --directory --mode 0755 /var/log/portacode
245
+ checkpath --directory --mode 0755 /usr/local/share/portacode
246
+ touch "{self.log_path}"
247
+ chown {self.user} "{self.log_path}"
248
+ chown {self.user} /var/log/portacode
249
+ }}
250
+ """).lstrip()
251
+
252
+ tmp_path = Path(tempfile.gettempdir()) / f"portacode-init-{os.getpid()}"
253
+ tmp_path.write_text(script)
254
+ if os.geteuid() != 0:
255
+ self._run("install", "-m", "755", str(tmp_path), str(self.init_path))
256
+ else:
257
+ self.init_path.parent.mkdir(parents=True, exist_ok=True)
258
+ shutil.copyfile(tmp_path, self.init_path)
259
+ self.init_path.chmod(0o755)
260
+ try:
261
+ tmp_path.unlink()
262
+ except Exception:
263
+ pass
264
+
265
+ def _write_wrapper_script(self) -> None:
266
+ script = textwrap.dedent(f"""
267
+ #!/bin/sh
268
+ cd "{self.home}"
269
+ exec "{self.python}" -m portacode connect --non-interactive >> "{self.log_path}" 2>&1
270
+ """).lstrip()
271
+ tmp_path = Path(tempfile.gettempdir()) / f"portacode-wrapper-{os.getpid()}"
272
+ tmp_path.write_text(script)
273
+ if os.geteuid() != 0:
274
+ self._run("install", "-m", "755", str(tmp_path), str(self.wrapper_path))
275
+ else:
276
+ self.wrapper_path.parent.mkdir(parents=True, exist_ok=True)
277
+ shutil.copyfile(tmp_path, self.wrapper_path)
278
+ self.wrapper_path.chmod(0o755)
279
+ try:
280
+ tmp_path.unlink()
281
+ except Exception:
282
+ pass
283
+
284
+ def install(self) -> None:
285
+ self._write_init_script()
286
+ self._run("rc-update", "add", self.NAME, "default")
287
+ self._run("rc-service", self.NAME, "start")
288
+
289
+ def uninstall(self) -> None:
290
+ self._run("rc-service", self.NAME, "stop")
291
+ self._run("rc-update", "del", self.NAME, "default")
292
+ if self.init_path.exists():
293
+ if os.geteuid() != 0:
294
+ self._run("rm", "-f", str(self.init_path))
295
+ else:
296
+ self.init_path.unlink()
297
+
298
+ def start(self) -> None:
299
+ self._run("rc-service", self.NAME, "start")
300
+
301
+ def stop(self) -> None:
302
+ self._run("rc-service", self.NAME, "stop")
303
+
304
+ def status(self) -> str:
305
+ res = self._run("rc-service", self.NAME, "status")
306
+ out = (res.stdout or res.stderr or "").strip().lower()
307
+ if "started" in out or "running" in out:
308
+ return "running"
309
+ if "stopped" in out:
310
+ return "stopped"
311
+ return out or "unknown"
312
+
313
+ def status_verbose(self) -> str:
314
+ res = self._run("rc-service", self.NAME, "status")
315
+ status = res.stdout or res.stderr or ""
316
+ log_tail = "<no logs>"
317
+ try:
318
+ if self.log_path.exists():
319
+ with self.log_path.open("r", encoding="utf-8", errors="ignore") as fh:
320
+ lines = fh.readlines()
321
+ log_tail = "".join(lines[-20:]) or "<no logs>"
322
+ except Exception:
323
+ pass
324
+ return (status or "").rstrip() + "\n--- recent logs ---\n" + log_tail
325
+
326
+
202
327
  # ---------------------------------------------------------------------------
203
328
  # macOS – launchd (LaunchAgent) implementation
204
329
  # ---------------------------------------------------------------------------
@@ -422,9 +547,13 @@ class _WindowsTask:
422
547
  def get_manager(system_mode: bool = False) -> ServiceManager:
423
548
  system = platform.system().lower()
424
549
  if system == "linux":
425
- return _SystemdUserService(system_mode=system_mode) # type: ignore[return-value]
550
+ if shutil.which("systemctl"):
551
+ return _SystemdUserService(system_mode=system_mode) # type: ignore[return-value]
552
+ if shutil.which("rc-service") or Path("/sbin/openrc").exists():
553
+ return _OpenRCService() # type: ignore[return-value]
554
+ raise RuntimeError("Unsupported Linux init system (no systemctl or rc-service found)")
426
555
  if system == "darwin":
427
556
  return _LaunchdService() # type: ignore[return-value]
428
557
  if system.startswith("windows") or system == "windows":
429
558
  return _WindowsTask() # type: ignore[return-value]
430
- raise RuntimeError(f"Unsupported platform: {system}")
559
+ raise RuntimeError(f"Unsupported platform: {system}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.17.dev8
3
+ Version: 1.4.17.dev10
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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=rwm62A3_CwJ5c5GjxdVZUVMzSoUb2-9NpXonNAkuT7Y,719
4
+ portacode/_version.py,sha256=HLYWuN3eL2K1B7JHO-p2jakoiL3KWtYprDdYTrjf-dI,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
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=p-HHMOAl20QsdcJydcZ74Iqes-wl8G8HItdSim30pUk,16537
10
+ portacode/service.py,sha256=8dX9bmJ97r8UuM4PJMICQ_D8kWpMPRIdTgPJfsOKmrM,21617
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
@@ -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.dev8.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.17.dev10.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.dev8.dist-info/METADATA,sha256=BtpSzPdxbKOViRvQWUArwKnLV0OdUBm6EGKTI_ZkiSk,13051
95
- portacode-1.4.17.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.17.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.17.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.17.dev8.dist-info/RECORD,,
94
+ portacode-1.4.17.dev10.dist-info/METADATA,sha256=x0izWAdZ8_roBHdh2ZwXj2PvYX62ZscViAtNUNX21U0,13052
95
+ portacode-1.4.17.dev10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.17.dev10.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.17.dev10.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.17.dev10.dist-info/RECORD,,