mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 23:18:49 +00:00
- Added comprehensive tests for production command flag parsing to ensure correct handling of bootstrap, VPS IP, and peer configurations. - Updated production command help output to clarify the usage of new flags, including `--vps-ip` and `--bootstrap-join`. - Modified the configuration generation logic to incorporate the new `bootstrapJoin` parameter for secondary bootstrap nodes. - Enhanced systemd service generation to include the correct advertise IP and join address for non-bootstrap nodes. - Implemented tests for RQLite service generation to verify the inclusion of join addresses and advertise IPs in the generated units.
242 lines
6.9 KiB
Go
242 lines
6.9 KiB
Go
package production
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/environments/templates"
|
|
"github.com/libp2p/go-libp2p/core/crypto"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
)
|
|
|
|
// ConfigGenerator manages generation of node, gateway, and service configs
|
|
type ConfigGenerator struct {
|
|
debrosDir string
|
|
}
|
|
|
|
// NewConfigGenerator creates a new config generator
|
|
func NewConfigGenerator(debrosDir string) *ConfigGenerator {
|
|
return &ConfigGenerator{
|
|
debrosDir: debrosDir,
|
|
}
|
|
}
|
|
|
|
// GenerateNodeConfig generates node.yaml configuration
|
|
func (cg *ConfigGenerator) GenerateNodeConfig(isBootstrap bool, bootstrapPeers []string, vpsIP string, bootstrapJoin string) (string, error) {
|
|
var nodeID string
|
|
if isBootstrap {
|
|
nodeID = "bootstrap"
|
|
} else {
|
|
nodeID = "node"
|
|
}
|
|
|
|
if isBootstrap {
|
|
// Bootstrap node - populate peer list and optional join address
|
|
data := templates.BootstrapConfigData{
|
|
NodeID: nodeID,
|
|
P2PPort: 4001,
|
|
DataDir: filepath.Join(cg.debrosDir, "data", "bootstrap"),
|
|
RQLiteHTTPPort: 5001,
|
|
RQLiteRaftPort: 7001,
|
|
ClusterAPIPort: 9094,
|
|
IPFSAPIPort: 4501,
|
|
BootstrapPeers: bootstrapPeers,
|
|
RQLiteJoinAddress: bootstrapJoin,
|
|
}
|
|
return templates.RenderBootstrapConfig(data)
|
|
}
|
|
|
|
// Regular node - must have join address
|
|
rqliteJoinAddr := "localhost:7001"
|
|
if vpsIP != "" {
|
|
rqliteJoinAddr = vpsIP + ":7001"
|
|
}
|
|
|
|
data := templates.NodeConfigData{
|
|
NodeID: nodeID,
|
|
P2PPort: 4001,
|
|
DataDir: filepath.Join(cg.debrosDir, "data", "node"),
|
|
RQLiteHTTPPort: 5001,
|
|
RQLiteRaftPort: 7001,
|
|
RQLiteJoinAddress: rqliteJoinAddr,
|
|
BootstrapPeers: bootstrapPeers,
|
|
ClusterAPIPort: 9094,
|
|
IPFSAPIPort: 4501,
|
|
}
|
|
return templates.RenderNodeConfig(data)
|
|
}
|
|
|
|
// GenerateGatewayConfig generates gateway.yaml configuration
|
|
func (cg *ConfigGenerator) GenerateGatewayConfig(bootstrapPeers []string, enableHTTPS bool, domain string, olricServers []string) (string, error) {
|
|
tlsCacheDir := ""
|
|
if enableHTTPS {
|
|
tlsCacheDir = filepath.Join(cg.debrosDir, "tls-cache")
|
|
}
|
|
|
|
data := templates.GatewayConfigData{
|
|
ListenPort: 6001,
|
|
BootstrapPeers: bootstrapPeers,
|
|
OlricServers: olricServers,
|
|
ClusterAPIPort: 9094,
|
|
IPFSAPIPort: 4501,
|
|
EnableHTTPS: enableHTTPS,
|
|
DomainName: domain,
|
|
TLSCacheDir: tlsCacheDir,
|
|
RQLiteDSN: "", // Empty for now, can be configured later
|
|
}
|
|
return templates.RenderGatewayConfig(data)
|
|
}
|
|
|
|
// GenerateOlricConfig generates Olric configuration
|
|
func (cg *ConfigGenerator) GenerateOlricConfig(bindAddr string, httpPort, memberlistPort int) (string, error) {
|
|
data := templates.OlricConfigData{
|
|
BindAddr: bindAddr,
|
|
HTTPPort: httpPort,
|
|
MemberlistPort: memberlistPort,
|
|
}
|
|
return templates.RenderOlricConfig(data)
|
|
}
|
|
|
|
// SecretGenerator manages generation of shared secrets and keys
|
|
type SecretGenerator struct {
|
|
debrosDir string
|
|
}
|
|
|
|
// NewSecretGenerator creates a new secret generator
|
|
func NewSecretGenerator(debrosDir string) *SecretGenerator {
|
|
return &SecretGenerator{
|
|
debrosDir: debrosDir,
|
|
}
|
|
}
|
|
|
|
// EnsureClusterSecret gets or generates the IPFS Cluster secret
|
|
func (sg *SecretGenerator) EnsureClusterSecret() (string, error) {
|
|
secretPath := filepath.Join(sg.debrosDir, "secrets", "cluster-secret")
|
|
secretDir := filepath.Dir(secretPath)
|
|
|
|
// Ensure secrets directory exists
|
|
if err := os.MkdirAll(secretDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create secrets directory: %w", err)
|
|
}
|
|
|
|
// Try to read existing secret
|
|
if data, err := os.ReadFile(secretPath); err == nil {
|
|
secret := strings.TrimSpace(string(data))
|
|
if len(secret) == 64 {
|
|
return secret, nil
|
|
}
|
|
}
|
|
|
|
// Generate new secret (32 bytes = 64 hex chars)
|
|
bytes := make([]byte, 32)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", fmt.Errorf("failed to generate cluster secret: %w", err)
|
|
}
|
|
secret := hex.EncodeToString(bytes)
|
|
|
|
// Write and protect
|
|
if err := os.WriteFile(secretPath, []byte(secret), 0600); err != nil {
|
|
return "", fmt.Errorf("failed to save cluster secret: %w", err)
|
|
}
|
|
|
|
return secret, nil
|
|
}
|
|
|
|
// EnsureSwarmKey gets or generates the IPFS private swarm key
|
|
func (sg *SecretGenerator) EnsureSwarmKey() ([]byte, error) {
|
|
swarmKeyPath := filepath.Join(sg.debrosDir, "secrets", "swarm.key")
|
|
secretDir := filepath.Dir(swarmKeyPath)
|
|
|
|
// Ensure secrets directory exists
|
|
if err := os.MkdirAll(secretDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create secrets directory: %w", err)
|
|
}
|
|
|
|
// Try to read existing key
|
|
if data, err := os.ReadFile(swarmKeyPath); err == nil {
|
|
if strings.Contains(string(data), "/key/swarm/psk/1.0.0/") {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Generate new key (32 bytes)
|
|
keyBytes := make([]byte, 32)
|
|
if _, err := rand.Read(keyBytes); err != nil {
|
|
return nil, fmt.Errorf("failed to generate swarm key: %w", err)
|
|
}
|
|
|
|
keyHex := strings.ToUpper(hex.EncodeToString(keyBytes))
|
|
content := fmt.Sprintf("/key/swarm/psk/1.0.0/\n/base16/\n%s\n", keyHex)
|
|
|
|
// Write and protect
|
|
if err := os.WriteFile(swarmKeyPath, []byte(content), 0600); err != nil {
|
|
return nil, fmt.Errorf("failed to save swarm key: %w", err)
|
|
}
|
|
|
|
return []byte(content), nil
|
|
}
|
|
|
|
// EnsureNodeIdentity gets or generates the node's LibP2P identity
|
|
func (sg *SecretGenerator) EnsureNodeIdentity(nodeType string) (peer.ID, error) {
|
|
keyDir := filepath.Join(sg.debrosDir, "data", nodeType)
|
|
keyPath := filepath.Join(keyDir, "identity.key")
|
|
|
|
// Ensure data directory exists
|
|
if err := os.MkdirAll(keyDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create data directory: %w", err)
|
|
}
|
|
|
|
// Try to read existing key
|
|
if data, err := os.ReadFile(keyPath); err == nil {
|
|
priv, err := crypto.UnmarshalPrivateKey(data)
|
|
if err == nil {
|
|
pub := priv.GetPublic()
|
|
peerID, _ := peer.IDFromPublicKey(pub)
|
|
return peerID, nil
|
|
}
|
|
}
|
|
|
|
// Generate new identity
|
|
priv, pub, err := crypto.GenerateKeyPair(crypto.Ed25519, 2048)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate identity: %w", err)
|
|
}
|
|
|
|
peerID, _ := peer.IDFromPublicKey(pub)
|
|
|
|
// Marshal and save private key
|
|
keyData, err := crypto.MarshalPrivateKey(priv)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to marshal private key: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(keyPath, keyData, 0600); err != nil {
|
|
return "", fmt.Errorf("failed to save identity key: %w", err)
|
|
}
|
|
|
|
return peerID, nil
|
|
}
|
|
|
|
// SaveConfig writes a configuration file to disk
|
|
func (sg *SecretGenerator) SaveConfig(filename string, content string) error {
|
|
configDir := filepath.Join(sg.debrosDir, "configs")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create configs directory: %w", err)
|
|
}
|
|
|
|
configPath := filepath.Join(configDir, filename)
|
|
if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
|
|
return fmt.Errorf("failed to write config %s: %w", filename, err)
|
|
}
|
|
|
|
// Fix ownership
|
|
exec.Command("chown", "debros:debros", configPath).Run()
|
|
|
|
return nil
|
|
}
|