cloudflared-mirror/cmd/cloudflared/service_template.go

223 lines
5.2 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path"
"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 fmt.Errorf(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 := path.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
}
// openFile opens the file at path. If create is set and the file exists, returns nil, true, nil
func openFile(path string, create bool) (file *os.File, exists bool, err error) {
expandedPath, err := homedir.Expand(path)
if err != nil {
return nil, false, err
}
if create {
fileInfo, err := os.Stat(expandedPath)
if err == nil && fileInfo.Size() > 0 {
return nil, true, nil
}
file, err = os.OpenFile(expandedPath, os.O_RDWR|os.O_CREATE, 0600)
} else {
file, err = os.Open(expandedPath)
}
return file, false, err
}
func copyCredential(srcCredentialPath, destCredentialPath string) error {
destFile, exists, err := openFile(destCredentialPath, true)
if err != nil {
return err
} else if exists {
// credentials already exist, do nothing
return nil
}
defer destFile.Close()
srcFile, _, err := openFile(srcCredentialPath, false)
if err != nil {
return err
}
defer srcFile.Close()
// Copy certificate
_, err = io.Copy(destFile, srcFile)
if err != nil {
return fmt.Errorf("unable to copy %s to %s: %v", srcCredentialPath, destCredentialPath, err)
}
return nil
}
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
}
func copyConfig(srcConfigPath, destConfigPath string) error {
// Copy or create config
destFile, exists, err := openFile(destConfigPath, true)
if err != nil {
return fmt.Errorf("cannot open %s with error: %s", destConfigPath, err)
} else if exists {
// config already exists, do nothing
return nil
}
defer destFile.Close()
srcFile, _, err := openFile(srcConfigPath, false)
if err != nil {
fmt.Println("Your service needs a config file that at least specifies the hostname option.")
fmt.Println("Type in a hostname now, or leave it blank and create the config file later.")
fmt.Print("Hostname: ")
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
if input == "" {
return err
}
fmt.Fprintf(destFile, "hostname: %s\n", input)
} else {
defer srcFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err)
}
}
return nil
}