package lkgateway // Внешний адрес сервера bj (06.10.2026) — каким его видят снаружи. Нужен для письма в ARQA: поставщика просят // разрешить подключение к модулю неторговых поручений с нашего адреса, и внутренний адрес (10.x, 192.168.x) // ему ничего не даёт. Изнутри сети внешний адрес не узнать, поэтому спрашиваем наш же сервер лицензий // (GET /v1/ip — он видит, откуда пришёл запрос); сторонние «узнать мой IP» для сервера финансовой // организации не годятся. Раз в час, в фоне: письмо строится при показе страницы и сеть не ждёт. import ( "context" "io" "log" "net" "net/http" "strings" "sync" "time" ) var publicIPState struct { sync.Mutex ip string at time.Time } // publicIP — последний известный внешний адрес (пусто — сервер лицензий недоступен, например закрытый контур). func publicIP() string { publicIPState.Lock() defer publicIPState.Unlock() if time.Since(publicIPState.at) > 24*time.Hour { return "" } return publicIPState.ip } // publicIPRefresh — спросить внешний адрес у сервера лицензий. func publicIPRefresh(ctx context.Context) error { cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(cctx, http.MethodGet, licenseServer+"/v1/ip", nil) if err != nil { return err } resp, err := licenseHTTP.Do(req) if err != nil { return err } defer resp.Body.Close() b, _ := io.ReadAll(io.LimitReader(resp.Body, 64)) ip := net.ParseIP(strings.TrimSpace(string(b))) if resp.StatusCode != http.StatusOK || ip == nil { return &net.AddrError{Err: "сервер лицензий не сообщил внешний адрес", Addr: resp.Status} } publicIPState.Lock() changed := publicIPState.ip != ip.String() publicIPState.ip, publicIPState.at = ip.String(), time.Now() publicIPState.Unlock() if changed { log.Printf("lk-gateway: внешний адрес сервера (по серверу лицензий): %s", ip) } return nil } // publicIPLoop — внешний адрес при запуске и раз в час (адрес у провайдера может смениться). func publicIPLoop(ctx context.Context) { for { _ = publicIPRefresh(ctx) select { case <-ctx.Done(): return case <-time.After(time.Hour): } } }