init.go 7.49 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/errors"
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 107 108 109 110 111 112 113 114 115 116 117 118
	err = addTheWelcomeFile(conf, func(k u.Key) {
		fmt.Printf("\nto get started, enter: ipfs cat %s\n", k)
	})
	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
func addTheWelcomeFile(conf *config.Config, onSuccess func(u.Key)) error {
119
	// TODO extract this file creation operation into a function
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
120
	nd, err := core.NewIpfsNode(conf, false)
121
	if err != nil {
122
		return err
123 124 125 126
	}
	defer nd.Close()

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

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

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

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"

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

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

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

168 169 170
	identity, err := identityConfig(nBitsForKeypair, func() {
		fmt.Printf("generating key pair...")
	}, func(ident config.Identity) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
171
		fmt.Printf("done\n")
172 173
		fmt.Printf("peer identity: %s\n", ident.PeerID)
	})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174 175 176 177
	if err != nil {
		return nil, err
	}

178 179 180 181 182
	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	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,

201 202
		Logs: logConfig,

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
		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
}

223 224 225 226
// identityConfig initializes a new identity. It calls onBegin when it begins
// to generate the identity and it calls onSuccess once the operation is
// completed successfully
func identityConfig(nbits int, onBegin func(), onSuccess func(config.Identity)) (config.Identity, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
227 228 229 230 231 232
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
		return ident, errors.New("Bitsize less than 1024 is considered unsafe.")
	}

233
	onBegin()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
	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()
252
	onSuccess(ident)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
253 254
	return ident, nil
}
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
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
}

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
// 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
}