init.go 6.88 KB
Newer Older
1
package main
Matt Bell's avatar
Matt Bell committed
2 3

import (
4
	"bytes"
Matt Bell's avatar
Matt Bell committed
5
	"encoding/base64"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
6
	"fmt"
Matt Bell's avatar
Matt Bell committed
7
	"os"
8
	"path"
Matt Bell's avatar
Matt Bell committed
9 10 11 12
	"path/filepath"

	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
13
	core "github.com/jbenet/go-ipfs/core"
Matt Bell's avatar
Matt Bell committed
14
	ci "github.com/jbenet/go-ipfs/crypto"
15 16
	imp "github.com/jbenet/go-ipfs/importer"
	chunk "github.com/jbenet/go-ipfs/importer/chunk"
Matt Bell's avatar
Matt Bell committed
17 18
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
19
	errors "github.com/jbenet/go-ipfs/util/debugerror"
Matt Bell's avatar
Matt Bell committed
20 21 22
)

var initCmd = &cmds.Command{
23 24 25 26
	Helptext: cmds.HelpText{
		Tagline:          "Initializes IPFS config file",
		ShortDescription: "Initializes IPFS configuration files and generates a new keypair.",
	},
27

Matt Bell's avatar
Matt Bell committed
28
	Options: []cmds.Option{
29
		cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"),
30 31 32
		cmds.StringOption("passphrase", "p", "Passphrase for encrypting the private key"),
		cmds.BoolOption("force", "f", "Overwrite existing config (if it exists)"),
		cmds.StringOption("datastore", "d", "Location for the IPFS data store"),
33 34 35 36 37

		// TODO need to decide whether to expose the override as a file or a
		// directory. That is: should we allow the user to also specify the
		// name of the file?
		// TODO cmds.StringOption("event-logs", "l", "Location for machine-readable event logs"),
Matt Bell's avatar
Matt Bell committed
38
	},
39
	Run: func(req cmds.Request) (interface{}, error) {
40

41
		dspathOverride, _, err := req.Option("d").String() // if !found it's okay. Let == ""
42 43
		if err != nil {
			return nil, err
44 45
		}

46
		force, _, err := req.Option("f").Bool() // if !found, it's okay force == false
47 48
		if err != nil {
			return nil, err
49 50
		}

51
		nBitsForKeypair, bitsOptFound, err := req.Option("b").Int()
52 53 54
		if err != nil {
			return nil, err
		}
55
		if !bitsOptFound {
56 57 58
			nBitsForKeypair = 4096
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59
		return doInit(req.Context().ConfigRoot, dspathOverride, force, nBitsForKeypair)
60 61
	},
}
Matt Bell's avatar
Matt Bell committed
62

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
var errCannotInitConfigExists = errors.New(`ipfs configuration file already exists!
Reinitializing would overwrite your keys.
(use -f to force overwrite)
`)

var welcomeMsg = `Hello and Welcome to IPFS!

██╗██████╗ ███████╗███████╗
██║██╔══██╗██╔════╝██╔════╝
██║██████╔╝█████╗  ███████╗
██║██╔═══╝ ██╔══╝  ╚════██║
██║██║     ██║     ███████║
╚═╝╚═╝     ╚═╝     ╚══════╝

If you're seeing this, you have successfully installed
IPFS and are now interfacing with the ipfs merkledag!

For a short demo of what you can do, enter 'ipfs tour'
`

Brian Tiger Chow's avatar
Brian Tiger Chow committed
83
// NB: if dspath is not provided, it will be retrieved from the config
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84
func doInit(configRoot string, dspathOverride string, force bool, nBitsForKeypair int) (interface{}, error) {
85

86
	u.POut("initializing ipfs node at %s\n", configRoot)
87 88

	configFilename, err := config.Filename(configRoot)
89
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
90
		return nil, errors.New("Couldn't get home directory path")
91 92
	}

93
	fi, err := os.Lstat(configFilename)
94 95
	if fi != nil || (err != nil && !os.IsNotExist(err)) {
		if !force {
96
			// TODO multi-line string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
97
			return nil, errCannotInitConfigExists
Matt Bell's avatar
Matt Bell committed
98
		}
99
	}
Matt Bell's avatar
Matt Bell committed
100

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101
	conf, err := initConfig(configFilename, dspathOverride, nBitsForKeypair)
102
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
103
		return nil, err
104
	}
105

106
	// TODO extract this file creation operation into a function
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
107
	nd, err := core.NewIpfsNode(conf, false)
108
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
109
		return nil, err
110 111 112 113
	}
	defer nd.Close()

	// Set up default file
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
114
	reader := bytes.NewBufferString(welcomeMsg)
115 116 117

	defnd, err := imp.BuildDagFromReader(reader, nd.DAG, nd.Pinning.GetManual(), chunk.DefaultSplitter)
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118
		return nil, err
119 120
	}

121 122 123 124
	k, err := defnd.Key()
	if err != nil {
		return nil, fmt.Errorf("failed to write test file: %s", err)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
125 126
	fmt.Printf("done.\nto test, enter: ipfs cat %s\n", k)
	return nil, nil
Matt Bell's avatar
Matt Bell committed
127
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
128 129 130 131 132 133 134 135 136 137 138 139 140

func datastoreConfig(dspath string) (config.Datastore, error) {
	ds := config.Datastore{}
	if len(dspath) == 0 {
		var err error
		dspath, err = config.DataStorePath("")
		if err != nil {
			return ds, err
		}
	}
	ds.Path = dspath
	ds.Type = "leveldb"

141 142 143
	err := initCheckDir(dspath)
	if err != nil {
		return ds, errors.Errorf("datastore: %s", err)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
144 145 146 147
	}

	return ds, nil
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
148

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
149 150 151 152 153 154 155 156 157 158 159
func initConfig(configFilename string, dspathOverride string, nBitsForKeypair int) (*config.Config, error) {
	ds, err := datastoreConfig(dspathOverride)
	if err != nil {
		return nil, err
	}

	identity, err := identityConfig(nBitsForKeypair)
	if err != nil {
		return nil, err
	}

160 161 162 163 164
	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	conf := &config.Config{

		// setup the node addresses.
		Addresses: config.Addresses{
			Swarm: "/ip4/0.0.0.0/tcp/4001",
			API:   "/ip4/127.0.0.1/tcp/5001",
		},

		Bootstrap: []*config.BootstrapPeer{
			&config.BootstrapPeer{ // Use these hardcoded bootstrap peers for now.
				// mars.i.ipfs.io
				PeerID:  "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
				Address: "/ip4/104.131.131.82/tcp/4001",
			},
		},

		Datastore: ds,

183 184
		Logs: logConfig,

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
		Identity: identity,

		// setup the node mount points.
		Mounts: config.Mounts{
			IPFS: "/ipfs",
			IPNS: "/ipns",
		},

		// tracking ipfs version used to generate the init folder and adding
		// update checker default setting.
		Version: config.VersionDefaultValue(),
	}

	if err := config.WriteConfigFile(configFilename, conf); err != nil {
		return nil, err
	}

	return conf, nil
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
205 206 207 208 209 210 211
func identityConfig(nbits int) (config.Identity, error) {
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
		return ident, errors.New("Bitsize less than 1024 is considered unsafe.")
	}

212
	fmt.Println("generating key pair...")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}

	// currently storing key unencrypted. in the future we need to encrypt it.
	// TODO(security)
	skbytes, err := sk.Bytes()
	if err != nil {
		return ident, err
	}
	ident.PrivKey = base64.StdEncoding.EncodeToString(skbytes)

	id, err := peer.IDFromPubKey(pk)
	if err != nil {
		return ident, err
	}
	ident.PeerID = id.Pretty()
231
	fmt.Println("peer identity: %s", id.Pretty())
Brian Tiger Chow's avatar
Brian Tiger Chow committed
232 233 234

	return ident, nil
}
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
func initLogs(logpath string) (config.Logs, error) {
	if len(logpath) == 0 {
		var err error
		logpath, err = config.LogsPath("")
		if err != nil {
			return config.Logs{}, errors.Wrap(err)
		}
	}

	err := initCheckDir(logpath)
	if err != nil {
		return config.Logs{}, errors.Errorf("logs: %s", err)
	}

	return config.Logs{
		Filename: path.Join(logpath, "events.log"),
	}, nil
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
// initCheckDir ensures the directory exists and is writable
func initCheckDir(path string) error {
	// Construct the path if missing
	if err := os.MkdirAll(path, os.ModePerm); err != nil {
		return err
	}

	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(path, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return errors.New("'" + path + "' is not writeable")
	}
	return nil
}