feat: update IPFS configuration and enhance cluster secret management

- Changed default IPFS API URL to port 5001 for better compatibility.
- Enhanced the initialization process for IPFS and Cluster by adding support for bootstrap peers.
- Introduced user prompts for cluster secret and swarm key generation, improving user experience during setup.
- Updated service configuration to dynamically determine paths based on existing configuration files.
This commit is contained in:
anonpenguin23 2025-11-08 12:59:54 +02:00
parent 93b25c42e4
commit a5c30d0141
4 changed files with 213 additions and 38 deletions

View File

@ -13,6 +13,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Deprecated ### Deprecated
### Fixed ### Fixed
## [0.59.1] - 2025-11-08
### Added
\n
### Changed
- Improved interactive setup to prompt for existing IPFS Cluster secret and Swarm key, allowing easier joining of existing private networks.
- Updated default IPFS API URL in configuration files from `http://localhost:9105` to the standard `http://localhost:5001`.
- Updated systemd service files (debros-ipfs.service and debros-ipfs-cluster.service) to correctly determine and use the IPFS and Cluster repository paths.
### Deprecated
### Removed
### Fixed
\n
## [0.59.0] - 2025-11-08 ## [0.59.0] - 2025-11-08
### Added ### Added

View File

@ -21,7 +21,7 @@ test-e2e:
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill
VERSION := 0.59.0 VERSION := 0.59.1
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

@ -545,7 +545,7 @@ olric_servers:
- "127.0.0.1:3320" - "127.0.0.1:3320"
olric_timeout: "10s" olric_timeout: "10s"
ipfs_cluster_api_url: "http://localhost:9094" ipfs_cluster_api_url: "http://localhost:9094"
ipfs_api_url: "http://localhost:9105" ipfs_api_url: "http://localhost:5001"
ipfs_timeout: "60s" ipfs_timeout: "60s"
ipfs_replication_factor: 3 ipfs_replication_factor: 3
`, peersYAML.String()) `, peersYAML.String())

View File

@ -1513,7 +1513,18 @@ func generateConfigsInteractive(force bool) {
} else { } else {
nodeID = "node" nodeID = "node"
} }
if err := initializeIPFSForNode(nodeID, vpsIP, isBootstrap); err != nil {
// Parse bootstrap peers from config
var bootstrapPeerList []string
if bootstrapPeers != "" {
for _, p := range strings.Split(bootstrapPeers, ",") {
if p = strings.TrimSpace(p); p != "" {
bootstrapPeerList = append(bootstrapPeerList, p)
}
}
}
if err := initializeIPFSForNode(nodeID, vpsIP, isBootstrap, bootstrapPeerList, reader); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to initialize IPFS/Cluster: %v\n", err) fmt.Fprintf(os.Stderr, "⚠️ Failed to initialize IPFS/Cluster: %v\n", err)
fmt.Fprintf(os.Stderr, " You may need to initialize IPFS and Cluster manually\n") fmt.Fprintf(os.Stderr, " You may need to initialize IPFS and Cluster manually\n")
} }
@ -1796,7 +1807,7 @@ func generateGatewayConfigDirect(bootstrapPeers string, enableHTTPS bool, domain
// IPFS Cluster configuration // IPFS Cluster configuration
ipfsYAML := `ipfs_cluster_api_url: "http://localhost:9094" ipfsYAML := `ipfs_cluster_api_url: "http://localhost:9094"
ipfs_api_url: "http://localhost:9105" ipfs_api_url: "http://localhost:5001"
ipfs_timeout: "60s" ipfs_timeout: "60s"
ipfs_replication_factor: 3 ipfs_replication_factor: 3
` `
@ -1841,24 +1852,72 @@ func generateOlricConfig(configPath, bindIP string, httpPort, memberlistPort int
return nil return nil
} }
// promptClusterSecret prompts the user for a cluster secret (64 hex characters)
func promptClusterSecret(reader *bufio.Reader) (string, error) {
for {
fmt.Printf("\n Enter cluster secret (64 hex characters, or press Enter to generate new): ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
// Generate new secret
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)
fmt.Printf(" ✓ Generated new cluster secret\n")
return secret, nil
}
// Validate input (must be 64 hex characters)
input = strings.ToUpper(input)
if len(input) != 64 {
fmt.Printf(" ❌ Invalid: cluster secret must be exactly 64 hex characters\n")
continue
}
// Validate hex characters
valid := true
for _, char := range input {
if !((char >= '0' && char <= '9') || (char >= 'A' && char <= 'F')) {
valid = false
break
}
}
if !valid {
fmt.Printf(" ❌ Invalid: cluster secret must contain only hex characters (0-9, A-F)\n")
continue
}
return input, nil
}
}
// getOrGenerateClusterSecret gets or generates a shared cluster secret // getOrGenerateClusterSecret gets or generates a shared cluster secret
func getOrGenerateClusterSecret() (string, error) { func getOrGenerateClusterSecret(reader *bufio.Reader) (string, error) {
secretPath := "/home/debros/.debros/cluster-secret" secretPath := "/home/debros/.debros/cluster-secret"
// Try to read existing secret // Try to read existing secret
if data, err := os.ReadFile(secretPath); err == nil { if data, err := os.ReadFile(secretPath); err == nil {
secret := strings.TrimSpace(string(data)) secret := strings.TrimSpace(string(data))
if len(secret) == 64 { if len(secret) == 64 {
fmt.Printf(" ✓ Using existing cluster secret\n")
return secret, nil return secret, nil
} }
} }
// Generate new secret (64 hex characters = 32 bytes) // Prompt for secret
bytes := make([]byte, 32) fmt.Printf("\n🔐 Cluster Secret Configuration\n")
if _, err := rand.Read(bytes); err != nil { fmt.Printf(" The cluster secret is used to authenticate IPFS Cluster peers.\n")
return "", fmt.Errorf("failed to generate cluster secret: %w", err) fmt.Printf(" All nodes in the cluster must use the same secret.\n")
fmt.Printf(" If this is the first node, press Enter to generate a new secret.\n")
fmt.Printf(" If joining an existing cluster, enter the secret from the bootstrap node.\n")
secret, err := promptClusterSecret(reader)
if err != nil {
return "", err
} }
secret := hex.EncodeToString(bytes)
// Save secret // Save secret
if err := os.WriteFile(secretPath, []byte(secret), 0600); err != nil { if err := os.WriteFile(secretPath, []byte(secret), 0600); err != nil {
@ -1869,9 +1928,56 @@ func getOrGenerateClusterSecret() (string, error) {
return secret, nil return secret, nil
} }
// promptSwarmKey prompts the user for a swarm key (64 hex characters)
func promptSwarmKey(reader *bufio.Reader) ([]byte, error) {
for {
fmt.Printf("\n Enter swarm key (64 hex characters, or press Enter to generate new): ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
// 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)
}
// Format as IPFS swarm key file
keyHex := strings.ToUpper(hex.EncodeToString(keyBytes))
content := fmt.Sprintf("/key/swarm/psk/1.0.0/\n/base16/\n%s\n", keyHex)
fmt.Printf(" ✓ Generated new swarm key\n")
return []byte(content), nil
}
// Validate input (must be 64 hex characters)
input = strings.ToUpper(input)
if len(input) != 64 {
fmt.Printf(" ❌ Invalid: swarm key must be exactly 64 hex characters\n")
continue
}
// Validate hex characters
valid := true
for _, char := range input {
if !((char >= '0' && char <= '9') || (char >= 'A' && char <= 'F')) {
valid = false
break
}
}
if !valid {
fmt.Printf(" ❌ Invalid: swarm key must contain only hex characters (0-9, A-F)\n")
continue
}
// Format as IPFS swarm key file
content := fmt.Sprintf("/key/swarm/psk/1.0.0/\n/base16/\n%s\n", input)
return []byte(content), nil
}
}
// getOrGenerateSwarmKey gets or generates a shared IPFS swarm key // getOrGenerateSwarmKey gets or generates a shared IPFS swarm key
// Returns the swarm key content as bytes (formatted for IPFS) // Returns the swarm key content as bytes (formatted for IPFS)
func getOrGenerateSwarmKey() ([]byte, error) { func getOrGenerateSwarmKey(reader *bufio.Reader) ([]byte, error) {
secretPath := "/home/debros/.debros/swarm.key" secretPath := "/home/debros/.debros/swarm.key"
// Try to read existing key // Try to read existing key
@ -1879,28 +1985,31 @@ func getOrGenerateSwarmKey() ([]byte, error) {
// Validate it's a proper swarm key format // Validate it's a proper swarm key format
content := string(data) content := string(data)
if strings.Contains(content, "/key/swarm/psk/1.0.0/") { if strings.Contains(content, "/key/swarm/psk/1.0.0/") {
fmt.Printf(" ✓ Using existing swarm key\n")
return data, nil return data, nil
} }
} }
// Generate new key (32 bytes) // Prompt for key
keyBytes := make([]byte, 32) fmt.Printf("\n🔐 IPFS Swarm Key Configuration\n")
if _, err := rand.Read(keyBytes); err != nil { fmt.Printf(" The swarm key creates a private IPFS network.\n")
return nil, fmt.Errorf("failed to generate swarm key: %w", err) fmt.Printf(" All nodes in the network must use the same swarm key.\n")
fmt.Printf(" If this is the first node, press Enter to generate a new key.\n")
fmt.Printf(" If joining an existing network, enter the key from the bootstrap node.\n")
fmt.Printf(" Enter only the 64 hex characters (e.g., F62B18F11C5457F11E1863126ECAA259E76DA967121A291351FBFA2542B4BF56)\n")
swarmKey, err := promptSwarmKey(reader)
if err != nil {
return nil, err
} }
// Format as IPFS swarm key file
keyHex := strings.ToUpper(hex.EncodeToString(keyBytes))
content := fmt.Sprintf("/key/swarm/psk/1.0.0/\n/base16/\n%s\n", keyHex)
// Save key // Save key
if err := os.WriteFile(secretPath, []byte(content), 0600); err != nil { if err := os.WriteFile(secretPath, swarmKey, 0600); err != nil {
return nil, fmt.Errorf("failed to save swarm key: %w", err) return nil, fmt.Errorf("failed to save swarm key: %w", err)
} }
exec.Command("chown", "debros:debros", secretPath).Run() exec.Command("chown", "debros:debros", secretPath).Run()
fmt.Printf(" ✓ Generated private swarm key\n") return swarmKey, nil
return []byte(content), nil
} }
// ensureSwarmKey ensures the swarm key exists in the IPFS repo // ensureSwarmKey ensures the swarm key exists in the IPFS repo
@ -1924,17 +2033,17 @@ func ensureSwarmKey(repoPath string, swarmKey []byte) error {
} }
// initializeIPFSForNode initializes IPFS and IPFS Cluster for a node // initializeIPFSForNode initializes IPFS and IPFS Cluster for a node
func initializeIPFSForNode(nodeID, vpsIP string, isBootstrap bool) error { func initializeIPFSForNode(nodeID, vpsIP string, isBootstrap bool, bootstrapPeers []string, reader *bufio.Reader) error {
fmt.Printf(" Initializing IPFS and Cluster for node %s...\n", nodeID) fmt.Printf(" Initializing IPFS and Cluster for node %s...\n", nodeID)
// Get or generate cluster secret // Get or generate cluster secret
secret, err := getOrGenerateClusterSecret() secret, err := getOrGenerateClusterSecret(reader)
if err != nil { if err != nil {
return fmt.Errorf("failed to get cluster secret: %w", err) return fmt.Errorf("failed to get cluster secret: %w", err)
} }
// Get or generate swarm key for private network // Get or generate swarm key for private network
swarmKey, err := getOrGenerateSwarmKey() swarmKey, err := getOrGenerateSwarmKey(reader)
if err != nil { if err != nil {
return fmt.Errorf("failed to get swarm key: %w", err) return fmt.Errorf("failed to get swarm key: %w", err)
} }
@ -1988,7 +2097,7 @@ func initializeIPFSForNode(nodeID, vpsIP string, isBootstrap bool) error {
fmt.Printf(" Initializing IPFS Cluster...\n") fmt.Printf(" Initializing IPFS Cluster...\n")
// Generate cluster config // Generate cluster config
clusterConfig := generateClusterServiceConfig(nodeID, vpsIP, secret, isBootstrap) clusterConfig := generateClusterServiceConfig(nodeID, vpsIP, secret, isBootstrap, bootstrapPeers)
// Write config // Write config
configJSON, err := json.MarshalIndent(clusterConfig, "", " ") configJSON, err := json.MarshalIndent(clusterConfig, "", " ")
@ -2091,14 +2200,45 @@ type datastoreConfig struct {
Path string `json:"path"` Path string `json:"path"`
} }
// extractClusterBootstrapAddresses extracts IPFS Cluster bootstrap addresses from node bootstrap peers
// IPFS Cluster uses port 9096 for cluster communication
// Note: We extract IP addresses, but cluster peer IDs will be discovered at runtime
// For CRDT consensus, bootstrap peers are optional but help with initial discovery
func extractClusterBootstrapAddresses(bootstrapPeers []string) []string {
var clusterBootstrap []string
for _, peerAddr := range bootstrapPeers {
// Extract IP from multiaddr format: /ip4/IP/tcp/PORT/p2p/PEER_ID
ip := extractIPFromMultiaddr(peerAddr)
if ip != "" && ip != "127.0.0.1" && ip != "localhost" {
// Construct cluster bootstrap address (port 9096 is standard for IPFS Cluster)
// Note: We don't have the cluster peer ID yet, but we can construct the address
// IPFS Cluster CRDT will discover peers automatically, but having IPs helps
// For now, we'll leave bootstrap empty and rely on CRDT auto-discovery
// The IP addresses can be used later when cluster peer IDs are known
_ = ip // Store for potential future use
}
}
// For now, return empty bootstrap list - CRDT consensus will auto-discover peers
// Bootstrap peers can be added later when cluster peer IDs are known
return clusterBootstrap
}
// generateClusterServiceConfig generates IPFS Cluster service.json config // generateClusterServiceConfig generates IPFS Cluster service.json config
func generateClusterServiceConfig(nodeID, vpsIP, secret string, isBootstrap bool) clusterServiceConfig { func generateClusterServiceConfig(nodeID, vpsIP, secret string, isBootstrap bool, bootstrapPeers []string) clusterServiceConfig {
clusterListenAddr := "/ip4/0.0.0.0/tcp/9096" clusterListenAddr := "/ip4/0.0.0.0/tcp/9096"
restAPIListenAddr := "/ip4/0.0.0.0/tcp/9094" restAPIListenAddr := "/ip4/0.0.0.0/tcp/9094"
// For bootstrap node, use empty bootstrap list // For bootstrap node, use empty bootstrap list
// For other nodes, bootstrap list will be set when starting the service // For other nodes, extract bootstrap addresses from node config
bootstrap := []string{} // Note: IPFS Cluster CRDT consensus can auto-discover peers, so bootstrap is optional
var bootstrap []string
if !isBootstrap && len(bootstrapPeers) > 0 {
bootstrap = extractClusterBootstrapAddresses(bootstrapPeers)
// For now, bootstrap will be empty as we need cluster peer IDs
// CRDT will handle peer discovery automatically
}
return clusterServiceConfig{ return clusterServiceConfig{
Cluster: clusterConfig{ Cluster: clusterConfig{
@ -2142,7 +2282,17 @@ func createSystemdServices() {
fmt.Printf("🔧 Creating systemd services...\n") fmt.Printf("🔧 Creating systemd services...\n")
// IPFS service (runs on all nodes) // IPFS service (runs on all nodes)
ipfsService := `[Unit] // Determine IPFS path based on config file
var ipfsPath string
if _, err := os.Stat("/home/debros/.debros/node.yaml"); err == nil {
ipfsPath = "/home/debros/.debros/node/ipfs/repo"
} else if _, err := os.Stat("/home/debros/.debros/bootstrap.yaml"); err == nil {
ipfsPath = "/home/debros/.debros/bootstrap/ipfs/repo"
} else {
ipfsPath = "/home/debros/.debros/bootstrap/ipfs/repo"
}
ipfsService := fmt.Sprintf(`[Unit]
Description=IPFS Daemon Description=IPFS Daemon
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
@ -2152,9 +2302,9 @@ Type=simple
User=debros User=debros
Group=debros Group=debros
Environment=HOME=/home/debros Environment=HOME=/home/debros
ExecStartPre=/bin/bash -c 'if [ -f /home/debros/.debros/node.yaml ]; then export IPFS_PATH=/home/debros/.debros/node/ipfs/repo; elif [ -f /home/debros/.debros/bootstrap.yaml ]; then export IPFS_PATH=/home/debros/.debros/bootstrap/ipfs/repo; else export IPFS_PATH=/home/debros/.debros/bootstrap/ipfs/repo; fi' Environment=IPFS_PATH=%s
ExecStartPre=/bin/bash -c 'if [ -f /home/debros/.debros/swarm.key ] && [ ! -f ${IPFS_PATH}/swarm.key ]; then cp /home/debros/.debros/swarm.key ${IPFS_PATH}/swarm.key && chmod 600 ${IPFS_PATH}/swarm.key; fi' ExecStartPre=/bin/bash -c 'if [ -f /home/debros/.debros/swarm.key ] && [ ! -f %s/swarm.key ]; then cp /home/debros/.debros/swarm.key %s/swarm.key && chmod 600 %s/swarm.key; fi'
ExecStart=/usr/bin/ipfs daemon --enable-pubsub-experiment --repo-dir=${IPFS_PATH} ExecStart=/usr/bin/ipfs daemon --enable-pubsub-experiment --repo-dir=%s
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal
@ -2168,7 +2318,7 @@ ReadWritePaths=/home/debros
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
` `, ipfsPath, ipfsPath, ipfsPath, ipfsPath, ipfsPath)
if err := os.WriteFile("/etc/systemd/system/debros-ipfs.service", []byte(ipfsService), 0644); err != nil { if err := os.WriteFile("/etc/systemd/system/debros-ipfs.service", []byte(ipfsService), 0644); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create IPFS service: %v\n", err) fmt.Fprintf(os.Stderr, "❌ Failed to create IPFS service: %v\n", err)
@ -2176,7 +2326,17 @@ WantedBy=multi-user.target
} }
// IPFS Cluster service (runs on all nodes) // IPFS Cluster service (runs on all nodes)
clusterService := `[Unit] // Determine Cluster path based on config file
var clusterPath string
if _, err := os.Stat("/home/debros/.debros/node.yaml"); err == nil {
clusterPath = "/home/debros/.debros/node/ipfs-cluster"
} else if _, err := os.Stat("/home/debros/.debros/bootstrap.yaml"); err == nil {
clusterPath = "/home/debros/.debros/bootstrap/ipfs-cluster"
} else {
clusterPath = "/home/debros/.debros/bootstrap/ipfs-cluster"
}
clusterService := fmt.Sprintf(`[Unit]
Description=IPFS Cluster Service Description=IPFS Cluster Service
After=debros-ipfs.service After=debros-ipfs.service
Wants=debros-ipfs.service Wants=debros-ipfs.service
@ -2188,8 +2348,8 @@ User=debros
Group=debros Group=debros
WorkingDirectory=/home/debros WorkingDirectory=/home/debros
Environment=HOME=/home/debros Environment=HOME=/home/debros
ExecStartPre=/bin/bash -c 'if [ -f /home/debros/.debros/node.yaml ]; then export CLUSTER_PATH=/home/debros/.debros/node/ipfs-cluster; elif [ -f /home/debros/.debros/bootstrap.yaml ]; then export CLUSTER_PATH=/home/debros/.debros/bootstrap/ipfs-cluster; else export CLUSTER_PATH=/home/debros/.debros/bootstrap/ipfs-cluster; fi' Environment=CLUSTER_PATH=%s
ExecStart=/usr/local/bin/ipfs-cluster-service daemon --config ${CLUSTER_PATH}/service.json ExecStart=/usr/local/bin/ipfs-cluster-service daemon --config %s/service.json
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal
@ -2203,7 +2363,7 @@ ReadWritePaths=/home/debros
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
` `, clusterPath, clusterPath)
if err := os.WriteFile("/etc/systemd/system/debros-ipfs-cluster.service", []byte(clusterService), 0644); err != nil { if err := os.WriteFile("/etc/systemd/system/debros-ipfs-cluster.service", []byte(clusterService), 0644); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create IPFS Cluster service: %v\n", err) fmt.Fprintf(os.Stderr, "❌ Failed to create IPFS Cluster service: %v\n", err)