init.go 7.28 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
	cmds "github.com/jbenet/go-ipfs/commands"
13
	core "github.com/jbenet/go-ipfs/core"
14
	corecmds "github.com/jbenet/go-ipfs/core/commands"
15 16
	imp "github.com/jbenet/go-ipfs/importer"
	chunk "github.com/jbenet/go-ipfs/importer/chunk"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17
	ci "github.com/jbenet/go-ipfs/p2p/crypto"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18
	peer "github.com/jbenet/go-ipfs/p2p/peer"
19
	repo "github.com/jbenet/go-ipfs/repo"
20
	config "github.com/jbenet/go-ipfs/repo/config"
21
	"github.com/jbenet/go-ipfs/repo/fsrepo"
Matt Bell's avatar
Matt Bell committed
22
	u "github.com/jbenet/go-ipfs/util"
23
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
Matt Bell's avatar
Matt Bell committed
24 25
)

26 27
const nBitsForKeypairDefault = 4096

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

Matt Bell's avatar
Matt Bell committed
34
	Options: []cmds.Option{
35
		cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"),
36 37
		cmds.StringOption("passphrase", "p", "Passphrase for encrypting the private key"),
		cmds.BoolOption("force", "f", "Overwrite existing config (if it exists)"),
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
		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
			nBitsForKeypair = nBitsForKeypairDefault
57 58
		}

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

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

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

88
func doInit(configRoot string, force bool, nBitsForKeypair int) (interface{}, error) {
89

90
	u.POut("initializing ipfs node at %s\n", configRoot)
91

92
	if fsrepo.ConfigIsInitialized(configRoot) && !force {
93
		return nil, errCannotInitConfigExists
94
	}
Matt Bell's avatar
Matt Bell committed
95

96
	conf, err := initConfig(nBitsForKeypair)
97
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98
		return nil, err
99
	}
100

101 102 103 104 105 106 107 108
	repo := fsrepo.At(configRoot)
	if err := repo.Open(); err != nil {
		return nil, err
	}
	if err := repo.SetConfig(conf); err != nil {
		return nil, err
	}
	if err := repo.Close(); err != nil {
109 110 111
		return nil, err
	}

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

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

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

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

148
func datastoreConfig() (*config.Datastore, error) {
149 150
	dspath, err := config.DataStorePath("")
	if err != nil {
151
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
152
	}
153 154 155 156
	return &config.Datastore{
		Path: dspath,
		Type: "leveldb",
	}, nil
Brian Tiger Chow's avatar
Brian Tiger Chow committed
157
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
158

159 160
func initConfig(nBitsForKeypair int) (*config.Config, error) {
	ds, err := datastoreConfig()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162 163 164
	if err != nil {
		return nil, err
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
165
	identity, err := identityConfig(nBitsForKeypair)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166 167 168 169
	if err != nil {
		return nil, err
	}

170 171 172 173 174
	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

175 176 177 178 179
	bootstrapPeers, err := corecmds.DefaultBootstrapPeers()
	if err != nil {
		return nil, err
	}

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

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

192
		Bootstrap: bootstrapPeers,
193
		Datastore: *ds,
194 195
		Logs:      logConfig,
		Identity:  identity,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

		// 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(),
	}

	return conf, nil
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
211 212
// identityConfig initializes a new identity.
func identityConfig(nbits int) (config.Identity, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
213 214 215
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
216
		return ident, debugerror.New("Bitsize less than 1024 is considered unsafe.")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
217 218
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
219
	fmt.Printf("generating key pair...")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
220 221 222 223
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
224
	fmt.Printf("done\n")
Brian Tiger Chow's avatar
Brian Tiger Chow committed
225 226 227 228 229 230 231 232 233

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

234
	id, err := peer.IDFromPublicKey(pk)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
235 236 237 238
	if err != nil {
		return ident, err
	}
	ident.PeerID = id.Pretty()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
239
	fmt.Printf("peer identity: %s\n", ident.PeerID)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
240 241
	return ident, nil
}
242

243 244
// initLogs initializes the event logger at the specified path. It uses the
// default log path if no path is provided.
245 246 247 248 249
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
250
			return config.Logs{}, debugerror.Wrap(err)
251 252 253 254
		}
	}
	err := initCheckDir(logpath)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
255
		return config.Logs{}, debugerror.Errorf("logs: %s", err)
256
	}
257
	conf := config.Logs{
258
		Filename: path.Join(logpath, "events.log"),
259 260 261 262 263 264
	}
	err = repo.ConfigureEventLogger(conf)
	if err != nil {
		return conf, err
	}
	return conf, nil
265 266
}

267 268 269 270 271 272 273 274 275 276 277
// 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
278
		return debugerror.New("'" + path + "' is not writeable")
279 280 281
	}
	return nil
}