跳到主要内容

Pinggy Tunnel

通过 Pinggy 的 SSH 零安装本地隧道。

技能元数据

来源可选 — 使用 hermes skills install official/devops/pinggy-tunnel 安装
路径optional-skills/devops/pinggy-tunnel
版本0.1.0
作者Teknium (teknium1), Hermes 智能体
许可证MIT
平台linux, macos, windows
标签Pinggy, 隧道, 网络, SSH, Webhook, 本地主机
相关技能cloudflared-quick-tunnel, webhook-subscriptions
信息

以下是 Hermes 加载此技能时看到的完整技能定义。这是该技能激活时智能体看到的说明。

Pinggy 隧道技能

使用 Pinggy SSH 反向隧道将本地服务(开发服务器、Webhook 接收器、MCP 端点、演示)暴露到公共互联网。无需安装守护程序 —— 用户的原生 SSH 客户端连接到 a.pinggy.io:443,Pinggy 会返回一个公共 HTTP/HTTPS URL。

免费套餐:60 分钟隧道、随机子域名、无需注册。Pro 套餐($3/月)是一个需要令牌的可选升级。

何时使用

  • 用户要求“在本地暴露”、“共享我的开发服务器”、“使此 URL 公开”、“隧道端口 N”、“为 Webhook 获取公共 URL”
  • 需要在本地任务期间接收 Webhook 回调(Stripe、GitHub、Discord、AgentMail)
  • 与远程方共享一次性 HTTP 演示(MCP 服务器、Ollama/vLLM 端点、仪表板)
  • 主机上有 SSH 但没有 cloudflared / ngrok 二进制文件,并且安装它们显得小题大做

如果主机已配置 cloudflared,请优先使用 cloudflared-quick-tunnel 技能 —— Cloudflare 快速隧道不会在 60 分钟后过期。

前提条件

  • PATH 中有 sshssh -V)。在 Linux、macOS 和 Windows 10+ 上默认可用。无需其他安装。
  • 隧道启动前,本地服务正在监听 127.0.0.1:<port>。Pinggy 会返回 URL,但在本地源服务启动前它们会返回 502。

可选:

  • PINGGY_TOKEN 环境变量用于付费 Pro 功能(持久子域名、自定义域名、多隧道、无 60 分钟限制)。免费套餐无需凭据。

快速参考

# 端口 8000 的纯 HTTP/HTTPS 隧道(免费套餐)
ssh -p 443 -o StrictHostKeyChecking=no -o ServerAliveInterval=30 \
-R0:localhost:8000 free@a.pinggy.io

# TCP 隧道(数据库、原始 SSH 等)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:5432 tcp@a.pinggy.io

# TLS 隧道(Pinggy 无法解密 —— 需要在源端使用自己的证书)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:443 tls@a.pinggy.io

# 基本身份验证网关 (b:user:pass)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
"b:admin:secret+free@a.pinggy.io"

# Bearer 令牌网关 (k:token)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
"k:mysecrettoken+free@a.pinggy.io"

# IP 白名单 (w:CIDR)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
"w:203.0.113.0/24+free@a.pinggy.io"

# 启用 CORS 并强制 HTTPS 重定向
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
"co+x:https+free@a.pinggy.io"

# Pro 套餐(持久 URL,无 60 分钟限制)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 "$PINGGY_TOKEN+a.pinggy.io"

过程 —— 启动隧道并获取 URL

模型应使用 terminal 工具。隧道必须在共享期间保持运行,因此应将其作为后台进程运行,并从 stdout 解析公共 URL。

1. 确认本地源服务正在运行

curl -sI http://127.0.0.1:8000/ | head -1
# 预期返回 HTTP/1.x 200(或任何非连接拒绝的响应)

如果尚无服务在监听,请先启动它(例如 python3 -m http.server 8000 --bind 127.0.0.1)。Pinggy 会欣然返回一个指向空服务的 URL —— 在源服务启动前,用户会看到 502 错误。

2. 以后台进程启动隧道

使用 terminal(background=True) 并将输出捕获到日志文件(Pinggy 在 stdout 上打印 URL,然后保持连接打开):

LOG=/tmp/pinggy-8000.log
nohup ssh -p 443 \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-R0:localhost:8000 free@a.pinggy.io \
> "$LOG" 2>&1 &
echo $! > /tmp/pinggy-8000.pid

StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null 跳过首次运行的主机密钥提示。ServerAliveInterval=30 防止 SSH 会话因空闲 NAT 而被断开。

3. 从日志中解析 URL

sleep 4
grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' /tmp/pinggy-8000.log | head -1

预期输出类似:

You are not authenticated.
Your tunnel will expire in 60 minutes.
http://yqycl-98-162-69-48.a.free.pinggy.link
https://yqycl-98-162-69-48.a.free.pinggy.link

https://...pinggy.link URL 提供给用户。

4. 验证

curl -sI https://<the-url>/ | head -3
# 预期返回 200/302 或实际本地源服务返回的状态码

如果收到 502 Bad Gateway,说明 SSH 会话已建立但本地源服务未监听 —— 请先解决步骤 1 的问题。

5. 拆卸

kill "$(cat /tmp/pinggy-8000.pid)"
# 或者,如果 pid 文件丢失:
pkill -f 'ssh -p 443 .* free@a\.pinggy\.io'

如果你有来自 terminal(background=True) 的 session_id,请优先使用 process(action='kill', session_id=...)

通过用户名关键字进行访问控制

Pinggy 使用 + 分隔的控制标志组合到 SSH 用户名中。当用户名包含 + 时,请始终用引号括起整个 user@host 参数:

关键字效果
b:user:passHTTP 基本身份验证网关
k:tokenBearer 令牌头网关 (Authorization: Bearer <token>)
w:CIDRIP 白名单(单个 IP 或 CIDR,可重复)
co添加 Access-Control-Allow-Origin: * (CORS)
x:https强制 HTTPS —— 自动将 HTTP 重定向到 HTTPS
a:Name:Value添加请求头
u:Name:Value更新请求头
r:Name移除请求头
qr将 URL 的 QR 码打印到 stdout(方便移动共享)

可自由组合:"b:admin:secret+co+x:https+free@a.pinggy.io"

Web 调试器(可选)

Pinggy 可以将入站流量镜像到 localhost:4300 以供检查。在 SSH 命令中添加一个本地转发:

ssh -p 443 -L4300:localhost:4300 -R0:localhost:8000 free@a.pinggy.io

然后在浏览器中打开 http://localhost:4300 查看实时的请求/响应对。

陷阱

  • 免费套餐有 60 分钟的硬性限制。 SSH 会话在 60 分钟时终止;URL 失效。对于更长的共享,要么使用 PINGGY_TOKEN(Pro),要么使用 shell 循环自动重启(注意免费套餐每次重启 URL 都会变化)。
  • 免费套餐 URL 是随机的,重启后会变化。 不要将其加入书签,不要粘贴到配置文件中。每次从日志中重新解析。
  • 并发免费隧道限制为每个源 IP 一个。 从同一台机器启动第二个隧道通常会杀死第一个。Pro 套餐无此限制。
  • 用户名中的 + 必须用引号括起。 裸的 ssh ... b:admin:secret+free@a.pinggy.io 在 bash 中有效,但在将 + 视为特殊字符的 shell 中或编程式组装时会失败。请始终用双引号括起。
  • 如果没有访问控制标志,请勿隧道传输任何敏感内容。 裸 HTTP 隧道对任何拥有 URL 的人都可达。对于非公共服务,请使用 b:k:w:
  • process(action='log') 可能会遗漏 SSH 横幅输出。 Pinggy 打印 URL 后,SSH 会话变为交互式。请始终重定向到日志文件并直接 grep 文件 —— 与 cloudflared-quick-tunnel 的模式相同。
  • 首次运行时的主机密钥提示。 默认的 OpenSSH 配置会要求用户接受 Pinggy 的主机密钥。对于无人值守的运行,请始终传递 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
  • TCP 和 TLS 隧道返回 <subdomain>.a.pinggy.online:<port> 对,而不是 https URL。 使用不同的正则表达式解析(tcp:// 和端口)。不要假设每个 Pinggy 隧道都是 HTTP。
  • Pro 模式要求令牌作为用户名,而不是标志。 使用 "$PINGGY_TOKEN+a.pinggy.io"(没有 free@)。使用令牌时,你还可以添加 :persistent 以获得稳定的子域名 —— 请参阅 pinggy.io/docs/

方案

将本地源服务与 Pinggy 隧道相结合的组合模式。每个方案都是独立的 —— 启动源服务,启动隧道,解析 URL,将其返回给用户。

方案 1 —— 接收 Webhook 回调

当外部服务(Stripe、GitHub、Discord、AgentMail 等)需要在本地任务期间向公共可访问 URL 发送 POST 请求时使用此方案。

# 1. 微型捕获服务器:每个请求都追加到 /tmp/webhook-hits.log
cat >/tmp/webhook-server.py <<'PY'
import http.server, json, datetime, pathlib
LOG = pathlib.Path("/tmp/webhook-hits.log")
class H(http.server.BaseHTTPRequestHandler):
def _capture(self):
n = int(self.headers.get("content-length") or 0)
body = self.rfile.read(n).decode("utf-8", "replace") if n else ""
rec = {"t": datetime.datetime.utcnow().isoformat(), "path": self.path,
"method": self.command, "headers": dict(self.headers), "body": body}
with LOG.open("a") as f: f.write(json.dumps(rec) + "\n")
self.send_response(200); self.send_header("content-type","application/json")
self.end_headers(); self.wfile.write(b'{"ok":true}\n')
def do_GET(self): self._capture()
def do_POST(self): self._capture()
def log_message(self,*a,**k): pass
http.server.HTTPServer(("127.0.0.1", 18080), H).serve_forever()
PY
nohup python3 /tmp/webhook-server.py >/tmp/webhook-server.log 2>&1 &
echo $! >/tmp/webhook-server.pid

# 2. 隧道 —— 使用 Bearer 令牌网关,防止随机用户污染捕获日志
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 \
-R0:localhost:18080 "k:$(openssl rand -hex 12)+free@a.pinggy.io" \
>/tmp/webhook-pinggy.log 2>&1 &
echo $! >/tmp/webhook-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' /tmp/webhook-pinggy.log | head -1)
echo "Webhook URL: $URL"

# 3. 在智能体工作期间,监控请求到达
tail -f /tmp/webhook-hits.log

$URL 提供给需要调用你的服务。拆卸:kill $(cat /tmp/webhook-server.pid) $(cat /tmp/webhook-pinggy.pid)

方案 2 —— 通过 HTTP/SSE 暴露 MCP 服务器

当远程 MCP 客户端(另一台机器上的 Claude Desktop、队友的编辑器等)需要访问运行在本地机器上的 MCP 服务器时使用。仅适用于使用 HTTP 传输的 MCP 服务器 —— stdio 模式的服务器无法被隧道传输。

# 1. 以 HTTP 模式启动 MCP 服务器(示例:端口 8765 上的 FastMCP 服务器)
nohup python3 my_mcp_server.py --transport http --port 8765 \
>/tmp/mcp-server.log 2>&1 &
echo $! >/tmp/mcp-server.pid

# 2. 使用 Bearer 令牌隧道 —— MCP 流量不应向互联网开放
TOKEN=$(openssl rand -hex 16)
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 \
-R0:localhost:8765 "k:$TOKEN+free@a.pinggy.io" \
>/tmp/mcp-pinggy.log 2>&1 &
echo $! >/tmp/mcp-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' /tmp/mcp-pinggy.log | head -1)
echo "MCP URL: $URL"
echo "Bearer token: $TOKEN"

远程客户端使用 Authorization: Bearer $TOKEN 连接到 $URL。Hermes 自身的原生 MCP 客户端配置:{"transport": "http", "url": "<URL>", "headers": {"Authorization": "Bearer <TOKEN>"}}

方案 3 —— 暴露本地 LLM 端点(Ollama / vLLM / llama.cpp)

与远程调用者(另一个智能体、手机、队友)共享本地模型。Ollama 监听 :11434,vLLM 和 llama.cpp 通常监听 :8000

# 前提:模型服务器已在 127.0.0.1:11434(Ollama 默认端口)上运行
TOKEN=$(openssl rand -hex 16)
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 \
-R0:localhost:11434 "k:$TOKEN+co+free@a.pinggy.io" \
>/tmp/llm-pinggy.log 2>&1 &
echo $! >/tmp/llm-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' /tmp/llm-pinggy.log | head -1)
echo "Endpoint: $URL"
echo "Token: $TOKEN"

# 验证
curl -s "$URL/api/tags" -H "Authorization: Bearer $TOKEN" | head

co 启用 CORS,以便浏览器调用者可以访问该端点。对于仅后端调用者,可以省略 co。对于兼容 OpenAI 的 vLLM/llama.cpp 端点,调用者使用基础 URL $URL/v1Authorization: Bearer $TOKEN —— 但请注意,Pinggy 不会对请求体进行任何剥离或替换,因此模型服务器本身会看到 Pinggy 的令牌;本地服务器应配置为忽略认证(因为它在 127.0.0.1 上),并让 Pinggy 来处理网关功能。

方案 4 —— 使用一次性密码共享开发服务器

最快捷的“让队友访问我正在运行的应用”模式。随机密码,打印一次,按 Ctrl-C 时终止。

PASS=$(openssl rand -base64 12 | tr -d '+/=' | head -c 12)
echo "Dev server password: $PASS"
ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 \
-R0:localhost:3000 "b:dev:$PASS+co+x:https+free@a.pinggy.io"
# URL 打印到终端。共享 URL 和密码。按 Ctrl-C 拆卸。

b:dev:$PASS 使用 HTTP 基本身份验证保护 URL。x:https 强制使用 TLS。co 为 SPA 前端添加 CORS。

验证

# 端到端测试:启动一个简单的源服务器,创建隧道,发送请求,然后关闭
python3 -m http.server 18000 --bind 127.0.0.1 >/tmp/origin.log 2>&1 &
ORIGIN_PID=$!

nohup ssh -p 443 \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-R0:localhost:18000 free@a.pinggy.io >/tmp/pinggy-verify.log 2>&1 &
SSH_PID=$!

sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' /tmp/pinggy-verify.log | head -1)
echo "URL: $URL"
curl -sI "$URL/" | head -1

kill "$SSH_PID" "$ORIGIN_PID"

预期结果:获得一个 pinggy.link URL,并且 curl 头部返回 HTTP/2 200