diff --git a/cmd/cloudflared/config/configuration.go b/cmd/cloudflared/config/configuration.go index 82ce2255..8233b160 100644 --- a/cmd/cloudflared/config/configuration.go +++ b/cmd/cloudflared/config/configuration.go @@ -326,6 +326,9 @@ func GetConfiguration() *Configuration { func ReadConfigFile(c *cli.Context, log logger.Service) (*configFileSettings, error) { configFile := c.String("config") if configuration.Source() == configFile || configFile == "" { + if configuration.Source() == "" { + return nil, ErrNoConfigFile + } return &configuration, nil } diff --git a/cmd/cloudflared/linux_service.go b/cmd/cloudflared/linux_service.go index 460c2666..3e04c704 100644 --- a/cmd/cloudflared/linux_service.go +++ b/cmd/cloudflared/linux_service.go @@ -7,11 +7,12 @@ import ( "os" "path/filepath" + "github.com/pkg/errors" + cli "github.com/urfave/cli/v2" + "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" "github.com/cloudflare/cloudflared/cmd/cloudflared/config" "github.com/cloudflare/cloudflared/logger" - "github.com/pkg/errors" - cli "github.com/urfave/cli/v2" ) func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) { @@ -23,6 +24,12 @@ func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) { Name: "install", Usage: "Install Argo Tunnel as a system service", Action: cliutil.ErrorHandler(installLinuxService), + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "legacy", + Usage: "Generate service file for non-named tunnels", + }, + }, }, &cli.Command{ Name: "uninstall", @@ -40,6 +47,7 @@ const ( serviceConfigDir = "/etc/cloudflared" serviceConfigFile = "config.yml" serviceCredentialFile = "cert.pem" + serviceConfigPath = serviceConfigDir + "/" + serviceConfigFile ) var systemdTemplates = []ServiceTemplate{ @@ -52,7 +60,7 @@ After=network.target [Service] TimeoutStartSec=0 Type=notify -ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --no-autoupdate +ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }} Restart=on-failure RestartSec=5s @@ -102,7 +110,7 @@ var sysvTemplate = ServiceTemplate{ # Description: Argo Tunnel agent ### END INIT INFO name=$(basename $(readlink -f $0)) -cmd="{{.Path}} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s" +cmd="{{.Path}} --config /etc/cloudflared/config.yml --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}" pid_file="/var/run/$name.pid" stdout_log="/var/log/$name.log" stderr_log="/var/log/$name.err" @@ -182,10 +190,6 @@ func isSystemd() bool { } func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string, logger logger.Service) error { - if err := ensureConfigDirExists(serviceConfigDir); err != nil { - return err - } - srcCredentialPath := filepath.Join(userConfigDir, userCredentialFile) destCredentialPath := filepath.Join(serviceConfigDir, serviceCredentialFile) if srcCredentialPath != destCredentialPath { @@ -216,23 +220,63 @@ func installLinuxService(c *cli.Context) error { if err != nil { return fmt.Errorf("error determining executable path: %v", err) } - templateArgs := ServiceTemplateArgs{Path: etPath} + templateArgs := ServiceTemplateArgs{ + Path: etPath, + } - userConfigDir := filepath.Dir(c.String("config")) - userConfigFile := filepath.Base(c.String("config")) - userCredentialFile := config.DefaultCredentialFile - if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile, logger); err != nil { - logger.Errorf("Failed to copy user configuration: %s. Before running the service, ensure that %s contains two files, %s and %s", err, - serviceConfigDir, serviceCredentialFile, serviceConfigFile) + if err := ensureConfigDirExists(serviceConfigDir); err != nil { return err } + if c.Bool("legacy") { + userConfigDir := filepath.Dir(c.String("config")) + userConfigFile := filepath.Base(c.String("config")) + userCredentialFile := config.DefaultCredentialFile + if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile, logger); err != nil { + logger.Errorf("Failed to copy user configuration: %s. Before running the service, ensure that %s contains two files, %s and %s", err, + serviceConfigDir, serviceCredentialFile, serviceConfigFile) + return err + } + templateArgs.ExtraArgs = []string{ + "--origincert", serviceConfigDir + "/" + serviceCredentialFile, + } + } else { + src, err := config.ReadConfigFile(c, logger) + if err != nil { + return err + } + + // can't use context because this command doesn't define "credentials-file" flag + configPresent := func(s string) bool { + val, err := src.String(s) + return err == nil && val != "" + } + if src.TunnelID == "" || !configPresent("credentials-file") { + return fmt.Errorf(`Configuration file %s must contain entries for the tunnel to run and its associated credentials: +tunnel: TUNNEL-UUID +credentials-file: CREDENTIALS-FILE +`, src.Source()) + } + if src.Source() != serviceConfigPath { + if exists, err := config.FileExists(serviceConfigPath); err != nil || exists { + return fmt.Errorf("Possible conflicting configuration in %[1]s and %[2]s. Either remove %[2]s or run `cloudflared --config %[2]s service install`", src.Source(), serviceConfigPath) + } + + if err := copyFile(src.Source(), serviceConfigPath); err != nil { + return fmt.Errorf("failed to copy %s to %s: %w", src.Source(), serviceConfigPath, err) + } + } + + templateArgs.ExtraArgs = []string{ + "tunnel", "run", + } + } switch { case isSystemd(): logger.Infof("Using Systemd") return installSystemd(&templateArgs, logger) default: - logger.Infof("Using Sysv") + logger.Infof("Using SysV") return installSysv(&templateArgs, logger) } } @@ -291,7 +335,7 @@ func uninstallLinuxService(c *cli.Context) error { logger.Infof("Using Systemd") return uninstallSystemd(logger) default: - logger.Infof("Using Sysv") + logger.Infof("Using SysV") return uninstallSysv(logger) } } diff --git a/cmd/cloudflared/service_template.go b/cmd/cloudflared/service_template.go index 2d87a7ce..79576ed0 100644 --- a/cmd/cloudflared/service_template.go +++ b/cmd/cloudflared/service_template.go @@ -10,8 +10,9 @@ import ( "os/exec" "text/template" - "github.com/cloudflare/cloudflared/cmd/cloudflared/config" "github.com/mitchellh/go-homedir" + + "github.com/cloudflare/cloudflared/cmd/cloudflared/config" ) type ServiceTemplate struct { @@ -21,7 +22,8 @@ type ServiceTemplate struct { } type ServiceTemplateArgs struct { - Path string + Path string + ExtraArgs []string } func (st *ServiceTemplate) ResolvePath() (string, error) { @@ -139,6 +141,33 @@ func copyCredential(srcCredentialPath, destCredentialPath string) error { 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)