Files

139 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cmd
import (
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/qingchencloud/cftunnel/internal/authproxy"
"github.com/qingchencloud/cftunnel/internal/cfapi"
"github.com/qingchencloud/cftunnel/internal/config"
"github.com/spf13/cobra"
)
var addDomain string
var addAuth string
func init() {
addCmd.Flags().StringVar(&addDomain, "domain", "", "完整域名 (如 webhook.example.com)")
addCmd.MarkFlagRequired("domain")
addCmd.Flags().StringVar(&addAuth, "auth", "", "启用密码保护 (格式: 用户名:密码)")
rootCmd.AddCommand(addCmd)
}
// pushIngress 推送当前所有路由的 ingress 配置到远端
func pushIngress(client *cfapi.Client, ctx context.Context, cfg *config.Config) error {
var rules []cfapi.IngressRule
for _, r := range cfg.Routes {
rules = append(rules, cfapi.IngressRule{Hostname: r.Hostname, Service: r.Service})
}
return client.PushIngressConfig(ctx, cfg.Tunnel.ID, rules)
}
// findZoneForDomain 通过遍历账户 Zone 列表匹配域名(支持多级 TLD)
func findZoneForDomain(client *cfapi.Client, ctx context.Context, domain string) (*cfapi.ZoneInfo, error) {
zoneList, err := client.ListZones(ctx)
if err != nil {
return nil, err
}
for _, z := range zoneList {
if domain == z.Name || strings.HasSuffix(domain, "."+z.Name) {
return &cfapi.ZoneInfo{ID: z.ID, Name: z.Name}, nil
}
}
return nil, fmt.Errorf("未找到域名 %s 对应的 Zone,请确认域名已添加到 Cloudflare", domain)
}
var addCmd = &cobra.Command{
Use: "add <名称> <端口>",
Short: "添加路由(自动创建 CNAME + 更新 ingress",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
name, port := args[0], args[1]
service := "http://localhost:" + port
cfg, err := config.Load()
if err != nil {
return err
}
if cfg.Tunnel.ID == "" {
return fmt.Errorf("请先运行 cftunnel init && cftunnel create <名称>")
}
if cfg.FindRoute(name) != nil {
return fmt.Errorf("路由 %s 已存在", name)
}
client := cfapi.New(cfg.Auth.APIToken, cfg.Auth.AccountID)
ctx := context.Background()
// 查找域名对应的 Zone(支持多级 TLD)
zone, err := findZoneForDomain(client, ctx, addDomain)
if err != nil {
return err
}
// 检查 DNS 记录是否已存在
target := cfg.Tunnel.ID + ".cfargotunnel.com"
existingRecordID, err := client.FindDNSRecord(ctx, zone.ID, addDomain)
if err != nil {
return err
}
var recordID string
if existingRecordID != "" {
// 记录已存在,更新
fmt.Printf("DNS 记录已存在,正在更新 %s → %s\n", addDomain, target)
if err := client.UpdateCNAME(ctx, zone.ID, existingRecordID, addDomain, target); err != nil {
return err
}
recordID = existingRecordID
} else {
// 记录不存在,创建
fmt.Printf("正在创建 DNS 记录 %s → %s\n", addDomain, target)
recordID, err = client.CreateCNAME(ctx, zone.ID, addDomain, target)
if err != nil {
return err
}
}
// 构建路由配置
route := config.RouteConfig{
Name: name,
Hostname: addDomain,
Service: service,
ZoneID: zone.ID,
DNSRecordID: recordID,
}
// 如果指定了 --auth,填充鉴权配置
if addAuth != "" {
user, pass, err := parseAuth(addAuth)
if err != nil {
return err
}
route.Auth = &config.AuthProxy{
Username: user,
Password: pass,
SigningKey: hex.EncodeToString(authproxy.RandomKey()),
}
fmt.Printf("已启用密码保护: %s\n", addDomain)
}
// 保存路由
cfg.Routes = append(cfg.Routes, route)
if err := cfg.Save(); err != nil {
return err
}
// 推送 ingress 配置到远端
fmt.Println("正在同步 ingress 配置...")
if err := pushIngress(client, ctx, cfg); err != nil {
return fmt.Errorf("推送 ingress 失败: %wDNS 记录已创建,请排查后重试 add 或手动删除 DNS 记录)", err)
}
fmt.Printf("路由已添加: %s → %s (%s)\n", addDomain, service, name)
return nil
},
}