mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-17 01:34:13 +00:00
fix(gateway): plumb ntfy_base_url into gateway config so push fan-out activates (#858)
The ntfy fan-out (publish each push to every active push node so a round-robin-DNS-pinned subscriber receives it) was coded but INERT: the gateway's cfg.NtfyBaseURL was never populated, so the fan-out resolver was never built and pushes went single-host (the ~87% loss the bug describes). The orchestrator already derives https://push.<dnsZone> for the ntfy server + Caddy reverse-proxy but never put it in node.yaml's http_gateway. Same regression class as the v0.122.42 secrets_encryption_key fix (consumer landed; template + parse field + node->gateway mapping were missed). Plumb it through all four layers: render it under http_gateway (derived as push.<dnsZone>, matching the ntfy host), parse it in HTTPGatewayConfig, map it onto gateway.Config. Rolling-upgrade safe: Phase 4 regen runs under the new binary (post-swap), so an old binary never reads a node.yaml with the new field. DecodeStrict regression guard added (mirrors secrets_encryption_key).
This commit is contained in:
parent
3779ba9502
commit
e3b2f08a0a
@ -235,6 +235,29 @@ http_gateway:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDecodeStrict_ntfyBaseURL guards the same v0.122.42-class boot crash for
|
||||||
|
// the bugboard #858 ntfy fan-out: Phase 4 now emits `ntfy_base_url` under
|
||||||
|
// http_gateway, so HTTPGatewayConfig MUST carry a matching field or
|
||||||
|
// KnownFields(true) rejects the whole node.yaml and orama-node crash-loops.
|
||||||
|
// If someone deletes the parse field, the render tests still pass but
|
||||||
|
// production crash-loops — this guard catches that.
|
||||||
|
func TestDecodeStrict_ntfyBaseURL(t *testing.T) {
|
||||||
|
yamlInput := `
|
||||||
|
node:
|
||||||
|
id: "test-node"
|
||||||
|
http_gateway:
|
||||||
|
enabled: true
|
||||||
|
ntfy_base_url: "https://push.dbrs.space"
|
||||||
|
`
|
||||||
|
var cfg Config
|
||||||
|
if err := DecodeStrict(strings.NewReader(yamlInput), &cfg); err != nil {
|
||||||
|
t.Fatalf("node.yaml with ntfy_base_url must parse (bugboard #858): %v", err)
|
||||||
|
}
|
||||||
|
if cfg.HTTPGateway.NtfyBaseURL != "https://push.dbrs.space" {
|
||||||
|
t.Errorf("NtfyBaseURL = %q, want https://push.dbrs.space", cfg.HTTPGateway.NtfyBaseURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestDecodeStrict_sniRouterBlock guards against a recurrence of the
|
// TestDecodeStrict_sniRouterBlock guards against a recurrence of the
|
||||||
// v0.122.42-class boot crash for the feat-124 stealth SNI router: Phase 4
|
// v0.122.42-class boot crash for the feat-124 stealth SNI router: Phase 4
|
||||||
// always emits a top-level `sni_router:` block into node.yaml, so the root
|
// always emits a top-level `sni_router:` block into node.yaml, so the root
|
||||||
|
|||||||
@ -30,6 +30,16 @@ type HTTPGatewayConfig struct {
|
|||||||
// and the node→gateway mapping were missed).
|
// and the node→gateway mapping were missed).
|
||||||
SecretsEncryptionKey string `yaml:"secrets_encryption_key"`
|
SecretsEncryptionKey string `yaml:"secrets_encryption_key"`
|
||||||
|
|
||||||
|
// NtfyBaseURL is the shared self-hosted ntfy base URL (e.g.
|
||||||
|
// "https://push.orama-devnet.network"). When set, the push ntfy provider
|
||||||
|
// fans each publish out to every active push node so a subscriber pinned
|
||||||
|
// to any instance by round-robin DNS receives it (bugboard #858). Rendered
|
||||||
|
// under http_gateway by Phase 4 config generation as "https://push."+dnsZone
|
||||||
|
// — matching the ntfy server + Caddy reverse-proxy host. Empty → no fan-out
|
||||||
|
// (single-host delivery, the ~87% loss the fix exists to remove). MUST exist
|
||||||
|
// here or the node→gateway mapping cannot populate gateway.Config.NtfyBaseURL.
|
||||||
|
NtfyBaseURL string `yaml:"ntfy_base_url"`
|
||||||
|
|
||||||
// WebRTC configuration (optional, enabled per-namespace)
|
// WebRTC configuration (optional, enabled per-namespace)
|
||||||
WebRTC WebRTCConfig `yaml:"webrtc"`
|
WebRTC WebRTCConfig `yaml:"webrtc"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,6 +220,18 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri
|
|||||||
data.SecretsEncryptionKey = strings.TrimSpace(string(keyBytes))
|
data.SecretsEncryptionKey = strings.TrimSpace(string(keyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared self-hosted ntfy base URL (bugboard #858). Derive it the SAME way
|
||||||
|
// the orchestrator derives the ntfy server + Caddy reverse-proxy host
|
||||||
|
// (push.<dnsZone>, dnsZone = baseDomain or the node domain), so the gateway's
|
||||||
|
// NtfyBaseURL matches and the push provider fans each publish out to every
|
||||||
|
// active push node instead of single-host delivery. Without this the fan-out
|
||||||
|
// code is inert and ~87% of publishes never reach a pinned subscriber.
|
||||||
|
if dnsZone := baseDomain; dnsZone != "" {
|
||||||
|
data.NtfyBaseURL = "https://push." + dnsZone
|
||||||
|
} else if domain != "" {
|
||||||
|
data.NtfyBaseURL = "https://push." + domain
|
||||||
|
}
|
||||||
|
|
||||||
// WebRTC/TURN config (feat-124 #913). The TURN secret lives in the secrets
|
// WebRTC/TURN config (feat-124 #913). The TURN secret lives in the secrets
|
||||||
// dir so it survives Phase4 config regeneration; turn_domain/sfu_port/enabled
|
// dir so it survives Phase4 config regeneration; turn_domain/sfu_port/enabled
|
||||||
// are operator-set values that only exist in the previous node.yaml, so we
|
// are operator-set values that only exist in the previous node.yaml, so we
|
||||||
|
|||||||
37
core/pkg/environments/production/config_ntfy_test.go
Normal file
37
core/pkg/environments/production/config_ntfy_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package production
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bugboard #858 — the ntfy fan-out only activates when the gateway config
|
||||||
|
// carries NtfyBaseURL. The fan-out consumer (dependencies.go) shipped, but the
|
||||||
|
// template + parse field + node→gateway mapping were missed, so cfg.NtfyBaseURL
|
||||||
|
// stayed empty and every publish went single-host (~87% loss). These pin that
|
||||||
|
// the generated node.yaml now renders ntfy_base_url under http_gateway, derived
|
||||||
|
// as push.<dnsZone> to match the ntfy server + Caddy reverse-proxy host.
|
||||||
|
|
||||||
|
func TestGenerateNodeConfig_rendersNtfyBaseURL_fromBaseDomain(t *testing.T) {
|
||||||
|
cg := NewConfigGenerator(t.TempDir())
|
||||||
|
out, err := cg.GenerateNodeConfig(nil, "10.0.0.5", "", "node-1.dbrs.space", "dbrs.space", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateNodeConfig failed: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, `ntfy_base_url: "https://push.dbrs.space"`) {
|
||||||
|
t.Errorf("node.yaml missing ntfy_base_url derived from base domain\n---\n%s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateNodeConfig_ntfyBaseURL_fallsBackToDomain(t *testing.T) {
|
||||||
|
// No base domain → derive from the node domain (matches the orchestrator's
|
||||||
|
// dnsZone := baseDomain; if empty -> domain).
|
||||||
|
cg := NewConfigGenerator(t.TempDir())
|
||||||
|
out, err := cg.GenerateNodeConfig(nil, "10.0.0.5", "", "anchor.example.net", "", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateNodeConfig failed: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, `ntfy_base_url: "https://push.anchor.example.net"`) {
|
||||||
|
t.Errorf("node.yaml missing ntfy_base_url fallback to node domain\n---\n%s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -102,6 +102,14 @@ http_gateway:
|
|||||||
# (bugboard #837). Sourced from ~/.orama/secrets/secrets-encryption-key.
|
# (bugboard #837). Sourced from ~/.orama/secrets/secrets-encryption-key.
|
||||||
secrets_encryption_key: "{{.SecretsEncryptionKey}}"
|
secrets_encryption_key: "{{.SecretsEncryptionKey}}"
|
||||||
{{- end}}
|
{{- end}}
|
||||||
|
{{- if .NtfyBaseURL}}
|
||||||
|
# Shared self-hosted ntfy base URL (bugboard #858). Same push.<dnsZone> host
|
||||||
|
# the ntfy server + Caddy reverse-proxy use, so the gateway's push provider
|
||||||
|
# fans each publish out to every active push node (each node runs an
|
||||||
|
# independent ntfy with no shared store; without fan-out a subscriber pinned
|
||||||
|
# to a different instance than the publish lands on misses it ~87% of the time).
|
||||||
|
ntfy_base_url: "{{.NtfyBaseURL}}"
|
||||||
|
{{- end}}
|
||||||
{{- if .TURNSecret}}
|
{{- if .TURNSecret}}
|
||||||
# WebRTC/TURN config (feat-124 #913). turn_secret is sourced from
|
# WebRTC/TURN config (feat-124 #913). turn_secret is sourced from
|
||||||
# ~/.orama/secrets/turn-secret so it survives config regeneration;
|
# ~/.orama/secrets/turn-secret so it survives config regeneration;
|
||||||
|
|||||||
@ -56,6 +56,12 @@ type NodeConfigData struct {
|
|||||||
// stays disabled until the key is configured).
|
// stays disabled until the key is configured).
|
||||||
SecretsEncryptionKey string
|
SecretsEncryptionKey string
|
||||||
|
|
||||||
|
// NtfyBaseURL is the shared self-hosted ntfy base URL (e.g.
|
||||||
|
// "https://push.<dnsZone>"), rendered under http_gateway as ntfy_base_url.
|
||||||
|
// When set, the gateway's push provider fans each ntfy publish out to every
|
||||||
|
// active push node (bugboard #858). Empty → omitted (single-host delivery).
|
||||||
|
NtfyBaseURL string
|
||||||
|
|
||||||
// WebRTC/TURN configuration, rendered under http_gateway.webrtc when
|
// WebRTC/TURN configuration, rendered under http_gateway.webrtc when
|
||||||
// WebRTCEnabled is true (feat-124 #913). TURNSecret is sourced from
|
// WebRTCEnabled is true (feat-124 #913). TURNSecret is sourced from
|
||||||
// ~/.orama/secrets/turn-secret so it survives Phase4 config regeneration;
|
// ~/.orama/secrets/turn-secret so it survives Phase4 config regeneration;
|
||||||
|
|||||||
@ -85,6 +85,7 @@ func (n *Node) startHTTPGateway(ctx context.Context) error {
|
|||||||
ClusterSecret: clusterSecret,
|
ClusterSecret: clusterSecret,
|
||||||
APIKeyHMACSecret: apiKeyHMACSecret,
|
APIKeyHMACSecret: apiKeyHMACSecret,
|
||||||
SecretsEncryptionKey: secretsEncryptionKey,
|
SecretsEncryptionKey: secretsEncryptionKey,
|
||||||
|
NtfyBaseURL: n.config.HTTPGateway.NtfyBaseURL,
|
||||||
WebRTCEnabled: n.config.HTTPGateway.WebRTC.Enabled,
|
WebRTCEnabled: n.config.HTTPGateway.WebRTC.Enabled,
|
||||||
SFUPort: n.config.HTTPGateway.WebRTC.SFUPort,
|
SFUPort: n.config.HTTPGateway.WebRTC.SFUPort,
|
||||||
TURNDomain: n.config.HTTPGateway.WebRTC.TURNDomain,
|
TURNDomain: n.config.HTTPGateway.WebRTC.TURNDomain,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user