148 lines
4.6 KiB
Python
148 lines
4.6 KiB
Python
import subprocess
|
|
import os
|
|
import logging
|
|
import time
|
|
import platform
|
|
import stat
|
|
import urllib.request
|
|
import re
|
|
import tarfile
|
|
import tempfile
|
|
import shutil
|
|
import select
|
|
from typing import Optional
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
_sshx_link = None
|
|
_sshx_process: Optional[subprocess.Popen] = None
|
|
_sshx_installing = False
|
|
_sshx_bin = "/tmp/sshx"
|
|
_sshx_bin_alt = os.path.expanduser("~/.local/bin/sshx")
|
|
_sshx_tar = "/tmp/sshx.tar.gz"
|
|
_sshx_last_output = ""
|
|
_sshx_last_error = ""
|
|
|
|
LINK_RE = re.compile(r"https://sshx\.io/s/[A-Za-z0-9\-_]+(?:#[^\s\"']*)?")
|
|
|
|
|
|
def _detect_sshx_arch():
|
|
machine = platform.machine().lower()
|
|
system = platform.system().lower()
|
|
if system == "darwin":
|
|
suffix = "-apple-darwin"
|
|
if machine in ("aarch64", "arm64", "armv8l", "armv8b"):
|
|
arch = "aarch64"
|
|
elif machine in ("x86_64", "x64", "amd64"):
|
|
arch = "x86_64"
|
|
else:
|
|
arch = "aarch64" if "arm" in machine else "x86_64"
|
|
return arch, suffix
|
|
suffix = "-unknown-linux-musl"
|
|
if machine in ("aarch64", "aarch64_be", "arm64", "armv8b", "armv8l"):
|
|
arch = "aarch64"
|
|
elif machine in ("x86_64", "x64", "amd64"):
|
|
arch = "x86_64"
|
|
elif machine == "armv6l":
|
|
arch = "arm"
|
|
suffix += "eabihf"
|
|
elif machine == "armv7l":
|
|
arch = "armv7"
|
|
suffix += "eabihf"
|
|
else:
|
|
arch = "x86_64"
|
|
return arch, suffix
|
|
|
|
|
|
def _get_sshx_url() -> str:
|
|
arch, suffix = _detect_sshx_arch()
|
|
url = f"https://s3.amazonaws.com/sshx/sshx-{arch}{suffix}.tar.gz"
|
|
return url
|
|
|
|
|
|
def _find_sshx_bin() -> Optional[str]:
|
|
for p in [_sshx_bin, _sshx_bin_alt, shutil.which("sshx")]:
|
|
if p and os.path.exists(p) and os.access(p, os.X_OK):
|
|
return p
|
|
return None
|
|
|
|
|
|
def _is_alive() -> bool:
|
|
global _sshx_process
|
|
if not _sshx_process:
|
|
return False
|
|
return _sshx_process.poll() is None
|
|
|
|
|
|
@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}
|
|
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": "stopped", "link": None}
|
|
|
|
|
|
@router.get("/debug")
|
|
async def debug_info():
|
|
return {
|
|
"bin": _find_sshx_bin(),
|
|
"is_alive": _is_alive(),
|
|
"pid": _sshx_process.pid if _sshx_process and _is_alive() else None,
|
|
"link": _sshx_link,
|
|
"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"})
|
|
|
|
@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
|
|
_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"}
|
|
|
|
@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."}
|