// Package garage contains helper functions and types which are useful for // setting up garage configs, processes, and deployments. package garagesrv import ( "crypto/ed25519" "crypto/rand" "encoding/hex" "errors" "fmt" "io/fs" "os" "path/filepath" ) // DBEngine enumerates the garage db engines which are supported by Isle. type DBEngine string // Enumeration of DBEngine values. const ( DBEngineLMDB DBEngine = "lmdb" DBEngineSqlite DBEngine = "sqlite" ) func nodeKeyPath(metaDirPath string) string { return filepath.Join(metaDirPath, "node_key") } func nodeKeyPubPath(metaDirPath string) string { return filepath.Join(metaDirPath, "node_key.pub") } // LoadAllocID returns the peer ID (ie the public key) of the node at the given // meta directory. func LoadAllocID(metaDirPath string) (string, error) { nodeKeyPubPath := nodeKeyPubPath(metaDirPath) pubKey, err := os.ReadFile(nodeKeyPubPath) if err != nil { return "", fmt.Errorf("reading %q: %w", nodeKeyPubPath, err) } return hex.EncodeToString(pubKey), nil } // generatePeerKey generates and returns a public/private key pair for a garage // instance. func generatePeerKey() (pubKey, privKey []byte) { pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(err) } return pubKey, privKey } // InitAlloc initializes the meta directory and keys for a particular // allocation, if it hasn't been done so already. It returns the peer ID (ie the // public key) in any case. func InitAlloc(metaDirPath string) (string, error) { initDirFor := func(path string) error { dir := filepath.Dir(path) return os.MkdirAll(dir, 0750) } var err error exists := func(path string) bool { if err != nil { return false } else if _, err = os.Stat(path); errors.Is(err, fs.ErrNotExist) { err = nil return false } else if err != nil { err = fmt.Errorf("checking if %q exists: %w", path, err) return false } return true } var ( nodeKeyPath = nodeKeyPath(metaDirPath) nodeKeyPubPath = nodeKeyPubPath(metaDirPath) nodeKeyPathExists = exists(nodeKeyPath) nodeKeyPubPathExists = exists(nodeKeyPubPath) ) if err != nil { return "", err } else if nodeKeyPubPathExists != nodeKeyPathExists { return "", fmt.Errorf( "%q or %q exist without the other existing", nodeKeyPath, nodeKeyPubPath, ) } var pubKeyStr string if nodeKeyPathExists { if pubKeyStr, err = LoadAllocID(metaDirPath); err != nil { return "", fmt.Errorf("reading node public key file: %w", err) } } else { if err := initDirFor(nodeKeyPath); err != nil { return "", fmt.Errorf( "creating directory for %q: %w", nodeKeyPath, err, ) } pubKey, privKey := generatePeerKey() if err := os.WriteFile(nodeKeyPath, privKey, 0400); err != nil { return "", fmt.Errorf( "writing private key to %q: %w", nodeKeyPath, err, ) } else if err := os.WriteFile(nodeKeyPubPath, pubKey, 0440); err != nil { return "", fmt.Errorf( "writing public key to %q: %w", nodeKeyPubPath, err, ) } pubKeyStr = hex.EncodeToString(pubKey) } return pubKeyStr, nil } // GetDBEngine returns the DBEngine currently being used at a particular meta // data directory, or returns the default if the directory doesn't exist or // hasn't been fully initialized yet. func GetDBEngine( metaDirPath string, defaultDBEngine DBEngine, ) ( DBEngine, error, ) { search := []struct { dbEngine DBEngine path string pathIsDir bool }{ {DBEngineLMDB, filepath.Join(metaDirPath, "db.lmdb"), true}, {DBEngineSqlite, filepath.Join(metaDirPath, "db.sqlite"), false}, } for _, s := range search { stat, err := os.Stat(s.path) if errors.Is(err, fs.ErrNotExist) { continue } else if err != nil { return "", fmt.Errorf("checking if %q exists: %w", s.path, err) } else if stat.IsDir() != s.pathIsDir { continue } return s.dbEngine, nil } return defaultDBEngine, nil }