bootstrap.go 7.79 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 10 11 12 13
	cmds "github.com/ipfs/go-ipfs/commands"
	repo "github.com/ipfs/go-ipfs/repo"
	config "github.com/ipfs/go-ipfs/repo/config"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
	u "github.com/ipfs/go-ipfs/util"
14 15 16
)

type BootstrapOutput struct {
17
	Peers []string
18 19
}

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

22
var BootstrapCmd = &cmds.Command{
23
	Helptext: cmds.HelpText{
rht's avatar
rht committed
24
		Tagline: "Show or edit the list of bootstrap peers.",
25
		Synopsis: `
26 27
ipfs bootstrap list             - Show peers in the bootstrap list
ipfs bootstrap add <peer>...    - Add peers to the bootstrap list
28
ipfs bootstrap rm <peer>... - Removes peers from the bootstrap list
29 30 31
`,
		ShortDescription: `
Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'.
32
` + bootstrapSecurityWarning,
33
	},
34

35 36 37
	Run:        bootstrapListCmd.Run,
	Marshalers: bootstrapListCmd.Marshalers,
	Type:       bootstrapListCmd.Type,
38

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

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

54
	Arguments: []cmds.Argument{
55
		cmds.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
56
	},
57 58 59 60 61

	Options: []cmds.Option{
		cmds.BoolOption("default", "add default bootstrap nodes"),
	},

62
	Run: func(req cmds.Request, res cmds.Response) {
63
		inputPeers, err := config.ParseBootstrapPeers(req.Arguments())
64
		if err != nil {
65 66
			res.SetError(err, cmds.ErrNormal)
			return
67 68
		}

Jeromy's avatar
Jeromy committed
69
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
70
		if err != nil {
71 72
			res.SetError(err, cmds.ErrNormal)
			return
73
		}
74
		defer r.Close()
75 76 77 78 79
		cfg, err := r.Config()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
80

81 82
		deflt, _, err := req.Option("default").Bool()
		if err != nil {
83 84
			res.SetError(err, cmds.ErrNormal)
			return
85 86 87 88
		}

		if deflt {
			// parse separately for meaningful, correct error.
89
			defltPeers, err := config.DefaultBootstrapPeers()
90
			if err != nil {
91 92
				res.SetError(err, cmds.ErrNormal)
				return
93 94 95 96 97
			}

			inputPeers = append(inputPeers, defltPeers...)
		}

98 99
		if len(inputPeers) == 0 {
			res.SetError(errors.New("no bootstrap peers to add"), cmds.ErrClient)
100
			return
101 102
		}

103 104 105
		added, err := bootstrapAdd(r, cfg, inputPeers)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
106
			return
107 108
		}

109
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(added)})
110
	},
111
	Type: BootstrapOutput{},
112
	Marshalers: cmds.MarshalerMap{
113
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
114 115 116
			v, ok := res.Output().(*BootstrapOutput)
			if !ok {
				return nil, u.ErrCast()
117
			}
118

119 120
			buf := new(bytes.Buffer)
			if err := bootstrapWritePeers(buf, "added ", v.Peers); err != nil {
121 122 123
				return nil, err
			}

124
			return buf, nil
125 126 127 128 129
		},
	},
}

var bootstrapRemoveCmd = &cmds.Command{
130
	Helptext: cmds.HelpText{
rht's avatar
rht committed
131
		Tagline: "Removes peers from the bootstrap list.",
132
		ShortDescription: `Outputs the list of peers that were removed.
133
` + bootstrapSecurityWarning,
134
	},
135

136
	Arguments: []cmds.Argument{
137
		cmds.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
138 139 140
	},
	Options: []cmds.Option{
		cmds.BoolOption("all", "Remove all bootstrap peers."),
141
	},
142
	Run: func(req cmds.Request, res cmds.Response) {
143
		input, err := config.ParseBootstrapPeers(req.Arguments())
144
		if err != nil {
145 146
			res.SetError(err, cmds.ErrNormal)
			return
147 148
		}

Jeromy's avatar
Jeromy committed
149
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
150
		if err != nil {
151 152
			res.SetError(err, cmds.ErrNormal)
			return
153
		}
154
		defer r.Close()
155 156 157 158 159
		cfg, err := r.Config()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
160

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162
		all, _, err := req.Option("all").Bool()
		if err != nil {
163 164
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
165 166
		}

167
		var removed []config.BootstrapPeer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
168
		if all {
169
			removed, err = bootstrapRemoveAll(r, cfg)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170
		} else {
171
			removed, err = bootstrapRemove(r, cfg, input)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
172
		}
173
		if err != nil {
174 175
			res.SetError(err, cmds.ErrNormal)
			return
176 177
		}

178
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(removed)})
179
	},
180
	Type: BootstrapOutput{},
181
	Marshalers: cmds.MarshalerMap{
182
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
183 184 185
			v, ok := res.Output().(*BootstrapOutput)
			if !ok {
				return nil, u.ErrCast()
186
			}
187

188 189 190
			buf := new(bytes.Buffer)
			err := bootstrapWritePeers(buf, "removed ", v.Peers)
			return buf, err
191 192 193 194 195
		},
	},
}

var bootstrapListCmd = &cmds.Command{
196
	Helptext: cmds.HelpText{
rht's avatar
rht committed
197
		Tagline:          "Show peers in the bootstrap list.",
198 199
		ShortDescription: "Peers are output in the format '<multiaddr>/<peerID>'.",
	},
200

201
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
202
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
203
		if err != nil {
204 205
			res.SetError(err, cmds.ErrNormal)
			return
206
		}
207
		defer r.Close()
208 209 210 211 212
		cfg, err := r.Config()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
213

214 215 216 217 218
		peers, err := cfg.BootstrapPeers()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
219
		res.SetOutput(&BootstrapOutput{config.BootstrapPeerStrings(peers)})
220
		return
221
	},
222
	Type: BootstrapOutput{},
223 224
	Marshalers: cmds.MarshalerMap{
		cmds.Text: bootstrapMarshaler,
225 226 227
	},
}

228
func bootstrapMarshaler(res cmds.Response) (io.Reader, error) {
229 230 231 232
	v, ok := res.Output().(*BootstrapOutput)
	if !ok {
		return nil, u.ErrCast()
	}
233

234 235 236
	buf := new(bytes.Buffer)
	err := bootstrapWritePeers(buf, "", v.Peers)
	return buf, err
237 238
}

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

241 242
	sort.Stable(sort.StringSlice(peers))
	for _, peer := range peers {
243
		_, err := w.Write([]byte(peer + "\n"))
244 245 246 247 248
		if err != nil {
			return err
		}
	}
	return nil
249 250
}

251
func bootstrapAdd(r repo.Repo, cfg *config.Config, peers []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
252 253
	addedMap := map[string]struct{}{}
	addedList := make([]config.BootstrapPeer, 0, len(peers))
254

255 256 257 258 259
	// re-add cfg bootstrap peers to rm dupes
	bpeers := cfg.Bootstrap
	cfg.Bootstrap = nil

	// add new peers
260
	for _, peer := range peers {
261 262 263
		s := peer.String()
		if _, found := addedMap[s]; found {
			continue
264 265
		}

266 267 268 269 270 271 272 273 274
		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
275
		}
276 277 278

		cfg.Bootstrap = append(cfg.Bootstrap, s)
		addedMap[s] = struct{}{}
279 280
	}

281
	if err := r.SetConfig(cfg); err != nil {
282 283 284
		return nil, err
	}

285
	return addedList, nil
286 287
}

288
func bootstrapRemove(r repo.Repo, cfg *config.Config, toRemove []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
289 290
	removed := make([]config.BootstrapPeer, 0, len(toRemove))
	keep := make([]config.BootstrapPeer, 0, len(cfg.Bootstrap))
291

292 293 294 295 296 297
	peers, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}

	for _, peer := range peers {
298
		found := false
299
		for _, peer2 := range toRemove {
300
			if peer.Equal(peer2) {
301 302 303 304 305 306 307 308 309 310
				found = true
				removed = append(removed, peer)
				break
			}
		}

		if !found {
			keep = append(keep, peer)
		}
	}
311
	cfg.SetBootstrapPeers(keep)
312

313
	if err := r.SetConfig(cfg); err != nil {
314 315 316 317 318 319
		return nil, err
	}

	return removed, nil
}

320
func bootstrapRemoveAll(r repo.Repo, cfg *config.Config) ([]config.BootstrapPeer, error) {
321 322 323 324
	removed, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
325 326

	cfg.Bootstrap = nil
327
	if err := r.SetConfig(cfg); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
328 329 330 331 332 333
		return nil, err
	}

	return removed, nil
}

334 335 336 337 338 339 340 341 342
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.

`