feat: add chain and paths config

This commit is contained in:
2025-12-31 22:21:31 -08:00
parent bdb8628921
commit 4d7a878c7a
6 changed files with 787 additions and 92 deletions
+202
View File
@@ -0,0 +1,202 @@
// Copyright (C) 2021-2025, Lux Industries Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// ChainConfig represents the chain configuration files
type ChainConfig struct {
Name string // Chain name (e.g., "zoo", "mychain")
Genesis json.RawMessage // Genesis JSON
Config json.RawMessage // Chain config JSON (eth APIs, etc.)
Upgrade json.RawMessage // Upgrade config JSON
}
// ChainManager handles unified chain configuration across all nodes
type ChainManager struct {
paths *Paths
}
// NewChainManager creates a new chain manager
func NewChainManager(paths *Paths) *ChainManager {
return &ChainManager{paths: paths}
}
// DefaultChainManager creates a chain manager with default paths
func DefaultChainManager() (*ChainManager, error) {
paths, err := DefaultPaths()
if err != nil {
return nil, err
}
return NewChainManager(paths), nil
}
// ListChains returns all configured chains
func (cm *ChainManager) ListChains() ([]string, error) {
chainsDir := cm.paths.ChainsBaseDir()
entries, err := os.ReadDir(chainsDir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var chains []string
for _, entry := range entries {
if entry.IsDir() {
// Verify it has a genesis file
genesisPath := cm.paths.ChainGenesis(entry.Name())
if Exists(genesisPath) {
chains = append(chains, entry.Name())
}
}
}
return chains, nil
}
// ChainExists checks if a chain configuration exists
func (cm *ChainManager) ChainExists(chainName string) bool {
return Exists(cm.paths.ChainGenesis(chainName))
}
// LoadChain loads all configuration for a chain
func (cm *ChainManager) LoadChain(chainName string) (*ChainConfig, error) {
cc := &ChainConfig{Name: chainName}
// Load genesis (required)
genesis, err := os.ReadFile(cm.paths.ChainGenesis(chainName))
if err != nil {
return nil, fmt.Errorf("failed to read genesis for chain %s: %w", chainName, err)
}
cc.Genesis = genesis
// Load config (optional)
configPath := cm.paths.ChainConfig(chainName)
if Exists(configPath) {
config, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config for chain %s: %w", chainName, err)
}
cc.Config = config
}
// Load upgrade (optional)
upgradePath := cm.paths.ChainUpgrade(chainName)
if Exists(upgradePath) {
upgrade, err := os.ReadFile(upgradePath)
if err != nil {
return nil, fmt.Errorf("failed to read upgrade for chain %s: %w", chainName, err)
}
cc.Upgrade = upgrade
}
return cc, nil
}
// SaveChain saves chain configuration
func (cm *ChainManager) SaveChain(cc *ChainConfig) error {
// Ensure chain directory exists
if err := cm.paths.EnsureChainDir(cc.Name); err != nil {
return fmt.Errorf("failed to create chain directory: %w", err)
}
// Save genesis (required)
if len(cc.Genesis) > 0 {
if err := os.WriteFile(cm.paths.ChainGenesis(cc.Name), cc.Genesis, 0644); err != nil {
return fmt.Errorf("failed to write genesis: %w", err)
}
}
// Save config (optional)
if len(cc.Config) > 0 {
if err := os.WriteFile(cm.paths.ChainConfig(cc.Name), cc.Config, 0644); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
}
// Save upgrade (optional)
if len(cc.Upgrade) > 0 {
if err := os.WriteFile(cm.paths.ChainUpgrade(cc.Name), cc.Upgrade, 0644); err != nil {
return fmt.Errorf("failed to write upgrade: %w", err)
}
}
return nil
}
// LoadGenesis loads just the genesis file for a chain
func (cm *ChainManager) LoadGenesis(chainName string) ([]byte, error) {
return os.ReadFile(cm.paths.ChainGenesis(chainName))
}
// SaveGenesis saves just the genesis file for a chain
func (cm *ChainManager) SaveGenesis(chainName string, genesis []byte) error {
if err := cm.paths.EnsureChainDir(chainName); err != nil {
return err
}
return os.WriteFile(cm.paths.ChainGenesis(chainName), genesis, 0644)
}
// DeleteChain removes all configuration for a chain
func (cm *ChainManager) DeleteChain(chainName string) error {
chainDir := cm.paths.ChainDir(chainName)
if !Exists(chainDir) {
return nil
}
return os.RemoveAll(chainDir)
}
// CopyChainConfigsToNode copies chain configs to a node's chain directory
// This is used when starting nodes to provide chain-specific configuration
// Destination: <nodeDir>/configs/chains/<chainID>/
func (cm *ChainManager) CopyChainConfigsToNode(chainName, chainID, nodeDir string) error {
// Load chain config
cc, err := cm.LoadChain(chainName)
if err != nil {
return err
}
// Create node's chain config directory
nodeChainDir := filepath.Join(nodeDir, "configs", "chains", chainID)
if err := os.MkdirAll(nodeChainDir, 0755); err != nil {
return err
}
// Copy config file if exists
if len(cc.Config) > 0 {
configDest := filepath.Join(nodeChainDir, ConfigFile)
if err := os.WriteFile(configDest, cc.Config, 0644); err != nil {
return err
}
}
// Copy upgrade file if exists
if len(cc.Upgrade) > 0 {
upgradeDest := filepath.Join(nodeChainDir, UpgradeFile)
if err := os.WriteFile(upgradeDest, cc.Upgrade, 0644); err != nil {
return err
}
}
return nil
}
// GetChainIDFromGenesis extracts chainID from an EVM genesis file
func GetChainIDFromGenesis(genesis []byte) (uint64, error) {
var g struct {
Config struct {
ChainID uint64 `json:"chainId"`
} `json:"config"`
}
if err := json.Unmarshal(genesis, &g); err != nil {
return 0, fmt.Errorf("failed to parse genesis: %w", err)
}
return g.Config.ChainID, nil
}
+18 -21
View File
@@ -1,32 +1,29 @@
module github.com/luxfi/config
go 1.24.5
go 1.24.0
require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
go.uber.org/zap v1.27.0
github.com/btcsuite/btcutil v1.0.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
go.uber.org/zap v1.27.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+61 -62
View File
@@ -1,78 +1,77 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+356
View File
@@ -0,0 +1,356 @@
// 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
}
+2 -2
View File
@@ -26,13 +26,13 @@ import (
// │ │ └── v0.1.0/
// │ └── myuser/
// │ └── myvm/
// ├── active/ # VMID symlinks (what node uses)
// ├── current/ # VMID symlinks (what node uses)
// │ └── ag3GReYPNuSR... -> ../packages/luxfi/evm/v1.0.0/evm
// └── registry.json # Local registry of installed packages
const (
packagesDir = "packages"
activeDir = "active" // Symlinks by VMID for node compatibility
activeDir = "current" // Symlinks by VMID for node compatibility (unified with SDK constants.CurrentPluginDir)
registryFile = "registry.json"
)
+148 -7
View File
@@ -5,13 +5,47 @@ package config
import (
"context"
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/btcsuite/btcutil/base58"
)
const (
// Well-known VM names
VMNameLuxEVM = "Lux EVM"
VMNameCoreVM = "Core VM"
VMNameAVM = "AVM"
)
// VMID computes the VM ID from a VM name.
// This is the standard way to compute VMID: base58check(sha256(pad32(vmName)))
// Example: "Lux EVM" -> "ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE"
func VMID(vmName string) string {
// Pad to 32 bytes
padded := make([]byte, 32)
copy(padded, []byte(vmName))
// SHA256 hash
hash := sha256.Sum256(padded)
// Base58 encode (with checksum)
return base58.CheckEncode(hash[:], 0)
}
// WellKnownVMIDs returns a map of well-known VM names to their IDs
func WellKnownVMIDs() map[string]string {
return map[string]string{
VMNameLuxEVM: VMID(VMNameLuxEVM),
VMNameCoreVM: VMID(VMNameCoreVM),
VMNameAVM: VMID(VMNameAVM),
}
}
// PluginInfo describes an installed plugin
type PluginInfo struct {
// VMID is the VM identifier (hash)
@@ -252,8 +286,115 @@ func (pm *DefaultPluginManager) Exists(vmID string) bool {
return err == nil && !info.IsDir()
}
// Link creates a symlink from the plugin directory to a VM binary.
// This is the preferred way to "install" a VM for development.
func (pm *DefaultPluginManager) Link(vmID, binaryPath string) error {
// Ensure plugin directory exists
if err := pm.EnsureDir(); err != nil {
return fmt.Errorf("failed to create plugin directory: %w", err)
}
// Resolve the binary path to absolute
absPath, err := filepath.Abs(binaryPath)
if err != nil {
return fmt.Errorf("failed to resolve binary path: %w", err)
}
// Verify binary exists and is executable
info, err := os.Stat(absPath)
if err != nil {
return fmt.Errorf("binary not found: %w", err)
}
if info.Mode()&0111 == 0 {
return fmt.Errorf("binary is not executable: %s", absPath)
}
linkPath := pm.GetPath(vmID)
// Remove existing file/symlink if present
if _, err := os.Lstat(linkPath); err == nil {
if err := os.Remove(linkPath); err != nil {
return fmt.Errorf("failed to remove existing file: %w", err)
}
}
// Create symlink
if err := os.Symlink(absPath, linkPath); err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
return nil
}
// LinkByName creates a symlink using the VM name to compute VMID
func (pm *DefaultPluginManager) LinkByName(vmName, binaryPath string) error {
vmID := VMID(vmName)
return pm.Link(vmID, binaryPath)
}
// IsSymlink checks if a plugin path is a symlink
func (pm *DefaultPluginManager) IsSymlink(vmID string) bool {
path := pm.GetPath(vmID)
info, err := os.Lstat(path)
if err != nil {
return false
}
return info.Mode()&os.ModeSymlink != 0
}
// GetTarget returns the target of a plugin symlink
func (pm *DefaultPluginManager) GetTarget(vmID string) (string, error) {
path := pm.GetPath(vmID)
if !pm.IsSymlink(vmID) {
return "", fmt.Errorf("plugin %s is not a symlink", vmID)
}
return os.Readlink(path)
}
// Verify checks if a plugin is properly installed and executable
func (pm *DefaultPluginManager) Verify(vmID string) error {
pluginPath := pm.GetPath(vmID)
// Check if exists
info, err := os.Lstat(pluginPath)
if os.IsNotExist(err) {
return fmt.Errorf("plugin %s not installed", vmID)
}
if err != nil {
return fmt.Errorf("failed to check plugin: %w", err)
}
// If symlink, verify target
if info.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(pluginPath)
if err != nil {
return fmt.Errorf("failed to read symlink: %w", err)
}
// Resolve relative symlinks
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(pluginPath), target)
}
info, err = os.Stat(target)
if os.IsNotExist(err) {
return fmt.Errorf("plugin symlink target missing: %s", target)
}
if err != nil {
return fmt.Errorf("failed to check symlink target: %w", err)
}
}
// Verify executable
if info.Mode()&0111 == 0 {
return fmt.Errorf("plugin is not executable")
}
return nil
}
// ResolvePluginBaseDir returns the base plugin directory
// This contains packages/, active/, and registry.json
// This contains packages/, current/, and registry.json
func ResolvePluginBaseDir() string {
// 1. Check environment variable first
if dir := os.Getenv("LUX_PLUGIN_DIR"); dir != "" {
@@ -284,19 +425,19 @@ func ResolvePluginBaseDir() string {
}
// ResolvePluginDir resolves the plugin directory using the configuration stack
// This returns the "active" directory where VMID symlinks live for node compatibility
// This returns the "current" directory where VMID symlinks live for node compatibility
// Structure:
// ~/.lux/plugins/
// ├── packages/luxfi/evm/v1.0.0/ # Actual packages
// ├── active/ag3GReY.../ # VMID symlinks (what node uses)
// ├── current/ag3GReY.../ # VMID symlinks (what node uses)
// └── registry.json
func ResolvePluginDir() string {
baseDir := ResolvePluginBaseDir()
// Check if new structure exists (has active/ subdirectory)
activeDir := filepath.Join(baseDir, "active")
if info, err := os.Stat(activeDir); err == nil && info.IsDir() {
return activeDir
// Check if new structure exists (has current/ subdirectory)
currentDir := filepath.Join(baseDir, "current")
if info, err := os.Stat(currentDir); err == nil && info.IsDir() {
return currentDir
}
// Fall back to legacy structure (plugins directly in base dir)