init.go 6.51 KB
Newer Older
1
package main
Matt Bell's avatar
Matt Bell committed
2 3

import (
Jan Winkelmann's avatar
Jan Winkelmann committed
4
	"context"
5
	"encoding/json"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
6
	"errors"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
7
	"fmt"
8
	"io"
9 10
	"os"
	"path"
11
	"strings"
Matt Bell's avatar
Matt Bell committed
12

13 14 15 16 17 18
	assets "github.com/ipfs/go-ipfs/assets"
	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	namesys "github.com/ipfs/go-ipfs/namesys"
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
Jan Winkelmann's avatar
Jan Winkelmann committed
19 20

	"gx/ipfs/QmSNbH2A1evCCbJSDC6u3RV3GGDhgu6pRGbXHvrN89tMKf/go-ipfs-cmdkit"
Matt Bell's avatar
Matt Bell committed
21 22
)

23 24 25
const (
	nBitsForKeypairDefault = 2048
)
26

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

33 34 35 36 37 38
If you are going to run IPFS in server environment, you may want to
initialize it using 'server' profile.

Available profiles:
    'server' - Disables local host discovery, recommended when
        running IPFS on machines with public IPv4 addresses.
Łukasz Magiera's avatar
Łukasz Magiera committed
39 40
    'test' - Reduces external interference of IPFS daemon, this
        is useful when using the daemon in test environments.
41

42 43 44
ipfs uses a repository in the local file system. By default, the repo is
located at ~/.ipfs. To change the repo location, set the $IPFS_PATH
environment variable:
45 46 47

    export IPFS_PATH=/path/to/ipfsrepo
`,
48
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
49 50
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("default-config", false, false, "Initialize with the given configuration.").EnableStdin(),
51
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
52 53 54 55
	Options: []cmdkit.Option{
		cmdkit.IntOption("bits", "b", "Number of bits to use in the generated RSA private key.").Default(nBitsForKeypairDefault),
		cmdkit.BoolOption("empty-repo", "e", "Don't add and pin help files to the local storage.").Default(false),
		cmdkit.StringOption("profile", "p", "Apply profile settings to config. Multiple profiles can be separated by ','"),
56 57 58 59

		// 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?
Jan Winkelmann's avatar
Jan Winkelmann committed
60
		// TODO cmdkit.StringOption("event-logs", "l", "Location for machine-readable event logs."),
Matt Bell's avatar
Matt Bell committed
61
	},
62
	PreRun: func(req cmds.Request) error {
Jeromy's avatar
Jeromy committed
63
		daemonLocked, err := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot)
64 65 66
		if err != nil {
			return err
		}
67 68 69

		log.Info("checking if daemon is running...")
		if daemonLocked {
70
			log.Debug("ipfs daemon is running")
71 72 73 74 75 76
			e := "ipfs daemon is running. please stop it to run this command"
			return cmds.ClientError(e)
		}

		return nil
	},
77
	Run: func(req cmds.Request, res cmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
78 79 80
		// needs to be called at least once
		res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
81
		if req.InvocContext().Online {
Jan Winkelmann's avatar
Jan Winkelmann committed
82
			res.SetError(errors.New("init must be run offline only!"), cmdkit.ErrNormal)
83 84
			return
		}
85

86
		empty, _, err := req.Option("e").Bool()
87
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
88
			res.SetError(err, cmdkit.ErrNormal)
89 90 91
			return
		}

92
		nBitsForKeypair, _, err := req.Option("b").Int()
93
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
94
			res.SetError(err, cmdkit.ErrNormal)
95
			return
96
		}
Henry's avatar
Henry committed
97

98 99 100 101 102 103
		var conf *config.Config

		f := req.Files()
		if f != nil {
			confFile, err := f.NextFile()
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
104
				res.SetError(err, cmdkit.ErrNormal)
105 106 107 108 109
				return
			}

			conf = &config.Config{}
			if err := json.NewDecoder(confFile).Decode(conf); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
110
				res.SetError(err, cmdkit.ErrNormal)
111 112 113 114
				return
			}
		}

115 116
		profile, _, err := req.Option("profile").String()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
117
			res.SetError(err, cmdkit.ErrNormal)
118 119 120 121 122 123 124 125 126
			return
		}

		var profiles []string
		if profile != "" {
			profiles = strings.Split(profile, ",")
		}

		if err := doInit(os.Stdout, req.InvocContext().ConfigRoot, empty, nBitsForKeypair, profiles, conf); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
127
			res.SetError(err, cmdkit.ErrNormal)
128 129
			return
		}
130 131
	},
}
Matt Bell's avatar
Matt Bell committed
132

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
var errRepoExists = errors.New(`ipfs configuration file already exists!
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
134 135 136
Reinitializing would overwrite your keys.
`)

137
func initWithDefaults(out io.Writer, repoRoot string) error {
138
	return doInit(out, repoRoot, false, nBitsForKeypairDefault, nil, nil)
139 140
}

141
func doInit(out io.Writer, repoRoot string, empty bool, nBitsForKeypair int, confProfiles []string, conf *config.Config) error {
142
	if _, err := fmt.Fprintf(out, "initializing IPFS node at %s\n", repoRoot); err != nil {
143 144
		return err
	}
145

146 147 148 149
	if err := checkWriteable(repoRoot); err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
150
	if fsrepo.IsInitialized(repoRoot) {
151
		return errRepoExists
152
	}
Henry's avatar
Henry committed
153

154 155 156 157 158 159
	if conf == nil {
		var err error
		conf, err = config.Init(out, nBitsForKeypair)
		if err != nil {
			return err
		}
160
	}
Henry's avatar
Henry committed
161

162
	for _, profile := range confProfiles {
163 164
		transformer, ok := config.ConfigProfiles[profile]
		if !ok {
165 166 167 168 169 170 171 172
			return fmt.Errorf("invalid configuration profile: %s", profile)
		}

		if err := transformer(conf); err != nil {
			return err
		}
	}

173
	if err := fsrepo.Init(repoRoot, conf); err != nil {
174
		return err
175
	}
176

177 178 179 180
	if !empty {
		if err := addDefaultAssets(out, repoRoot); err != nil {
			return err
		}
Jeromy's avatar
Jeromy committed
181
	}
182 183

	return initializeIpnsKeyspace(repoRoot)
184 185
}

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
func checkWriteable(dir string) error {
	_, err := os.Stat(dir)
	if err == nil {
		// dir exists, make sure we can write to it
		testfile := path.Join(dir, "test")
		fi, err := os.Create(testfile)
		if err != nil {
			if os.IsPermission(err) {
				return fmt.Errorf("%s is not writeable by the current user", dir)
			}
			return fmt.Errorf("unexpected error while checking writeablility of repo root: %s", err)
		}
		fi.Close()
		return os.Remove(testfile)
	}

	if os.IsNotExist(err) {
		// dir doesnt exist, check that we can create it
		return os.Mkdir(dir, 0775)
	}

	if os.IsPermission(err) {
		return fmt.Errorf("cannot write to %s, incorrect permissions", err)
	}

	return err
}

214
func addDefaultAssets(out io.Writer, repoRoot string) error {
215
	ctx, cancel := context.WithCancel(context.Background())
216
	defer cancel()
Henry's avatar
Henry committed
217

218 219
	r, err := fsrepo.Open(repoRoot)
	if err != nil { // NB: repo is owned by the node
220 221
		return err
	}
Henry's avatar
Henry committed
222

223
	nd, err := core.NewNode(ctx, &core.BuildCfg{Repo: r})
224
	if err != nil {
225
		return err
226 227 228
	}
	defer nd.Close()

Henry's avatar
Henry committed
229
	dkey, err := assets.SeedInitDocs(nd)
230
	if err != nil {
Henry's avatar
Henry committed
231
		return fmt.Errorf("init: seeding init docs failed: %s", err)
232
	}
rht's avatar
rht committed
233
	log.Debugf("init: seeded init docs %s", dkey)
234

Henry's avatar
Henry committed
235 236 237 238 239 240
	if _, err = fmt.Fprintf(out, "to get started, enter:\n"); err != nil {
		return err
	}

	_, err = fmt.Fprintf(out, "\n\tipfs cat /ipfs/%s/readme\n\n", dkey)
	return err
Matt Bell's avatar
Matt Bell committed
241
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
242

Jeromy's avatar
Jeromy committed
243 244 245 246
func initializeIpnsKeyspace(repoRoot string) error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

247 248
	r, err := fsrepo.Open(repoRoot)
	if err != nil { // NB: repo is owned by the node
Jeromy's avatar
Jeromy committed
249 250 251
		return err
	}

252
	nd, err := core.NewNode(ctx, &core.BuildCfg{Repo: r})
Jeromy's avatar
Jeromy committed
253 254 255 256 257 258 259 260 261 262
	if err != nil {
		return err
	}
	defer nd.Close()

	err = nd.SetupOfflineRouting()
	if err != nil {
		return err
	}

263
	return namesys.InitializeKeyspace(ctx, nd.DAG, nd.Namesys, nd.Pinning, nd.PrivateKey)
Jeromy's avatar
Jeromy committed
264
}