init.go 8.21 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
	peer "github.com/jbenet/go-ipfs/peer"
18
	repo "github.com/jbenet/go-ipfs/repo"
Matt Bell's avatar
Matt Bell committed
19
	u "github.com/jbenet/go-ipfs/util"
20
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
Matt Bell's avatar
Matt Bell committed
21 22
)

23 24
const nBitsForKeypairDefault = 4096

Matt Bell's avatar
Matt Bell committed
25
var initCmd = &cmds.Command{
26 27 28 29
	Helptext: cmds.HelpText{
		Tagline:          "Initializes IPFS config file",
		ShortDescription: "Initializes IPFS configuration files and generates a new keypair.",
	},
30

Matt Bell's avatar
Matt Bell committed
31
	Options: []cmds.Option{
32
		cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"),
33 34 35
		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"),
36 37 38 39 40

		// 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
41
	},
42
	Run: func(req cmds.Request) (interface{}, error) {
43

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

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

54
		nBitsForKeypair, bitsOptFound, err := req.Option("b").Int()
55 56 57
		if err != nil {
			return nil, err
		}
58
		if !bitsOptFound {
59
			nBitsForKeypair = nBitsForKeypairDefault
60 61
		}

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
66
var errCannotInitConfigExists = debugerror.New(`ipfs configuration file already exists!
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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'
`

86 87
func initWithDefaults(configRoot string) error {
	_, err := doInit(configRoot, "", false, nBitsForKeypairDefault)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
88
	return debugerror.Wrap(err)
89 90
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
91
func doInit(configRoot string, dspathOverride string, force bool, nBitsForKeypair int) (interface{}, error) {
92

93
	u.POut("initializing ipfs node at %s\n", configRoot)
94 95

	configFilename, err := config.Filename(configRoot)
96
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
97
		return nil, debugerror.New("Couldn't get home directory path")
98 99
	}

100 101
	if u.FileExists(configFilename) && !force {
		return nil, errCannotInitConfigExists
102
	}
Matt Bell's avatar
Matt Bell committed
103

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

Brian Tiger Chow's avatar
fix  
Brian Tiger Chow committed
109
	err = addTheWelcomeFile(conf)
110 111 112 113 114 115 116 117 118
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// addTheWelcomeFile adds a file containing the welcome message to the newly
// minted node. On success, it calls onSuccess
Brian Tiger Chow's avatar
fix  
Brian Tiger Chow committed
119
func addTheWelcomeFile(conf *config.Config) error {
120
	// TODO extract this file creation operation into a function
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
	nd, err := core.NewIpfsNode(conf, false)
122
	if err != nil {
123
		return err
124 125 126 127
	}
	defer nd.Close()

	// Set up default file
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
128
	reader := bytes.NewBufferString(welcomeMsg)
129 130 131

	defnd, err := imp.BuildDagFromReader(reader, nd.DAG, nd.Pinning.GetManual(), chunk.DefaultSplitter)
	if err != nil {
132
		return err
133 134
	}

135 136
	k, err := defnd.Key()
	if err != nil {
137
		return fmt.Errorf("failed to write test file: %s", err)
138
	}
Brian Tiger Chow's avatar
fix  
Brian Tiger Chow committed
139
	fmt.Printf("\nto get started, enter: ipfs cat %s\n", k)
140
	return nil
Matt Bell's avatar
Matt Bell committed
141
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
142 143 144 145 146 147 148 149 150 151 152 153 154

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"

155 156
	err := initCheckDir(dspath)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
157
		return ds, debugerror.Errorf("datastore: %s", err)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
158 159 160 161
	}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163 164 165 166 167 168
func initConfig(configFilename string, dspathOverride string, nBitsForKeypair int) (*config.Config, error) {
	ds, err := datastoreConfig(dspathOverride)
	if err != nil {
		return nil, err
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
169
	identity, err := identityConfig(nBitsForKeypair)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171 172 173
	if err != nil {
		return nil, err
	}

174 175 176 177 178
	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179 180
	conf := &config.Config{

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
181 182
		// setup the node's default addresses.
		// Note: two swarm listen addrs, one tcp, one utp.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
183
		Addresses: config.Addresses{
184 185 186 187 188
			Swarm: []string{
				"/ip4/0.0.0.0/tcp/4001",
				"/ip4/0.0.0.0/udp/4002/utp",
			},
			API: "/ip4/127.0.0.1/tcp/5001",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
189 190 191 192 193 194 195 196
		},

		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",
			},
197
			&config.BootstrapPeer{
198
				// Neptune
199 200 201 202
				PeerID:  "QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z",
				Address: "/ip4/104.236.176.52/tcp/4001",
			},
			&config.BootstrapPeer{
203
				// Pluto
204 205 206 207
				PeerID:  "QmSoLpPVmHKQ4XTPdz8tjDFgdeRFkpV8JgYq8JVJ69RrZm",
				Address: "/ip4/104.236.179.241/tcp/4001",
			},
			&config.BootstrapPeer{
208
				// Uranus
209
				PeerID:  "QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm",
210
				Address: "/ip4/162.243.248.213/tcp/4001",
211 212
			},
			&config.BootstrapPeer{
213
				// Saturn
214
				PeerID:  "QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu",
215
				Address: "/ip4/128.199.219.111/tcp/4001",
216
			},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217 218 219 220
		},

		Datastore: ds,

221 222
		Logs: logConfig,

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
		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
243 244
// identityConfig initializes a new identity.
func identityConfig(nbits int) (config.Identity, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
245 246 247
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
248
		return ident, debugerror.New("Bitsize less than 1024 is considered unsafe.")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
249 250
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
251
	fmt.Printf("generating key pair...")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
252 253 254 255
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
256
	fmt.Printf("done\n")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270

	// 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()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
271
	fmt.Printf("peer identity: %s\n", ident.PeerID)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
272 273
	return ident, nil
}
274

275 276
// initLogs initializes the event logger at the specified path. It uses the
// default log path if no path is provided.
277 278 279 280 281
func initLogs(logpath string) (config.Logs, error) {
	if len(logpath) == 0 {
		var err error
		logpath, err = config.LogsPath("")
		if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
282
			return config.Logs{}, debugerror.Wrap(err)
283 284 285 286
		}
	}
	err := initCheckDir(logpath)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
287
		return config.Logs{}, debugerror.Errorf("logs: %s", err)
288
	}
289
	conf := config.Logs{
290
		Filename: path.Join(logpath, "events.log"),
291 292 293 294 295 296
	}
	err = repo.ConfigureEventLogger(conf)
	if err != nil {
		return conf, err
	}
	return conf, nil
297 298
}

299 300 301 302 303 304 305 306 307 308 309
// 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 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
310
		return debugerror.New("'" + path + "' is not writeable")
311 312 313
	}
	return nil
}