// Copyright 2022 E99p1ant. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package cmd import ( "fmt" "net/http" "time" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/uptrace/opentelemetry-go-extra/otellogrus" "github.com/uptrace/uptrace-go/uptrace" "github.com/urfave/cli/v2" "github.com/tamakyi/TamaBox/internal/conf" "github.com/tamakyi/TamaBox/internal/db" "github.com/tamakyi/TamaBox/internal/route" "github.com/tamakyi/TamaBox/internal/shared" "github.com/tamakyi/TamaBox/internal/tracing" ) var Web = &cli.Command{ Name: "web", Usage: "Start web server", Action: runWeb, } func runWeb(ctx *cli.Context) error { if err := conf.Init(); err != nil { return errors.Wrap(err, "load configuration") } // 解析可信代理网段(用于客户端 IP 解析,避免维护 Cloudflare 庞大 CIDR 清单)。 if err := shared.InitTrustedProxies(); err != nil { return errors.Wrap(err, "init trusted proxies") } if conf.App.UptraceDSN != "" { uptrace.ConfigureOpentelemetry( uptrace.WithDSN(conf.App.UptraceDSN), uptrace.WithServiceName("tamabox"), uptrace.WithServiceVersion(conf.BuildCommit), ) logrus.WithContext(ctx.Context).Debug("Tracing enabled.") } logrus.AddHook(otellogrus.NewHook(otellogrus.WithLevels( logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, ))) dbType := conf.Database.Type var dsn string switch dbType { case "mysql", "": dsn = conf.MySQLDsn() case "postgres": dsn = conf.PostgresDsn() default: return errors.Errorf("unknown database type: %q", dbType) } conf.Database.DSN = dsn _, err := db.Init(dbType, dsn) if err != nil { return errors.Wrap(err, "connect to database") } // 蜜罐命中记录保留期清理:honeypot_hits 是只追加的审计表,写入完全由 // 外部扫描流量驱动(不可预期、不可节流)。没有自动清理的话,表体积只增不减。 // 保留期复用 honeypot_retain_days(与 CLI prune 同一个口径),开关为 honeypot_auto_clean。 stopRetention := make(chan struct{}) if conf.Security.HoneypotAutoClean { db.NewHoneypotRetention(db.Honeypots, conf.Security.HoneypotRetainDays, conf.Security.HoneypotMaxRows, 0).Start(stopRetention) defer close(stopRetention) } logrus.WithContext(ctx.Context).WithField("external_url", conf.App.ExternalURL).Info("Starting web server") // 站点默认语言初始化(default_lang 配置,未配置时回退 zh-CN) shared.App.SetLang(conf.App.DefaultLang) r := route.New() r.Use(tracing.Middleware("TamaBox")) // 最外层挂载 gzip 压缩,并在 http.Server 上配置超时: // - ReadHeaderTimeout 防止慢速客户端占用连接 // - ReadTimeout / WriteTimeout 防止读写挂死 // - IdleTimeout 复用 keep-alive 连接 // listen 留空时回退到 ":port"(监听所有网卡);Cloudflare Tunnel 部署建议设为 // "127.0.0.1:",使源站端口仅对本地 cloudflared 可见,即使防火墙误配也无法被外部直连。 addr := conf.Server.Listen if addr == "" { addr = fmt.Sprintf(":%d", conf.Server.Port) } server := &http.Server{ Addr: addr, Handler: route.GzipHandler(r), ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, } logrus.WithContext(ctx.Context).WithField("port", conf.Server.Port).Info("Listening on") if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { return errors.Wrap(err, "start http server") } return nil }