mirror of
https://github.com/luxfi/config.git
synced 2026-09-24 00:24:16 +00:00
357 lines
11 KiB
Go
357 lines
11 KiB
Go
// Copyright (C) 2021-2025, Lux Industries Inc. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package config provides unified configuration and path management for all Lux tools.
|
|
// This is the single source of truth for directory structures across CLI, netrunner, and node.
|
|
//
|
|
// Directory Structure:
|
|
//
|
|
// ~/.lux/ # Root data directory
|
|
// ├── chains/ # Unified chain configs (shared by all nodes)
|
|
// │ └── <chainName>/
|
|
// │ ├── genesis.json
|
|
// │ ├── config.json
|
|
// │ └── upgrade.json
|
|
// ├── networks/ # Network-specific data
|
|
// │ └── <networkName>/ # mainnet, testnet, local
|
|
// │ └── runs/
|
|
// │ └── <runID>/ # run_20251222_102823
|
|
// │ ├── node1/
|
|
// │ ├── node2/
|
|
// │ └── ...
|
|
// ├── plugins/ # VM plugins
|
|
// │ └── current/ # Active plugins (symlinks)
|
|
// │ └── <vmid> # e.g., ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE
|
|
// ├── keys/ # Validator keys
|
|
// │ └── <networkName>/
|
|
// │ └── <nodeName>/
|
|
// │ ├── staking.key
|
|
// │ ├── staking.crt
|
|
// │ └── signer.key
|
|
// └── snapshots/ # Network snapshots for save/restore
|
|
// └── <snapshotName>/
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// Directory names - single source of truth
|
|
const (
|
|
// Root directory name
|
|
LuxDir = ".lux"
|
|
|
|
// Top-level directories under ~/.lux/
|
|
ChainsDir = "chains"
|
|
NetworksDir = "networks"
|
|
PluginsDir = "plugins"
|
|
KeysDir = "keys"
|
|
SnapshotsDir = "snapshots"
|
|
|
|
// Subdirectories
|
|
RunsDir = "runs"
|
|
CurrentPluginsDir = "current"
|
|
|
|
// File names for chain configs
|
|
GenesisFile = "genesis.json"
|
|
ConfigFile = "config.json"
|
|
UpgradeFile = "upgrade.json"
|
|
|
|
// File names for node keys
|
|
StakingKeyFile = "staking.key"
|
|
StakingCertFile = "staking.crt"
|
|
SignerKeyFile = "signer.key"
|
|
|
|
// Network names
|
|
NetworkMainnet = "mainnet"
|
|
NetworkTestnet = "testnet"
|
|
NetworkLocal = "local"
|
|
|
|
// Run directory prefix
|
|
RunPrefix = "run"
|
|
)
|
|
|
|
// Paths provides unified path management for Lux tools.
|
|
// Create one instance and use it throughout your application.
|
|
type Paths struct {
|
|
// BaseDir is the root data directory (default: ~/.lux)
|
|
BaseDir string
|
|
}
|
|
|
|
// DefaultPaths returns a Paths instance using the default base directory (~/.lux)
|
|
func DefaultPaths() (*Paths, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
return &Paths{BaseDir: filepath.Join(homeDir, LuxDir)}, nil
|
|
}
|
|
|
|
// NewPaths creates a Paths instance with a custom base directory
|
|
func NewPaths(baseDir string) *Paths {
|
|
return &Paths{BaseDir: baseDir}
|
|
}
|
|
|
|
// --- Chain Config Paths ---
|
|
|
|
// ChainsBaseDir returns the base directory for all chain configs
|
|
// Returns: ~/.lux/chains/
|
|
func (p *Paths) ChainsBaseDir() string {
|
|
return filepath.Join(p.BaseDir, ChainsDir)
|
|
}
|
|
|
|
// ChainDir returns the config directory for a specific chain
|
|
// Returns: ~/.lux/chains/<chainName>/
|
|
func (p *Paths) ChainDir(chainName string) string {
|
|
return filepath.Join(p.ChainsBaseDir(), chainName)
|
|
}
|
|
|
|
// ChainGenesis returns the genesis file path for a chain
|
|
// Returns: ~/.lux/chains/<chainName>/genesis.json
|
|
func (p *Paths) ChainGenesis(chainName string) string {
|
|
return filepath.Join(p.ChainDir(chainName), GenesisFile)
|
|
}
|
|
|
|
// ChainConfig returns the config file path for a chain
|
|
// Returns: ~/.lux/chains/<chainName>/config.json
|
|
func (p *Paths) ChainConfig(chainName string) string {
|
|
return filepath.Join(p.ChainDir(chainName), ConfigFile)
|
|
}
|
|
|
|
// ChainUpgrade returns the upgrade file path for a chain
|
|
// Returns: ~/.lux/chains/<chainName>/upgrade.json
|
|
func (p *Paths) ChainUpgrade(chainName string) string {
|
|
return filepath.Join(p.ChainDir(chainName), UpgradeFile)
|
|
}
|
|
|
|
// --- Network Paths ---
|
|
|
|
// NetworksBaseDir returns the base directory for all networks
|
|
// Returns: ~/.lux/networks/
|
|
func (p *Paths) NetworksBaseDir() string {
|
|
return filepath.Join(p.BaseDir, NetworksDir)
|
|
}
|
|
|
|
// NetworkDir returns the directory for a specific network
|
|
// Returns: ~/.lux/networks/<networkName>/
|
|
func (p *Paths) NetworkDir(networkName string) string {
|
|
return filepath.Join(p.NetworksBaseDir(), networkName)
|
|
}
|
|
|
|
// NetworkRunsDir returns the runs directory for a network
|
|
// Returns: ~/.lux/networks/<networkName>/runs/
|
|
func (p *Paths) NetworkRunsDir(networkName string) string {
|
|
return filepath.Join(p.NetworkDir(networkName), RunsDir)
|
|
}
|
|
|
|
// NetworkRunDir returns a specific run directory
|
|
// Returns: ~/.lux/networks/<networkName>/runs/<runID>/
|
|
func (p *Paths) NetworkRunDir(networkName, runID string) string {
|
|
return filepath.Join(p.NetworkRunsDir(networkName), runID)
|
|
}
|
|
|
|
// NodeDir returns the directory for a specific node within a run
|
|
// Returns: ~/.lux/networks/<networkName>/runs/<runID>/<nodeName>/
|
|
func (p *Paths) NodeDir(networkName, runID, nodeName string) string {
|
|
return filepath.Join(p.NetworkRunDir(networkName, runID), nodeName)
|
|
}
|
|
|
|
// --- Plugin Paths ---
|
|
|
|
// PluginsBaseDir returns the base directory for all plugins
|
|
// Returns: ~/.lux/plugins/
|
|
func (p *Paths) PluginsBaseDir() string {
|
|
return filepath.Join(p.BaseDir, PluginsDir)
|
|
}
|
|
|
|
// CurrentPluginsDir returns the directory for active plugin symlinks
|
|
// Returns: ~/.lux/plugins/current/
|
|
func (p *Paths) CurrentPluginsDir() string {
|
|
return filepath.Join(p.PluginsBaseDir(), CurrentPluginsDir)
|
|
}
|
|
|
|
// PluginPath returns the path for a specific VM plugin
|
|
// Returns: ~/.lux/plugins/current/<vmID>
|
|
func (p *Paths) PluginPath(vmID string) string {
|
|
return filepath.Join(p.CurrentPluginsDir(), vmID)
|
|
}
|
|
|
|
// --- Key Paths ---
|
|
|
|
// KeysBaseDir returns the base directory for all keys
|
|
// Returns: ~/.lux/keys/
|
|
func (p *Paths) KeysBaseDir() string {
|
|
return filepath.Join(p.BaseDir, KeysDir)
|
|
}
|
|
|
|
// NetworkKeysDir returns the keys directory for a specific network
|
|
// Returns: ~/.lux/keys/<networkName>/
|
|
func (p *Paths) NetworkKeysDir(networkName string) string {
|
|
return filepath.Join(p.KeysBaseDir(), networkName)
|
|
}
|
|
|
|
// NodeKeysDir returns the keys directory for a specific node
|
|
// Returns: ~/.lux/keys/<networkName>/<nodeName>/
|
|
func (p *Paths) NodeKeysDir(networkName, nodeName string) string {
|
|
return filepath.Join(p.NetworkKeysDir(networkName), nodeName)
|
|
}
|
|
|
|
// NodeStakingKey returns the staking key path for a node
|
|
// Returns: ~/.lux/keys/<networkName>/<nodeName>/staking.key
|
|
func (p *Paths) NodeStakingKey(networkName, nodeName string) string {
|
|
return filepath.Join(p.NodeKeysDir(networkName, nodeName), StakingKeyFile)
|
|
}
|
|
|
|
// NodeStakingCert returns the staking cert path for a node
|
|
// Returns: ~/.lux/keys/<networkName>/<nodeName>/staking.crt
|
|
func (p *Paths) NodeStakingCert(networkName, nodeName string) string {
|
|
return filepath.Join(p.NodeKeysDir(networkName, nodeName), StakingCertFile)
|
|
}
|
|
|
|
// NodeSignerKey returns the signer key path for a node
|
|
// Returns: ~/.lux/keys/<networkName>/<nodeName>/signer.key
|
|
func (p *Paths) NodeSignerKey(networkName, nodeName string) string {
|
|
return filepath.Join(p.NodeKeysDir(networkName, nodeName), SignerKeyFile)
|
|
}
|
|
|
|
// --- Snapshot Paths ---
|
|
|
|
// SnapshotsBaseDir returns the base directory for all snapshots
|
|
// Returns: ~/.lux/snapshots/
|
|
func (p *Paths) SnapshotsBaseDir() string {
|
|
return filepath.Join(p.BaseDir, SnapshotsDir)
|
|
}
|
|
|
|
// SnapshotDir returns the directory for a specific snapshot
|
|
// Returns: ~/.lux/snapshots/<snapshotName>/
|
|
func (p *Paths) SnapshotDir(snapshotName string) string {
|
|
return filepath.Join(p.SnapshotsBaseDir(), snapshotName)
|
|
}
|
|
|
|
// --- Directory Creation Helpers ---
|
|
|
|
// EnsureDir creates a directory if it doesn't exist
|
|
func (p *Paths) EnsureDir(path string) error {
|
|
return os.MkdirAll(path, 0755)
|
|
}
|
|
|
|
// EnsureChainDir creates the chain config directory
|
|
func (p *Paths) EnsureChainDir(chainName string) error {
|
|
return p.EnsureDir(p.ChainDir(chainName))
|
|
}
|
|
|
|
// EnsureNetworkRunsDir creates the runs directory for a network
|
|
func (p *Paths) EnsureNetworkRunsDir(networkName string) error {
|
|
return p.EnsureDir(p.NetworkRunsDir(networkName))
|
|
}
|
|
|
|
// EnsureCurrentPluginsDir creates the current plugins directory
|
|
func (p *Paths) EnsureCurrentPluginsDir() error {
|
|
return p.EnsureDir(p.CurrentPluginsDir())
|
|
}
|
|
|
|
// EnsureNodeKeysDir creates the keys directory for a node
|
|
func (p *Paths) EnsureNodeKeysDir(networkName, nodeName string) error {
|
|
return p.EnsureDir(p.NodeKeysDir(networkName, nodeName))
|
|
}
|
|
|
|
// --- Run Management ---
|
|
|
|
// NewRunID generates a new timestamped run ID
|
|
// Returns: run_20251222_102823
|
|
func NewRunID() string {
|
|
return fmt.Sprintf("%s_%s", RunPrefix, time.Now().Format("20060102_150405"))
|
|
}
|
|
|
|
// FindLatestRun finds the most recent run directory with node data
|
|
// Returns the run ID (not full path) or empty string if none found
|
|
func (p *Paths) FindLatestRun(networkName string) (string, error) {
|
|
runsDir := p.NetworkRunsDir(networkName)
|
|
entries, err := os.ReadDir(runsDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
var latestRunID string
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
name := entry.Name()
|
|
if len(name) < len(RunPrefix)+1 || name[:len(RunPrefix)+1] != RunPrefix+"_" {
|
|
continue
|
|
}
|
|
|
|
// Check if this run has node directories
|
|
runPath := filepath.Join(runsDir, name)
|
|
nodeEntries, _ := os.ReadDir(runPath)
|
|
hasNodes := false
|
|
for _, nodeEntry := range nodeEntries {
|
|
if nodeEntry.IsDir() && len(nodeEntry.Name()) >= 4 && nodeEntry.Name()[:4] == "node" {
|
|
hasNodes = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if hasNodes {
|
|
// Timestamps sort lexicographically
|
|
if latestRunID == "" || name > latestRunID {
|
|
latestRunID = name
|
|
}
|
|
}
|
|
}
|
|
|
|
return latestRunID, nil
|
|
}
|
|
|
|
// GetOrCreateRun finds existing run or creates new one
|
|
// Returns the full path to the run directory
|
|
func (p *Paths) GetOrCreateRun(networkName string) (string, error) {
|
|
// Ensure runs directory exists
|
|
if err := p.EnsureNetworkRunsDir(networkName); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Try to find existing run
|
|
latestRunID, err := p.FindLatestRun(networkName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if latestRunID != "" {
|
|
return p.NetworkRunDir(networkName, latestRunID), nil
|
|
}
|
|
|
|
// Create new run
|
|
runID := NewRunID()
|
|
runDir := p.NetworkRunDir(networkName, runID)
|
|
if err := p.EnsureDir(runDir); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return runDir, nil
|
|
}
|
|
|
|
// --- Utility Functions ---
|
|
|
|
// Exists checks if a path exists
|
|
func Exists(path string) bool {
|
|
_, err := os.Stat(path)
|
|
return err == nil
|
|
}
|
|
|
|
// IsSymlink checks if a path is a symlink
|
|
func IsSymlink(path string) bool {
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return info.Mode()&os.ModeSymlink != 0
|
|
}
|