// 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) }
// Stop stops the service. func (s *Service) Stop() error { svc, err := service.New(nopService{}, s.config()) if err != nil { return err } return svc.Stop() }
// 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() }
// 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() }
// 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 }