Documentation
¶
Overview ¶
Package license — banner.go provides warning banner text for grace period states. Used by CLI header output and nself-admin UI.
Package license — cache.go implements the local license cache with Ed25519 signature verification and grace-period-aware freshness checks.
Cache location: ~/.cache/nself/license.json (0600) Fields: key_hash, tier, plugins_allowed, fetched_at, expires_at, signature
Package license — grace.go implements the license grace period state machine and degradation mode enforcement.
States: valid -> grace_soft -> grace_hard -> expired -> revoked Grace periods:
- <24h offline: proceed silently (valid)
- 24h-7d offline: WARNING banner (grace_soft)
- >7d offline: read-only degraded mode (grace_hard)
- Expired license: refuse to start (expired)
- Revoked license: refuse to start (revoked)
Package license — multi-key support for product-based licensing.
Keys are collected from environment variables:
- NSELF_PLUGIN_LICENSE_KEY (legacy single key)
- NSELF_LICENSE_KEY_1 through NSELF_LICENSE_KEY_10
And from the stored key file at ~/.nself/license/key.
Package license provides CLI-level license key management operations: setting, reading, clearing, and displaying license keys.
Key storage: ~/.config/nself/license.json (chmod 0600) Legacy storage (v1): ~/.nself/license.json Env override: NSELF_PLUGIN_LICENSE_KEY
Package license — simulate.go provides offline simulation mode for testing grace period behavior without actually blocking network access.
Simulation writes an override to the license cache that makes the system believe it has been offline for N days. Guarded by LICENSE_ALLOW_SIMULATION env var (must be "true" explicitly; defaults to false in production).
Package license — validate.go implements the full license validation flow with ping.nself.org and grace state machine integration.
Flow: try remote validation -> update cache -> fall back to cached result with grace state evaluation on network failure.
Index ¶
- Constants
- Variables
- func AddKey(key string) error
- func BannerAtExpiry() string
- func BannerAtWarning(result GraceCheckResult) string
- func CachePath() (string, error)
- func ClearKey() error
- func ClearSimulation() error
- func CollectLicenseKeys() []string
- func DeleteCache() error
- func ExportCache() ([]byte, error)
- func GetAllStoredKeys() []string
- func GetKey() (string, error)
- func HashKey(key string) string
- func ImportCache(data []byte) error
- func MaskKey(key string) string
- func MigrateLicenseFromV1(home string) error
- func PingURL() string
- func RemoveKey(query string) (int, error)
- func SetKey(key string) error
- func SetKeyReplaceAll(key string) (int, error)
- func ShowKey() (masked string, tier string, err error)
- func ValidateKeyFormat(key string) error
- func WriteCache(entry *CacheEntry) error
- type CacheEntry
- type GraceCheckResult
- type GraceState
- type ProductPrefix
- type PublicKeyEntry
- type ValidateResponse
- type ValidationResult
Constants ¶
const DefaultCheckInterval = 6 * time.Hour
DefaultCheckInterval is the default time between license checks (6 hours).
const DefaultPingURL = "https://ping.nself.org"
DefaultPingURL is the production license validation endpoint.
const GraceHardThreshold = 7 * 24 * time.Hour
GraceHardThreshold is when hard degradation begins (7 days). Configurable via LICENSE_GRACE_DAYS env var.
const GraceSoftThreshold = 24 * time.Hour
GraceSoftThreshold is when the soft grace warning starts (24 hours).
const LicenseDirV1 = ".nself"
LicenseDirV1 is the v1 license directory name under $HOME.
const LicenseDirV2 = ".config/nself"
LicenseDirV2 is the v2 license directory name under $HOME.
const LicenseFile = "license.json"
LicenseFile is the license JSON filename used in both v1 and v2.
const SimulationGuardEnv = "LICENSE_ALLOW_SIMULATION"
SimulationGuardEnv is the env var that must be set to "true" to allow simulation mode. This prevents accidental use in production.
Variables ¶
var ErrSimulationNotAllowed = fmt.Errorf( "simulation mode is disabled — set %s=true to enable (never in production)", SimulationGuardEnv, )
ErrSimulationNotAllowed is returned when simulation is attempted without the guard env var being set.
Functions ¶
func AddKey ¶ added in v1.0.4
AddKey stores an additional license key. If no keys exist yet, it writes to the primary key file. If a key already exists, it stores as a numbered key file (key.2, key.3, etc.) in ~/.nself/license/.
func BannerAtExpiry ¶ added in v1.0.6
func BannerAtExpiry() string
BannerAtExpiry returns the hard-stop banner text when grace period is exhausted (day 14+).
func BannerAtWarning ¶ added in v1.0.6
func BannerAtWarning(result GraceCheckResult) string
BannerAtWarning returns the yellow-banner text shown when the license server has been unreachable for 7+ days (grace_soft state). Returns empty string if no banner is needed.
func CachePath ¶ added in v1.0.6
CachePath returns the full path to the license cache file. It respects LICENSE_CACHE_PATH if set.
func ClearKey ¶
func ClearKey() error
ClearKey removes both the stored license key file and the validation cache file from ~/.nself/license/.
func ClearSimulation ¶ added in v1.0.6
func ClearSimulation() error
ClearSimulation restores the cache to current time (as if just validated). Also guarded by LICENSE_ALLOW_SIMULATION.
func CollectLicenseKeys ¶ added in v1.0.4
func CollectLicenseKeys() []string
CollectLicenseKeys reads all configured license keys from environment variables and the stored key file. It deduplicates and returns all non-empty keys. The order is: NSELF_PLUGIN_LICENSE_KEY, then NSELF_LICENSE_KEY_1 through _10, then the stored key file.
func DeleteCache ¶ added in v1.0.6
func DeleteCache() error
DeleteCache removes the license cache file.
func ExportCache ¶ added in v1.0.6
ExportCache reads the current cache and returns it as JSON bytes suitable for air-gap transfer.
func GetAllStoredKeys ¶ added in v1.0.4
func GetAllStoredKeys() []string
GetAllStoredKeys returns all keys stored in key files (not env vars).
func GetKey ¶
GetKey returns the current license key. It checks the NSELF_PLUGIN_LICENSE_KEY environment variable first, then falls back to reading ~/.nself/license/key. Returns ("", nil) when no key is configured.
func ImportCache ¶ added in v1.0.6
ImportCache reads a previously exported cache file and writes it to the local cache location. It verifies the Ed25519 signature before accepting.
func MaskKey ¶ added in v1.0.4
MaskKey returns a masked version of the key showing the first 10 and last 4 characters. Delegates to the existing maskKey function.
func MigrateLicenseFromV1 ¶
MigrateLicenseFromV1 copies ~/.nself/license.json to ~/.config/nself/license.json when upgrading from v1. The function is a no-op when:
- v2 already exists (NEVER overwrite existing v2 license)
- v1 does not exist (nothing to migrate)
On success the v1 file is preserved (non-destructive). File is written with mode 0600 matching the key file convention.
func RemoveKey ¶ added in v1.0.4
RemoveKey removes a key by prefix match or product name. Returns the number of keys removed.
func SetKey ¶
SetKey validates the key format, creates the license directory if needed, and writes the key to ~/.nself/license/key with mode 0600.
func SetKeyReplaceAll ¶ added in v1.0.4
SetKeyReplaceAll replaces all stored keys with a single new key. Returns the number of keys that were replaced.
func ShowKey ¶
ShowKey returns a masked version of the current key and its detected tier. The masked key shows the first 10 and last 4 characters with the middle replaced by asterisks. If no key is set, all return values are empty strings with a nil error.
func ValidateKeyFormat ¶ added in v1.0.4
ValidateKeyFormat checks that a key has a recognized product prefix and meets the minimum length requirement. Returns errs.ErrInvalidLicenseKey on failure.
func WriteCache ¶ added in v1.0.6
func WriteCache(entry *CacheEntry) error
WriteCache writes the cache entry to disk with 0600 permissions.
Types ¶
type CacheEntry ¶ added in v1.0.6
type CacheEntry struct {
KeyHash string `json:"key_hash"`
Tier string `json:"tier"`
PluginsAllowed []string `json:"plugins_allowed"`
FetchedAt int64 `json:"fetched_at"`
ExpiresAt int64 `json:"expires_at"`
Signature string `json:"signature"`
SignatureKeyID int `json:"signature_key_id"`
}
CacheEntry represents a cached license validation response with Ed25519 signature from the server.
func ReadCache ¶ added in v1.0.6
func ReadCache() (*CacheEntry, error)
ReadCache reads and parses the license cache file. Returns nil, nil if the cache file does not exist.
func (*CacheEntry) CacheAge ¶ added in v1.0.6
func (c *CacheEntry) CacheAge() time.Duration
CacheAge returns how long ago the cache was fetched.
func (*CacheEntry) VerifySignature ¶ added in v1.0.6
func (c *CacheEntry) VerifySignature() bool
VerifySignature verifies the cache entry's Ed25519 signature against the bundled public keys. It accepts the current key (keyID N) and the previous key (keyID N-1) to support rotation windows.
type GraceCheckResult ¶ added in v1.0.6
type GraceCheckResult struct {
State GraceState
CacheAge time.Duration
ExpiresAt time.Time
Tier string
Message string
CanProceed bool
WriteAllowed bool
}
GraceCheckResult contains the outcome of a grace period check.
func DetermineGraceState ¶ added in v1.0.6
func DetermineGraceState(entry *CacheEntry) GraceCheckResult
DetermineGraceState evaluates the current grace state from a cache entry. If entry is nil, returns GraceExpired (no cache means no validation).
func SimulateOffline ¶ added in v1.0.6
func SimulateOffline(days int) (*GraceCheckResult, error)
SimulateOffline modifies the license cache to appear as if the system has been offline for the given number of days. This is used for testing grace period transitions without iptables manipulation.
The function: 1. Checks the LICENSE_ALLOW_SIMULATION guard 2. Reads the current cache 3. Backdates FetchedAt by the specified number of days 4. Writes the modified cache back
func (GraceCheckResult) IsWriteAllowed ¶ added in v1.0.6
func (r GraceCheckResult) IsWriteAllowed() bool
IsWriteAllowed checks if write operations on paid plugins are permitted given the current grace state.
func (GraceCheckResult) NeedsBanner ¶ added in v1.0.6
func (r GraceCheckResult) NeedsBanner() bool
NeedsBanner returns true if a warning banner should be displayed.
type GraceState ¶ added in v1.0.6
type GraceState string
GraceState represents the license grace period state.
const ( // GraceValid means the license is validated and current. GraceValid GraceState = "valid" // GraceSoft means the cache is 24h-7d old; show warning banner. GraceSoft GraceState = "grace_soft" // GraceHard means the cache is >7d old; paid plugin writes are refused. GraceHard GraceState = "grace_hard" // GraceExpired means the license has expired. GraceExpired GraceState = "expired" // GraceRevoked means the license was explicitly revoked. GraceRevoked GraceState = "revoked" )
type ProductPrefix ¶ added in v1.0.4
ProductPrefix maps key prefixes to product display names.
func DetectProduct ¶ added in v1.0.4
func DetectProduct(key string) *ProductPrefix
DetectProduct returns the product info for a key based on its prefix. Returns nil if the prefix is not recognized.
type PublicKeyEntry ¶ added in v1.0.6
PublicKeyEntry holds a versioned Ed25519 public key.
func GetPublicKeys ¶ added in v1.0.6
func GetPublicKeys() []PublicKeyEntry
GetPublicKeys returns the active public keys, respecting the LICENSE_PUBLIC_KEY_OVERRIDE environment variable for testing.
type ValidateResponse ¶ added in v1.0.6
type ValidateResponse struct {
Valid bool `json:"valid"`
Reason string `json:"reason,omitempty"`
Tier string `json:"tier"`
Plugins []string `json:"plugins"`
ExpiresAt string `json:"expires_at,omitempty"`
Signature string `json:"signature,omitempty"`
KeyID int `json:"key_id,omitempty"`
}
ValidateResponse is the JSON body returned by /license/validate on HTTP 200.
type ValidationResult ¶ added in v1.0.6
type ValidationResult struct {
Valid bool
Tier string
Plugins []string
ExpiresAt time.Time
GraceState GraceState
Message string
FromCache bool
WriteAllowed bool
}
ValidationResult holds the outcome of a full license validation.
func RefreshCache ¶ added in v1.0.6
func RefreshCache(ctx context.Context, key string) (*ValidationResult, error)
RefreshCache forces a remote validation and updates the cache. Returns the validation result or an error if the remote call fails.
func ValidateFull ¶ added in v1.0.6
func ValidateFull(ctx context.Context, key string) (*ValidationResult, error)
ValidateFull performs the complete license validation flow: 1. Try remote validation against ping.nself.org 2. On success: update cache, return valid 3. On network failure: fall back to cache with grace state evaluation 4. On invalid response: mark cache as invalid, return error