🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

frp-tunnel

by @darwin7381

Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...

Versionv1.0.0
Downloads792
TERMINAL
clawhub install frp-tunnel

📖 About This Skill


name: frp-tunnel description: Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clients, or test on mobile. Replaces ngrok/localhost.run with a stable, unlimited, self-hosted solution.

frp-tunnel — 自建內網穿透

用自建的 frp + Caddy + Hetzner VPS 分享本地開發中的網站。自訂域名、自動 HTTPS、無限 tunnel。

> 替代方案(無 VPS 時):見 old-share-local-site skill(ngrok/localhost.run)

⚙️ Config(自訂區塊)

使用此 skill 前,請將以下值替換成你自己的環境:

| 變數 | 目前的值 | 說明 | |------|---------|------| | VPS IP | 5.223.75.160 | 你的 VPS 公網 IP | | 域名 | *.tunnel.fud.city | 你的 wildcard 子域名 | | DNS Provider | Cloudflare (fud.city) | 管理你域名 DNS 的服務 | | GitHub 備份 | https://github.com/darwin7381/frp-tunnel | 你的 private repo(可選) |

> Dashboard 密碼:不記在這裡。在 VPS 的 frps.toml 裡設定你自己的密碼。

基礎資訊

| 項目 | 值 | |------|-----| | VPS IP | 5.223.75.160 | | VPS Provider | Hetzner Cloud, Singapore, CPX12 | | 域名 | *.tunnel.fud.city | | DNS | Cloudflare (fud.city) | | HTTPS 方式 | Wildcard cert (DNS-01 challenge via Cloudflare API) | | frp Dashboard | http://5.223.75.160:7500 | | GitHub 備份 | https://github.com/darwin7381/frp-tunnel (private) | | 本地 config | ~/.frp/frpc.toml | | 本地 tmux session | frpc | | VPS services | frps (systemd) + caddy (systemd) |

目前的 Tunnels

| 名稱 | 本地 Port | URL | |------|----------|-----| | news-dashboard | 5173 | https://news.tunnel.fud.city | | oldweb | 8080 | https://oldweb.tunnel.fud.city | | api | 8000 | https://api.tunnel.fud.city | | fin-terminal | 5177 | https://terminal.tunnel.fud.city | | fin-terminal-api | 3002 | https://terminal-api.tunnel.fud.city |

HTTPS 方式:Wildcard vs Per-Domain

VPS 上的 Caddy 有兩種方式為 tunnel 提供 HTTPS。目前使用 Wildcard(推薦)。

方式 A:Wildcard Certificate(目前使用 ✅)

原理:一張 *.tunnel.fud.city wildcard cert 涵蓋所有子域名。使用 DNS-01 challenge — Caddy 透過 Cloudflare API 自動加 TXT record 驗證。

Caddyfile:

*.tunnel.fud.city {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    reverse_proxy localhost:8080
}

需要:

  • Caddy with Cloudflare DNS plugin(標準版沒有,需重新編譯或下載)
  • Cloudflare API Token(Zone:DNS:Edit 權限,只限 fud.city)
  • Token 設定在 /etc/systemd/system/caddy.service.d/override.conf
  • 新增 tunnel 只需改本地 frpc.toml,不用動 VPS。

    安裝步驟(已完成,僅供紀錄):

    # 1. 下載帶 cloudflare plugin 的 Caddy
    curl -s "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fcloudflare" -o /usr/bin/caddy
    chmod +x /usr/bin/caddy

    2. 驗證 module 存在

    caddy list-modules | grep cloudflare

    3. 設定 CF token

    mkdir -p /etc/systemd/system/caddy.service.d cat > /etc/systemd/system/caddy.service.d/override.conf << EOF [Service] Environment="CF_API_TOKEN=你的token" EOF

    4. 更新 Caddyfile

    cat > /etc/caddy/Caddyfile << EOF *.tunnel.fud.city { tls { dns cloudflare {env.CF_API_TOKEN} } reverse_proxy localhost:8080 } EOF

    5. 重啟

    systemctl daemon-reload systemctl restart caddy

    方式 B:Per-Domain Certificate(備用)

    原理:每個子域名獨立簽一張 cert。使用 HTTP-01 challenge — Let's Encrypt 訪問 http://xxx/.well-known/acme-challenge/ 驗證。

    Caddyfile:

    news.tunnel.fud.city {
        reverse_proxy localhost:8080
    }

    terminal.tunnel.fud.city { reverse_proxy localhost:8080 }

    每加一個 tunnel 就加一段

    需要:標準版 Caddy 即可,不需要 API token。

    缺點:每次加 tunnel 都要 SSH 進 VPS 改 Caddyfile 再 reload Caddy。

    適合場景

  • 不想給 Cloudflare API token
  • 需要不同子域名指向不同 upstream(不只是 frp)
  • 臨時測試
  • 如何切換

    Wildcard → Per-Domain:

    ssh root@5.223.75.160
    

    刪除 override.conf(或保留也無妨)

    把 Caddyfile 改成逐一列出每個域名

    systemctl restart caddy

    Per-Domain → Wildcard: 按上面方式 A 的安裝步驟。

    日常操作

    啟動 frpc(Mac 重啟後需要)

    SOCK=/tmp/openclaw-tmux/openclaw.sock
    tmux -S $SOCK new-session -d -s frpc 'frpc -c ~/.frp/frpc.toml'
    

    檢查 frpc 狀態

    SOCK=/tmp/openclaw-tmux/openclaw.sock
    tmux -S $SOCK capture-pane -t frpc -p -S -10
    

    加新 tunnel(Wildcard 模式 — 只需改本地)

    Step 1 — 本地 config

    編輯 ~/.frp/frpc.toml,加一段:

    [[proxies]]
    name = "new-project"
    type = "http"
    localPort = 3000
    customDomains = ["new.tunnel.fud.city"]
    

    Step 2 — 重啟本地 frpc

    SOCK=/tmp/openclaw-tmux/openclaw.sock
    tmux -S $SOCK send-keys -t frpc C-c
    sleep 2
    tmux -S $SOCK send-keys -t frpc 'frpc -c ~/.frp/frpc.toml' Enter
    

    Step 3 — 驗證(不需要動 VPS!Wildcard cert 自動涵蓋)

    curl -sI https://new.tunnel.fud.city | head -5
    

    確認回傳 HTTP/2 200 才告訴用戶。

    Step 4 — 同步 GitHub 備份

    cd ~/Projects/frp-tunnel
    cp ~/.frp/frpc.toml ./frpc.toml
    git add -A && git commit -m "Add tunnel: new-project" && git push
    

    加新 tunnel(Per-Domain 模式 — 需改 VPS)

    同上 Step 1-2,但在 Step 2 和 3 之間多一步:

    ssh root@5.223.75.160
    

    編輯 /etc/caddy/Caddyfile,加:

    new.tunnel.fud.city {

    reverse_proxy localhost:8080

    }

    systemctl reload caddy

    等 30 秒讓 cert 簽發

    前端 Vite 的 allowedHosts

    Vite dev server 預設會拒絕非 localhost 的 Host header。加 tunnel 時前端 vite.config.ts 也要加:

    server: {
      allowedHosts: ['xxx.tunnel.fud.city'],
    }
    

    臨時開一條(不改 config)

    frpc http --server-addr 5.223.75.160:7000 --local-port 3000 --custom-domain temp.tunnel.fud.city
    

    Wildcard 模式下,臨時 tunnel 也自動有 HTTPS。

    發送 URL 前必做的檢查(SOP)

    每次發送 tunnel URL 給用戶前:

    1. 確認 frpc 在跑tmux capture-pane -t frpc 2. 確認 proxy 成功 — 看到 start proxy success 4. 確認回傳 200 — 不是 502/404/連線失敗 5. 確認內容正確curl -s https://xxx.tunnel.fud.city | grep ""</code>,title 必須是預期的網站名稱 6. <strong style="color:#e5e7eb">確認本地 server 是活的,不是僵屍進程</strong> — <code style="background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em">ps aux | grep <port></code> 檢查進程啟動時間,如果是幾天前啟動的舊進程,很可能已經僵屍化(佔 port 但不正常服務,偶爾回 200 但頁面空白)。殺掉重啟。 7. <strong style="color:#e5e7eb">瀏覽器驗證時禁止信任 cache</strong> — 用無痕模式或 <code style="background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em">curl --resolve</code> 強制走 VPS IP,不要用已開過的 tab</p><p style="margin:8px 0"><h4 style="color:#d1d5db;margin:14px 0 6px;font-size:.95em">⚠️ 已知 Failure Mode:僵屍進程(2026-03-04 事故)</h4></p><p style="margin:8px 0">本地 dev server(如 Vite)長時間不重啟會變成僵屍:佔著 port、偶爾回 HTTP 200,但實際頁面是空白。 curl 檢查看似正常,瀏覽器有 cache 也看似正常,但外部用戶(包含手機)打開是空白。</p><p style="margin:8px 0"><strong style="color:#e5e7eb">排查步驟</strong>: 1. <code style="background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em">ps aux | grep <port></code> — 看進程啟動時間,超過 1-2 天就該懷疑 2. 殺掉舊進程,在 tmux 裡重新 <code style="background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em">npm run dev</code> 3. 重啟後再 curl + grep title 確認</p><p style="margin:8px 0"><h3 style="color:#e5e7eb;margin:18px 0 8px;font-size:1.05em">VPS 故障排除</h3></p><p style="margin:8px 0"><pre style="background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0"><code style="color:#a5f3fc;background:none;padding:0;font-size:1em"># SSH 進 VPS ssh root@5.223.75.160</p><p style="margin:8px 0"><h2 style="color:#f3f4f6;margin:20px 0 10px;font-size:1.15em">檢查 frps</h2> systemctl status frps journalctl -u frps --no-pager -n 20</p><p style="margin:8px 0"><h2 style="color:#f3f4f6;margin:20px 0 10px;font-size:1.15em">檢查 Caddy</h2> systemctl status caddy journalctl -u caddy --no-pager -n 20</p><p style="margin:8px 0"><h2 style="color:#f3f4f6;margin:20px 0 10px;font-size:1.15em">檢查 Caddy modules(確認有 cloudflare)</h2> caddy list-modules | grep cloudflare</p><p style="margin:8px 0"><h2 style="color:#f3f4f6;margin:20px 0 10px;font-size:1.15em">檢查 cert 狀態</h2> caddy cert list 2>/dev/null || journalctl -u caddy | grep "certificate obtained"</p><p style="margin:8px 0"><h2 style="color:#f3f4f6;margin:20px 0 10px;font-size:1.15em">重啟</h2> systemctl restart frps systemctl restart caddy </code></pre></p><p style="margin:8px 0"><h3 style="color:#e5e7eb;margin:18px 0 8px;font-size:1.05em">防火牆規則(VPS ufw)</h3></p><p style="margin:8px 0">| Port | 用途 | |------|------| | 22 | SSH | | 80 | HTTP (Caddy + Let's Encrypt) | | 443 | HTTPS (Caddy) | | 7000 | frp client 連線 | | 7500 | frp Web Dashboard |</p><p style="margin:8px 0"><h3 style="color:#e5e7eb;margin:18px 0 8px;font-size:1.05em">費用</h3></p><p style="margin:8px 0"><li style="color:#94a3b8;margin:3px 0">VPS: $7.59/月(Hetzner CPX12 Singapore + IPv4)</li> <li style="color:#94a3b8;margin:3px 0">域名: 已有 fud.city</li> <li style="color:#94a3b8;margin:3px 0">SSL: 免費(Let's Encrypt via Caddy)</li> <li style="color:#94a3b8;margin:3px 0">流量: 0.5 TB/月(實際用量約 10-15 GB/月)</li> </p></div></section></div><div class="two-col-side"></div></div></div><script> document.querySelectorAll('.copy-btn, .script-copy-btn').forEach(btn => { btn.addEventListener('click', () => { const cmd = btn.getAttribute('data-cmd'); if (!cmd) return; navigator.clipboard.writeText(cmd).then(() => { const orig = btn.textContent; btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = orig, 1500); }).catch(() => {}); }); }); </script><!--$--><!--/$--></main><footer style="background:var(--bg-primary);border-top:1px solid var(--border-secondary);margin-top:60px"><div style="border-top:1px solid var(--border-light);max-width:1200px;margin:0 auto;padding:24px 20px"><div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:24px;margin-bottom:24px"><div><div style="font-weight:700;color:var(--text-muted);margin-bottom:8px">BytesAgain</div><div style="color:var(--text-muted3);font-size:.82em;max-width:200px">Discover the best AI agent skills for your workflow.</div></div><div><div style="color:var(--text-muted);font-size:.75em;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px">Explore</div><div style="margin-bottom:6px"><a href="/skills" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Skills</a></div><div style="margin-bottom:6px"><a href="/articles" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Articles</a></div><div style="margin-bottom:6px"><a href="/use-case" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Cases</a></div></div><div><div style="color:var(--text-muted);font-size:.75em;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px">Company</div><div style="margin-bottom:6px"><a href="/about" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">About</a></div><div style="margin-bottom:6px"><a href="/contact" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Contact</a></div><div style="margin-bottom:6px"><a href="/privacy-policy" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Privacy Policy</a></div><div style="margin-bottom:6px"><a href="/terms" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Terms</a></div><div style="margin-bottom:6px"><a href="/feedback" style="color:var(--text-muted2);text-decoration:none;font-size:.85em">Feedback</a></div></div></div><div style="border-top:1px solid var(--border-light);padding-top:16px"><div style="color:var(--text-muted4);font-size:.8em;margin-bottom:8px">© <!-- -->2026<!-- --> BytesAgain. All rights reserved.</div><div style="color:var(--text-muted5);font-size:.75em;line-height:1.6;max-width:720px">BytesAgain is an independent skill directory. We index and link to third-party content (ClawHub, GitHub, LobeHub, Dify, etc.) for informational purposes only. All trademarks, skill names, and content are the property of their respective owners. BytesAgain does not claim ownership of any indexed content.</div></div></div></footer><button style="position:fixed;bottom:28px;right:28px;z-index:1000;width:48px;height:48px;border-radius:50%;border:none;cursor:pointer;background:linear-gradient(135deg,#667eea,#00d4ff);color:#fff;font-size:1.3em;box-shadow:0 4px 20px #667eea66;display:flex;align-items:center;justify-content:center;transition:transform .2s">💬</button><script src="/_next/static/chunks/0ze4gu236oq96.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[62894,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"LangProvider\"]\n3:I[89220,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"ThemeProvider\"]\n4:I[16988,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\ne:I[68027,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\",1]\n:HL[\"/_next/static/chunks/051nc0vy_6.rl.css?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"style\"]\n:HL[\"/_next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n5:Td5e,"])</script><script>self.__next_f.push([1,"[{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"BytesAgain\",\"url\":\"https://bytesagain.com\",\"description\":\"Search 60,000+ verified AI agent skills via MCP API or REST. Supports 7 languages. Free, no auth required.\",\"inLanguage\":[\"en\",\"zh\",\"es\",\"fr\",\"de\",\"ja\",\"ko\"],\"potentialAction\":{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https://bytesagain.com/skills?q={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}},{\"@context\":\"https://schema.org\",\"@type\":\"Organization\",\"name\":\"BytesAgain\",\"url\":\"https://bytesagain.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://bytesagain.com/og-image.png\"},\"description\":\"AI agent skill directory. Search 60,000+ skills, 1,000+ use cases, and community requests.\",\"foundingDate\":\"2026\",\"foundingLocation\":{\"@type\":\"Place\",\"name\":\"Global\"},\"sameAs\":[\"https://x.com/bytesagain\",\"https://github.com/bytesagain/ai-skills\",\"https://clawhub.ai/profile/bytesagain\"],\"contactPoint\":{\"@type\":\"ContactPoint\",\"email\":\"hello@bytesagain.com\",\"contactType\":\"customer support\"},\"numberOfEmployees\":{\"@type\":\"QuantitativeValue\",\"value\":1}},{\"@context\":\"https://schema.org\",\"@type\":\"WebApplication\",\"name\":\"BytesAgain AI Skills Search\",\"url\":\"https://bytesagain.com\",\"applicationCategory\":\"DeveloperApplication\",\"operatingSystem\":\"Web\",\"description\":\"Search engine and MCP API for 60,000+ AI agent skills. Semantic search, role recommendations, and use case packs.\",\"offers\":{\"@type\":\"Offer\",\"price\":\"0\",\"priceCurrency\":\"USD\"},\"featureList\":[\"Search 60,000+ AI agent skills\",\"Role-based recommendations for developers, creators, and traders\",\"1,000+ curated use case packs\",\"Free MCP API and REST API\",\"Multi-language search (EN, ZH, ES, FR, DE, JA, KO)\"],\"potentialAction\":{\"@type\":\"SearchAction\",\"target\":\"https://bytesagain.com/skills?q={search_term_string}\",\"query-input\":\"required name=search_term_string\"},\"dateModified\":\"2026-07-18\"},{\"@context\":\"https://schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"What is BytesAgain?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"BytesAgain is a curated directory of 60,000+ AI agent skills from ClawHub, GitHub, LobeHub, and Dify. Search skills by keyword in 7 languages, browse by role (developer, creator, trader, marketer) or by use case.\"}},{\"@type\":\"Question\",\"name\":\"How do I find AI skills on BytesAgain?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Use the search bar on BytesAgain.com to search by keyword in 7 languages. You can also browse by role (developer, creator, trader, marketer) or by use case. Each skill shows install instructions for Claude, Cursor, OpenClaw, Continue, and more.\"}},{\"@type\":\"Question\",\"name\":\"Is BytesAgain free?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes, BytesAgain is completely free. No registration required for searching skills. The MCP API is also free with rate limits.\"}},{\"@type\":\"Question\",\"name\":\"Does BytesAgain have an API for AI agents?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes! BytesAgain provides a free MCP SSE endpoint at /api/mcp/sse for AI agents, plus a REST API at /api/mcp?action=search\u0026q=\u003cquery\u003e. No authentication needed.\"}},{\"@type\":\"Question\",\"name\":\"Can I request a new AI skill on BytesAgain?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes! Visit the Requests page on BytesAgain.com to submit a skill request. Your request will be visible to the community and notified to the site admin.\"}}]}]"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"skill\",\"frp-tunnel\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"skill\",{\"children\":[[\"slug\",\"frp-tunnel\",\"d\",null],{\"children\":[\"__PAGE__\",{}]}]}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/051nc0vy_6.rl.css?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-2\",{\"src\":\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"link\",null,{\"rel\":\"llms\",\"href\":\"/llms.txt\"}],[\"$\",\"link\",null,{\"rel\":\"llms-full\",\"href\":\"/llms-full.txt\"}],[\"$\",\"script\",null,{\"async\":true,\"src\":\"https://www.googletagmanager.com/gtag/js?id=G-3C1MM9FWYF\"}],[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"\\n window.dataLayer = window.dataLayer || [];\\n function gtag(){dataLayer.push(arguments);}\\n gtag('js', new Date());\\n gtag('config', 'G-3C1MM9FWYF');\\n \"}}]]}],[\"$\",\"body\",null,{\"className\":\"geist_9e050971-module__05dp7a__className\",\"style\":{\"margin\":0},\"children\":[\"$\",\"$L2\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[[\"$\",\"div\",null,{\"style\":{\"width\":\"100%\",\"background\":\"var(--bg-subscribe)\",\"borderBottom\":\"1px solid var(--border-primary)\",\"padding\":\"8px 20px\",\"textAlign\":\"center\",\"fontSize\":\".82em\",\"color\":\"#818cf8\"},\"children\":[\"🎁 \",[\"$\",\"strong\",null,{\"style\":{\"color\":\"var(--text-primary)\"},\"children\":\"Get the FREE AI Skills Starter Guide\"}],\" — \",[\"$\",\"a\",null,{\"href\":\"/register\",\"style\":{\"color\":\"#00d4ff\",\"textDecoration\":\"underline\"},\"children\":\"Subscribe →\"}]]}],[\"$\",\"$L4\",null,{}],[\"$\",\"script\",null,{\"type\":\"application/ld+json\",\"dangerouslySetInnerHTML\":{\"__html\":\"$5\"}}],\"$L6\",\"$L7\",\"$L8\"]}]}]}]]}]]}],{\"children\":[\"$L9\",{\"children\":[\"$La\",{\"children\":[\"$Lb\",{},null,false,null]},null,false,\"$@c\"]},null,false,\"$@c\"]},null,false,null],\"$Ld\",false]],\"m\":\"$undefined\",\"G\":[\"$e\",[\"$Lf\"]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"10:I[39756,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n11:I[37457,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n12:I[22016,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0ka051yepewro.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"\"]\n13:I[90940,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n14:I[16397,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n16:I[97367,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"OutletBoundary\"]\n17:\"$Sreact.suspense\"\n1a:I[97367,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"ViewportBoundary\"]\n1c:I[97367,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"MetadataBoundary\"]\n"])</script><script>self.__next_f.push([1,"6:[\"$\",\"main\",null,{\"children\":[\"$\",\"$L10\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L11\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"main\",null,{\"style\":{\"minHeight\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"background\":\"#050611\",\"color\":\"#e5e7eb\"},\"children\":[[\"$\",\"style\",null,{\"children\":\"\\n .nf-box { text-align: center; padding: 60px 32px; }\\n .nf-code { font-size: 6rem; font-weight: 900; color: #22d3ee; line-height: 1; margin: 0; }\\n .nf-title { font-size: 1.8rem; font-weight: 800; margin: 12px 0 8px; }\\n .nf-desc { color: var(--text-muted2); font-size: 1rem; margin-bottom: 32px; max-width: 440px; }\\n .nf-link { display: inline-block; padding: 12px 28px; background: linear-gradient(135deg,#34d399,#22d3ee); color: #000; font-weight: 900; border-radius: 12px; text-decoration: none; }\\n \"}],[\"$\",\"div\",null,{\"className\":\"nf-box\",\"children\":[[\"$\",\"p\",null,{\"className\":\"nf-code\",\"children\":\"404\"}],[\"$\",\"h1\",null,{\"className\":\"nf-title\",\"children\":\"Page Not Found\"}],[\"$\",\"p\",null,{\"className\":\"nf-desc\",\"children\":\"The skill or page you're looking for doesn't exist or has been moved.\"}],[\"$\",\"$L12\",null,{\"className\":\"nf-link\",\"href\":\"/\",\"children\":\"Back to BytesAgain\"}]]}]]}],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]\n"])</script><script>self.__next_f.push([1,"7:[\"$\",\"$L13\",null,{}]\n8:[\"$\",\"$L14\",null,{}]\n9:[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L10\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L11\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}]\na:[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L10\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L11\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}]\nb:[\"$\",\"$1\",\"c\",{\"children\":[\"$L15\",[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/12w5ognupk9fb.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L16\",null,{\"children\":[\"$\",\"$17\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@18\"}]}]]}]\n19:[]\nc:\"$W19\"\nd:[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L1a\",null,{\"children\":\"$L1b\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$L1c\",null,{\"children\":[\"$\",\"$17\",null,{\"name\":\"Next.Metadata\",\"children\":\"$L1d\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}]\nf:[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/051nc0vy_6.rl.css?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]\n"])</script><script>self.__next_f.push([1,"1b:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"1e:I[27201,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"IconMark\"]\n18:null\n"])</script><script>self.__next_f.push([1,"1d:[[\"$\",\"title\",\"0\",{\"children\":\"frp-tunnel — AI Agent Skill | BytesAgain | BytesAgain\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...\"}],[\"$\",\"meta\",\"2\",{\"name\":\"robots\",\"content\":\"index, follow\"}],[\"$\",\"meta\",\"3\",{\"name\":\"googlebot\",\"content\":\"index, follow, max-image-preview:large, max-snippet:-1\"}],[\"$\",\"meta\",\"4\",{\"name\":\"llms-txt\",\"content\":\"https://bytesagain.com/llms.txt\"}],[\"$\",\"meta\",\"5\",{\"name\":\"llms-full-txt\",\"content\":\"https://bytesagain.com/llms-full.txt\"}],[\"$\",\"link\",\"6\",{\"rel\":\"canonical\",\"href\":\"https://bytesagain.com/skill/frp-tunnel\"}],[\"$\",\"meta\",\"7\",{\"name\":\"baidu-site-verification\",\"content\":\"codeva-0evUqX1TFs\"}],[\"$\",\"meta\",\"8\",{\"property\":\"og:title\",\"content\":\"frp-tunnel — AI Agent Skill | BytesAgain\"}],[\"$\",\"meta\",\"9\",{\"property\":\"og:description\",\"content\":\"Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...\"}],[\"$\",\"meta\",\"10\",{\"property\":\"og:url\",\"content\":\"https://bytesagain.com/skill/frp-tunnel\"}],[\"$\",\"meta\",\"11\",{\"property\":\"og:site_name\",\"content\":\"BytesAgain\"}],[\"$\",\"meta\",\"12\",{\"property\":\"og:image\",\"content\":\"https://bytesagain.com/social-preview.png\"}],[\"$\",\"meta\",\"13\",{\"property\":\"og:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"14\",{\"property\":\"og:image:height\",\"content\":\"630\"}],[\"$\",\"meta\",\"15\",{\"property\":\"og:type\",\"content\":\"website\"}],[\"$\",\"meta\",\"16\",{\"name\":\"twitter:card\",\"content\":\"summary_large_image\"}],[\"$\",\"meta\",\"17\",{\"name\":\"twitter:title\",\"content\":\"frp-tunnel — AI Agent Skill | BytesAgain\"}],[\"$\",\"meta\",\"18\",{\"name\":\"twitter:description\",\"content\":\"Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...\"}],[\"$\",\"meta\",\"19\",{\"name\":\"twitter:image\",\"content\":\"https://bytesagain.com/social-preview.png\"}],[\"$\",\"meta\",\"20\",{\"name\":\"twitter:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"21\",{\"name\":\"twitter:image:height\",\"content\":\"630\"}],[\"$\",\"link\",\"22\",{\"rel\":\"icon\",\"href\":\"/favicon.ico?favicon.0x3dzn~oxb6tn.ico\",\"sizes\":\"256x256\",\"type\":\"image/x-icon\"}],[\"$\",\"$L1e\",\"23\",{}]]\n"])</script><script>self.__next_f.push([1,"1f:T1562,"])</script><script>self.__next_f.push([1,"\n .skill-page { max-width: 1100px; margin: 0 auto; padding: 32px 20px 80px; }\n .two-col { display: flex; gap: 32px; align-items: flex-start; }\n .two-col-main { flex: 1; min-width: 0; }\n .two-col-side { width: 300px; flex-shrink: 0; }\n @media (max-width: 860px) {\n .two-col { flex-direction: column; }\n .two-col-side { width: 100%; }\n }\n .breadcrumb { font-size: .82em; color: var(--text-muted2); margin-bottom: 28px; }\n .breadcrumb a { color: #818cf8; text-decoration: none; }\n .breadcrumb a:hover { text-decoration: underline; }\n .skill-card { background: var(--bg-card); border: 1px solid var(--border-card); border-radius: 20px; padding: 28px; margin-bottom: 24px; }\n .skill-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }\n .skill-badges { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n .skill-top-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }\n .badge { display: inline-flex; align-items: center; gap: 5px; font-size: .75em; font-weight: 600; padding: 4px 12px; border-radius: 999px; border: 1px solid transparent; }\n .skill-title { font-size: 1.6em; font-weight: 800; color: var(--text-primary); margin: 0 0 4px; line-height: 1.2; }\n .skill-owner { font-size: .82em; color: var(--text-muted2); margin: 0 0 14px; }\n .skill-owner span { color: #818cf8; }\n .skill-desc { font-size: .92em; color: var(--text-secondary); line-height: 1.65; margin: 0 0 16px; }\n .skill-meta { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--border-card); }\n .meta-item { display: flex; flex-direction: column; gap: 2px; }\n .meta-label { font-size: .7em; color: var(--text-muted5); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; }\n .meta-value { font-size: .92em; color: var(--text-muted2); font-weight: 600; }\n .tags-row { display: flex; gap: 6px; flex-wrap: wrap; }\n .tag { font-size: .75em; color: #6366f1; background: #6366f115; border: 1px solid #6366f130; border-radius: 6px; padding: 3px 10px; text-decoration: none; }\n .tag:hover { background: #6366f125; }\n .install-box { background: var(--bg-deep); border: 1px solid var(--border-card); border-radius: 12px; overflow: hidden; margin-bottom: 24px; }\n .install-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid var(--border-card); }\n .install-dots { display: flex; gap: 6px; }\n .dot { width: 10px; height: 10px; border-radius: 50%; }\n .install-label { font-size: .72em; color: var(--text-muted5); font-family: monospace; letter-spacing: 1px; }\n .install-body { padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n .install-cmd { color: var(--text-code);\n font-family: 'Courier New', monospace; font-size: 1em; }\n .copy-btn { font-size: .75em; color: #6366f1; background: #6366f115; border: 1px solid #6366f130; border-radius: 6px; padding: 5px 12px; cursor: pointer; white-space: nowrap; transition: all .15s; }\n .copy-btn:hover { background: #6366f125; }\n .btn-secondary { display: inline-flex; align-items: center; gap: 8px; padding: 13px 24px; background: transparent; border: 1px solid var(--border-card); border-radius: 10px; color: #6b7280; text-decoration: none; font-weight: 600; font-size: .95em; transition: all .15s; }\n .btn-secondary:hover { border-color: #818cf8; color: #818cf8; }\n .ours-badge { display: inline-flex; align-items: center; gap: 6px; font-size: .72em; font-weight: 700; color: #22d3ee; background: #22d3ee10; border: 1px solid #22d3ee30; border-radius: 999px; padding: 4px 14px; }\n .section-card { background: var(--bg-card); border: 1px solid var(--border-card); border-radius: 16px; padding: 22px 24px; margin-bottom: 20px; }\n .section-title { color: var(--text-primary); font-size: 1.08em; font-weight: 800; margin: 0 0 12px; display: flex; align-items: center; gap: 8px; }\n /* Script box */\n .script-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 14px; background: var(--bg-input); border-bottom: 1px solid var(--border-card); }\n .script-filename { font-size: .72em; color: var(--text-muted2); font-family: 'Courier New', monospace; }\n .script-copy-btn { font-size: .72em; color: #6366f1; background: none; border: 1px solid #6366f130; border-radius: 4px; padding: 2px 10px; cursor: pointer; }\n .script-copy-btn:hover { background: #6366f115; }\n .script-body { padding: 14px 16px; font-family: 'Courier New', monospace; font-size: .82em; line-height: 1.6; color: var(--text-code); overflow-x: auto; max-height: 420px; overflow-y: auto; white-space: pre; }\n /* Articles */\n .article-card { display: block; background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: 10px; padding: 14px 16px; text-decoration: none; transition: border-color .15s; }\n .article-card:hover { border-color: #6366f1; }\n @media (max-width: 600px) {\n .skill-card { padding: 20px; }\n .skill-title { font-size: 1.5em; }\n }\n "])</script><script>self.__next_f.push([1,"15:[[\"$\",\"style\",null,{\"children\":\"$1f\"}],\"$L20\",\"$L21\"]\n"])</script><script>self.__next_f.push([1,"22:I[78297,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/12w5ognupk9fb.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n23:T4ad6,"])</script><script>self.__next_f.push([1,"\u003cp style=\"margin:8px 0\"\u003e\u003chr style=\"border:none;border-top:1px solid #1e1e3f;margin:12px 0\"\u003e\nname: frp-tunnel\ndescription: Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clients, or test on mobile. Replaces ngrok/localhost.run with a stable, unlimited, self-hosted solution.\n\u003chr style=\"border:none;border-top:1px solid #1e1e3f;margin:12px 0\"\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003efrp-tunnel — 自建內網穿透\u003c/h2\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e用自建的 frp + Caddy + Hetzner VPS 分享本地開發中的網站。自訂域名、自動 HTTPS、無限 tunnel。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003e 替代方案(無 VPS 時):見 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003eold-share-local-site\u003c/code\u003e skill(ngrok/localhost.run)\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e⚙️ Config(自訂區塊)\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e使用此 skill 前,請將以下值替換成你自己的環境:\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e| 變數 | 目前的值 | 說明 |\n|------|---------|------|\n| VPS IP | \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003e5.223.75.160\u003c/code\u003e | 你的 VPS 公網 IP |\n| 域名 | \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003e*.tunnel.fud.city\u003c/code\u003e | 你的 wildcard 子域名 |\n| DNS Provider | Cloudflare (\u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003efud.city\u003c/code\u003e) | 管理你域名 DNS 的服務 |\n| GitHub 備份 | \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003ehttps://github.com/darwin7381/frp-tunnel\u003c/code\u003e | 你的 private repo(可選) |\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003e \u003cstrong style=\"color:#e5e7eb\"\u003eDashboard 密碼\u003c/strong\u003e:不記在這裡。在 VPS 的 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003efrps.toml\u003c/code\u003e 裡設定你自己的密碼。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e基礎資訊\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e| 項目 | 值 |\n|------|-----|\n| VPS IP | 5.223.75.160 |\n| VPS Provider | Hetzner Cloud, Singapore, CPX12 |\n| 域名 | *.tunnel.fud.city |\n| DNS | Cloudflare (fud.city) |\n| HTTPS 方式 | \u003cstrong style=\"color:#e5e7eb\"\u003eWildcard cert\u003c/strong\u003e (DNS-01 challenge via Cloudflare API) |\n| frp Dashboard | http://5.223.75.160:7500 |\n| GitHub 備份 | https://github.com/darwin7381/frp-tunnel (private) |\n| 本地 config | ~/.frp/frpc.toml |\n| 本地 tmux session | \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003efrpc\u003c/code\u003e |\n| VPS services | frps (systemd) + caddy (systemd) |\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e目前的 Tunnels\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e| 名稱 | 本地 Port | URL |\n|------|----------|-----|\n| news-dashboard | 5173 | https://news.tunnel.fud.city |\n| oldweb | 8080 | https://oldweb.tunnel.fud.city |\n| api | 8000 | https://api.tunnel.fud.city |\n| fin-terminal | 5177 | https://terminal.tunnel.fud.city |\n| fin-terminal-api | 3002 | https://terminal-api.tunnel.fud.city |\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003eHTTPS 方式:Wildcard vs Per-Domain\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003eVPS 上的 Caddy 有兩種方式為 tunnel 提供 HTTPS。\u003cstrong style=\"color:#e5e7eb\"\u003e目前使用 Wildcard(推薦)。\u003c/strong\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e方式 A:Wildcard Certificate(目前使用 ✅)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e原理\u003c/strong\u003e:一張 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003e*.tunnel.fud.city\u003c/code\u003e wildcard cert 涵蓋所有子域名。使用 DNS-01 challenge — Caddy 透過 Cloudflare API 自動加 TXT record 驗證。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eCaddyfile:\u003c/strong\u003e\n\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003e*.tunnel.fud.city {\n tls {\n dns cloudflare {env.CF_API_TOKEN}\n }\n reverse_proxy localhost:8080\n}\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e需要:\u003c/strong\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003eCaddy with Cloudflare DNS plugin(標準版沒有,需重新編譯或下載)\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003eCloudflare API Token(Zone:DNS:Edit 權限,只限 fud.city)\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003eToken 設定在 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003e/etc/systemd/system/caddy.service.d/override.conf\u003c/code\u003e\u003c/li\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e新增 tunnel 只需改本地 frpc.toml,不用動 VPS。\u003c/strong\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e安裝步驟(已完成,僅供紀錄):\u003c/strong\u003e\n\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003e# 1. 下載帶 cloudflare plugin 的 Caddy\ncurl -s \"https://caddyserver.com/api/download?os=linux\u0026arch=amd64\u0026p=github.com%2Fcaddy-dns%2Fcloudflare\" -o /usr/bin/caddy\nchmod +x /usr/bin/caddy\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e2. 驗證 module 存在\u003c/h2\u003e\ncaddy list-modules | grep cloudflare\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e3. 設定 CF token\u003c/h2\u003e\nmkdir -p /etc/systemd/system/caddy.service.d\ncat \u003e /etc/systemd/system/caddy.service.d/override.conf \u003c\u003c EOF\n[Service]\nEnvironment=\"CF_API_TOKEN=你的token\"\nEOF\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e4. 更新 Caddyfile\u003c/h2\u003e\ncat \u003e /etc/caddy/Caddyfile \u003c\u003c EOF\n*.tunnel.fud.city {\n tls {\n dns cloudflare {env.CF_API_TOKEN}\n }\n reverse_proxy localhost:8080\n}\nEOF\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e5. 重啟\u003c/h2\u003e\nsystemctl daemon-reload\nsystemctl restart caddy\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e方式 B:Per-Domain Certificate(備用)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e原理\u003c/strong\u003e:每個子域名獨立簽一張 cert。使用 HTTP-01 challenge — Let's Encrypt 訪問 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003ehttp://xxx/.well-known/acme-challenge/\u003c/code\u003e 驗證。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eCaddyfile:\u003c/strong\u003e\n\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003enews.tunnel.fud.city {\n reverse_proxy localhost:8080\n}\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003eterminal.tunnel.fud.city {\n reverse_proxy localhost:8080\n}\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e每加一個 tunnel 就加一段\u003c/h2\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e需要\u003c/strong\u003e:標準版 Caddy 即可,不需要 API token。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e缺點\u003c/strong\u003e:每次加 tunnel 都要 SSH 進 VPS 改 Caddyfile 再 reload Caddy。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e適合場景\u003c/strong\u003e:\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003e不想給 Cloudflare API token\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003e需要不同子域名指向不同 upstream(不只是 frp)\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003e臨時測試\u003c/li\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e如何切換\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eWildcard → Per-Domain:\u003c/strong\u003e\n\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003essh root@5.223.75.160\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e刪除 override.conf(或保留也無妨)\u003c/h2\u003e\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e把 Caddyfile 改成逐一列出每個域名\u003c/h2\u003e\nsystemctl restart caddy\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003ePer-Domain → Wildcard:\u003c/strong\u003e\n按上面方式 A 的安裝步驟。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e日常操作\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e啟動 frpc(Mac 重啟後需要)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003eSOCK=/tmp/openclaw-tmux/openclaw.sock\ntmux -S $SOCK new-session -d -s frpc 'frpc -c ~/.frp/frpc.toml'\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e檢查 frpc 狀態\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003eSOCK=/tmp/openclaw-tmux/openclaw.sock\ntmux -S $SOCK capture-pane -t frpc -p -S -10\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e加新 tunnel(Wildcard 模式 — 只需改本地)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eStep 1 — 本地 config\u003c/strong\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e編輯 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003e~/.frp/frpc.toml\u003c/code\u003e,加一段:\n\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003e[[proxies]]\nname = \"new-project\"\ntype = \"http\"\nlocalPort = 3000\ncustomDomains = [\"new.tunnel.fud.city\"]\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eStep 2 — 重啟本地 frpc\u003c/strong\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003eSOCK=/tmp/openclaw-tmux/openclaw.sock\ntmux -S $SOCK send-keys -t frpc C-c\nsleep 2\ntmux -S $SOCK send-keys -t frpc 'frpc -c ~/.frp/frpc.toml' Enter\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eStep 3 — 驗證\u003c/strong\u003e(不需要動 VPS!Wildcard cert 自動涵蓋)\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003ecurl -sI https://new.tunnel.fud.city | head -5\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e確認回傳 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003eHTTP/2 200\u003c/code\u003e 才告訴用戶。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003eStep 4 — 同步 GitHub 備份\u003c/strong\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003ecd ~/Projects/frp-tunnel\ncp ~/.frp/frpc.toml ./frpc.toml\ngit add -A \u0026\u0026 git commit -m \"Add tunnel: new-project\" \u0026\u0026 git push\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e加新 tunnel(Per-Domain 模式 — 需改 VPS)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e同上 Step 1-2,但在 Step 2 和 3 之間多一步:\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003essh root@5.223.75.160\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e編輯 /etc/caddy/Caddyfile,加:\u003c/h2\u003e\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003enew.tunnel.fud.city {\u003c/h2\u003e\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e reverse_proxy localhost:8080\u003c/h2\u003e\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e}\u003c/h2\u003e\nsystemctl reload caddy\n\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e等 30 秒讓 cert 簽發\u003c/h2\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e前端 Vite 的 allowedHosts\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003eVite dev server 預設會拒絕非 localhost 的 Host header。加 tunnel 時前端 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003evite.config.ts\u003c/code\u003e 也要加:\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003eserver: {\n allowedHosts: ['xxx.tunnel.fud.city'],\n}\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e臨時開一條(不改 config)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003efrpc http --server-addr 5.223.75.160:7000 --local-port 3000 --custom-domain temp.tunnel.fud.city\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003eWildcard 模式下,臨時 tunnel 也自動有 HTTPS。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e發送 URL 前必做的檢查(SOP)\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e每次\u003c/strong\u003e發送 tunnel URL 給用戶前:\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e1. \u003cstrong style=\"color:#e5e7eb\"\u003e確認 frpc 在跑\u003c/strong\u003e — \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003etmux capture-pane -t frpc\u003c/code\u003e\n2. \u003cstrong style=\"color:#e5e7eb\"\u003e確認 proxy 成功\u003c/strong\u003e — 看到 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003estart proxy success\u003c/code\u003e\n4. \u003cstrong style=\"color:#e5e7eb\"\u003e確認回傳 200\u003c/strong\u003e — 不是 502/404/連線失敗\n5. \u003cstrong style=\"color:#e5e7eb\"\u003e確認內容正確\u003c/strong\u003e — \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003ecurl -s https://xxx.tunnel.fud.city | grep \"\u003ctitle\u003e\"\u003c/code\u003e,title 必須是預期的網站名稱\n6. \u003cstrong style=\"color:#e5e7eb\"\u003e確認本地 server 是活的,不是僵屍進程\u003c/strong\u003e — \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003eps aux | grep \u003cport\u003e\u003c/code\u003e 檢查進程啟動時間,如果是幾天前啟動的舊進程,很可能已經僵屍化(佔 port 但不正常服務,偶爾回 200 但頁面空白)。殺掉重啟。\n7. \u003cstrong style=\"color:#e5e7eb\"\u003e瀏覽器驗證時禁止信任 cache\u003c/strong\u003e — 用無痕模式或 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003ecurl --resolve\u003c/code\u003e 強制走 VPS IP,不要用已開過的 tab\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch4 style=\"color:#d1d5db;margin:14px 0 6px;font-size:.95em\"\u003e⚠️ 已知 Failure Mode:僵屍進程(2026-03-04 事故)\u003c/h4\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e本地 dev server(如 Vite)長時間不重啟會變成僵屍:佔著 port、偶爾回 HTTP 200,但實際頁面是空白。\ncurl 檢查看似正常,瀏覽器有 cache 也看似正常,但外部用戶(包含手機)打開是空白。\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cstrong style=\"color:#e5e7eb\"\u003e排查步驟\u003c/strong\u003e:\n1. \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003eps aux | grep \u003cport\u003e\u003c/code\u003e — 看進程啟動時間,超過 1-2 天就該懷疑\n2. 殺掉舊進程,在 tmux 裡重新 \u003ccode style=\"background:#0d0d1e;color:#a5f3fc;padding:1px 5px;border-radius:3px;font-size:.88em\"\u003enpm run dev\u003c/code\u003e\n3. 重啟後再 curl + grep title 確認\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003eVPS 故障排除\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cpre style=\"background:#0a0a1c;border:1px solid #1e1e3f;border-radius:6px;padding:10px 12px;overflow-x:auto;font-size:.9em;margin:8px 0\"\u003e\u003ccode style=\"color:#a5f3fc;background:none;padding:0;font-size:1em\"\u003e# SSH 進 VPS\nssh root@5.223.75.160\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e檢查 frps\u003c/h2\u003e\nsystemctl status frps\njournalctl -u frps --no-pager -n 20\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e檢查 Caddy\u003c/h2\u003e\nsystemctl status caddy\njournalctl -u caddy --no-pager -n 20\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e檢查 Caddy modules(確認有 cloudflare)\u003c/h2\u003e\ncaddy list-modules | grep cloudflare\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e檢查 cert 狀態\u003c/h2\u003e\ncaddy cert list 2\u003e/dev/null || journalctl -u caddy | grep \"certificate obtained\"\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch2 style=\"color:#f3f4f6;margin:20px 0 10px;font-size:1.15em\"\u003e重啟\u003c/h2\u003e\nsystemctl restart frps\nsystemctl restart caddy\n\u003c/code\u003e\u003c/pre\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e防火牆規則(VPS ufw)\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e| Port | 用途 |\n|------|------|\n| 22 | SSH |\n| 80 | HTTP (Caddy + Let's Encrypt) |\n| 443 | HTTPS (Caddy) |\n| 7000 | frp client 連線 |\n| 7500 | frp Web Dashboard |\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003ch3 style=\"color:#e5e7eb;margin:18px 0 8px;font-size:1.05em\"\u003e費用\u003c/h3\u003e\u003c/p\u003e\u003cp style=\"margin:8px 0\"\u003e\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003eVPS: $7.59/月(Hetzner CPX12 Singapore + IPv4)\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003e域名: 已有 fud.city\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003eSSL: 免費(Let's Encrypt via Caddy)\u003c/li\u003e\n\u003cli style=\"color:#94a3b8;margin:3px 0\"\u003e流量: 0.5 TB/月(實際用量約 10-15 GB/月)\u003c/li\u003e\n\u003c/p\u003e"])</script><script>self.__next_f.push([1,"20:[\"$\",\"div\",null,{\"className\":\"skill-page\",\"children\":[[\"$\",\"script\",null,{\"type\":\"application/ld+json\",\"dangerouslySetInnerHTML\":{\"__html\":\"{\\\"@context\\\":\\\"https://schema.org\\\",\\\"@type\\\":\\\"SoftwareApplication\\\",\\\"name\\\":\\\"frp-tunnel\\\",\\\"description\\\":\\\"Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...\\\",\\\"url\\\":\\\"https://bytesagain.com/skill/frp-tunnel\\\",\\\"applicationCategory\\\":\\\"clawhub\\\",\\\"operatingSystem\\\":\\\"Any\\\",\\\"offers\\\":{\\\"@type\\\":\\\"Offer\\\",\\\"price\\\":\\\"0\\\",\\\"priceCurrency\\\":\\\"USD\\\"},\\\"publisher\\\":{\\\"@type\\\":\\\"Organization\\\",\\\"name\\\":\\\"BytesAgain\\\",\\\"url\\\":\\\"https://bytesagain.com\\\"}}\"}}],[\"$\",\"div\",null,{\"className\":\"breadcrumb\",\"children\":[[\"$\",\"a\",null,{\"href\":\"/\",\"children\":\"BytesAgain\"}],\" › \",[\"$\",\"a\",null,{\"href\":\"/skills\",\"children\":\"Skills\"}],\" › \",\"frp-tunnel\"]}],[\"$\",\"div\",null,{\"className\":\"two-col\",\"children\":[[\"$\",\"div\",null,{\"className\":\"two-col-main\",\"children\":[[\"$\",\"div\",null,{\"className\":\"skill-card\",\"children\":[[\"$\",\"div\",null,{\"className\":\"skill-header\",\"children\":[[\"$\",\"div\",null,{\"className\":\"skill-badges\",\"children\":[[\"$\",\"span\",null,{\"className\":\"badge\",\"style\":{\"color\":\"#818cf8\",\"background\":\"#818cf822\",\"borderColor\":\"#818cf844\"},\"children\":[\"🦀\",\" \",\"ClawHub\"]}],false]}],[\"$\",\"div\",null,{\"className\":\"skill-top-actions\",\"children\":[\"$\",\"$L22\",null,{\"slug\":\"frp-tunnel\"}]}]]}],[\"$\",\"h1\",null,{\"className\":\"skill-title\",\"children\":\"frp-tunnel\"}],[\"$\",\"p\",null,{\"className\":\"skill-owner\",\"children\":[\"by \",[\"$\",\"span\",null,{\"children\":[\"@\",\"darwin7381\"]}]]}],[\"$\",\"p\",null,{\"className\":\"skill-desc\",\"children\":\"Share local development servers via self-hosted frp tunnel with custom domains and auto HTTPS. Use when you need to share localhost with others, demo to clie...\"}],[\"$\",\"div\",null,{\"className\":\"skill-meta\",\"children\":[[\"$\",\"div\",null,{\"className\":\"meta-item\",\"children\":[[\"$\",\"span\",null,{\"className\":\"meta-label\",\"children\":\"Version\"}],[\"$\",\"span\",null,{\"className\":\"meta-value\",\"children\":[\"v\",\"1.0.0\"]}]]}],[\"$\",\"div\",null,{\"className\":\"meta-item\",\"children\":[[\"$\",\"span\",null,{\"className\":\"meta-label\",\"children\":\"Downloads\"}],[\"$\",\"span\",null,{\"className\":\"meta-value\",\"children\":\"792\"}]]}],false,false,false,[\"$\",\"div\",null,{\"className\":\"meta-item\",\"style\":{\"flexDirection\":\"row\",\"gap\":6,\"alignItems\":\"center\"},\"children\":[[\"$\",\"a\",\"clawhub\",{\"href\":\"/?q=clawhub\",\"className\":\"tag\",\"children\":[\"#\",\"clawhub\"]}]]}]]}],[\"$\",\"div\",null,{\"style\":{\"marginTop\":6},\"children\":[\"$\",\"a\",null,{\"href\":\"https://clawhub.ai/darwin7381/frp-tunnel\",\"target\":\"_blank\",\"rel\":\"noopener\",\"className\":\"btn-secondary\",\"style\":{\"padding\":\"6px 12px\",\"fontSize\":\".82em\",\"borderRadius\":8,\"background\":\"transparent\",\"border\":\"1px solid var(--border-card)\",\"color\":\"var(--text-muted2)\",\"textDecoration\":\"none\",\"whiteSpace\":\"nowrap\"},\"children\":[\"View on \",\"ClawHub\",\" →\"]}]}]]}],[\"$\",\"div\",null,{\"className\":\"install-box\",\"children\":[[\"$\",\"div\",null,{\"className\":\"install-header\",\"children\":[[\"$\",\"div\",null,{\"className\":\"install-dots\",\"children\":[[\"$\",\"div\",null,{\"className\":\"dot\",\"style\":{\"background\":\"#ef4444\"}}],[\"$\",\"div\",null,{\"className\":\"dot\",\"style\":{\"background\":\"#eab308\"}}],[\"$\",\"div\",null,{\"className\":\"dot\",\"style\":{\"background\":\"#22c55e\"}}]]}],[\"$\",\"span\",null,{\"className\":\"install-label\",\"children\":\"TERMINAL\"}]]}],[\"$\",\"div\",null,{\"className\":\"install-body\",\"style\":{\"flexWrap\":\"wrap\"},\"children\":[[\"$\",\"code\",null,{\"className\":\"install-cmd\",\"children\":\"clawhub install frp-tunnel\"}],[\"$\",\"button\",null,{\"className\":\"copy-btn\",\"data-cmd\":\"clawhub install frp-tunnel\",\"style\":{\"fontWeight\":700},\"children\":\"Copy\"}]]}]]}],[\"$\",\"section\",null,{\"className\":\"skill-card\",\"style\":{\"marginBottom\":20},\"children\":[[\"$\",\"h2\",null,{\"style\":{\"color\":\"#f8fafc\",\"fontSize\":\"1.2em\",\"fontWeight\":800,\"margin\":\"0 0 16px\",\"display\":\"flex\",\"alignItems\":\"center\",\"gap\":8},\"children\":\"📖 About This Skill\"}],[\"$\",\"div\",null,{\"style\":{\"fontSize\":\".92em\",\"color\":\"#94a3b8\",\"lineHeight\":1.75},\"dangerouslySetInnerHTML\":{\"__html\":\"$23\"}}]]}],null,null,null,null,null,null,null,false,false]}],\"$L24\"]}]]}]\n"])</script><script>self.__next_f.push([1,"21:[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"\\n document.querySelectorAll('.copy-btn, .script-copy-btn').forEach(btn =\u003e {\\n btn.addEventListener('click', () =\u003e {\\n const cmd = btn.getAttribute('data-cmd');\\n if (!cmd) return;\\n navigator.clipboard.writeText(cmd).then(() =\u003e {\\n const orig = btn.textContent;\\n btn.textContent = 'Copied!';\\n setTimeout(() =\u003e btn.textContent = orig, 1500);\\n }).catch(() =\u003e {});\\n });\\n });\\n \"}}]\n"])</script><script>self.__next_f.push([1,"25:I[71521,[\"/_next/static/chunks/0ph_0rx9ah5dc.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/0i_x3w546rsb3.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/06ig5gym-0n-u.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\",\"/_next/static/chunks/12w5ognupk9fb.js?dpl=dpl_9zPFH6pojPkqyspdDNz28GzCc6KQ\"],\"default\"]\n24:[\"$\",\"div\",null,{\"className\":\"two-col-side\",\"children\":[\"$\",\"$L25\",null,{\"category\":\"clawhub\",\"currentSlug\":\"frp-tunnel\",\"name\":\"frp-tunnel\",\"tags\":[\"clawhub\"]}]}]\n"])</script></body></html>