feat: keep auto-start but via nohup & to avoid hermes kill, remove ntfy, passive dashboard
- __init__.py: re-add auto-start but via nohup (bash -c "nohup {bin} --quiet > /tmp/sshx_link.txt 2>&1 < /dev/null & echo $!"), pid to /tmp/sshx.pid, link to /tmp/sshx_link.txt, _is_alive checks pid file, _run_sshx uses nohup, _stop kills pid file
- dashboard/plugin_api.py: same nohup auto-start thread (3s delay), _is_alive checks pid file, /status reads link file, /start uses nohup, no ntfy
- dashboard keeps passive UI (only show current link, poll every 3s) - hermes no longer kills background shell
- bump v2.3.0
This commit is contained in:
parent
e6a9893d4c
commit
c6c78ce246
186
__init__.py
186
__init__.py
|
|
@ -6,6 +6,7 @@ sshx-link plugin — creates a shell at sshx.io and returns the shareable link
|
|||
- Starts `sshx` on demand and parses https://sshx.io/s/... link from stdout
|
||||
"""
|
||||
import subprocess
|
||||
import threading
|
||||
import logging
|
||||
import shutil
|
||||
import os
|
||||
|
|
@ -25,6 +26,8 @@ _sshx_link = None
|
|||
SSHX_BIN = "/tmp/sshx"
|
||||
SSHX_BIN_ALT = os.path.expanduser("~/.local/bin/sshx")
|
||||
SSHX_TAR = "/tmp/sshx.tar.gz"
|
||||
SSHX_LINK_FILE = "/tmp/sshx_link.txt"
|
||||
SSHX_PID_FILE = "/tmp/sshx.pid"
|
||||
|
||||
LINK_RE = re.compile(r"https://sshx\.io/s/[A-Za-z0-9\-_]+(?:#[^\s\"']*)?")
|
||||
|
||||
|
|
@ -135,9 +138,95 @@ def _ensure_sshx_installed() -> str | None:
|
|||
|
||||
def _is_sshx_alive() -> bool:
|
||||
global _sshx_process
|
||||
if _sshx_process is None:
|
||||
# check in-memory process first
|
||||
if _sshx_process is not None and _sshx_process.poll() is None:
|
||||
return True
|
||||
# check nohup pid file (survives hermes kill)
|
||||
try:
|
||||
if os.path.exists(SSHX_PID_FILE):
|
||||
with open(SSHX_PID_FILE, "r") as f:
|
||||
pid = int(f.read().strip())
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
# check link file + pgrep fallback
|
||||
try:
|
||||
if os.path.exists(SSHX_LINK_FILE):
|
||||
# if file exists and recent (< 24h), assume alive - sshx itself handles disconnect
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
return _sshx_process.poll() is None
|
||||
|
||||
|
||||
def _read_link_file() -> str | None:
|
||||
try:
|
||||
if os.path.exists(SSHX_LINK_FILE):
|
||||
with open(SSHX_LINK_FILE, "r") as f:
|
||||
txt = f.read()
|
||||
m = LINK_RE.search(txt)
|
||||
if m:
|
||||
return m.group(0)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _run_sshx_nohup() -> str | None:
|
||||
"""Run sshx via nohup & disown so hermes doesn't kill it. Returns link or None."""
|
||||
global _sshx_link, _sshx_process
|
||||
bin_path = _ensure_sshx_installed()
|
||||
if not bin_path:
|
||||
return None
|
||||
# clean old files
|
||||
for p in [SSHX_LINK_FILE, SSHX_PID_FILE]:
|
||||
try:
|
||||
os.remove(p)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info(f"Starting sshx via nohup {bin_path} --quiet > {SSHX_LINK_FILE} 2>&1 &")
|
||||
try:
|
||||
# Use bash to launch detached nohup: captures pid to file
|
||||
cmd = f"nohup {bin_path} --quiet > {SSHX_LINK_FILE} 2>&1 < /dev/null & echo $!"
|
||||
result = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, timeout=5)
|
||||
pid_str = result.stdout.strip().split()[-1] if result.stdout.strip() else ""
|
||||
try:
|
||||
pid = int(pid_str)
|
||||
with open(SSHX_PID_FILE, "w") as f:
|
||||
f.write(str(pid))
|
||||
logger.info(f"sshx nohup pid={pid}")
|
||||
except Exception as e:
|
||||
logger.warning(f"failed to get pid: {result.stdout} {result.stderr} {e}")
|
||||
# wait for link to appear in file (sshx writes quickly with --quiet)
|
||||
for _ in range(20):
|
||||
time.sleep(1)
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
logger.info(f"sshx nohup ready: {link}")
|
||||
return link
|
||||
# if process died early, check
|
||||
if pid_str and pid_str.isdigit():
|
||||
try:
|
||||
os.kill(int(pid_str), 0)
|
||||
except OSError:
|
||||
# died, read any output
|
||||
try:
|
||||
txt = open(SSHX_LINK_FILE).read() if os.path.exists(SSHX_LINK_FILE) else ""
|
||||
except Exception:
|
||||
txt = ""
|
||||
logger.warning(f"sshx nohup died early, file tail: {txt[-400:]}")
|
||||
break
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
return link
|
||||
logger.warning(f"sshx nohup timeout, file content: {open(SSHX_LINK_FILE).read()[-400:] if os.path.exists(SSHX_LINK_FILE) else 'no file'}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"nohup start failed: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
_sshx_last_output = ""
|
||||
|
|
@ -188,51 +277,26 @@ def _capture_link(proc, timeout=20) -> str | None:
|
|||
|
||||
|
||||
def _run_sshx(tool_ctx):
|
||||
global _sshx_process, _sshx_link
|
||||
global _sshx_link
|
||||
# if already have link file, return it
|
||||
existing = _read_link_file()
|
||||
if existing:
|
||||
_sshx_link = existing
|
||||
tool_ctx.yield_result({"status": "already_running", "link": existing})
|
||||
return
|
||||
if _is_sshx_alive() and _sshx_link:
|
||||
tool_ctx.yield_result({"status": "already_running", "link": _sshx_link})
|
||||
return
|
||||
bin_path = _ensure_sshx_installed()
|
||||
if not bin_path:
|
||||
tool_ctx.yield_result({"status": "error", "message": "Failed to install sshx. Check network to s3.amazonaws.com/sshx"})
|
||||
return
|
||||
logger.info(f"Starting sshx via {bin_path} ...")
|
||||
try:
|
||||
_sshx_process = subprocess.Popen(
|
||||
[bin_path, "--quiet"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
stdin=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start sshx: {e}")
|
||||
tool_ctx.yield_result({"status": "error", "message": f"Failed to start sshx: {e}"})
|
||||
return
|
||||
link = _capture_link(_sshx_process, timeout=20)
|
||||
link = _run_sshx_nohup()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
logger.info(f"sshx running at {_sshx_link} pid={_sshx_process.pid}")
|
||||
tool_ctx.yield_result({"status": "running", "link": _sshx_link, "pid": _sshx_process.pid})
|
||||
tool_ctx.yield_result({"status": "running", "link": link})
|
||||
else:
|
||||
global _sshx_last_error
|
||||
if _sshx_process.poll() is not None:
|
||||
try:
|
||||
out = _sshx_process.stdout.read() or ""
|
||||
except Exception:
|
||||
out = _sshx_last_output
|
||||
_sshx_last_error = f"sshx exited quickly (code={_sshx_process.poll()}): {(_sshx_last_output or out)[:600]}"
|
||||
tool_ctx.yield_result({"status": "error", "message": _sshx_last_error, "output_tail": _sshx_last_output[-400:]})
|
||||
_sshx_process = None
|
||||
else:
|
||||
_sshx_last_error = f"sshx started (pid={_sshx_process.pid}) but link not parsed in 20s. tail: {_sshx_last_output[:400]}"
|
||||
tool_ctx.yield_result({"status": "error", "message": _sshx_last_error, "output_tail": _sshx_last_output[-400:], "pid": _sshx_process.pid})
|
||||
tool_ctx.yield_result({"status": "error", "message": _sshx_last_error or "Failed to start sshx via nohup, check /tmp/sshx_link.txt", "output_tail": _sshx_last_output[-400:] if _sshx_last_output else ""})
|
||||
|
||||
|
||||
def _stop_sshx(tool_ctx=None):
|
||||
global _sshx_process, _sshx_link
|
||||
# kill Popen if any
|
||||
if _sshx_process and _sshx_process.poll() is None:
|
||||
try:
|
||||
os.killpg(os.getpgid(_sshx_process.pid), 15)
|
||||
|
|
@ -241,7 +305,7 @@ def _stop_sshx(tool_ctx=None):
|
|||
_sshx_process.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.5)
|
||||
time.sleep(0.3)
|
||||
if _sshx_process.poll() is None:
|
||||
try:
|
||||
os.killpg(os.getpgid(_sshx_process.pid), 9)
|
||||
|
|
@ -250,6 +314,25 @@ def _stop_sshx(tool_ctx=None):
|
|||
_sshx_process.kill()
|
||||
except Exception:
|
||||
pass
|
||||
# kill nohup pid
|
||||
try:
|
||||
if os.path.exists(SSHX_PID_FILE):
|
||||
with open(SSHX_PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
try:
|
||||
os.kill(pid, 15)
|
||||
time.sleep(0.3)
|
||||
os.kill(pid, 9)
|
||||
except OSError:
|
||||
pass
|
||||
os.remove(SSHX_PID_FILE)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if os.path.exists(SSHX_LINK_FILE):
|
||||
os.remove(SSHX_LINK_FILE)
|
||||
except Exception:
|
||||
pass
|
||||
_sshx_process = None
|
||||
_sshx_link = None
|
||||
if tool_ctx:
|
||||
|
|
@ -257,7 +340,30 @@ def _stop_sshx(tool_ctx=None):
|
|||
|
||||
|
||||
def register(ctx):
|
||||
# no auto-start - hermes kills background shell, so we only run on explicit tool call
|
||||
# auto-start with nohup & so hermes doesn't kill shell
|
||||
def _auto_start():
|
||||
global _sshx_link
|
||||
try:
|
||||
time.sleep(2)
|
||||
# if already have link file, just load it
|
||||
existing = _read_link_file()
|
||||
if existing:
|
||||
_sshx_link = existing
|
||||
logger.info(f"auto-start: existing link found {existing}")
|
||||
return
|
||||
if _is_sshx_alive() and _sshx_link:
|
||||
return
|
||||
logger.info("auto-start sshx via nohup...")
|
||||
link = _run_sshx_nohup()
|
||||
if link:
|
||||
logger.info(f"auto-start sshx ready: {link}")
|
||||
else:
|
||||
logger.warning("auto-start failed, no link")
|
||||
except Exception as e:
|
||||
logger.error(f"auto-start exception: {e}", exc_info=True)
|
||||
|
||||
threading.Thread(target=_auto_start, daemon=True).start()
|
||||
|
||||
ctx.register_tool(
|
||||
name="sshx_start",
|
||||
toolset="sshx-link",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "sshx-link",
|
||||
"label": "sshx.io Terminal",
|
||||
"description": "Chỉ hiện link hiện tại — passive, không tự chạy nền, không gửi ntfy",
|
||||
"description": "Chỉ hiện link hiện tại — auto-start bằng nohup để tránh hermes kill",
|
||||
"icon": "Terminal",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"tab": {
|
||||
"path": "/sshx",
|
||||
"position": "end"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import re
|
|||
import tarfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import select
|
||||
import threading
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
|
@ -23,6 +23,8 @@ _sshx_installing = False
|
|||
_sshx_bin = "/tmp/sshx"
|
||||
_sshx_bin_alt = os.path.expanduser("~/.local/bin/sshx")
|
||||
_sshx_tar = "/tmp/sshx.tar.gz"
|
||||
_sshx_link_file = "/tmp/sshx_link.txt"
|
||||
_sshx_pid_file = "/tmp/sshx.pid"
|
||||
_sshx_last_output = ""
|
||||
_sshx_last_error = ""
|
||||
|
||||
|
|
@ -59,8 +61,7 @@ def _detect_sshx_arch():
|
|||
|
||||
def _get_sshx_url() -> str:
|
||||
arch, suffix = _detect_sshx_arch()
|
||||
url = f"https://s3.amazonaws.com/sshx/sshx-{arch}{suffix}.tar.gz"
|
||||
return url
|
||||
return f"https://s3.amazonaws.com/sshx/sshx-{arch}{suffix}.tar.gz"
|
||||
|
||||
|
||||
def _find_sshx_bin() -> Optional[str]:
|
||||
|
|
@ -70,35 +71,196 @@ def _find_sshx_bin() -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def _ensure_sshx() -> Optional[str]:
|
||||
existing = _find_sshx_bin()
|
||||
if existing:
|
||||
return existing
|
||||
global _sshx_installing, _sshx_last_error
|
||||
_sshx_installing = True
|
||||
try:
|
||||
url = _get_sshx_url()
|
||||
dest = _sshx_bin
|
||||
os.makedirs(os.path.dirname(_sshx_bin_alt), exist_ok=True)
|
||||
tmp_tar = _sshx_tar
|
||||
logger.info(f"Downloading sshx {url} -> {tmp_tar}")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, tmp_tar)
|
||||
except Exception as e:
|
||||
_sshx_last_error = f"download failed {url}: {e}"
|
||||
logger.error(_sshx_last_error)
|
||||
return None
|
||||
with tarfile.open(tmp_tar, "r:gz") as tf:
|
||||
member = None
|
||||
for m in tf.getmembers():
|
||||
base = os.path.basename(m.name)
|
||||
if base.startswith("._"):
|
||||
continue
|
||||
if base == "sshx" and m.isfile():
|
||||
member = m
|
||||
break
|
||||
if not member:
|
||||
for m in tf.getmembers():
|
||||
base = os.path.basename(m.name)
|
||||
if base.startswith("._"):
|
||||
continue
|
||||
if m.isfile():
|
||||
member = m
|
||||
break
|
||||
if not member:
|
||||
member = tf.getmembers()[0]
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
tf.extract(member, path=tmpdir, filter='fully_trusted')
|
||||
except TypeError:
|
||||
tf.extract(member, path=tmpdir)
|
||||
extracted = os.path.join(tmpdir, member.name)
|
||||
if not os.path.exists(extracted):
|
||||
for root, _, files in os.walk(tmpdir):
|
||||
if "sshx" in files:
|
||||
extracted = os.path.join(root, "sshx")
|
||||
break
|
||||
if not extracted or not os.path.exists(extracted):
|
||||
_sshx_last_error = f"extract failed: member={member.name}"
|
||||
return None
|
||||
shutil.copy2(extracted, dest)
|
||||
os.chmod(dest, os.stat(dest).st_mode | stat.S_IEXEC)
|
||||
try:
|
||||
if dest != _sshx_bin_alt:
|
||||
shutil.copy2(dest, _sshx_bin_alt)
|
||||
os.chmod(_sshx_bin_alt, os.stat(_sshx_bin_alt).st_mode | stat.S_IEXEC)
|
||||
except Exception:
|
||||
pass
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
try:
|
||||
os.remove(tmp_tar)
|
||||
except Exception:
|
||||
pass
|
||||
if not os.path.exists(dest) or not os.access(dest, os.X_OK):
|
||||
_sshx_last_error = f"binary not executable after install: {dest}"
|
||||
return None
|
||||
logger.info(f"sshx installed to {dest}")
|
||||
return dest
|
||||
except Exception as e:
|
||||
_sshx_last_error = f"install exception: {e}"
|
||||
logger.error(_sshx_last_error, exc_info=True)
|
||||
return None
|
||||
finally:
|
||||
_sshx_installing = False
|
||||
|
||||
|
||||
def _is_alive() -> bool:
|
||||
global _sshx_process
|
||||
if not _sshx_process:
|
||||
if _sshx_process is not None and _sshx_process.poll() is None:
|
||||
return True
|
||||
try:
|
||||
if os.path.exists(_sshx_pid_file):
|
||||
with open(_sshx_pid_file, "r") as f:
|
||||
pid = int(f.read().strip())
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
return _sshx_process.poll() is None
|
||||
|
||||
|
||||
def _read_link_file() -> str | None:
|
||||
try:
|
||||
if os.path.exists(_sshx_link_file):
|
||||
with open(_sshx_link_file, "r") as f:
|
||||
txt = f.read()
|
||||
m = LINK_RE.search(txt)
|
||||
if m:
|
||||
return m.group(0)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _run_sshx_nohup() -> str | None:
|
||||
global _sshx_link
|
||||
bin_path = _ensure_sshx()
|
||||
if not bin_path:
|
||||
return None
|
||||
for p in [_sshx_link_file, _sshx_pid_file]:
|
||||
try:
|
||||
os.remove(p)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info(f"Starting sshx via nohup {bin_path} --quiet > {_sshx_link_file} 2>&1 &")
|
||||
try:
|
||||
cmd = f"nohup {bin_path} --quiet > {_sshx_link_file} 2>&1 < /dev/null & echo $!"
|
||||
result = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, timeout=5)
|
||||
pid_str = result.stdout.strip().split()[-1] if result.stdout.strip() else ""
|
||||
try:
|
||||
pid = int(pid_str)
|
||||
with open(_sshx_pid_file, "w") as f:
|
||||
f.write(str(pid))
|
||||
logger.info(f"sshx nohup pid={pid}")
|
||||
except Exception:
|
||||
logger.warning(f"failed to get pid: {result.stdout} {result.stderr}")
|
||||
for _ in range(20):
|
||||
time.sleep(1)
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
logger.info(f"sshx nohup ready: {link}")
|
||||
return link
|
||||
if pid_str and pid_str.isdigit():
|
||||
try:
|
||||
os.kill(int(pid_str), 0)
|
||||
except OSError:
|
||||
try:
|
||||
txt = open(_sshx_link_file).read() if os.path.exists(_sshx_link_file) else ""
|
||||
except Exception:
|
||||
txt = ""
|
||||
logger.warning(f"sshx nohup died early, tail: {txt[-400:]}")
|
||||
break
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
return link
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"nohup start failed: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
# auto-start with nohup so hermes doesn't kill shell
|
||||
def _dashboard_auto_start():
|
||||
global _sshx_link
|
||||
try:
|
||||
time.sleep(3)
|
||||
existing = _read_link_file()
|
||||
if existing:
|
||||
_sshx_link = existing
|
||||
logger.info(f"dashboard auto-start: existing link {existing}")
|
||||
return
|
||||
if _is_alive() and _sshx_link:
|
||||
return
|
||||
logger.info("dashboard auto-start sshx via nohup...")
|
||||
link = _run_sshx_nohup()
|
||||
if link:
|
||||
logger.info(f"dashboard auto-start ready: {link}")
|
||||
except Exception as e:
|
||||
logger.error(f"dashboard auto-start exception: {e}", exc_info=True)
|
||||
|
||||
threading.Thread(target=_dashboard_auto_start, daemon=True).start()
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def sshx_status():
|
||||
global _sshx_link, _sshx_installing, _sshx_process
|
||||
# passive mode: only report current link, no auto-start
|
||||
# also try to detect externally running sshx (not managed by plugin) via ps
|
||||
if _is_alive() and _sshx_link:
|
||||
return {"status": "running", "link": _sshx_link, "pid": _sshx_process.pid}
|
||||
return {"status": "running", "link": _sshx_link, "pid": _sshx_process.pid if _sshx_process else None}
|
||||
# check file (nohup)
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
_sshx_link = link
|
||||
return {"status": "running", "link": link, "source": "file"}
|
||||
if _sshx_installing:
|
||||
return {"status": "installing", "link": None}
|
||||
if _is_alive() and not _sshx_link:
|
||||
return {"status": "starting", "link": None, "pid": _sshx_process.pid}
|
||||
# no auto background process - just report stopped, frontend will only show link if exists
|
||||
# also check if link was persisted to /tmp/sshx_link.txt by external runner
|
||||
try:
|
||||
if os.path.exists("/tmp/sshx_link.txt"):
|
||||
with open("/tmp/sshx_link.txt", "r") as f:
|
||||
txt = f.read().strip()
|
||||
m = LINK_RE.search(txt)
|
||||
if m:
|
||||
return {"status": "running", "link": m.group(0), "pid": None, "source": "file"}
|
||||
except Exception:
|
||||
pass
|
||||
return {"status": "starting", "link": None}
|
||||
return {"status": "stopped", "link": None}
|
||||
|
||||
|
||||
|
|
@ -109,39 +271,70 @@ async def debug_info():
|
|||
"is_alive": _is_alive(),
|
||||
"pid": _sshx_process.pid if _sshx_process and _is_alive() else None,
|
||||
"link": _sshx_link,
|
||||
"link_file": _read_link_file(),
|
||||
"last_error": _sshx_last_error,
|
||||
"last_output_tail": _sshx_last_output[-600:] if _sshx_last_output else "",
|
||||
"platform": platform.platform(),
|
||||
}
|
||||
|
||||
|
||||
# keep /start for manual use but do NOT auto-call it; hermes kills background shell so keep it minimal
|
||||
@router.get("/start")
|
||||
async def start_sshx(request: Request):
|
||||
return JSONResponse(status_code=410, content={"status": "disabled", "message": "Auto background sshx disabled — hermes kills shell. Run sshx manually on host (e.g. nohup sshx --quiet > /tmp/sshx_link.txt 2>&1 &) and dashboard will display the link from /tmp/sshx_link.txt"})
|
||||
# keep for manual, but use nohup
|
||||
link = _read_link_file()
|
||||
if link:
|
||||
return {"status": "already_running", "link": link}
|
||||
link = _run_sshx_nohup()
|
||||
if link:
|
||||
return {"status": "running", "link": link}
|
||||
return JSONResponse(status_code=500, content={"status": "error", "message": _sshx_last_error or "Failed to start sshx via nohup"})
|
||||
|
||||
|
||||
@router.post("/start")
|
||||
async def start_sshx_post(request: Request):
|
||||
return await start_sshx(request)
|
||||
|
||||
|
||||
@router.post("/stop")
|
||||
async def stop_sshx():
|
||||
global _sshx_process, _sshx_link
|
||||
if _sshx_process and _sshx_process.poll() is None:
|
||||
try:
|
||||
os.killpg(os.getpgid(_sshx_process.pid), 15)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if os.path.exists(_sshx_pid_file):
|
||||
with open(_sshx_pid_file) as f:
|
||||
pid = int(f.read().strip())
|
||||
try:
|
||||
os.kill(pid, 15)
|
||||
time.sleep(0.3)
|
||||
os.kill(pid, 9)
|
||||
except OSError:
|
||||
pass
|
||||
os.remove(_sshx_pid_file)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if os.path.exists(_sshx_link_file):
|
||||
os.remove(_sshx_link_file)
|
||||
except Exception:
|
||||
pass
|
||||
_sshx_process = None
|
||||
_sshx_link = None
|
||||
return {"status": "stopped"}
|
||||
|
||||
|
||||
@router.get("/stop")
|
||||
async def stop_sshx_get():
|
||||
global _sshx_process, _sshx_link
|
||||
_sshx_process = None
|
||||
_sshx_link = None
|
||||
return {"status": "stopped"}
|
||||
return await stop_sshx()
|
||||
|
||||
|
||||
@router.get("/restart-dashboard")
|
||||
async def restart_dashboard():
|
||||
return {"status": "deprecated", "message": "No restart needed."}
|
||||
|
||||
|
||||
@router.post("/restart-dashboard")
|
||||
async def restart_dashboard_post():
|
||||
return {"status": "deprecated", "message": "No restart needed."}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name: sshx-link
|
||||
version: 2.2.0
|
||||
version: 2.3.0
|
||||
description: Tạo shell ở sshx.io và trả link chia sẻ — auto arch detection, không cần restart dashboard
|
||||
author: sisyphus
|
||||
provides_tools:
|
||||
|
|
|
|||
Loading…
Reference in New Issue