From b72a6b7f10b565bc3095240935774e127cd7ccdb Mon Sep 17 00:00:00 2001 From: Enrique Lacal Date: Thu, 23 Jul 2026 13:08:32 +0100 Subject: [PATCH] fix: private IPFS swarm nodes never peer, breaking shared storage The pinned ipfs/go-ipfs:v0.10.0 image has a private-network (pnet) connection bug where swarm connections drop immediately after handshake, so org IPFS nodes never peer with each other. This causes "failed to bootstrap (no peers found)" and shared storage downloads to time out fetching content pinned only on another member's node. Bump to ipfs/kubo:v0.42.0, and since modern Kubo refuses to start (or never peers) in private-network mode with its default AutoConf/public bootstrap config, add a container-init.d script that disables it for private-mode stacks. Signed-off-by: Enrique Lacal Signed-off-by: Daniel Raper --- internal/constants/constants.go | 2 +- internal/docker/docker_config.go | 5 +++++ internal/stacks/ipfs_config.go | 17 +++++++++++++++++ internal/stacks/stack_manager.go | 26 ++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/internal/constants/constants.go b/internal/constants/constants.go index ac2d7bbf..21f47066 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -23,7 +23,7 @@ import ( var StacksDir = checkHome() var FireFlyCoreImageName = "ghcr.io/hyperledger-firefly/firefly" -var IPFSImageName = "ipfs/go-ipfs:v0.10.0" +var IPFSImageName = "ipfs/kubo:v0.42.0" var PostgresImageName = "postgres" var PrometheusImageName = "prom/prometheus" var SandboxImageName = "ghcr.io/hyperledger-firefly/sandbox:latest" diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index 97088d5a..383c4d17 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -167,6 +167,11 @@ func CreateDockerCompose(s *types.Stack) *DockerComposeConfig { "LIBP2P_FORCE_PNET": "1", }, ) + // Kubo's AutoConf/public-network defaults are incompatible with a + // private swarm and prevent peering with other members unless + // disabled via a container-init.d script - see ipfs_config.go. + sharedStorage.Volumes = append(sharedStorage.Volumes, fmt.Sprintf("ipfs_init_%s:/container-init.d", member.ID)) + compose.Volumes[fmt.Sprintf("ipfs_init_%s", member.ID)] = struct{}{} } else { sharedStorage.Environment = s.EnvironmentVars } diff --git a/internal/stacks/ipfs_config.go b/internal/stacks/ipfs_config.go index a63f006c..e05f8541 100644 --- a/internal/stacks/ipfs_config.go +++ b/internal/stacks/ipfs_config.go @@ -29,3 +29,20 @@ func GenerateSwarmKey() (string, error) { hexKey := hex.EncodeToString(key) return "/key/swarm/psk/1.0.0/\n/base16/\n" + hexKey, nil } + +// GenerateIPFSPrivateNetInitScript returns a container-init.d script that +// disables Kubo's AutoConf/public-network defaults, which are incompatible +// with a private swarm (swarm.key / LIBP2P_FORCE_PNET) and otherwise prevent +// the daemon from starting or from ever peering with other members. +func GenerateIPFSPrivateNetInitScript() string { + return `#!/bin/sh +ipfs config --json AutoConf.Enabled false +ipfs config --json Bootstrap '[]' +ipfs config --json DNS.Resolvers '{}' +ipfs config --json Routing.DelegatedRouters '[]' +ipfs config --json Ipns.DelegatedPublishers '[]' +ipfs config Routing.Type dht +ipfs config --json AutoTLS.Enabled false +ipfs config --json Swarm.Transports.Network.Websocket false +` +} diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index c8bfb7f6..e014b876 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -505,6 +505,13 @@ func (s *StackManager) writeConfig(options *types.InitOptions) error { } } + if s.Stack.IPFSMode.Equals(types.IPFSModePrivate) { + initScript := GenerateIPFSPrivateNetInitScript() + if err := os.WriteFile(path.Join(s.Stack.InitDir, "config", "ipfs_privatenet_init.sh"), []byte(initScript), 0755); err != nil { + return err + } + } + return nil } @@ -566,6 +573,21 @@ func (s *StackManager) copyDataExchangeConfigToVolumes() error { return nil } +func (s *StackManager) copyIPFSInitScriptToVolumes() error { + if !s.Stack.IPFSMode.Equals(types.IPFSModePrivate) { + return nil + } + configDir := filepath.Join(s.Stack.RuntimeDir, "config") + scriptPath := path.Join(configDir, "ipfs_privatenet_init.sh") + for _, member := range s.Stack.Members { + volumeName := fmt.Sprintf("%s_ipfs_init_%s", s.Stack.Name, member.ID) + if err := docker.CopyFileToVolume(s.ctx, volumeName, scriptPath, "/privatenet-init.sh"); err != nil { + return err + } + } + return nil +} + func (s *StackManager) createMember(id string, index int, options *types.InitOptions, external bool) (*types.Organization, error) { serviceBase := options.ServicesBasePort + (index * 100) ptmBase := options.PtmBasePort + (index * 10) @@ -908,6 +930,10 @@ func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages return messages, err } + if err := s.copyIPFSInitScriptToVolumes(); err != nil { + return messages, err + } + pullOptions := &types.PullOptions{ Retries: 2, }