portacode 1.4.16.dev10__py3-none-any.whl → 1.4.17__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 +808 -568
- portacode/service.py +131 -2
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/METADATA +1 -1
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/RECORD +9 -9
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/WHEEL +0 -0
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.16.dev10.dist-info → portacode-1.4.17.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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,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=8lzbms9BpD-Oht4SHc7EY9GwLJHe1VkS9qzxWPibYsY,706
|
|
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=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
|
|
@@ -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=6Av5QeNQGh38U0kRzoB4ZICmSrRaWLT3YXHoAqUisv4,99102
|
|
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.
|
|
68
|
+
portacode-1.4.17.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.
|
|
95
|
-
portacode-1.4.
|
|
96
|
-
portacode-1.4.
|
|
97
|
-
portacode-1.4.
|
|
98
|
-
portacode-1.4.
|
|
94
|
+
portacode-1.4.17.dist-info/METADATA,sha256=aGOO0qvH294SN-7MqFeLFJ9x3Em1cnRu-CKPq_zVAUY,13046
|
|
95
|
+
portacode-1.4.17.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
portacode-1.4.17.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.17.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|