isle/go/garage/garagesrv/garagesrv.go

162 lines
3.8 KiB
Go

// 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
}