package license import ( "errors" "os" "path/filepath" "strings" "sync/atomic" ) // KeyStore holds the per-node license key on disk. Every node in a // cluster runs its own verification against license.netcell-it.com // with its own fingerprint + key, so the file lives under // /var/lib/edgeguard/ (node-local, never replicated via the config // fan-out). // // Shape on disk: a single-line text file, leading/trailing whitespace // stripped. Mode 0600, owner nmg so a compromised read of a // checked-out working copy never leaks the key. type KeyStore struct { Path string // cached holds the last-loaded key so Get() is cheap; Reload() // flushes it. cached atomic.Value // string } const DefaultKeyStorePath = "/var/lib/edgeguard/license_key" // NewKeyStore returns a store writing to /var/lib/edgeguard/license_key. func NewKeyStore() *KeyStore { return &KeyStore{Path: DefaultKeyStorePath} } // Get returns the currently persisted key, or "" when the file is // absent / empty. Never returns an error so callers can happily fall // through to the trial-mode path. func (s *KeyStore) Get() string { if v, ok := s.cached.Load().(string); ok && v != "" { return v } data, err := os.ReadFile(s.Path) if err != nil { return "" } k := strings.TrimSpace(string(data)) s.cached.Store(k) return k } // Save writes the key atomically (tmp + rename) with mode 0600 and // primes the in-memory cache. Empty key removes the file — that's // how an admin returns the node to trial mode. func (s *KeyStore) Save(key string) error { key = strings.TrimSpace(key) dir := filepath.Dir(s.Path) if err := os.MkdirAll(dir, 0o755); err != nil { return err } if key == "" { if err := os.Remove(s.Path); err != nil && !errors.Is(err, os.ErrNotExist) { return err } s.cached.Store("") return nil } tmp := s.Path + ".tmp" if err := os.WriteFile(tmp, []byte(key+"\n"), 0o600); err != nil { return err } if err := os.Chmod(tmp, 0o600); err != nil { _ = os.Remove(tmp) return err } if err := os.Rename(tmp, s.Path); err != nil { return err } s.cached.Store(key) return nil }