多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。 Features: - 用户名/密码注册登录(首个用户自动管理员) - JWT 认证 + bcrypt 密码加密 - 积分系统与多用户管理 - HuggingFace Space 弹性 Worker 节点管理 - Cloudflare Worker 负载均衡 - 实时日志推送(SSE/WebSocket) - 管理后台(用户管理、系统设置、代理池、卡池) Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
227 lines
7.5 KiB
Bash
Executable File
227 lines
7.5 KiB
Bash
Executable File
#!/bin/bash
|
||
set -eu
|
||
|
||
if [ -n "${PROXY_URL:-}" ]; then
|
||
export HTTP_PROXY="$PROXY_URL"
|
||
export HTTPS_PROXY="$PROXY_URL"
|
||
export http_proxy="$PROXY_URL"
|
||
export https_proxy="$PROXY_URL"
|
||
fi
|
||
|
||
# Firefox/Playwright 不支持带认证的 SOCKS5 代理(socks5://user:pass@host:port)
|
||
# 检测到时,用 pproxy 在本地建无认证中转,再把环境变量指向本地
|
||
_PROXY_SRC="${HTTPS_PROXY:-${HTTP_PROXY:-}}"
|
||
if echo "$_PROXY_SRC" | grep -qE '^socks5://[^@:]+:[^@]+@'; then
|
||
pproxy -l socks5://127.0.0.1:18080 -r "$_PROXY_SRC" &
|
||
sleep 1
|
||
export HTTPS_PROXY="socks5://127.0.0.1:18080"
|
||
export HTTP_PROXY="socks5://127.0.0.1:18080"
|
||
export https_proxy="socks5://127.0.0.1:18080"
|
||
export http_proxy="socks5://127.0.0.1:18080"
|
||
fi
|
||
|
||
# Mac Mini 本地部署可通过 SKIP_STARTUP_DELAY=1 跳过防检测延迟
|
||
if [ "${SKIP_STARTUP_DELAY:-0}" != "1" ]; then
|
||
sleep $((RANDOM % 10 + 5))
|
||
fi
|
||
|
||
if [ -z "${DATA_URL:-}" ]; then
|
||
exit 1
|
||
fi
|
||
|
||
mkdir -p /opt/workspace
|
||
cd /opt/workspace
|
||
|
||
# 缓存判断:volume 中已有处理好的 app.py 则跳过下载和 patch
|
||
_CACHED_APP="$(find /opt/workspace -name 'app.py' -type f 2>/dev/null | head -1)"
|
||
if [ -n "$_CACHED_APP" ] && [ "${FORCE_DOWNLOAD:-0}" != "1" ]; then
|
||
echo "[cache] 检测到已有缓存,跳过下载"
|
||
cd "$(dirname "$_CACHED_APP")"
|
||
Xvfb :99 -screen 0 1280x720x24 -nolisten tcp &
|
||
sleep 1
|
||
exec python -u app.py ${SVC_ARGS:---host 0.0.0.0 --port 7860 --browser_type foxfire --no-headless --thread 2}
|
||
fi
|
||
|
||
# --- 以下为首次下载 + patch 流程 ---
|
||
PKG_FILE="data_pkg"
|
||
|
||
AUTH="$(printf '%s' "${GH_PAT:-}" | tr -d '\r\n "')"
|
||
|
||
if [ -n "$AUTH" ]; then
|
||
SRC="$DATA_URL"
|
||
|
||
case "$SRC" in
|
||
https://github.com/*)
|
||
REPO="$(echo "$SRC" | sed -e 's|https://github.com/||' -e 's|/releases/.*||')"
|
||
VER="$(echo "$SRC" | sed -e 's|.*/releases/download/||' -e 's|/.*||')"
|
||
FNAME="$(echo "$SRC" | sed 's|.*/releases/download/[^/]*/||')"
|
||
|
||
ASSET="$(curl -fsSL \
|
||
-H "Authorization: token $AUTH" \
|
||
-H "Accept: application/vnd.github.v3+json" \
|
||
"https://api.github.com/repos/$REPO/releases/tags/$VER" \
|
||
| jq -r ".assets[] | select(.name==\"$FNAME\") | .url")"
|
||
|
||
if [ -z "$ASSET" ] || [ "$ASSET" = "null" ]; then
|
||
exit 1
|
||
fi
|
||
|
||
curl -fsSL \
|
||
-H "Authorization: token $AUTH" \
|
||
-H "Accept: application/octet-stream" \
|
||
"$ASSET" -o "$PKG_FILE"
|
||
;;
|
||
*)
|
||
curl -fsSL -H "Authorization: token $AUTH" "$SRC" -o "$PKG_FILE"
|
||
;;
|
||
esac
|
||
else
|
||
UA="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
|
||
if ! curl -fsSL -A "$UA" "$DATA_URL" -o "$PKG_FILE"; then
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
unzip -oq "$PKG_FILE" 2>/dev/null || tar -xzf "$PKG_FILE" 2>/dev/null
|
||
rm -f "$PKG_FILE"
|
||
|
||
Xvfb :99 -screen 0 1280x720x24 -nolisten tcp &
|
||
sleep 1
|
||
|
||
_a="$(printf '\x63\x61\x6d\x6f\x75\x66\x6f\x78')"
|
||
_A="$(printf '\x43\x61\x6d\x6f\x75\x66\x6f\x78')"
|
||
_b="$(printf '\x74\x75\x72\x6e\x73\x74\x69\x6c\x65')"
|
||
_B="$(printf '\x54\x75\x72\x6e\x73\x74\x69\x6c\x65')"
|
||
_c="$(printf '\x63\x61\x70\x74\x63\x68\x61')"
|
||
_C="$(printf '\x43\x61\x70\x74\x63\x68\x61')"
|
||
_CU="$(printf '\x43\x41\x50\x54\x43\x48\x41')"
|
||
_d="$(printf '\x73\x6f\x6c\x76\x65\x72')"
|
||
_D="$(printf '\x53\x6f\x6c\x76\x65\x72')"
|
||
_dd="$(printf '\x73\x6f\x6c\x76\x65\x64')"
|
||
_R="foxfire"
|
||
_P="$(printf '\x70\x61\x74\x63\x68\x72\x69\x67\x68\x74')"
|
||
|
||
ENTRY="$(find /opt/workspace -name "api_${_d}.py" -type f | head -1)"
|
||
if [ -z "$ENTRY" ]; then
|
||
exit 1
|
||
fi
|
||
|
||
cd "$(dirname "$ENTRY")"
|
||
mv "api_${_d}.py" app.py
|
||
|
||
_Q="webright"
|
||
_RC="Foxfire"
|
||
sed -i -e "s/\"${_a}\"/\"${_R}\"/g" -e "s/'${_a}'/'${_R}'/g" \
|
||
-e "s/from ${_a}\./from ${_R}./g" -e "s/import ${_a}/import ${_R}/g" \
|
||
-e "s/${_A}/${_RC}/g" \
|
||
-e "s/\"${_P}\"/\"${_Q}\"/g" -e "s/'${_P}'/'${_Q}'/g" \
|
||
-e "s/from ${_P}\./from ${_Q}./g" -e "s/import ${_P}/import ${_Q}/g" \
|
||
app.py
|
||
|
||
python3 << 'PYEOF'
|
||
import re
|
||
|
||
_T = bytes.fromhex('63616d6f75666f78').decode()
|
||
_R = 'foxfire'
|
||
_H1 = bytes.fromhex('5f63665f6368616c6c656e67655f70726f78795f68616e646c6572').decode()
|
||
_H2 = bytes.fromhex('5f63665f70726f78795f68616e646c65725f776974685f61637475616c5f70726f7879').decode()
|
||
_CSP = bytes.fromhex('6279706173735f637370').decode()
|
||
c = open('app.py').read()
|
||
|
||
def _guard(m):
|
||
head, sp, body = m.group(1), m.group(2), m.group(3)
|
||
return (head + sp + 'if not _p:\n'
|
||
+ sp + ' await route.continue_()\n'
|
||
+ sp + ' return\n' + sp + body)
|
||
|
||
c = re.sub(
|
||
rf'(async def {_H2}\(route, _p=_actual_proxy\):\n)(\s+)(await self\.{_H1}[^\n]+)',
|
||
_guard, c)
|
||
|
||
c = c.replace(
|
||
f'is_{_T} = self.browser_type == "{_R}"',
|
||
f'is_{_T} = False')
|
||
|
||
c = c.replace(f"context_options['{_CSP}'] = True", f"# context_options['{_CSP}'] = True")
|
||
|
||
open('app.py', 'w').write(c)
|
||
PYEOF
|
||
|
||
sed -i \
|
||
-e "s/${_B} ${_D} API/Benchmark API/g" \
|
||
-e "s/Challenge ${_D}/Benchmark Service/g" \
|
||
-e "s/ChallengeServer/AppServer/g" \
|
||
-e "s/${_CU}_FAIL/TASK_FAIL/g" \
|
||
-e "s/${_CU}_NOT_READY/TASK_PENDING/g" \
|
||
-e "s/${_dd} ${_c}/completed task/g" \
|
||
app.py
|
||
|
||
python3 << 'FAKEPAGE'
|
||
import re, random
|
||
|
||
c = open('app.py').read()
|
||
|
||
models = [
|
||
("distilbert-base-uncased", "text-classification", "84M"),
|
||
("all-MiniLM-L6-v2", "sentence-similarity", "22M"),
|
||
("bert-base-multilingual", "token-classification", "178M"),
|
||
("vit-base-patch16-224", "image-classification", "86M"),
|
||
("whisper-tiny", "automatic-speech-recognition", "39M"),
|
||
]
|
||
m = random.choice(models)
|
||
|
||
fake_html = f'''<!DOCTYPE html>
|
||
<html><head><title>Model Inference API</title>
|
||
<style>
|
||
body{{font-family:system-ui,-apple-system,sans-serif;max-width:640px;margin:60px auto;padding:0 20px;background:#0d1117;color:#c9d1d9}}
|
||
h1{{color:#58a6ff;font-size:1.4em}}
|
||
.card{{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;margin:12px 0}}
|
||
.label{{color:#8b949e;font-size:0.85em}}
|
||
.value{{color:#f0f6fc;font-weight:600}}
|
||
code{{background:#1f2937;padding:2px 6px;border-radius:4px;font-size:0.9em}}
|
||
.status{{color:#3fb950;font-weight:600}}
|
||
</style></head><body>
|
||
<h1>Model Inference API</h1>
|
||
<div class="card">
|
||
<div class="label">Model</div><div class="value">{m[0]}</div>
|
||
<div class="label" style="margin-top:8px">Task</div><div class="value">{m[1]}</div>
|
||
<div class="label" style="margin-top:8px">Parameters</div><div class="value">{m[2]}</div>
|
||
<div class="label" style="margin-top:8px">Status</div><div class="status">Ready</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="label">Endpoints</div>
|
||
<div style="margin-top:6px"><code>POST /predict</code> — Run inference</div>
|
||
<div style="margin-top:4px"><code>GET /health</code> — Health check</div>
|
||
</div>
|
||
<div style="margin-top:24px;color:#484f58;font-size:0.8em">Powered by ONNX Runtime</div>
|
||
</body></html>'''
|
||
|
||
fake_html_escaped = fake_html.replace("'", "\\'").replace("\n", "\\n")
|
||
|
||
patterns = [
|
||
(r'(index_html\s*=\s*)"""[\s\S]*?"""', r"\1'''" + fake_html + "'''"),
|
||
(r'(home_html\s*=\s*)"""[\s\S]*?"""', r"\1'''" + fake_html + "'''"),
|
||
(r'(INDEX_HTML\s*=\s*)"""[\s\S]*?"""', r"\1'''" + fake_html + "'''"),
|
||
(r'(index_html\s*=\s*)f"""[\s\S]*?"""', r"\1'''" + fake_html + "'''"),
|
||
(r'(home_html\s*=\s*)f"""[\s\S]*?"""', r"\1'''" + fake_html + "'''"),
|
||
]
|
||
|
||
replaced = False
|
||
for pat, repl in patterns:
|
||
if re.search(pat, c):
|
||
c = re.sub(pat, repl, c, count=1)
|
||
replaced = True
|
||
break
|
||
|
||
if not replaced:
|
||
c = re.sub(
|
||
r'(async def index\([^)]*\)[^{]*?return\s+\w+\.Response\(text=)(["\'][\s\S]*?)(,\s*content_type)',
|
||
r"\1'''" + fake_html + r"'''\3",
|
||
c, count=1
|
||
)
|
||
|
||
open('app.py', 'w').write(c)
|
||
FAKEPAGE
|
||
|
||
exec python -u app.py ${SVC_ARGS:---host 0.0.0.0 --port 7860 --browser_type foxfire --no-headless --thread 2}
|