portacode 1.4.17.dev8__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 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.dev9'
32
+ __version_tuple__ = version_tuple = (1, 4, 17, 'dev9')
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,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
- return _SystemdUserService(system_mode=system_mode) # type: ignore[return-value]
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.17.dev8
3
+ Version: 1.4.17.dev9
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=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=p-HHMOAl20QsdcJydcZ74Iqes-wl8G8HItdSim30pUk,16537
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
@@ -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.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.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.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,,