mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
Some checks failed
CodeQL / codeql (push) Has been cancelled
Docker Build / docker-build (push) Has been cancelled
Gateway Conformance / gateway-conformance (push) Has been cancelled
Gateway Conformance / gateway-conformance-libp2p-experiment (push) Has been cancelled
Go Build / go-build (push) Has been cancelled
Go Check / go-check (push) Has been cancelled
Go Lint / go-lint (push) Has been cancelled
Go Test / go-test (push) Has been cancelled
Interop / interop-prep (push) Has been cancelled
Sharness / sharness-test (push) Has been cancelled
Spell Check / spellcheck (push) Has been cancelled
Interop / helia-interop (push) Has been cancelled
Interop / ipfs-webui (push) Has been cancelled
https://github.com/ipfs/kubo/pull/10883 https://github.com/ipshipyard/config.ipfs-mainnet.org/issues/3 --------- Co-authored-by: gammazero <gammazero@users.noreply.github.com>
261 lines
6.9 KiB
Go
261 lines
6.9 KiB
Go
// package config implements the ipfs config file datastructures and utilities.
|
|
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/ipfs/kubo/misc/fsutil"
|
|
)
|
|
|
|
// Config is used to load ipfs config files.
|
|
type Config struct {
|
|
Identity Identity // local node's peer identity
|
|
Datastore Datastore // local node's storage
|
|
Addresses Addresses // local node's addresses
|
|
Mounts Mounts // local node's mount points
|
|
Discovery Discovery // local node's discovery mechanisms
|
|
Routing Routing // local node's routing settings
|
|
Ipns Ipns // Ipns settings
|
|
Bootstrap []string // local nodes's bootstrap peer addresses
|
|
Gateway Gateway // local node's gateway server options
|
|
API API // local node's API settings
|
|
Swarm SwarmConfig
|
|
AutoNAT AutoNATConfig
|
|
AutoTLS AutoTLS
|
|
Pubsub PubsubConfig
|
|
Peering Peering
|
|
DNS DNS
|
|
|
|
Migration Migration
|
|
AutoConf AutoConf
|
|
|
|
Provider Provider
|
|
Reprovider Reprovider
|
|
HTTPRetrieval HTTPRetrieval
|
|
Experimental Experiments
|
|
Plugins Plugins
|
|
Pinning Pinning
|
|
Import Import
|
|
Version Version
|
|
|
|
Internal Internal // experimental/unstable options
|
|
|
|
Bitswap Bitswap `json:",omitempty"`
|
|
}
|
|
|
|
const (
|
|
// DefaultPathName is the default config dir name.
|
|
DefaultPathName = ".ipfs"
|
|
// DefaultPathRoot is the path to the default config dir location.
|
|
DefaultPathRoot = "~/" + DefaultPathName
|
|
// DefaultConfigFile is the filename of the configuration file.
|
|
DefaultConfigFile = "config"
|
|
// EnvDir is the environment variable used to change the path root.
|
|
EnvDir = "IPFS_PATH"
|
|
)
|
|
|
|
// PathRoot returns the default configuration root directory.
|
|
func PathRoot() (string, error) {
|
|
dir := os.Getenv(EnvDir)
|
|
var err error
|
|
if len(dir) == 0 {
|
|
dir, err = fsutil.ExpandHome(DefaultPathRoot)
|
|
}
|
|
return dir, err
|
|
}
|
|
|
|
// Path returns the path `extension` relative to the configuration root. If an
|
|
// empty string is provided for `configroot`, the default root is used.
|
|
func Path(configroot, extension string) (string, error) {
|
|
if len(configroot) == 0 {
|
|
dir, err := PathRoot()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(dir, extension), nil
|
|
|
|
}
|
|
return filepath.Join(configroot, extension), nil
|
|
}
|
|
|
|
// Filename returns the configuration file path given a configuration root
|
|
// directory and a user-provided configuration file path argument with the
|
|
// following rules:
|
|
// - If the user-provided configuration file path is empty, use the default one.
|
|
// - If the configuration root directory is empty, use the default one.
|
|
// - If the user-provided configuration file path is only a file name, use the
|
|
// configuration root directory, otherwise use only the user-provided path
|
|
// and ignore the configuration root.
|
|
func Filename(configroot, userConfigFile string) (string, error) {
|
|
if userConfigFile == "" {
|
|
return Path(configroot, DefaultConfigFile)
|
|
}
|
|
|
|
if filepath.Dir(userConfigFile) == "." {
|
|
return Path(configroot, userConfigFile)
|
|
}
|
|
|
|
return userConfigFile, nil
|
|
}
|
|
|
|
// HumanOutput gets a config value ready for printing.
|
|
func HumanOutput(value interface{}) ([]byte, error) {
|
|
s, ok := value.(string)
|
|
if ok {
|
|
return []byte(strings.Trim(s, "\n")), nil
|
|
}
|
|
return Marshal(value)
|
|
}
|
|
|
|
// Marshal configuration with JSON.
|
|
func Marshal(value interface{}) ([]byte, error) {
|
|
// need to prettyprint, hence MarshalIndent, instead of Encoder
|
|
return json.MarshalIndent(value, "", " ")
|
|
}
|
|
|
|
func FromMap(v map[string]interface{}) (*Config, error) {
|
|
buf := new(bytes.Buffer)
|
|
if err := json.NewEncoder(buf).Encode(v); err != nil {
|
|
return nil, err
|
|
}
|
|
var conf Config
|
|
if err := json.NewDecoder(buf).Decode(&conf); err != nil {
|
|
return nil, fmt.Errorf("failure to decode config: %w", err)
|
|
}
|
|
return &conf, nil
|
|
}
|
|
|
|
func ToMap(conf *Config) (map[string]interface{}, error) {
|
|
buf := new(bytes.Buffer)
|
|
if err := json.NewEncoder(buf).Encode(conf); err != nil {
|
|
return nil, err
|
|
}
|
|
var m map[string]interface{}
|
|
if err := json.NewDecoder(buf).Decode(&m); err != nil {
|
|
return nil, fmt.Errorf("failure to decode config: %w", err)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// Convert config to a map, without using encoding/json, since
|
|
// zero/empty/'omitempty' fields are excluded by encoding/json during
|
|
// marshaling.
|
|
func ReflectToMap(conf interface{}) interface{} {
|
|
v := reflect.ValueOf(conf)
|
|
if !v.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
// Handle pointer type
|
|
if v.Kind() == reflect.Ptr {
|
|
if v.IsNil() {
|
|
// Create a zero value of the pointer's element type
|
|
elemType := v.Type().Elem()
|
|
zero := reflect.Zero(elemType)
|
|
return ReflectToMap(zero.Interface())
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
result := make(map[string]interface{})
|
|
t := v.Type()
|
|
for i := 0; i < v.NumField(); i++ {
|
|
field := v.Field(i)
|
|
// Only include exported fields
|
|
if field.CanInterface() {
|
|
result[t.Field(i).Name] = ReflectToMap(field.Interface())
|
|
}
|
|
}
|
|
return result
|
|
|
|
case reflect.Map:
|
|
result := make(map[string]interface{})
|
|
iter := v.MapRange()
|
|
for iter.Next() {
|
|
key := iter.Key()
|
|
// Convert map keys to strings for consistency
|
|
keyStr := fmt.Sprint(ReflectToMap(key.Interface()))
|
|
result[keyStr] = ReflectToMap(iter.Value().Interface())
|
|
}
|
|
// Add a sample to differentiate between a map and a struct on validation.
|
|
sample := reflect.Zero(v.Type().Elem())
|
|
if sample.CanInterface() {
|
|
result["*"] = ReflectToMap(sample.Interface())
|
|
}
|
|
return result
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
result := make([]interface{}, v.Len())
|
|
for i := 0; i < v.Len(); i++ {
|
|
result[i] = ReflectToMap(v.Index(i).Interface())
|
|
}
|
|
return result
|
|
|
|
default:
|
|
// For basic types (int, string, etc.), just return the value
|
|
if v.CanInterface() {
|
|
return v.Interface()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Clone copies the config. Use when updating.
|
|
func (c *Config) Clone() (*Config, error) {
|
|
var newConfig Config
|
|
var buf bytes.Buffer
|
|
|
|
if err := json.NewEncoder(&buf).Encode(c); err != nil {
|
|
return nil, fmt.Errorf("failure to encode config: %w", err)
|
|
}
|
|
|
|
if err := json.NewDecoder(&buf).Decode(&newConfig); err != nil {
|
|
return nil, fmt.Errorf("failure to decode config: %w", err)
|
|
}
|
|
|
|
return &newConfig, nil
|
|
}
|
|
|
|
// Check if the provided key is present in the structure.
|
|
func CheckKey(key string) error {
|
|
conf := Config{}
|
|
|
|
// Convert an empty config to a map without JSON.
|
|
cursor := ReflectToMap(&conf)
|
|
|
|
// Parse the key and verify it's presence in the map.
|
|
var ok bool
|
|
var mapCursor map[string]interface{}
|
|
|
|
parts := strings.Split(key, ".")
|
|
for i, part := range parts {
|
|
mapCursor, ok = cursor.(map[string]interface{})
|
|
if !ok {
|
|
if cursor == nil {
|
|
return nil
|
|
}
|
|
path := strings.Join(parts[:i], ".")
|
|
return fmt.Errorf("%s key is not a map", path)
|
|
}
|
|
|
|
cursor, ok = mapCursor[part]
|
|
if !ok {
|
|
// If the config sections is a map, validate against the default entry.
|
|
if cursor, ok = mapCursor["*"]; ok {
|
|
continue
|
|
}
|
|
path := strings.Join(parts[:i+1], ".")
|
|
return fmt.Errorf("%s not found", path)
|
|
}
|
|
}
|
|
return nil
|
|
}
|