init.go 7.74 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
	"path/filepath"

11
	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
Matt Bell's avatar
Matt Bell committed
12 13
	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
14
	core "github.com/jbenet/go-ipfs/core"
15
	corecmds "github.com/jbenet/go-ipfs/core/commands"
16 17
	imp "github.com/jbenet/go-ipfs/importer"
	chunk "github.com/jbenet/go-ipfs/importer/chunk"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18
	ci "github.com/jbenet/go-ipfs/p2p/crypto"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19
	peer "github.com/jbenet/go-ipfs/p2p/peer"
20
	repo "github.com/jbenet/go-ipfs/repo"
Matt Bell's avatar
Matt Bell committed
21
	u "github.com/jbenet/go-ipfs/util"
22
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
Matt Bell's avatar
Matt Bell committed
23 24
)

25 26
const nBitsForKeypairDefault = 4096

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Brian Tiger Chow's avatar
fix  
Brian Tiger Chow committed
111
	err = addTheWelcomeFile(conf)
112 113 114 115 116 117 118 119 120
	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
121
func addTheWelcomeFile(conf *config.Config) error {
122
	// TODO extract this file creation operation into a function
123
	ctx, cancel := context.WithCancel(context.Background())
124
	nd, err := core.NewIPFSNode(ctx, core.Offline(conf))
125
	if err != nil {
126
		return err
127 128
	}
	defer nd.Close()
129
	defer cancel()
130 131

	// Set up default file
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
132
	reader := bytes.NewBufferString(welcomeMsg)
133 134 135

	defnd, err := imp.BuildDagFromReader(reader, nd.DAG, nd.Pinning.GetManual(), chunk.DefaultSplitter)
	if err != nil {
136
		return err
137 138
	}

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

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"

159 160
	err := initCheckDir(dspath)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
161
		return ds, debugerror.Errorf("datastore: %s", err)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
162 163 164 165
	}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167 168 169 170 171 172
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
173
	identity, err := identityConfig(nBitsForKeypair)
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
	}

183 184 185 186 187
	bootstrapPeers, err := corecmds.DefaultBootstrapPeers()
	if err != nil {
		return nil, err
	}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190 191
		// setup the node's default addresses.
		// Note: two swarm listen addrs, one tcp, one utp.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
192
		Addresses: config.Addresses{
193 194
			Swarm: []string{
				"/ip4/0.0.0.0/tcp/4001",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
195
				// "/ip4/0.0.0.0/udp/4002/utp", // disabled for now.
196 197
			},
			API: "/ip4/127.0.0.1/tcp/5001",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
198 199
		},

200
		Bootstrap: bootstrapPeers,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
201
		Datastore: ds,
202 203
		Logs:      logConfig,
		Identity:  identity,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

		// 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
223 224
// identityConfig initializes a new identity.
func identityConfig(nbits int) (config.Identity, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
225 226 227
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
228
		return ident, debugerror.New("Bitsize less than 1024 is considered unsafe.")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
229 230
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
231
	fmt.Printf("generating key pair...")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
232 233 234 235
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
236
	fmt.Printf("done\n")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
237 238 239 240 241 242 243 244 245

	// 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)

246
	id, err := peer.IDFromPublicKey(pk)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
247 248 249 250
	if err != nil {
		return ident, err
	}
	ident.PeerID = id.Pretty()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
251
	fmt.Printf("peer identity: %s\n", ident.PeerID)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
252 253
	return ident, nil
}
254

255 256
// initLogs initializes the event logger at the specified path. It uses the
// default log path if no path is provided.
257 258 259 260 261
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
262
			return config.Logs{}, debugerror.Wrap(err)
263 264 265 266
		}
	}
	err := initCheckDir(logpath)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
267
		return config.Logs{}, debugerror.Errorf("logs: %s", err)
268
	}
269
	conf := config.Logs{
270
		Filename: path.Join(logpath, "events.log"),
271 272 273 274 275 276
	}
	err = repo.ConfigureEventLogger(conf)
	if err != nil {
		return conf, err
	}
	return conf, nil
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 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
290
		return debugerror.New("'" + path + "' is not writeable")
291 292 293
	}
	return nil
}