228 lines
8.5 KiB
Bash
Executable File
228 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# sshx_link.sh - run via hermes cron (no_agent: run script and deliver stdout verbatim)
|
|
# Workdir: /home/<user>/.hermes/plugins/sshx-link (or /root/.hermes/plugins/sshx-link)
|
|
# Cron: Script=scripts/sshx_link.sh Workdir=/absolute/path/to/hermes-sshx-link
|
|
# Output: stdout verbatim -> link https://sshx.io/s/...#... (also POST to ntfy and write to dist/link.json)
|
|
|
|
set -e
|
|
|
|
NTFY_URL="https://ntfy.sh/kUIJK0H1ettQ4VkR"
|
|
LINK_FILE="/tmp/sshx_link.txt"
|
|
PID_FILE="/tmp/sshx.pid"
|
|
DIST_LINK=""
|
|
# resolve dist file relative to script location
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
DIST_LINK_FILE="$SCRIPT_DIR/dashboard/dist/link.json"
|
|
# also try dashboard/dist from plugin root (when Workdir is plugin root, dist is dashboard/dist)
|
|
if [ -f "$SCRIPT_DIR/dashboard/dist/link.json" ] || [ -d "$SCRIPT_DIR/dashboard/dist" ]; then
|
|
DIST_LINK="$SCRIPT_DIR/dashboard/dist/link.json"
|
|
else
|
|
DIST_LINK="$LINK_FILE"
|
|
fi
|
|
|
|
# arch detection (same as plugin)
|
|
detect_arch() {
|
|
local m=$(uname -m | tr '[:upper:]' '[:lower:]')
|
|
local s=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
local arch="x86_64" suffix="-unknown-linux-musl"
|
|
if [ "$s" = "darwin" ]; then
|
|
suffix="-apple-darwin"
|
|
case "$m" in aarch64|arm64|armv8b|armv8l) arch="aarch64";; x86_64|x64|amd64) arch="x86_64";; *) arch="aarch64";; esac
|
|
else
|
|
case "$m" in aarch64|aarch64_be|arm64|armv8b|armv8l) arch="aarch64";; x86_64|x64|amd64) arch="x86_64";; armv6l) arch="arm"; suffix="${suffix}eabihf";; armv7l) arch="armv7"; suffix="${suffix}eabihf";; *) arch="x86_64";; esac
|
|
fi
|
|
echo "${arch}${suffix}"
|
|
}
|
|
|
|
SSHX_BIN="/tmp/sshx"
|
|
if [ ! -x "$SSHX_BIN" ]; then
|
|
SSHX_BIN="$HOME/.local/bin/sshx"
|
|
fi
|
|
if [ ! -x "$SSHX_BIN" ]; then
|
|
SSHX_BIN=$(which sshx 2>/dev/null || echo "")
|
|
fi
|
|
|
|
ensure_sshx() {
|
|
if [ -x "$SSHX_BIN" ]; then echo "$SSHX_BIN"; return 0; fi
|
|
local affix=$(detect_arch)
|
|
# affix is like x86_64-unknown-linux-musl
|
|
local arch=$(echo "$affix" | cut -d'-' -f1)
|
|
# reconstruct arch/suffix correctly: detect_arch outputs arch+suffix, but we need split
|
|
# easier: call detect logic again to get arch and suffix separate
|
|
# fallback: use url with affix
|
|
local url="https://s3.amazonaws.com/sshx/sshx-${affix}.tar.gz"
|
|
# try to handle arm variants where affix cut is wrong: just use detect_arch directly
|
|
# detect_arch already returns arch+suffix, so url is correct
|
|
echo "Downloading sshx from $url ..." >&2
|
|
local tmp="/tmp/sshx.tar.gz"
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -sL "$url" -o "$tmp" || { echo "curl failed" >&2; return 1; }
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget -q "$url" -O "$tmp" || { echo "wget failed" >&2; return 1; }
|
|
else
|
|
python3 -c "import urllib.request; urllib.request.urlretrieve('$url', '$tmp')" || return 1
|
|
fi
|
|
local dest="/tmp/sshx"
|
|
mkdir -p "$HOME/.local/bin"
|
|
tar -xzf "$tmp" -C /tmp 2>/dev/null || tar -xzf "$tmp" -C "$HOME/.local/bin" 2>/dev/null || {
|
|
# try python extract to handle AppleDouble
|
|
python3 <<PY
|
|
import tarfile, os, shutil, stat
|
|
tf=tarfile.open("$tmp","r:gz")
|
|
m=None
|
|
for x in tf.getmembers():
|
|
import os as _o
|
|
b=_o.path.basename(x.name)
|
|
if b.startswith("._"): continue
|
|
if b=="sshx" and x.isfile():
|
|
m=x; break
|
|
if not m:
|
|
for x in tf.getmembers():
|
|
b=_o.path.basename(x.name)
|
|
if b.startswith("._"): continue
|
|
if x.isfile():
|
|
m=x; break
|
|
import tempfile
|
|
tmpdir=tempfile.mkdtemp()
|
|
try:
|
|
tf.extract(m, path=tmpdir, filter='fully_trusted')
|
|
except: tf.extract(m, path=tmpdir)
|
|
import os as _o2
|
|
ext=_o2.path.join(tmpdir, m.name)
|
|
if not _o2.path.exists(ext):
|
|
for r,_,files in _o2.walk(tmpdir):
|
|
if "sshx" in files:
|
|
ext=_o2.path.join(r,"sshx"); break
|
|
shutil.copy2(ext, "$dest")
|
|
os.chmod("$dest", os.stat("$dest").st_mode | stat.S_IEXEC)
|
|
try: shutil.copy2("$dest","$HOME/.local/bin/sshx")
|
|
except: pass
|
|
PY
|
|
}
|
|
# if still not at /tmp/sshx, try find
|
|
if [ ! -x "/tmp/sshx" ] && [ -x "$HOME/.local/bin/sshx" ]; then
|
|
cp "$HOME/.local/bin/sshx" /tmp/sshx
|
|
fi
|
|
chmod +x /tmp/sshx 2>/dev/null || true
|
|
rm -f "$tmp"
|
|
SSHX_BIN="/tmp/sshx"
|
|
if [ -x "$SSHX_BIN" ]; then echo "$SSHX_BIN"; return 0; else return 1; fi
|
|
}
|
|
|
|
# if link already exists and process alive, just output it
|
|
if [ -f "$LINK_FILE" ]; then
|
|
link=$(grep -oE "https://sshx\.io/s/[A-Za-z0-9_-]+(#[^[:space:]]*)?" "$LINK_FILE" 2>/dev/null | head -n1)
|
|
if [ -n "$link" ]; then
|
|
# check pid alive if exists
|
|
if [ -f "$PID_FILE" ]; then
|
|
pid=$(head -n1 "$PID_FILE" | awk '{print $1}')
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
echo "$link"
|
|
# also ensure dist and ntfy
|
|
mkdir -p "$(dirname "$DIST_LINK")" 2>/dev/null; echo "$link" > "$DIST_LINK" 2>/dev/null || true
|
|
echo "$link" > "$LINK_FILE"
|
|
# ntfy (best effort)
|
|
curl -s -X POST -H "Title: sshx.io shell ready (cached)" -d "$link host $(hostname)" "$NTFY_URL" >/dev/null 2>&1 || true
|
|
exit 0
|
|
fi
|
|
else
|
|
# no pid file but link exists, assume ok
|
|
echo "$link"
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
BIN=$(ensure_sshx)
|
|
if [ -z "$BIN" ] || [ ! -x "$BIN" ]; then
|
|
echo "Failed to ensure sshx binary" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# clean old
|
|
rm -f "$LINK_FILE" "$PID_FILE" 2>/dev/null
|
|
rm -f "$DIST_LINK" 2>/dev/null
|
|
|
|
# choose launcher: systemd-run > nohup > setsid > fallback
|
|
LINK=""
|
|
if command -v systemd-run >/dev/null 2>&1 && systemd-run --user --scope --help >/dev/null 2>&1 && systemctl --user --version >/dev/null 2>&1; then
|
|
UNIT="hermes-sshx-$RANDOM"
|
|
echo "Trying systemd-run $UNIT ..." >&2
|
|
# shellcheck disable=SC2086
|
|
bash -c "systemd-run --user --scope --collect --unit $UNIT --quiet bash -c 'exec $BIN --quiet > $LINK_FILE 2>&1'" &
|
|
sleep 1
|
|
for i in $(seq 1 20); do
|
|
sleep 1
|
|
if [ -f "$LINK_FILE" ]; then
|
|
LINK=$(grep -oE "https://sshx\.io/s/[A-Za-z0-9_-]+(#[^[:space:]]*)?" "$LINK_FILE" 2>/dev/null | head -n1)
|
|
if [ -n "$LINK" ]; then break; fi
|
|
fi
|
|
done
|
|
# try to get pid via systemctl
|
|
if [ -n "$LINK" ]; then
|
|
PID=$(systemctl --user show -p MainPID "$UNIT" 2>/dev/null | cut -d= -f2)
|
|
if [ -n "$PID" ] && [ "$PID" != "0" ]; then
|
|
echo "$PID" > "$PID_FILE" 2>/dev/null
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$LINK" ]; then
|
|
HAS_NOHUP=0; command -v nohup >/dev/null 2>&1 && HAS_NOHUP=1
|
|
HAS_SETSID=0; command -v setsid >/dev/null 2>&1 && HAS_SETSID=1
|
|
if [ "$HAS_NOHUP" = "1" ]; then
|
|
echo "Starting via nohup $BIN ..." >&2
|
|
nohup "$BIN" --quiet > "$LINK_FILE" 2>&1 < /dev/null &
|
|
PID=$!
|
|
echo "$PID" > "$PID_FILE" 2>/dev/null
|
|
elif [ "$HAS_SETSID" = "1" ]; then
|
|
echo "Starting via setsid $BIN ..." >&2
|
|
setsid "$BIN" --quiet > "$LINK_FILE" 2>&1 < /dev/null &
|
|
PID=$!
|
|
echo "$PID" > "$PID_FILE" 2>/dev/null
|
|
else
|
|
echo "Starting via fallback setsid Python ..." >&2
|
|
# fallback: use setsid via python
|
|
python3 -c "import subprocess, os; open('$LINK_FILE','w').close(); p=subprocess.Popen(['$BIN','--quiet'], stdout=open('$LINK_FILE','w'), stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, start_new_session=True, close_fds=True); open('$PID_FILE','w').write(str(p.pid))" 2>&1
|
|
PID=$(cat "$PID_FILE" 2>/dev/null | awk '{print $1}')
|
|
fi
|
|
|
|
for i in $(seq 1 20); do
|
|
sleep 1
|
|
if [ -f "$LINK_FILE" ]; then
|
|
LINK=$(grep -oE "https://sshx\.io/s/[A-Za-z0-9_-]+(#[^[:space:]]*)?" "$LINK_FILE" 2>/dev/null | head -n1)
|
|
if [ -n "$LINK" ]; then break; fi
|
|
fi
|
|
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
|
|
echo "sshx died early, tail: $(tail -c 400 "$LINK_FILE" 2>/dev/null)" >&2
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$LINK" ] && [ -f "$LINK_FILE" ]; then
|
|
LINK=$(grep -oE "https://sshx\.io/s/[A-Za-z0-9_-]+(#[^[:space:]]*)?" "$LINK_FILE" 2>/dev/null | head -n1)
|
|
fi
|
|
|
|
if [ -z "$LINK" ]; then
|
|
echo "Failed to get sshx link, tail: $(tail -c 400 "$LINK_FILE" 2>/dev/null)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# write dist for dashboard static fallback
|
|
mkdir -p "$(dirname "$DIST_LINK")" 2>/dev/null
|
|
echo "$LINK" > "$DIST_LINK" 2>/dev/null || true
|
|
echo "$LINK" > "$LINK_FILE"
|
|
|
|
# ntfy (best effort, try curl/wget/python)
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -s -X POST -H "Title: sshx.io shell ready" -H "Priority: high" -d "sshx link: $LINK host $(hostname)" "$NTFY_URL" >/dev/null 2>&1 || true
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget -qO- --post-data="sshx link: $LINK host $(hostname)" --header="Title: sshx.io shell ready" "$NTFY_URL" >/dev/null 2>&1 || true
|
|
else
|
|
python3 -c "import urllib.request; req=urllib.request.Request('$NTFY_URL', data=b'sshx link: $LINK host ' + b'$(hostname)', method='POST'); req.add_header('Title','sshx.io shell ready'); urllib.request.urlopen(req, timeout=5)" 2>/dev/null || true
|
|
fi
|
|
|
|
# stdout verbatim for cron no_agent
|
|
echo "$LINK"
|