package main import ( "bytes" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "text/template" homedir "github.com/mitchellh/go-homedir" "github.com/cloudflare/cloudflared/config" ) type ServiceTemplate struct { Path string Content string FileMode os.FileMode } type ServiceTemplateArgs struct { Path string ExtraArgs []string } func (st *ServiceTemplate) ResolvePath() (string, error) { resolvedPath, err := homedir.Expand(st.Path) if err != nil { return "", fmt.Errorf("error resolving path %s: %v", st.Path, err) } return resolvedPath, nil } func (st *ServiceTemplate) Generate(args *ServiceTemplateArgs) error { tmpl, err := template.New(st.Path).Parse(st.Content) if err != nil { return fmt.Errorf("error generating %s template: %v", st.Path, err) } resolvedPath, err := st.ResolvePath() if err != nil { return err } if _, err = os.Stat(resolvedPath); err == nil { return errors.New(serviceAlreadyExistsWarn(resolvedPath)) } var buffer bytes.Buffer err = tmpl.Execute(&buffer, args) if err != nil { return fmt.Errorf("error generating %s: %v", st.Path, err) } fileMode := os.FileMode(0o644) if st.FileMode != 0 { fileMode = st.FileMode } plistFolder := filepath.Dir(resolvedPath) err = os.MkdirAll(plistFolder, 0o755) if err != nil { return fmt.Errorf("error creating %s: %v", plistFolder, err) } err = os.WriteFile(resolvedPath, buffer.Bytes(), fileMode) if err != nil { return fmt.Errorf("error writing %s: %v", resolvedPath, err) } return nil } func (st *ServiceTemplate) Remove() error { resolvedPath, err := st.ResolvePath() if err != nil { return err } err = os.Remove(resolvedPath) if err != nil { return fmt.Errorf("error deleting %s: %v", resolvedPath, err) } return nil } func serviceAlreadyExistsWarn(service string) string { return fmt.Sprintf("cloudflared service is already installed at %s; if you are running a cloudflared tunnel, you "+ "can point it to multiple origins, avoiding the need to run more than one cloudflared service in the "+ "same machine; otherwise if you are really sure, you can do `cloudflared service uninstall` to clean "+ "up the existing service and then try again this command", service, ) } func runCommand(command string, args ...string) error { cmd := exec.Command(command, args...) stderr, err := cmd.StderrPipe() if err != nil { return fmt.Errorf("error getting stderr pipe: %v", err) } err = cmd.Start() if err != nil { return fmt.Errorf("error starting %s: %v", command, err) } output, _ := io.ReadAll(stderr) err = cmd.Wait() if err != nil { return fmt.Errorf("%s %v returned with error code %v due to: %v", command, args, err, string(output)) } return nil } func ensureConfigDirExists(configDir string) error { ok, err := config.FileExists(configDir) if !ok && err == nil { err = os.Mkdir(configDir, 0755) } return err } func copyFile(src, dest string) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() destFile, err := os.Create(dest) if err != nil { return err } ok := false defer func() { destFile.Close() if !ok { _ = os.Remove(dest) } }() if _, err := io.Copy(destFile, srcFile); err != nil { return err } ok = true return nil }