Files
cftunnel/internal/daemon/quick.go
T
admin 9a53aa77a9 feat: 新增 quick 命令 — 免域名一键穿透
cftunnel quick <端口> 无需账户/Token/域名,
自动生成 *.trycloudflare.com 随机公网地址。
2026-02-23 00:30:42 +08:00

82 lines
1.7 KiB
Go

package daemon
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
)
// StartQuick 启动免域名模式(前台运行,Ctrl+C 退出)
func StartQuick(port string) error {
binPath, err := EnsureCloudflared()
if err != nil {
return err
}
if Running() {
return fmt.Errorf("cloudflared 已在运行,请先执行 cftunnel down")
}
cmd := exec.Command(binPath, "tunnel", "--url", "http://localhost:"+port)
// 捕获 stderr 提取随机域名
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
return fmt.Errorf("启动 cloudflared 失败: %w", err)
}
// 后台读取 stderr,提取域名并转发输出
go scanForURL(stderr)
// 捕获 Ctrl+C 优雅退出
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case <-sig:
cmd.Process.Signal(syscall.SIGINT)
<-done
case err := <-done:
if err != nil {
return fmt.Errorf("cloudflared 异常退出: %w", err)
}
}
return nil
}
func scanForURL(r io.Reader) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// cloudflared 输出格式: ... https://xxx.trycloudflare.com ...
if strings.Contains(line, "trycloudflare.com") {
url := extractURL(line)
if url != "" {
fmt.Printf("\n✔ 隧道已启动: %s\n\n", url)
}
}
fmt.Fprintln(os.Stderr, line)
}
}
func extractURL(line string) string {
for _, part := range strings.Fields(line) {
if strings.Contains(part, "trycloudflare.com") && strings.HasPrefix(part, "http") {
return part
}
}
return ""
}