// Package garage contains helper functions and types which are useful for // setting up garage configs, processes, and deployments. package garage import ( "encoding/hex" "errors" "fmt" "io/fs" "os" "path/filepath" "strconv" ) const ( // Region is the region which garage is configured with. Region = "garage" // GlobalBucket is the name of the global garage bucket which is // accessible to all hosts in the network. GlobalBucket = "global-shared" // ReplicationFactor indicates the replication factor set on the garage // cluster. We currently only support a factor of 3. ReplicationFactor = 3 ) func nodeKeyPath(metaDirPath string) string { return filepath.Join(metaDirPath, "node_key") } func nodeKeyPubPath(metaDirPath string) string { return filepath.Join(metaDirPath, "node_key.pub") } func nodeRPCPortPath(metaDirPath string) string { return filepath.Join(metaDirPath, "isle", "rpc_port") } // 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 } // 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) and the rpc port in any case. func InitAlloc(metaDirPath string, initRPCPort int) (string, int, 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 } nodeKeyPath := nodeKeyPath(metaDirPath) nodeKeyPubPath := nodeKeyPubPath(metaDirPath) nodeRPCPortPath := nodeRPCPortPath(metaDirPath) nodeKeyPathExists := exists(nodeKeyPath) nodeKeyPubPathExists := exists(nodeKeyPubPath) nodeRPCPortPathExists := exists(nodeRPCPortPath) if err != nil { return "", 0, err } else if nodeKeyPubPathExists != nodeKeyPathExists { return "", 0, fmt.Errorf("%q or %q exist without the other existing", nodeKeyPath, nodeKeyPubPath) } var ( pubKeyStr string rpcPort int ) if nodeKeyPathExists { if pubKeyStr, err = loadAllocID(metaDirPath); err != nil { return "", 0, fmt.Errorf("reading node public key file: %w", err) } } else { if err := initDirFor(nodeKeyPath); err != nil { return "", 0, fmt.Errorf("creating directory for %q: %w", nodeKeyPath, err) } pubKey, privKey := GeneratePeerKey() if err := os.WriteFile(nodeKeyPath, privKey, 0400); err != nil { return "", 0, fmt.Errorf("writing private key to %q: %w", nodeKeyPath, err) } else if err := os.WriteFile(nodeKeyPubPath, pubKey, 0440); err != nil { return "", 0, fmt.Errorf("writing public key to %q: %w", nodeKeyPubPath, err) } pubKeyStr = hex.EncodeToString(pubKey) } if nodeRPCPortPathExists { if rpcPortStr, err := os.ReadFile(nodeRPCPortPath); err != nil { return "", 0, fmt.Errorf("reading rpc port from %q: %w", nodeRPCPortPath, err) } else if rpcPort, err = strconv.Atoi(string(rpcPortStr)); err != nil { return "", 0, fmt.Errorf("parsing rpc port %q from %q: %w", rpcPortStr, nodeRPCPortPath, err) } } else { if err := initDirFor(nodeRPCPortPath); err != nil { return "", 0, fmt.Errorf("creating directory for %q: %w", nodeRPCPortPath, err) } rpcPortStr := strconv.Itoa(initRPCPort) if err := os.WriteFile(nodeRPCPortPath, []byte(rpcPortStr), 0440); err != nil { return "", 0, fmt.Errorf("writing rpc port %q to %q: %w", rpcPortStr, nodeRPCPortPath, err) } rpcPort = initRPCPort } return pubKeyStr, rpcPort, nil }