2026-08-17 15:15:05 +00:00
"""
2026-09-03 04:21:03 +00:00
sshx - link plugin — creates a shell at sshx . io and returns the shareable link
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
- Auto arch detection ( x86_64 / aarch64 / armv6 / armv7 )
- Downloads sshx binary from S3 without curl | sh
- Starts ` sshx ` and parses https : / / sshx . io / s / . . . link from stdout
- No dashboard restart needed
2026-08-17 15:15:05 +00:00
"""
import subprocess
import threading
import logging
import shutil
import os
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
import platform
import stat
import urllib . request
2026-09-03 04:21:03 +00:00
import re
import tarfile
import tempfile
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
import time
2026-08-17 15:15:05 +00:00
logger = logging . getLogger ( __name__ )
2026-09-03 04:21:03 +00:00
_sshx_process = None
_sshx_link = None
_sshx_log = " /tmp/sshx_link.log "
SSHX_BIN = " /tmp/sshx "
SSHX_BIN_ALT = os . path . expanduser ( " ~/.local/bin/sshx " )
SSHX_TAR = " /tmp/sshx.tar.gz "
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
2026-09-03 04:21:03 +00:00
# Reuse ttyd fallback for offline mode (optional)
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
TTYD_BIN = " /tmp/ttyd "
2026-09-03 04:21:03 +00:00
LINK_RE = re . compile ( r " https://sshx \ .io/s/[A-Za-z0-9 \ -_]+(?:#[^ \ s \" ' ]*)? " )
def _detect_sshx_arch ( ) :
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
machine = platform . machine ( ) . lower ( )
2026-09-03 04:21:03 +00:00
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
# default linux
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 "
logger . info ( f " Detected arch= { arch } suffix= { suffix } -> sshx url= { url } " )
return url
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
2026-09-03 04:21:03 +00:00
def _find_sshx_bin ( ) - > str | None :
for p in [ SSHX_BIN , SSHX_BIN_ALT , shutil . which ( " sshx " ) ] :
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
if p and os . path . exists ( p ) and os . access ( p , os . X_OK ) :
return p
return None
2026-09-03 04:21:03 +00:00
def _ensure_sshx_installed ( ) - > str | None :
""" Ensure sshx binary exists, download + extract if needed. Returns path or None. """
existing = _find_sshx_bin ( )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
if existing :
2026-09-03 04:21:03 +00:00
logger . info ( f " sshx already installed at { existing } " )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
return existing
2026-09-03 04:21:03 +00:00
url = _get_sshx_url ( )
dest = SSHX_BIN
os . makedirs ( os . path . dirname ( SSHX_BIN_ALT ) , exist_ok = True )
tmp_tar = SSHX_TAR
2026-08-17 15:15:05 +00:00
try :
2026-09-03 04:21:03 +00:00
logger . info ( f " Downloading sshx from { url } -> { tmp_tar } " )
urllib . request . urlretrieve ( url , tmp_tar )
# extract tar.gz - contains single file 'sshx'
logger . info ( f " Extracting { tmp_tar } " )
with tarfile . open ( tmp_tar , " r:gz " ) as tf :
# find member named sshx, skip AppleDouble ._ files
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 ]
# extract to tmp dir then move
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 )
# member.name may contain subdir
if not os . path . exists ( extracted ) :
# try find sshx recursively
for root , _ , files in os . walk ( tmpdir ) :
if " sshx " in files :
extracted = os . path . join ( root , " sshx " )
break
shutil . copy2 ( extracted , dest )
os . chmod ( dest , os . stat ( dest ) . st_mode | stat . S_IEXEC )
# cache copy
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 )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
try :
2026-09-03 04:21:03 +00:00
os . remove ( tmp_tar )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
except Exception :
pass
2026-09-03 04:21:03 +00:00
logger . info ( f " sshx installed successfully to { dest } " )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
return dest
except Exception as e :
2026-09-03 04:21:03 +00:00
logger . error ( f " sshx install failed from { url } : { e } " )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
return None
2026-09-03 04:21:03 +00:00
def _is_sshx_alive ( ) - > bool :
global _sshx_process
if _sshx_process is None :
return False
return _sshx_process . poll ( ) is None
2026-09-03 04:23:49 +00:00
_sshx_last_output = " "
_sshx_last_error = " "
def _capture_link ( proc , timeout = 20 ) - > str | None :
2026-09-03 04:21:03 +00:00
""" Read stdout until link found or timeout. Returns link or None. """
2026-09-03 04:23:49 +00:00
global _sshx_last_output
2026-09-03 04:21:03 +00:00
link = None
start = time . time ( )
import select
output = " "
while time . time ( ) - start < timeout :
if proc . poll ( ) is not None :
# process died, collect remaining
try :
rem = proc . stdout . read ( ) or " "
output + = rem
except Exception :
pass
break
# check if data available
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
try :
2026-09-03 04:21:03 +00:00
# select on stdout fd
r , _ , _ = select . select ( [ proc . stdout ] , [ ] , [ ] , 0.5 )
if r :
line = proc . stdout . readline ( )
if not line :
time . sleep ( 0.1 )
continue
output + = line
logger . info ( f " sshx stdout: { line . strip ( ) } " )
m = LINK_RE . search ( line )
if m :
link = m . group ( 0 )
break
# also check whole output so far (link may span?)
m2 = LINK_RE . search ( output )
if m2 :
link = m2 . group ( 0 )
break
except Exception as e :
logger . debug ( f " capture error: { e } " )
time . sleep ( 0.2 )
if not link :
m = LINK_RE . search ( output )
if m :
link = m . group ( 0 )
2026-09-03 04:23:49 +00:00
_sshx_last_output = output [ - 2000 : ]
2026-09-03 04:21:03 +00:00
if not link :
2026-09-03 04:23:49 +00:00
logger . warning ( f " sshx capture timeout, output tail: { output [ - 600 : ] } " )
2026-09-03 04:21:03 +00:00
return link
def _run_sshx ( tool_ctx ) :
""" Start sshx and return link. Reuse if already running. """
global _sshx_process , _sshx_link
if _is_sshx_alive ( ) and _sshx_link :
tool_ctx . yield_result ( { " status " : " already_running " , " link " : _sshx_link } )
2026-08-17 15:15:05 +00:00
return
2026-09-03 04:21:03 +00:00
bin_path = _ensure_sshx_installed ( )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
if not bin_path :
2026-09-03 04:21:03 +00:00
tool_ctx . yield_result ( { " status " : " error " , " message " : " Failed to install sshx. Check network to s3.amazonaws.com/sshx " } )
2026-08-17 15:15:05 +00:00
return
2026-09-03 04:21:03 +00:00
logger . info ( f " Starting sshx via { bin_path } ... " )
2026-08-19 00:00:34 +00:00
try :
2026-09-03 04:21:03 +00:00
# --quiet prints only the link (https://sshx.io/s/...), far more reliable to parse
# fallback to no-flag if --quiet not supported (old version)
_sshx_process = subprocess . Popen (
[ bin_path , " --quiet " ] ,
stdout = subprocess . PIPE ,
stderr = subprocess . STDOUT ,
text = True ,
bufsize = 1 ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
stdin = subprocess . DEVNULL ,
start_new_session = True ,
2026-08-19 00:00:34 +00:00
)
except Exception as e :
2026-09-03 04:21:03 +00:00
logger . error ( f " Failed to start sshx: { e } " )
tool_ctx . yield_result ( { " status " : " error " , " message " : f " Failed to start sshx: { e } " } )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
return
2026-09-03 04:23:49 +00:00
link = _capture_link ( _sshx_process , timeout = 20 )
2026-09-03 04:21:03 +00:00
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 } )
else :
2026-09-03 04:23:49 +00:00
global _sshx_last_error
2026-09-03 04:21:03 +00:00
if _sshx_process . poll ( ) is not None :
try :
out = _sshx_process . stdout . read ( ) or " "
except Exception :
2026-09-03 04:23:49 +00:00
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 : ] } )
2026-09-03 04:21:03 +00:00
_sshx_process = None
else :
2026-09-03 04:23:49 +00:00
_sshx_last_error = f " sshx started (pid= { _sshx_process . pid } ) but link not parsed in 20s. tail: { _sshx_last_output [ : 400 ] } . Check outbound to https://sshx.io "
tool_ctx . yield_result ( { " status " : " error " , " message " : _sshx_last_error , " output_tail " : _sshx_last_output [ - 400 : ] , " pid " : _sshx_process . pid } )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
2026-09-03 04:21:03 +00:00
def _stop_sshx ( tool_ctx = None ) :
global _sshx_process , _sshx_link
if _sshx_process and _sshx_process . poll ( ) is None :
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
try :
2026-09-03 04:21:03 +00:00
os . killpg ( os . getpgid ( _sshx_process . pid ) , 15 )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
except Exception :
2026-09-03 04:21:03 +00:00
try :
_sshx_process . terminate ( )
except Exception :
pass
time . sleep ( 0.5 )
if _sshx_process . poll ( ) is None :
try :
os . killpg ( os . getpgid ( _sshx_process . pid ) , 9 )
except Exception :
try :
_sshx_process . kill ( )
except Exception :
pass
_sshx_process = None
_sshx_link = None
if tool_ctx :
tool_ctx . yield_result ( { " status " : " stopped " } )
2026-08-19 00:00:34 +00:00
2026-08-17 15:15:05 +00:00
def register ( ctx ) :
2026-09-03 04:21:03 +00:00
# warmup download in background — no restart needed
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
def _warmup ( ) :
try :
2026-09-03 04:21:03 +00:00
if not _find_sshx_bin ( ) :
logger . info ( " Warming up sshx install... " )
_ensure_sshx_installed ( )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
except Exception as e :
logger . debug ( f " warmup failed: { e } " )
threading . Thread ( target = _warmup , daemon = True ) . start ( )
2026-08-17 15:15:05 +00:00
ctx . register_tool (
name = " sshx_start " ,
toolset = " sshx-link " ,
schema = {
2026-09-03 04:21:03 +00:00
" description " : " Create a shell at sshx.io and return the shareable link. Auto arch detection, downloads sshx binary if needed. " ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
" parameters " : { " type " : " object " , " properties " : { } , " required " : [ ] } ,
2026-08-17 15:15:05 +00:00
} ,
2026-09-03 04:21:03 +00:00
handler = _run_sshx ,
2026-08-17 15:15:05 +00:00
)
2026-09-03 04:21:03 +00:00
ctx . register_tool (
name = " sshx_stop " ,
toolset = " sshx-link " ,
schema = {
" description " : " Stop the running sshx session " ,
" parameters " : { " type " : " object " , " properties " : { } , " required " : [ ] } ,
} ,
handler = _stop_sshx ,
)
# alias for compat with old ttyd plugin
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
ctx . register_tool (
name = " ttyd_start " ,
toolset = " sshx-link " ,
schema = {
2026-09-03 04:21:03 +00:00
" description " : " Alias of sshx_start (kept for compatibility) " ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
" parameters " : { " type " : " object " , " properties " : { } , " required " : [ ] } ,
} ,
2026-09-03 04:21:03 +00:00
handler = _run_sshx ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
)
2026-09-03 04:21:03 +00:00
logger . info ( " sshx-link plugin registered (sshx.io, auto arch, no restart) " )