From 8b4abb7eefa496129cd6a52d7dbf1f420111927e Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Thu, 14 May 2026 11:51:08 +0300 Subject: [PATCH] feat(#72): install ntfy on every node, drop --with-ntfy gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ntfy is now part of the standard node install, just like Caddy. The binary, /etc/ntfy/server.yml, and the Caddy push. reverse- proxy block are written unconditionally on every node, and the ntfy.service starts as part of the standard service order. Why uniform: ntfy listens on 127.0.0.1:NtfyListenPort only, reachable exclusively via the local Caddy reverse-proxy block. Nodes that don't serve a public push.* DNS entry just have an idle ntfy with no inbound traffic — zero operational cost, zero attack surface change. Removing the flag means no per-node toggling, no preference drift between nodes, no "did we remember to set --with-ntfy" mistakes when DNS topology changes (e.g. promoting a node to nameserver later). Removed: - NodePreferences.NtfyHost (yaml: ntfy_host) - ProductionSetup.isNtfyHost field, SetNtfyHost, IsNtfyHost - install/flags.go --with-ntfy + NtfyHost field - upgrade/flags.go --with-ntfy + NtfyHost field + isFlagPassed helper (was only used for --with-ntfy tri-state semantics) - upgrade/orchestrator.go preference-load and persist for ntfy - upgrade/remote.go --with-ntfy forwarding Phase 2 always calls InstallNtfy. Phase 4 always calls EnableCaddyNtfyProxy + ConfigureNtfy. Phase 5 always enables ntfy.service. Phase 5b always starts ntfy.service. VERSION bumped to 0.122.16. --- VERSION | 2 +- core/pkg/cli/production/install/flags.go | 2 - .../cli/production/install/orchestrator.go | 1 - core/pkg/cli/production/upgrade/flags.go | 21 ----- .../cli/production/upgrade/orchestrator.go | 18 ---- core/pkg/cli/production/upgrade/remote.go | 18 ++-- .../environments/production/orchestrator.go | 82 ++++++++----------- .../environments/production/preferences.go | 1 - sdk/package.json | 2 +- 9 files changed, 39 insertions(+), 108 deletions(-) diff --git a/VERSION b/VERSION index 0ec315c..7cddd56 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.122.15 +0.122.16 diff --git a/core/pkg/cli/production/install/flags.go b/core/pkg/cli/production/install/flags.go index d50c798..d3a360d 100644 --- a/core/pkg/cli/production/install/flags.go +++ b/core/pkg/cli/production/install/flags.go @@ -15,7 +15,6 @@ type Flags struct { DryRun bool SkipChecks bool Nameserver bool // Make this node a nameserver (runs CoreDNS + Caddy) - NtfyHost bool // Host the self-hosted ntfy server on this node (feature #72) JoinAddress string // HTTPS URL of existing node (e.g., https://node1.dbrs.space) Token string // Invite token for joining (from orama invite) ClusterSecret string // Deprecated: use --token instead @@ -65,7 +64,6 @@ func ParseFlags(args []string) (*Flags, error) { fs.BoolVar(&flags.DryRun, "dry-run", false, "Show what would be done without making changes") fs.BoolVar(&flags.SkipChecks, "skip-checks", false, "Skip minimum resource checks (RAM/CPU)") fs.BoolVar(&flags.Nameserver, "nameserver", false, "Make this node a nameserver (runs CoreDNS + Caddy)") - fs.BoolVar(&flags.NtfyHost, "with-ntfy", false, "Host the self-hosted ntfy server (feature #72; usually colocated with --nameserver on devnet)") // Cluster join flags fs.StringVar(&flags.JoinAddress, "join", "", "Join existing cluster via HTTPS URL (e.g. https://node1.dbrs.space)") diff --git a/core/pkg/cli/production/install/orchestrator.go b/core/pkg/cli/production/install/orchestrator.go index a32024f..58f0f0d 100644 --- a/core/pkg/cli/production/install/orchestrator.go +++ b/core/pkg/cli/production/install/orchestrator.go @@ -46,7 +46,6 @@ func NewOrchestrator(flags *Flags) (*Orchestrator, error) { setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, flags.SkipChecks) setup.SetNameserver(flags.Nameserver) - setup.SetNtfyHost(flags.NtfyHost) // Configure Anyone mode if flags.AnyoneRelay && flags.AnyoneClient { diff --git a/core/pkg/cli/production/upgrade/flags.go b/core/pkg/cli/production/upgrade/flags.go index 8db41a6..1071c79 100644 --- a/core/pkg/cli/production/upgrade/flags.go +++ b/core/pkg/cli/production/upgrade/flags.go @@ -12,7 +12,6 @@ type Flags struct { RestartServices bool SkipChecks bool Nameserver *bool // Pointer so we can detect if explicitly set vs default - NtfyHost *bool // Feature #72: nil = use saved preference; non-nil = explicit override // Remote upgrade flags Env string // Target environment for remote rolling upgrade @@ -51,8 +50,6 @@ func ParseFlags(args []string) (*Flags, error) { // Nameserver flag - use pointer to detect if explicitly set nameserver := fs.Bool("nameserver", false, "Make this node a nameserver (uses saved preference if not specified)") - // Ntfy host flag (feature #72) — same pattern, sticks via preferences.yaml. - ntfyHost := fs.Bool("with-ntfy", false, "Host the self-hosted ntfy server on this node (uses saved preference if not specified)") // Anyone flags fs.BoolVar(&flags.AnyoneClient, "anyone-client", false, "Install Anyone as client-only (SOCKS5 proxy on port 9050, no relay)") @@ -78,25 +75,7 @@ func ParseFlags(args []string) (*Flags, error) { if *nameserver { flags.Nameserver = nameserver } - // Set ntfy_host only when explicitly passed (default false != "use saved"). - // Without explicit set, the orchestrator reads the saved preference. - if isFlagPassed(fs, "with-ntfy") { - flags.NtfyHost = ntfyHost - } return flags, nil } -// isFlagPassed reports whether the named flag was explicitly set on -// the command line, not just defaulted. Used to distinguish "user -// didn't say anything; honor saved preference" from "user wrote -// --with-ntfy=false; turn it OFF". -func isFlagPassed(fs *flag.FlagSet, name string) bool { - passed := false - fs.Visit(func(f *flag.Flag) { - if f.Name == name { - passed = true - } - }) - return passed -} diff --git a/core/pkg/cli/production/upgrade/orchestrator.go b/core/pkg/cli/production/upgrade/orchestrator.go index 722771f..38f3319 100644 --- a/core/pkg/cli/production/upgrade/orchestrator.go +++ b/core/pkg/cli/production/upgrade/orchestrator.go @@ -38,17 +38,8 @@ func NewOrchestrator(flags *Flags) *Orchestrator { isNameserver = *flags.Nameserver } - // Feature #72: ntfy-host preference survives upgrades the same way - // nameserver does — loaded from preferences.yaml unless the upgrade - // flags override it explicitly. - isNtfyHost := prefs.NtfyHost - if flags.NtfyHost != nil { - isNtfyHost = *flags.NtfyHost - } - setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, flags.SkipChecks) setup.SetNameserver(isNameserver) - setup.SetNtfyHost(isNtfyHost) // Configure Anyone mode (explicit flags > saved preferences > auto-detect) // Explicit flags always win — they represent the user's current intent. @@ -220,15 +211,6 @@ func (o *Orchestrator) handleBranchPreferences() error { fmt.Printf(" Nameserver mode: enabled (CoreDNS + Caddy)\n") } - // If ntfy-host was explicitly provided, persist it (feature #72). - if o.flags.NtfyHost != nil { - prefs.NtfyHost = *o.flags.NtfyHost - prefsChanged = true - } - if o.setup.IsNtfyHost() { - fmt.Printf(" ntfy host: enabled (self-hosted ntfy on push.)\n") - } - // Anyone client and relay are mutually exclusive — setting one clears the other. if o.flags.AnyoneClient { prefs.AnyoneClient = true diff --git a/core/pkg/cli/production/upgrade/remote.go b/core/pkg/cli/production/upgrade/remote.go index 09f9211..cb641bb 100644 --- a/core/pkg/cli/production/upgrade/remote.go +++ b/core/pkg/cli/production/upgrade/remote.go @@ -68,24 +68,16 @@ func (r *RemoteUpgrader) Execute() error { } // upgradeNode runs `orama node upgrade --restart` on a single remote node, -// forwarding the per-node flags the operator passed locally (--with-ntfy, -// --nameserver, --force, --skip-checks) so the remote orchestrator sees the -// same intent. Without this forwarding, the remote command would always use -// the saved preference, silently dropping operator overrides like -// `--with-ntfy` on the floor. +// forwarding the per-node flags the operator passed locally (--nameserver, +// --force, --skip-checks) so the remote orchestrator sees the same intent. +// Without this forwarding, the remote command would always use the saved +// preference, silently dropping operator overrides on the floor. func (r *RemoteUpgrader) upgradeNode(node inspector.Node) error { sudo := remotessh.SudoPrefix(node) cmd := fmt.Sprintf("%sorama node upgrade --restart", sudo) - // Tri-state pointer flags: forward only when explicitly set locally. + // Tri-state pointer flag: forward only when explicitly set locally. // nil = "honor saved preference on the remote" — don't pass anything. - if r.flags.NtfyHost != nil { - if *r.flags.NtfyHost { - cmd += " --with-ntfy" - } else { - cmd += " --with-ntfy=false" - } - } if r.flags.Nameserver != nil { if *r.flags.Nameserver { cmd += " --nameserver" diff --git a/core/pkg/environments/production/orchestrator.go b/core/pkg/environments/production/orchestrator.go index 6aa1e2d..4704ed1 100644 --- a/core/pkg/environments/production/orchestrator.go +++ b/core/pkg/environments/production/orchestrator.go @@ -38,7 +38,6 @@ type ProductionSetup struct { skipOptionalDeps bool skipResourceChecks bool isNameserver bool // Whether this node is a nameserver (runs CoreDNS + Caddy) - isNtfyHost bool // Feature #72: whether this node hosts the self-hosted ntfy server isAnyoneClient bool // Whether this node runs Anyone as client-only (SOCKS5 proxy) anyoneRelayConfig *AnyoneRelayConfig // Configuration for Anyone relay mode privChecker *PrivilegeChecker @@ -136,20 +135,6 @@ func (ps *ProductionSetup) IsNameserver() bool { return ps.isNameserver } -// SetNtfyHost flags this node as the host for the self-hosted ntfy -// server (feature #72). When set, Phase 2 installs ntfy and Phase 4 -// generates /etc/ntfy/server.yml plus a Caddy reverse-proxy block for -// push.. Requires isNameserver=true for devnet (ns1 also -// runs Caddy); production deployments may colocate or split. -func (ps *ProductionSetup) SetNtfyHost(isNtfy bool) { - ps.isNtfyHost = isNtfy -} - -// IsNtfyHost returns whether this node hosts ntfy. -func (ps *ProductionSetup) IsNtfyHost() bool { - return ps.isNtfyHost -} - // SetAnyoneRelayConfig sets the Anyone relay configuration func (ps *ProductionSetup) SetAnyoneRelayConfig(config *AnyoneRelayConfig) { ps.anyoneRelayConfig = config @@ -359,12 +344,14 @@ func (ps *ProductionSetup) installFromSource() error { ps.logf(" ⚠️ Caddy install warning: %v", err) } - // Install ntfy only on nodes flagged as the ntfy host (feature #72). - // On devnet this is ns1; on production it can be a dedicated node. - if ps.isNtfyHost { - if err := ps.binaryInstaller.InstallNtfy(); err != nil { - ps.logf(" ⚠️ ntfy install warning: %v", err) - } + // Install ntfy on every node (feature #72). ntfy listens on + // 127.0.0.1:NtfyListenPort and is only reachable via the local + // Caddy reverse-proxy block, so it's safe to run cluster-wide: + // nodes that don't host a public push.* DNS entry simply have + // an idle ntfy with no inbound traffic. Uniform install means no + // per-node toggling and no surprises when DNS topology changes. + if err := ps.binaryInstaller.InstallNtfy(); err != nil { + ps.logf(" ⚠️ ntfy install warning: %v", err) } // These are pre-built binary downloads (not Go compilation), always run them @@ -725,20 +712,19 @@ func (ps *ProductionSetup) Phase4GenerateConfigs(peerAddresses []string, vpsIP s email := "admin@" + caddyDomain acmeEndpoint := "http://localhost:6001/v1/internal/acme" - // Self-hosted ntfy (feature #72): when this node hosts ntfy, - // (a) tell the Caddy installer to emit a push. block - // pointing at the local ntfy listen port, and (b) write the - // ntfy server.yml. Both must happen BEFORE ConfigureCaddy is + // Self-hosted ntfy (feature #72): always emit the Caddy + // push. reverse-proxy block and write + // /etc/ntfy/server.yml. Must happen BEFORE ConfigureCaddy is // called below so the generated Caddyfile picks up the block. - if ps.isNtfyHost { - ntfyHost := "push." + dnsZone - ps.binaryInstaller.EnableCaddyNtfyProxy(ntfyHost) - ntfyBaseURL := "https://" + ntfyHost - if err := ps.binaryInstaller.ConfigureNtfy(ntfyBaseURL); err != nil { - ps.logf(" ⚠️ ntfy config warning: %v", err) - } else { - ps.logf(" ✓ ntfy config generated (base_url: %s)", ntfyBaseURL) - } + // ntfy is installed unconditionally on every node (see Phase 2) + // so the local 127.0.0.1:NtfyListenPort target always exists. + ntfyHost := "push." + dnsZone + ps.binaryInstaller.EnableCaddyNtfyProxy(ntfyHost) + ntfyBaseURL := "https://" + ntfyHost + if err := ps.binaryInstaller.ConfigureNtfy(ntfyBaseURL); err != nil { + ps.logf(" ⚠️ ntfy config warning: %v", err) + } else { + ps.logf(" ✓ ntfy config generated (base_url: %s)", ntfyBaseURL) } if err := ps.binaryInstaller.ConfigureCaddy(caddyDomain, email, acmeEndpoint, baseDomain); err != nil { @@ -899,12 +885,10 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error { if _, err := os.Stat("/usr/bin/caddy"); err == nil { services = append(services, "caddy.service") } - // Add ntfy when this node hosts the self-hosted ntfy server (#72). - // The unit file is written by installers/ntfy.go::writeSystemdUnit. - if ps.isNtfyHost { - if _, err := os.Stat("/usr/local/bin/ntfy"); err == nil { - services = append(services, "ntfy.service") - } + // Add ntfy on every node (#72). The unit file is written by + // installers/ntfy.go::writeSystemdUnit during Phase 2. + if _, err := os.Stat("/usr/local/bin/ntfy"); err == nil { + services = append(services, "ntfy.service") } for _, svc := range services { if err := ps.serviceController.EnableService(svc); err != nil { @@ -982,16 +966,14 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error { } } - // Start ntfy when this node hosts it (#72). Caddy must already be - // up (it terminates TLS for push.), which the order - // above guarantees. - if ps.isNtfyHost { - if _, err := os.Stat("/usr/local/bin/ntfy"); err == nil { - if err := ps.serviceController.RestartService("ntfy.service"); err != nil { - ps.logf(" ⚠️ Failed to start ntfy.service: %v", err) - } else { - ps.logf(" - ntfy.service started") - } + // Start ntfy on every node (#72). Caddy must already be up (it + // terminates TLS for push.), which the order above + // guarantees. + if _, err := os.Stat("/usr/local/bin/ntfy"); err == nil { + if err := ps.serviceController.RestartService("ntfy.service"); err != nil { + ps.logf(" ⚠️ Failed to start ntfy.service: %v", err) + } else { + ps.logf(" - ntfy.service started") } } diff --git a/core/pkg/environments/production/preferences.go b/core/pkg/environments/production/preferences.go index 554800f..38da5d5 100644 --- a/core/pkg/environments/production/preferences.go +++ b/core/pkg/environments/production/preferences.go @@ -11,7 +11,6 @@ import ( type NodePreferences struct { Branch string `yaml:"branch"` Nameserver bool `yaml:"nameserver"` - NtfyHost bool `yaml:"ntfy_host"` // Feature #72: this node hosts self-hosted ntfy AnyoneClient bool `yaml:"anyone_client"` AnyoneRelay bool `yaml:"anyone_relay"` AnyoneORPort int `yaml:"anyone_orport,omitempty"` // typically 9001 diff --git a/sdk/package.json b/sdk/package.json index 707d942..8da4bd5 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@debros/orama", - "version": "0.122.15", + "version": "0.122.16", "description": "TypeScript SDK for Orama Network - Database, PubSub, Cache, Storage, Vault, and more", "type": "module", "main": "./dist/index.js",