config: autodetect advertise IP

(cherry picked from commit 937a57b43e4a0a53c09e66c71ce232c7a0735fc9)
This commit is contained in:
A 2026-06-09 02:44:38 +08:00
parent 091d8f084b
commit 7ccb8ea20d
2 changed files with 162 additions and 5 deletions

View file

@ -2,8 +2,10 @@
package config
import (
"net"
"os"
"strconv"
"strings"
"time"
)
@ -45,9 +47,8 @@ type Config struct {
// StickerSeedMaxSets 限制导入的常规贴纸集数量(避免启动时导入过多包),<=0 表示不限。
StickerSeedMaxSets int
// OutboxWorkers 是并发 claim 的 outbox worker 数。worker 间用 FOR UPDATE SKIP LOCKED 互不重叠。
// 开发默认偏保守,避免 TDesktop 启动风暴下多个 worker 同扫分区父表触发 PG lock/shared-memory 压力;
// 压测或生产可按硬件用 TELESRV_OUTBOX_WORKERS 调高。
// OutboxWorkers 是并发 claim 的 outbox worker 数。默认 1保证同一用户 pts update
// 在线投递顺序与持久化顺序一致;后续需要吞吐时应改成按 target_user_id 分片的串行 worker。
OutboxWorkers int
// OutboxBatch 是 transactional outbox worker 每次 claim 的最大条数。
// 调大提升吞吐、增大单批 PG/推送压力;调小降低延迟抖动。配套压测见 docs/message-module.md。
@ -69,9 +70,13 @@ type Config struct {
// Load 从环境变量读取配置并填充默认值。第一阶段不做严格校验。
func Load() (Config, error) {
advertiseIP := os.Getenv("TELESRV_ADVERTISE_IP")
if advertiseIP == "" {
advertiseIP = defaultAdvertiseIP()
}
cfg := Config{
ListenAddr: envOr("TELESRV_LISTEN", "0.0.0.0:2398"),
AdvertiseIP: envOr("TELESRV_ADVERTISE_IP", "127.0.0.1"),
AdvertiseIP: advertiseIP,
RSAKeyPath: envOr("TELESRV_RSA_KEY", "data/server_rsa.pem"),
DC: envIntOr("TELESRV_DC", 2),
@ -88,7 +93,7 @@ func Load() (Config, error) {
StickerSeedDir: envOr("TELESRV_STICKER_SEED_DIR", "data/sticker-seed"),
StickerSeedMaxSets: envIntOr("TELESRV_STICKER_SEED_MAX_SETS", 40),
OutboxWorkers: envIntOr("TELESRV_OUTBOX_WORKERS", 2),
OutboxWorkers: envIntOr("TELESRV_OUTBOX_WORKERS", 1),
OutboxBatch: envIntOr("TELESRV_OUTBOX_BATCH", 100),
OutboxInterval: envDurationOr("TELESRV_OUTBOX_INTERVAL", 200*time.Millisecond),
OutboxLeaseTimeout: envDurationOr("TELESRV_OUTBOX_LEASE_TIMEOUT", 30*time.Second),
@ -100,6 +105,109 @@ func Load() (Config, error) {
return cfg, nil
}
func defaultAdvertiseIP() string {
if ip := detectAdvertiseIP(); ip != "" {
return ip
}
return "127.0.0.1"
}
func detectAdvertiseIP() string {
ifaces, err := net.Interfaces()
if err != nil {
return ""
}
var physical []net.IP
var virtual []net.IP
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ip := ipFromAddr(addr)
if ip == nil || !isPrivateIPv4(ip) {
continue
}
if likelyVirtualInterface(iface.Name) {
virtual = append(virtual, ip)
continue
}
physical = append(physical, ip)
}
}
if ip := preferredAdvertiseIP(physical); ip != "" {
return ip
}
return preferredAdvertiseIP(virtual)
}
func ipFromAddr(addr net.Addr) net.IP {
switch v := addr.(type) {
case *net.IPNet:
return v.IP.To4()
case *net.IPAddr:
return v.IP.To4()
default:
return nil
}
}
func preferredAdvertiseIP(ips []net.IP) string {
for _, ip := range ips {
if v4 := ip.To4(); v4 != nil && v4[0] == 10 {
return v4.String()
}
}
for _, ip := range ips {
if v4 := ip.To4(); v4 != nil && v4[0] == 192 && v4[1] == 168 {
return v4.String()
}
}
for _, ip := range ips {
if v4 := ip.To4(); v4 != nil && v4[0] == 172 && v4[1] >= 16 && v4[1] <= 31 {
return v4.String()
}
}
if len(ips) == 0 {
return ""
}
return ips[0].String()
}
func isPrivateIPv4(ip net.IP) bool {
v4 := ip.To4()
if v4 == nil {
return false
}
return v4[0] == 10 ||
(v4[0] == 172 && v4[1] >= 16 && v4[1] <= 31) ||
(v4[0] == 192 && v4[1] == 168)
}
func likelyVirtualInterface(name string) bool {
name = strings.ToLower(name)
virtualMarkers := []string{
"docker",
"hyper-v",
"tailscale",
"virtual",
"virtualbox",
"vethernet",
"vmware",
"wsl",
}
for _, marker := range virtualMarkers {
if strings.Contains(name, marker) {
return true
}
}
return false
}
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v

View file

@ -0,0 +1,49 @@
package config
import (
"net"
"testing"
)
func TestLoadUsesExplicitAdvertiseIP(t *testing.T) {
t.Setenv("TELESRV_ADVERTISE_IP", "10.172.61.102")
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.AdvertiseIP != "10.172.61.102" {
t.Fatalf("AdvertiseIP = %q, want explicit env", cfg.AdvertiseIP)
}
}
func TestPreferredAdvertiseIPPrefersPhysicalLANRanges(t *testing.T) {
got := preferredAdvertiseIP([]net.IP{
net.IPv4(172, 17, 0, 1),
net.IPv4(192, 168, 1, 20),
net.IPv4(10, 172, 61, 102),
})
if got != "10.172.61.102" {
t.Fatalf("preferredAdvertiseIP = %q, want 10.172.61.102", got)
}
got = preferredAdvertiseIP([]net.IP{
net.IPv4(172, 17, 0, 1),
net.IPv4(192, 168, 1, 20),
})
if got != "192.168.1.20" {
t.Fatalf("preferredAdvertiseIP = %q, want 192.168.1.20", got)
}
}
func TestPrivateIPv4AndVirtualInterfaceDetection(t *testing.T) {
if !isPrivateIPv4(net.IPv4(10, 172, 61, 102)) {
t.Fatal("10.172.61.102 should be private")
}
if isPrivateIPv4(net.IPv4(8, 8, 8, 8)) {
t.Fatal("8.8.8.8 should not be private")
}
if !likelyVirtualInterface("vEthernet (WSL)") {
t.Fatal("vEthernet (WSL) should be treated as virtual")
}
}