bootstrap.go 10.6 KB
Newer Older
1 2 3
package commands

import (
4
	"bytes"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"errors"
6
	"io"
7
	"sort"
8

9
	cmds "github.com/ipfs/go-ipfs/commands"
Jan Winkelmann's avatar
Jan Winkelmann committed
10
	e "github.com/ipfs/go-ipfs/core/commands/e"
11 12
	repo "github.com/ipfs/go-ipfs/repo"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
Jan Winkelmann's avatar
Jan Winkelmann committed
13

14
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
Lars Gierth's avatar
Lars Gierth committed
15
	config "gx/ipfs/QmSoYrBMibm2T3LupaLuez7LPGnyrJwdRxvTfPUyCp691u/go-ipfs-config"
16 17 18
)

type BootstrapOutput struct {
19
	Peers []string
20 21
}

22
var peerOptionDesc = "A peer to add to the bootstrap list (in the format '<multiaddr>/<peerID>')"
23

24
var BootstrapCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
25
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
26
		Tagline: "Show or edit the list of bootstrap peers.",
27 28
		ShortDescription: `
Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'.
29
` + bootstrapSecurityWarning,
30
	},
31

32 33 34
	Run:        bootstrapListCmd.Run,
	Marshalers: bootstrapListCmd.Marshalers,
	Type:       bootstrapListCmd.Type,
35

36
	Subcommands: map[string]*cmds.Command{
37 38 39
		"list": bootstrapListCmd,
		"add":  bootstrapAddCmd,
		"rm":   bootstrapRemoveCmd,
40 41 42 43
	},
}

var bootstrapAddCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
44
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
45
		Tagline: "Add peers to the bootstrap list.",
46
		ShortDescription: `Outputs a list of peers that were added (that weren't already
47 48
in the bootstrap list).
` + bootstrapSecurityWarning,
49
	},
50

Jan Winkelmann's avatar
Jan Winkelmann committed
51 52
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
53
	},
54

Jan Winkelmann's avatar
Jan Winkelmann committed
55 56
	Options: []cmdkit.Option{
		cmdkit.BoolOption("default", "Add default bootstrap nodes. (Deprecated, use 'default' subcommand instead)"),
57 58 59
	},
	Subcommands: map[string]*cmds.Command{
		"default": bootstrapAddDefaultCmd,
60 61
	},

62
	Run: func(req cmds.Request, res cmds.Response) {
63 64
		deflt, _, err := req.Option("default").Bool()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
65
			res.SetError(err, cmdkit.ErrNormal)
66
			return
67 68
		}

69
		var inputPeers []config.BootstrapPeer
70 71
		if deflt {
			// parse separately for meaningful, correct error.
72
			defltPeers, err := config.DefaultBootstrapPeers()
73
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
74
				res.SetError(err, cmdkit.ErrNormal)
75
				return
76 77
			}

78 79 80 81
			inputPeers = defltPeers
		} else {
			parsedPeers, err := config.ParseBootstrapPeers(req.Arguments())
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
82
				res.SetError(err, cmdkit.ErrNormal)
83 84 85 86
				return
			}

			inputPeers = parsedPeers
87 88
		}

89
		if len(inputPeers) == 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
90
			res.SetError(errors.New("no bootstrap peers to add"), cmdkit.ErrClient)
91
			return
92 93
		}

94 95
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
96
			res.SetError(err, cmdkit.ErrNormal)
97 98 99 100 101
			return
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
102
			res.SetError(err, cmdkit.ErrNormal)
103 104 105
			return
		}

106 107
		added, err := bootstrapAdd(r, cfg, inputPeers)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
108
			res.SetError(err, cmdkit.ErrNormal)
109
			return
110 111
		}

112
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(added)})
113
	},
114
	Type: BootstrapOutput{},
115
	Marshalers: cmds.MarshalerMap{
116
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
117 118 119 120 121 122
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			out, ok := v.(*BootstrapOutput)
123
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
124
				return nil, e.TypeErr(out, v)
125
			}
126

127
			buf := new(bytes.Buffer)
Jan Winkelmann's avatar
Jan Winkelmann committed
128
			if err := bootstrapWritePeers(buf, "added ", out.Peers); err != nil {
129 130 131
				return nil, err
			}

132
			return buf, nil
133 134 135 136
		},
	},
}

137
var bootstrapAddDefaultCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
138
	Helptext: cmdkit.HelpText{
139 140 141 142 143 144 145
		Tagline: "Add default peers to the bootstrap list.",
		ShortDescription: `Outputs a list of peers that were added (that weren't already
in the bootstrap list).`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		defltPeers, err := config.DefaultBootstrapPeers()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
146
			res.SetError(err, cmdkit.ErrNormal)
147 148 149 150 151
			return
		}

		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
152
			res.SetError(err, cmdkit.ErrNormal)
153 154 155 156 157 158
			return
		}

		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
159
			res.SetError(err, cmdkit.ErrNormal)
160 161 162 163 164
			return
		}

		added, err := bootstrapAdd(r, cfg, defltPeers)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
165
			res.SetError(err, cmdkit.ErrNormal)
166 167 168 169 170 171 172 173
			return
		}

		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(added)})
	},
	Type: BootstrapOutput{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
174 175 176 177 178 179
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			out, ok := v.(*BootstrapOutput)
180
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
181
				return nil, e.TypeErr(out, v)
182 183 184
			}

			buf := new(bytes.Buffer)
Jan Winkelmann's avatar
Jan Winkelmann committed
185
			if err := bootstrapWritePeers(buf, "added ", out.Peers); err != nil {
186 187 188 189 190 191 192 193
				return nil, err
			}

			return buf, nil
		},
	},
}

194
var bootstrapRemoveCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
195
	Helptext: cmdkit.HelpText{
196
		Tagline: "Remove peers from the bootstrap list.",
197
		ShortDescription: `Outputs the list of peers that were removed.
198
` + bootstrapSecurityWarning,
199
	},
200

Jan Winkelmann's avatar
Jan Winkelmann committed
201 202
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
203
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
204 205
	Options: []cmdkit.Option{
		cmdkit.BoolOption("all", "Remove all bootstrap peers. (Deprecated, use 'all' subcommand)"),
206 207 208
	},
	Subcommands: map[string]*cmds.Command{
		"all": bootstrapRemoveAllCmd,
209
	},
210
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
211
		all, _, err := req.Option("all").Bool()
212
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
213
			res.SetError(err, cmdkit.ErrNormal)
214
			return
215 216
		}

Jeromy's avatar
Jeromy committed
217
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
218
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
219
			res.SetError(err, cmdkit.ErrNormal)
220
			return
221
		}
222
		defer r.Close()
223 224
		cfg, err := r.Config()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
225
			res.SetError(err, cmdkit.ErrNormal)
226 227
			return
		}
228

229
		var removed []config.BootstrapPeer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
230
		if all {
231
			removed, err = bootstrapRemoveAll(r, cfg)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
232
		} else {
Jeromy's avatar
Jeromy committed
233 234
			input, perr := config.ParseBootstrapPeers(req.Arguments())
			if perr != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
235
				res.SetError(perr, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
236 237 238
				return
			}

239
			removed, err = bootstrapRemove(r, cfg, input)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
240
		}
241
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
242
			res.SetError(err, cmdkit.ErrNormal)
243
			return
244 245
		}

246
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(removed)})
247
	},
248
	Type: BootstrapOutput{},
249
	Marshalers: cmds.MarshalerMap{
250
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
251 252 253 254 255 256
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			out, ok := v.(*BootstrapOutput)
257
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
258
				return nil, e.TypeErr(out, v)
259
			}
260

261
			buf := new(bytes.Buffer)
Jan Winkelmann's avatar
Jan Winkelmann committed
262
			err = bootstrapWritePeers(buf, "removed ", out.Peers)
263
			return buf, err
264 265 266 267
		},
	},
}

268
var bootstrapRemoveAllCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
269
	Helptext: cmdkit.HelpText{
270
		Tagline:          "Remove all peers from the bootstrap list.",
271 272 273 274 275 276
		ShortDescription: `Outputs the list of peers that were removed.`,
	},

	Run: func(req cmds.Request, res cmds.Response) {
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
277
			res.SetError(err, cmdkit.ErrNormal)
278 279 280 281 282
			return
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
283
			res.SetError(err, cmdkit.ErrNormal)
284 285 286 287 288
			return
		}

		removed, err := bootstrapRemoveAll(r, cfg)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
289
			res.SetError(err, cmdkit.ErrNormal)
290 291 292 293 294 295 296 297
			return
		}

		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(removed)})
	},
	Type: BootstrapOutput{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
298 299 300 301 302 303
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			out, ok := v.(*BootstrapOutput)
304
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
305
				return nil, e.TypeErr(out, v)
306 307 308
			}

			buf := new(bytes.Buffer)
Jan Winkelmann's avatar
Jan Winkelmann committed
309
			err = bootstrapWritePeers(buf, "removed ", out.Peers)
310 311 312 313 314
			return buf, err
		},
	},
}

315
var bootstrapListCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
316
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
317
		Tagline:          "Show peers in the bootstrap list.",
318 319
		ShortDescription: "Peers are output in the format '<multiaddr>/<peerID>'.",
	},
320

321
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
322
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
323
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
324
			res.SetError(err, cmdkit.ErrNormal)
325
			return
326
		}
327
		defer r.Close()
328 329
		cfg, err := r.Config()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
330
			res.SetError(err, cmdkit.ErrNormal)
331 332
			return
		}
333

334 335
		peers, err := cfg.BootstrapPeers()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
336
			res.SetError(err, cmdkit.ErrNormal)
337 338
			return
		}
339
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(peers)})
340
	},
341
	Type: BootstrapOutput{},
342 343
	Marshalers: cmds.MarshalerMap{
		cmds.Text: bootstrapMarshaler,
344 345 346
	},
}

347
func bootstrapMarshaler(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
348 349 350 351 352 353
	v, err := unwrapOutput(res.Output())
	if err != nil {
		return nil, err
	}

	out, ok := v.(*BootstrapOutput)
354
	if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
355
		return nil, e.TypeErr(out, v)
356
	}
357

358
	buf := new(bytes.Buffer)
Jan Winkelmann's avatar
Jan Winkelmann committed
359
	err = bootstrapWritePeers(buf, "", out.Peers)
360
	return buf, err
361 362
}

363
func bootstrapWritePeers(w io.Writer, prefix string, peers []string) error {
364

365 366
	sort.Stable(sort.StringSlice(peers))
	for _, peer := range peers {
367
		_, err := w.Write([]byte(prefix + peer + "\n"))
368 369 370 371 372
		if err != nil {
			return err
		}
	}
	return nil
373 374
}

375
func bootstrapAdd(r repo.Repo, cfg *config.Config, peers []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
376 377
	addedMap := map[string]struct{}{}
	addedList := make([]config.BootstrapPeer, 0, len(peers))
378

379 380 381 382 383
	// re-add cfg bootstrap peers to rm dupes
	bpeers := cfg.Bootstrap
	cfg.Bootstrap = nil

	// add new peers
384
	for _, peer := range peers {
385 386 387
		s := peer.String()
		if _, found := addedMap[s]; found {
			continue
388 389
		}

390 391 392 393 394 395 396 397 398
		cfg.Bootstrap = append(cfg.Bootstrap, s)
		addedList = append(addedList, peer)
		addedMap[s] = struct{}{}
	}

	// add back original peers. in this order so that we output them.
	for _, s := range bpeers {
		if _, found := addedMap[s]; found {
			continue
399
		}
400 401 402

		cfg.Bootstrap = append(cfg.Bootstrap, s)
		addedMap[s] = struct{}{}
403 404
	}

405
	if err := r.SetConfig(cfg); err != nil {
406 407 408
		return nil, err
	}

409
	return addedList, nil
410 411
}

412
func bootstrapRemove(r repo.Repo, cfg *config.Config, toRemove []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
413 414
	removed := make([]config.BootstrapPeer, 0, len(toRemove))
	keep := make([]config.BootstrapPeer, 0, len(cfg.Bootstrap))
415

416 417 418 419 420 421
	peers, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}

	for _, peer := range peers {
422
		found := false
423
		for _, peer2 := range toRemove {
424
			if peer.Equal(peer2) {
425 426 427 428 429 430 431 432 433 434
				found = true
				removed = append(removed, peer)
				break
			}
		}

		if !found {
			keep = append(keep, peer)
		}
	}
435
	cfg.SetBootstrapPeers(keep)
436

437
	if err := r.SetConfig(cfg); err != nil {
438 439 440 441 442 443
		return nil, err
	}

	return removed, nil
}

444
func bootstrapRemoveAll(r repo.Repo, cfg *config.Config) ([]config.BootstrapPeer, error) {
445 446 447 448
	removed, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
449 450

	cfg.Bootstrap = nil
451
	if err := r.SetConfig(cfg); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
452 453 454 455 456 457
		return nil, err
	}

	return removed, nil
}

458 459 460 461 462 463 464 465 466
const bootstrapSecurityWarning = `
SECURITY WARNING:

The bootstrap command manipulates the "bootstrap list", which contains
the addresses of bootstrap nodes. These are the *trusted peers* from
which to learn about other peers in the network. Only edit this list
if you understand the risks of adding or removing nodes from this list.

`