Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
someguy
.autoconf-cache
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ The following emojis are used to highlight certain changes:

### Added

- Added automatic configuration (autoconf) of bootstrap nodes and delegated routing endpoints. Autoconf can be disabled and autoconf endpoints can be manually supplied to provide alternate configuration data. Delegated routing endpoints can also be manually supplied, overriding auto-configured values.
- AutoConf support with `auto` placeholders for bootstrap peers and delegated routing endpoints ([Autoconf #123](https://github.com/ipfs/someguy/pull/123))
- Configuration flags:
- `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable automatic configuration expansion (default: `true`)
- `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch autoconf data from (default: `https://conf.ipfs-mainnet.org/autoconf.json`)
- `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: Interval for refreshing autoconf data (default: `24h`)
- When autoconf is disabled, `auto` placeholders will cause an error, requiring explicit values

### Changed

- [go-libp2p v0.43.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.43.0)
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ If you don't want to run a server yourself, but want to query some other server,

For more details run `someguy ask --help`.

### AutoConf

Someguy supports automatic configuration of bootstrap peers and delegated routing endpoints through the autoconf feature. When enabled (default), Someguy will fetch configuration from a remote URL and automatically expand the special `auto` placeholder value with network-appropriate defaults.

This feature can be configured via:
- `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable autoconf (default: `true`)
- `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch configuration from (default: `https://conf.ipfs-mainnet.org/autoconf.json`)
- `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: How often to refresh configuration (default: `24h`)

The `auto` placeholder can be used in:
- `--endpoing` / `SOMEGUY_DELEGATED_ENDPOINT`: the Delegated Routing V1 endpoint to ask

**Note:** When autoconf is disabled (`--autoconf=false`), using the `auto` placeholder will cause an error. You must provide explicit values for these configurations when autoconf is disabled.

## Deployment

Suggested method for self-hosting is to run a [prebuilt Docker image](#docker).
Expand Down
153 changes: 153 additions & 0 deletions autoconf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package main

import (
"context"
"fmt"
"path/filepath"
"slices"
"strings"
"time"

"github.com/ipfs/boxo/autoconf"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
)

// autoConfConfig contains the configuration for the autoconf subsystem
type autoConfConfig struct {
// enabled determines whether to use autoconf
// Default: true
enabled bool

// url is the HTTP(S) URL to fetch the autoconf.json from
// Default: https://conf.ipfs-mainnet.org/autoconf.json
url string

// refreshInterval is how often to refresh autoconf data
// Default: 24h
refreshInterval time.Duration

// cacheDir is the directory to cache autoconf data
// Default: $SOMEGUY_DATADIR/.autoconf-cache
cacheDir string
}

func startAutoConf(ctx context.Context, cfg *config) (*autoconf.Config, error) {
var autoConf *autoconf.Config
if cfg.autoConf.enabled && cfg.autoConf.url != "" {
client, err := createAutoConfClient(cfg.autoConf)
if err != nil {
return nil, fmt.Errorf("failed to create autoconf client: %w", err)
}
// Start primes cache and starts background updater
// Note: Start() always returns a config (using fallback if needed)
autoConf, err = client.Start(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start autoconf updater: %w", err)
}
}
return autoConf, nil
}

func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.AddrInfo {
if autoConf != nil {
nativeSystems := getNativeSystems(cfg.dhtType)
return stringsToPeerAddrInfos(autoConf.GetBootstrapPeers(nativeSystems...))
}
// Fallback to hard-coded bootstrappers.
return dht.GetDefaultBootstrapPeerAddrInfos()
}

func expandContentEndpoints(cfg *config, autoConf *autoconf.Config) error {
if !cfg.autoConf.enabled {
if slices.Contains(cfg.contentEndpoints, autoconf.AutoPlaceholder) {
return autoconfDisabledError("endpoint option", "SOMEGUY_DELEGATED_ENDPOINT", "--endpoint")
}
return nil
}

nativeSystems := getNativeSystems(cfg.dhtType)

// Someguy only uses read-only endpoints for providers, peers, and IPNS
cfg.contentEndpoints = autoconf.ExpandDelegatedEndpoints(cfg.contentEndpoints, autoConf, nativeSystems,
autoconf.RoutingV1ProvidersPath,
autoconf.RoutingV1PeersPath,
autoconf.RoutingV1IPNSPath)

// Need to remove "/routing/v1/providers" because routing.FindProviders adds it back on.
for i, ep := range cfg.contentEndpoints {
ep = strings.TrimSuffix(ep, autoconf.RoutingV1ProvidersPath)
ep = strings.TrimSuffix(ep, autoconf.RoutingV1PeersPath)
ep = strings.TrimSuffix(ep, autoconf.RoutingV1IPNSPath)
cfg.contentEndpoints[i] = ep
}

// Remove duplicates.
slices.Sort(cfg.contentEndpoints)
cfg.contentEndpoints = slices.Compact(cfg.contentEndpoints)

return nil
}

// autoconfDisabledError returns a consistent error message when auto placeholder is found but autoconf is disabled
func autoconfDisabledError(configType, envVar, flag string) error {
return fmt.Errorf("'auto' placeholder found in %s but autoconf is disabled. Set explicit %s with %s or %s, or re-enable autoconf",
configType, configType, envVar, flag)
}

func stringsToPeerAddrInfos(addrs []string) []peer.AddrInfo {
addrInfos := make([]peer.AddrInfo, 0, len(addrs))

for _, s := range addrs {
ma, err := multiaddr.NewMultiaddr(s)
if err != nil {
logger.Error("bad multiaddr in bootstrapper autoconf data", "err", err)
continue
}

info, err := peer.AddrInfoFromP2pAddr(ma)
if err != nil {
logger.Errorw("failed to convert bootstrapper address to peer addr info", "address", ma.String(), err, "err")
continue
}
addrInfos = append(addrInfos, *info)
}

return addrInfos
}

// createAutoConfClient creates an autoconf client with the given configuration
func createAutoConfClient(config autoConfConfig) (*autoconf.Client, error) {
if config.cacheDir == "" {
config.cacheDir = filepath.Join(".", ".autoconf-cache")
}
if config.refreshInterval == 0 {
config.refreshInterval = autoconf.DefaultRefreshInterval
}
if config.url == "" {
config.url = autoconf.MainnetAutoConfURL
}

return autoconf.NewClient(
autoconf.WithCacheDir(config.cacheDir),
autoconf.WithUserAgent("someguy/"+version),
autoconf.WithCacheSize(autoconf.DefaultCacheSize),
autoconf.WithTimeout(autoconf.DefaultTimeout),
autoconf.WithURL(config.url),
autoconf.WithRefreshInterval(config.refreshInterval),
)
}

// getNativeSystems returns the list of systems that should be used natively based on routing type
func getNativeSystems(routingType string) []string {
switch routingType {
case "dht", "accelerated", "standard", "auto":
return []string{autoconf.SystemAminoDHT}
case "disabled", "off", "none", "delegated", "custom":
return []string{}
default:
logger.Warnf("getNativeSystems: unknown routing type %q, assuming no native systems", routingType)
return []string{}
}
}
92 changes: 92 additions & 0 deletions autoconf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"testing"

autoconf "github.com/ipfs/boxo/autoconf"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestGetNativeSystems verifies that routing types are correctly mapped to native systems
func TestGetNativeSystems(t *testing.T) {
tests := []struct {
name string
routingType string
expectedSystems []string
}{
{
name: "DHT routing",
routingType: "dht",
expectedSystems: []string{autoconf.SystemAminoDHT},
},
{
name: "Accelerated routing",
routingType: "accelerated",
expectedSystems: []string{autoconf.SystemAminoDHT},
},
{
name: "Standard routing",
routingType: "standard",
expectedSystems: []string{autoconf.SystemAminoDHT},
},
{
name: "Auto routing",
routingType: "auto",
expectedSystems: []string{autoconf.SystemAminoDHT},
},
{
name: "Off routing",
routingType: "off",
expectedSystems: []string{},
},
{
name: "None routing",
routingType: "none",
expectedSystems: []string{},
},
{
name: "Unknown routing type",
routingType: "custom",
expectedSystems: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
systems := getNativeSystems(tt.routingType)
assert.Equal(t, tt.expectedSystems, systems)
})
}
}

// TestExpandContentEndpoints verifies endpoint expansion behavior when autoconf is disabled
func TestExpandContentEndpoints(t *testing.T) {
cfg := config{
autoConf: autoConfConfig{
enabled: false,
},
contentEndpoints: []string{autoconf.AutoPlaceholder},
}

t.Run("auto placeholder errors when autoconf disabled", func(t *testing.T) {
err := expandContentEndpoints(&cfg, nil)
require.Error(t, err, "should error when 'auto' is used with autoconf disabled")
assert.Contains(t, err.Error(), "'auto' placeholder found in endpoint option", "error should mention bootstrap peers")
assert.Contains(t, err.Error(), "SOMEGUY_DELEGATED_ENDPOINT", "error should mention how to fix")
})

t.Run("custom endpoint preserved when autoconf disabled", func(t *testing.T) {
custom := []string{"https://example.com"}
cfg.contentEndpoints = custom
err := expandContentEndpoints(&cfg, nil)
require.NoError(t, err, "custom endpoint should not error")
assert.Equal(t, custom, cfg.contentEndpoints, "custom endpoint should be preserved")
})

t.Run("mixed auto and custom errors when autoconf disabled", func(t *testing.T) {
cfg.contentEndpoints = []string{autoconf.AutoPlaceholder, "https://example.com"}
err := expandContentEndpoints(&cfg, nil)
require.Error(t, err, "should error when 'auto' is mixed with custom values")
})
}
4 changes: 2 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutput bool) error {
drc, err := client.New(endpoint)
drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true))
if err != nil {
return err
}
Expand Down Expand Up @@ -77,7 +77,7 @@ func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutp
}

func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput bool) error {
drc, err := client.New(endpoint)
drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true))
if err != nil {
return err
}
Expand Down
Loading
Loading