Example #1
0
// newService provides a preconfigured (based on klientctl's config)
// service object to install, uninstall, start and stop Klient.
func newService(opts *ServiceOptions) (service.Service, error) {
	if opts == nil {
		opts = defaultServiceOpts
	}

	// TODO: Add hosts's username
	svcConfig := &service.Config{
		Name:        "klient",
		DisplayName: "klient",
		Description: "Koding Service Connector",
		Executable:  filepath.Join(KlientDirectory, "klient.sh"),
		Option: map[string]interface{}{
			"LogStderr":     true,
			"LogStdout":     true,
			"After":         "network.target",
			"RequiredStart": "$network",
			"LogFile":       true,
			"Environment": map[string]string{
				"USERNAME": opts.Username,
			},
		},
	}

	return service.New(&serviceProgram{}, svcConfig)
}
Example #2
0
// Stop stops the service.
func (s *Service) Stop() error {
	svc, err := service.New(nopService{}, s.config())
	if err != nil {
		return err
	}

	return svc.Stop()
}
Example #3
0
// Uninstall uninstalls the service.
func (s *Service) Uninstall() error {
	svc, err := service.New(nopService{}, s.config())
	if err != nil {
		return err
	}

	_ = svc.Stop()

	return svc.Uninstall()
}
Example #4
0
// Install installs the service.
func (s *Service) Install() error {
	fr, err := os.Open(s.KlientBin)
	if err != nil {
		return err
	}
	defer fr.Close()

	cfg := s.config()

	if absPath, err := filepath.Abs(s.KlientBin); err != nil || absPath != cfg.Executable {
		if err := os.MkdirAll(filepath.Dir(cfg.Executable), 0755); err != nil {
			return err
		}

		fw, err := os.OpenFile(cfg.Executable, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
		if err != nil {
			return err
		}

		_, err = io.Copy(fw, fr)
		if err := nonil(err, fw.Close()); err != nil {
			return err
		}
	}

	svc, err := service.New(nopService{}, cfg)
	if err != nil {
		return err
	}

	// All the following are best-effort methods
	// to either ensure log files have proper
	// permissions so klient can upload them,
	// or sets static routes for local routing
	// webterm optimizations.
	//
	// Ignore the errors as they're not vital.
	_ = uploader.FixPerms()
	_ = tlsproxy.Init()
	_ = svc.Uninstall()

	return svc.Install()
}
Example #5
0
// UpdateCommand updates this binary if there's an update available.
func UpdateCommand(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 0 {
		cli.ShowCommandHelp(c, "update")
		return 1
	}

	var (
		forceUpdate    = c.Bool("force")
		klientVersion  = c.Int("klient-version")
		klientChannel  = c.String("klient-channel")
		kdVersion      = c.Int("kd-version")
		kdChannel      = c.String("kd-channel")
		continueUpdate = c.Bool("continue")
	)

	if kdChannel == "" {
		kdChannel = config.Environment
	}

	if klientChannel == "" {
		klientChannel = config.Environment
	}

	// Create and open the log file, to be safe in case it's missing.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Update created log file at %q", LogFilePath)
	}

	if !shouldTryUpdate(kdVersion, klientVersion, forceUpdate) {
		yesUpdate, err := checkUpdate()
		if err != nil {
			log.Error("Error checking if update is available. err:%s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}

		if !yesUpdate {
			fmt.Println("No update available.")
			return 0
		} else {
			fmt.Println("An update is available.")
		}
	}

	if kdVersion == 0 {
		var err error

		kdVersion, err = latestVersion(config.Konfig.Endpoints.KDLatest.Public.String())
		if err != nil {
			log.Error("Error fetching klientctl update version. err: %s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}
	}

	if klientVersion == 0 {
		var err error

		klientVersion, err = latestVersion(config.Konfig.Endpoints.KlientLatest.Public.String())
		if err != nil {
			log.Error("Error fetching klient update version. err: %s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}
	}

	klientPath := filepath.Join(KlientDirectory, "klient")
	klientctlPath := filepath.Join(KlientctlDirectory, "kd")
	klientUrl := config.S3Klient(klientVersion, klientChannel)
	klientctlUrl := config.S3Klientctl(kdVersion, kdChannel)

	// If --continue is not passed, download kd and then call the new kd binary with
	// `kd update --continue`, so that the new code handles updates to klient.sh,
	// service, and any migration code needed.
	if !continueUpdate {
		// Only show this message once.
		fmt.Println("Updating...")

		if err := downloadRemoteToLocal(klientctlUrl, klientctlPath); err != nil {
			log.Error("Error downloading klientctl. err:%s", err)
			fmt.Println(FailedDownloadUpdate)
			return 1
		}

		flags := flagsFromContext(c)
		// Very important to pass --continue for the subprocess.
		// --force also helps ensure it updates, since the subprocess is technically
		// the latest KD version.
		flags = append([]string{"update", "--continue=true", "--force=true"}, flags...)
		log.Info("%s", flags)
		cmd := exec.Command(klientctlPath, flags...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			log.Error("error during --continue update. err: %s", err)
			return 1
		}

		return 0
	}

	klientSh := klientSh{
		User:          konfig.CurrentUser.Username,
		KlientBinPath: filepath.Join(KlientDirectory, "klient"),
	}

	// ensure the klient home dir is writeable by user
	if klientSh.User != "" {
		ensureWriteable(KlientctlDirectory, klientSh.User)
	}

	opts := &ServiceOptions{
		Username: klientSh.User,
	}

	s, err := newService(opts)
	if err != nil {
		log.Error("Error creating Service. err:%s", err)
		fmt.Println(GenericInternalNewCodeError)
		return 1
	}

	fmt.Printf("Stopping %s...\n", config.KlientName)

	// stop klient before we update it
	if err := s.Stop(); err != nil {
		log.Error("Error stopping Service. err:%s", err)
		fmt.Println(FailedStopKlient)
		return 1
	}

	if err := downloadRemoteToLocal(klientUrl, klientPath); err != nil {
		log.Error("Error downloading klient. err:%s", err)
		fmt.Println(FailedDownloadUpdate)
		return 1
	}

	klientScript := filepath.Join(KlientDirectory, "klient.sh")

	if err := klientSh.Create(klientScript); err != nil {
		log.Error("Error writing klient.sh file. err:%s", err)
		fmt.Println(FailedInstallingKlient)
		return 1
	}

	// try to migrate from old managed klient to new kd-installed klient
	switch runtime.GOOS {
	case "darwin":
		oldS, err := service.New(&serviceProgram{}, &service.Config{
			Name:       "com.koding.klient",
			Executable: klientScript,
		})

		if err != nil {
			break
		}

		oldS.Stop()
		oldS.Uninstall()
	}

	// try to uninstall first, otherwise Install may fail if
	// klient.plist or klient init script already exist
	s.Uninstall()

	// Install the klient binary as a OS service
	if err = s.Install(); err != nil {
		log.Error("Error installing Service. err:%s", err)
		fmt.Println(GenericInternalNewCodeError)
		return 1
	}

	// start klient now that it's done updating
	if err := s.Start(); err != nil {
		log.Error("Error starting Service. err:%s", err)
		fmt.Println(FailedStartKlient)
		return 1
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = uploader.FixPerms()
	_ = configstore.FixOwner()

	fmt.Printf("Successfully updated to latest version of %s.\n", config.Name)
	return 0
}