sshmd 0.1.0__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.
- sshm/__init__.py +3 -0
- sshm/__main__.py +4 -0
- sshm/autostart.py +163 -0
- sshm/cli.py +740 -0
- sshm/completions/sshm.fish +137 -0
- sshm/config.py +344 -0
- sshm/daemon.py +289 -0
- sshm/ipc.py +249 -0
- sshm/process.py +545 -0
- sshm/procutil.py +60 -0
- sshm/protocol.py +53 -0
- sshm/py.typed +0 -0
- sshm/state.py +76 -0
- sshm/terminal.py +199 -0
- sshmd-0.1.0.dist-info/METADATA +309 -0
- sshmd-0.1.0.dist-info/RECORD +19 -0
- sshmd-0.1.0.dist-info/WHEEL +4 -0
- sshmd-0.1.0.dist-info/entry_points.txt +4 -0
- sshmd-0.1.0.dist-info/licenses/LICENSE +21 -0
sshm/__init__.py
ADDED
sshm/__main__.py
ADDED
sshm/autostart.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Cross-platform autostart management for sshmd daemon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .procutil import daemon_interpreter
|
|
11
|
+
from .state import log_file, port_file, resolve_port, write_port
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _find_sshmd() -> str:
|
|
15
|
+
sshmd = shutil.which("sshmd")
|
|
16
|
+
if sshmd:
|
|
17
|
+
return sshmd
|
|
18
|
+
return f"{sys.executable} -m sshm.daemon"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# --- Windows ---
|
|
22
|
+
|
|
23
|
+
def _win_daemon_cmd() -> str:
|
|
24
|
+
"""Task command line: pythonw (no console window) from the current environment."""
|
|
25
|
+
return f'"{daemon_interpreter()}" -m sshm.daemon'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _win_install() -> None:
|
|
29
|
+
cmd = [
|
|
30
|
+
"schtasks", "/create",
|
|
31
|
+
"/tn", "sshmd",
|
|
32
|
+
"/tr", _win_daemon_cmd(),
|
|
33
|
+
"/sc", "onlogon",
|
|
34
|
+
"/rl", "limited",
|
|
35
|
+
"/f", # force overwrite
|
|
36
|
+
]
|
|
37
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
38
|
+
if result.returncode != 0:
|
|
39
|
+
raise RuntimeError(f"Failed to create scheduled task: {result.stderr}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _win_uninstall() -> None:
|
|
43
|
+
cmd = ["schtasks", "/delete", "/tn", "sshmd", "/f"]
|
|
44
|
+
subprocess.run(cmd, capture_output=True, text=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --- Linux (systemd) ---
|
|
48
|
+
|
|
49
|
+
_SYSTEMD_UNIT = """\
|
|
50
|
+
[Unit]
|
|
51
|
+
Description=SSH Session Manager Daemon
|
|
52
|
+
After=network.target
|
|
53
|
+
|
|
54
|
+
[Service]
|
|
55
|
+
Type=simple
|
|
56
|
+
ExecStart={cmd}
|
|
57
|
+
Restart=on-failure
|
|
58
|
+
RestartSec=5
|
|
59
|
+
|
|
60
|
+
[Install]
|
|
61
|
+
WantedBy=default.target
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _linux_install() -> None:
|
|
66
|
+
sshmd = _find_sshmd()
|
|
67
|
+
unit_dir = Path.home() / ".config" / "systemd" / "user"
|
|
68
|
+
unit_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
unit_file = unit_dir / "sshmd.service"
|
|
70
|
+
unit_file.write_text(_SYSTEMD_UNIT.format(cmd=sshmd), encoding="utf-8")
|
|
71
|
+
|
|
72
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
|
73
|
+
subprocess.run(["systemctl", "--user", "enable", "sshmd.service"], check=True)
|
|
74
|
+
subprocess.run(["systemctl", "--user", "start", "sshmd.service"], check=True)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _linux_uninstall() -> None:
|
|
78
|
+
subprocess.run(["systemctl", "--user", "stop", "sshmd.service"], capture_output=True)
|
|
79
|
+
subprocess.run(["systemctl", "--user", "disable", "sshmd.service"], capture_output=True)
|
|
80
|
+
unit_file = Path.home() / ".config" / "systemd" / "user" / "sshmd.service"
|
|
81
|
+
unit_file.unlink(missing_ok=True)
|
|
82
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], capture_output=True)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- macOS (launchd) ---
|
|
86
|
+
|
|
87
|
+
_LAUNCHD_PLIST = """\
|
|
88
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
89
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
90
|
+
<plist version="1.0">
|
|
91
|
+
<dict>
|
|
92
|
+
<key>Label</key>
|
|
93
|
+
<string>com.sshm.daemon</string>
|
|
94
|
+
<key>ProgramArguments</key>
|
|
95
|
+
<array>
|
|
96
|
+
{args}
|
|
97
|
+
</array>
|
|
98
|
+
<key>RunAtLoad</key>
|
|
99
|
+
<true/>
|
|
100
|
+
<key>KeepAlive</key>
|
|
101
|
+
<true/>
|
|
102
|
+
<key>StandardErrorPath</key>
|
|
103
|
+
<string>{log_path}</string>
|
|
104
|
+
</dict>
|
|
105
|
+
</plist>
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _macos_install() -> None:
|
|
110
|
+
sshmd = _find_sshmd()
|
|
111
|
+
parts = sshmd.split()
|
|
112
|
+
args_xml = "\n".join(f" <string>{p}</string>" for p in parts)
|
|
113
|
+
log_path = log_file()
|
|
114
|
+
|
|
115
|
+
plist_dir = Path.home() / "Library" / "LaunchAgents"
|
|
116
|
+
plist_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
plist_file = plist_dir / "com.sshm.daemon.plist"
|
|
118
|
+
plist_file.write_text(
|
|
119
|
+
_LAUNCHD_PLIST.format(args=args_xml, log_path=log_path),
|
|
120
|
+
encoding="utf-8",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
subprocess.run(["launchctl", "load", str(plist_file)], check=True)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _macos_uninstall() -> None:
|
|
127
|
+
plist_file = Path.home() / "Library" / "LaunchAgents" / "com.sshm.daemon.plist"
|
|
128
|
+
if plist_file.exists():
|
|
129
|
+
subprocess.run(["launchctl", "unload", str(plist_file)], capture_output=True)
|
|
130
|
+
plist_file.unlink(missing_ok=True)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# --- Public API ---
|
|
134
|
+
|
|
135
|
+
def install_autostart() -> str:
|
|
136
|
+
# Persist the effective port so the autostarted daemon — which doesn't
|
|
137
|
+
# inherit the interactive shell's SSHM_PORT — binds the same port the CLI
|
|
138
|
+
# will dial.
|
|
139
|
+
write_port(resolve_port())
|
|
140
|
+
|
|
141
|
+
if sys.platform == "win32":
|
|
142
|
+
_win_install()
|
|
143
|
+
return "Installed as Windows scheduled task (runs on logon)"
|
|
144
|
+
elif sys.platform == "darwin":
|
|
145
|
+
_macos_install()
|
|
146
|
+
return "Installed as macOS LaunchAgent"
|
|
147
|
+
else:
|
|
148
|
+
_linux_install()
|
|
149
|
+
return "Installed as systemd user service"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def uninstall_autostart() -> str:
|
|
153
|
+
port_file().unlink(missing_ok=True)
|
|
154
|
+
|
|
155
|
+
if sys.platform == "win32":
|
|
156
|
+
_win_uninstall()
|
|
157
|
+
return "Removed Windows scheduled task"
|
|
158
|
+
elif sys.platform == "darwin":
|
|
159
|
+
_macos_uninstall()
|
|
160
|
+
return "Removed macOS LaunchAgent"
|
|
161
|
+
else:
|
|
162
|
+
_linux_uninstall()
|
|
163
|
+
return "Removed systemd user service"
|