2021-11-09 11:37:51 +00:00
//go:build linux
2018-05-01 23:45:06 +00:00
package main
import (
"fmt"
"os"
2021-03-23 14:30:43 +00:00
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
2020-07-14 18:21:44 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2020-11-23 21:36:16 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
2021-03-08 16:46:23 +00:00
"github.com/cloudflare/cloudflared/config"
2020-04-29 20:51:32 +00:00
"github.com/cloudflare/cloudflared/logger"
2018-05-01 23:45:06 +00:00
)
2021-01-25 21:51:58 +00:00
func runApp ( app * cli . App , graceShutdownC chan struct { } ) {
2018-05-01 23:45:06 +00:00
app . Commands = append ( app . Commands , & cli . Command {
Name : "service" ,
2022-03-18 09:42:45 +00:00
Usage : "Manages the cloudflared system service" ,
2018-05-01 23:45:06 +00:00
Subcommands : [ ] * cli . Command {
2020-11-25 06:55:13 +00:00
{
2018-05-01 23:45:06 +00:00
Name : "install" ,
2022-03-18 09:42:45 +00:00
Usage : "Install cloudflared as a system service" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( installLinuxService ) ,
2023-07-18 12:02:46 +00:00
Flags : [ ] cli . Flag {
noUpdateServiceFlag ,
} ,
2018-05-01 23:45:06 +00:00
} ,
2020-11-25 06:55:13 +00:00
{
2018-05-01 23:45:06 +00:00
Name : "uninstall" ,
2022-03-18 09:42:45 +00:00
Usage : "Uninstall the cloudflared service" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( uninstallLinuxService ) ,
2018-05-01 23:45:06 +00:00
} ,
} ,
} )
app . Run ( os . Args )
}
2018-10-08 19:20:28 +00:00
// The directory and files that are used by the service.
// These are hard-coded in the templates below.
const (
2023-07-18 12:02:46 +00:00
serviceConfigDir = "/etc/cloudflared"
serviceConfigFile = "config.yml"
serviceCredentialFile = "cert.pem"
serviceConfigPath = serviceConfigDir + "/" + serviceConfigFile
cloudflaredService = "cloudflared.service"
cloudflaredUpdateService = "cloudflared-update.service"
cloudflaredUpdateTimer = "cloudflared-update.timer"
2018-10-08 19:20:28 +00:00
)
2018-05-01 23:45:06 +00:00
2023-07-18 12:02:46 +00:00
var systemdAllTemplates = map [ string ] ServiceTemplate {
cloudflaredService : {
2022-03-25 10:51:15 +00:00
Path : fmt . Sprintf ( "/etc/systemd/system/%s" , cloudflaredService ) ,
2018-05-01 23:45:06 +00:00
Content : ` [ Unit ]
2022-03-18 09:42:45 +00:00
Description = cloudflared
2018-05-01 23:45:06 +00:00
After = network . target
[ Service ]
TimeoutStartSec = 0
Type = notify
2022-02-23 16:18:45 +00:00
ExecStart = { { . Path } } -- no - autoupdate { { range . ExtraArgs } } { { . } } { { end } }
2018-05-01 23:45:06 +00:00
Restart = on - failure
RestartSec = 5 s
[ Install ]
WantedBy = multi - user . target
` ,
} ,
2023-07-18 12:02:46 +00:00
cloudflaredUpdateService : {
Path : fmt . Sprintf ( "/etc/systemd/system/%s" , cloudflaredUpdateService ) ,
2018-05-01 23:45:06 +00:00
Content : ` [ Unit ]
2022-03-18 09:42:45 +00:00
Description = Update cloudflared
2018-05-01 23:45:06 +00:00
After = network . target
[ Service ]
2020-09-17 21:52:21 +00:00
ExecStart = / bin / bash - c ' { { . Path } } update ; code = $ ? ; if [ $ code - eq 11 ] ; then systemctl restart cloudflared ; exit 0 ; fi ; exit $ code '
2018-05-01 23:45:06 +00:00
` ,
} ,
2023-07-18 12:02:46 +00:00
cloudflaredUpdateTimer : {
Path : fmt . Sprintf ( "/etc/systemd/system/%s" , cloudflaredUpdateTimer ) ,
2018-05-01 23:45:06 +00:00
Content : ` [ Unit ]
2022-03-18 09:42:45 +00:00
Description = Update cloudflared
2018-05-01 23:45:06 +00:00
[ Timer ]
2019-12-19 16:53:06 +00:00
OnCalendar = daily
2018-05-01 23:45:06 +00:00
[ Install ]
WantedBy = timers . target
` ,
} ,
}
var sysvTemplate = ServiceTemplate {
Path : "/etc/init.d/cloudflared" ,
FileMode : 0755 ,
2019-01-30 21:10:47 +00:00
Content : ` # ! / bin / sh
# For RedHat and cousins :
2018-05-01 23:45:06 +00:00
# chkconfig : 2345 99 01
2022-03-18 09:42:45 +00:00
# description : cloudflared
2018-05-01 23:45:06 +00:00
# processname : { { . Path } }
# # # BEGIN INIT INFO
# Provides : { { . Path } }
# Required - Start :
# Required - Stop :
# Default - Start : 2 3 4 5
# Default - Stop : 0 1 6
2022-03-18 09:42:45 +00:00
# Short - Description : cloudflared
# Description : cloudflared agent
2018-05-01 23:45:06 +00:00
# # # END INIT INFO
name = $ ( basename $ ( readlink - f $ 0 ) )
2023-07-18 12:02:46 +00:00
cmd = "{{.Path}} --pidfile /var/run/$name.pid {{ range .ExtraArgs }} {{ . }}{{ end }}"
2018-05-01 23:45:06 +00:00
pid_file = "/var/run/$name.pid"
stdout_log = "/var/log/$name.log"
stderr_log = "/var/log/$name.err"
[ - e / etc / sysconfig / $ name ] && . / etc / sysconfig / $ name
get_pid ( ) {
cat "$pid_file"
}
is_running ( ) {
[ - f "$pid_file" ] && ps $ ( get_pid ) > / dev / null 2 > & 1
}
case "$1" in
start )
if is_running ; then
echo "Already started"
else
echo "Starting $name"
$ cmd >> "$stdout_log" 2 >> "$stderr_log" &
echo $ ! > "$pid_file"
fi
; ;
stop )
if is_running ; then
echo - n "Stopping $name.."
kill $ ( get_pid )
for i in { 1. .10 }
do
if ! is_running ; then
break
fi
echo - n "."
sleep 1
done
echo
if is_running ; then
echo "Not stopped; may still be shutting down or shutdown may have failed"
exit 1
else
echo "Stopped"
if [ - f "$pid_file" ] ; then
rm "$pid_file"
fi
fi
else
echo "Not running"
fi
; ;
restart )
$ 0 stop
if is_running ; then
echo "Unable to stop, will not attempt to start"
exit 1
fi
$ 0 start
; ;
status )
if is_running ; then
echo "Running"
else
echo "Stopped"
exit 1
fi
; ;
* )
echo "Usage: $0 {start|stop|restart|status}"
exit 1
; ;
esac
exit 0
` ,
}
2023-07-18 12:02:46 +00:00
var (
noUpdateServiceFlag = & cli . BoolFlag {
Name : "no-update-service" ,
Usage : "Disable auto-update of the cloudflared linux service, which restarts the server to upgrade for new versions." ,
Value : false ,
}
)
2018-05-01 23:45:06 +00:00
func isSystemd ( ) bool {
if _ , err := os . Stat ( "/run/systemd/system" ) ; err == nil {
return true
}
return false
}
func installLinuxService ( c * cli . Context ) error {
2020-11-25 06:55:13 +00:00
log := logger . CreateLoggerFromContext ( c , logger . EnableTerminalLog )
2020-04-29 20:51:32 +00:00
2018-05-01 23:45:06 +00:00
etPath , err := os . Executable ( )
if err != nil {
return fmt . Errorf ( "error determining executable path: %v" , err )
}
2020-10-19 12:30:25 +00:00
templateArgs := ServiceTemplateArgs {
Path : etPath ,
}
2018-05-01 23:45:06 +00:00
2023-07-18 12:02:46 +00:00
// Check if the "no update flag" is set
autoUpdate := ! c . IsSet ( noUpdateServiceFlag . Name )
2022-02-23 16:18:45 +00:00
var extraArgsFunc func ( c * cli . Context , log * zerolog . Logger ) ( [ ] string , error )
if c . NArg ( ) == 0 {
extraArgsFunc = buildArgsForConfig
2020-10-19 12:30:25 +00:00
} else {
2022-02-23 16:18:45 +00:00
extraArgsFunc = buildArgsForToken
}
2020-10-19 12:30:25 +00:00
2022-02-23 16:18:45 +00:00
extraArgs , err := extraArgsFunc ( c , log )
if err != nil {
return err
2020-10-19 12:30:25 +00:00
}
2018-05-01 23:45:06 +00:00
2022-02-23 16:18:45 +00:00
templateArgs . ExtraArgs = extraArgs
2018-05-01 23:45:06 +00:00
switch {
case isSystemd ( ) :
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Using Systemd" )
2023-07-18 12:02:46 +00:00
err = installSystemd ( & templateArgs , autoUpdate , log )
2018-05-01 23:45:06 +00:00
default :
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Using SysV" )
2023-07-18 12:02:46 +00:00
err = installSysv ( & templateArgs , autoUpdate , log )
2018-05-01 23:45:06 +00:00
}
2022-03-18 09:42:45 +00:00
if err == nil {
log . Info ( ) . Msg ( "Linux service for cloudflared installed successfully" )
}
return err
2018-05-01 23:45:06 +00:00
}
2022-02-23 16:18:45 +00:00
func buildArgsForConfig ( c * cli . Context , log * zerolog . Logger ) ( [ ] string , error ) {
if err := ensureConfigDirExists ( serviceConfigDir ) ; err != nil {
return nil , err
}
src , _ , err := config . ReadConfigFile ( c , log )
if err != nil {
return nil , 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 ( tunnel . CredFileFlag ) {
return nil , 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 nil , 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 nil , fmt . Errorf ( "failed to copy %s to %s: %w" , src . Source ( ) , serviceConfigPath , err )
}
}
return [ ] string {
"--config" , "/etc/cloudflared/config.yml" , "tunnel" , "run" ,
} , nil
}
2023-07-18 12:02:46 +00:00
func installSystemd ( templateArgs * ServiceTemplateArgs , autoUpdate bool , log * zerolog . Logger ) error {
var systemdTemplates [ ] ServiceTemplate
if autoUpdate {
systemdTemplates = [ ] ServiceTemplate {
systemdAllTemplates [ cloudflaredService ] ,
systemdAllTemplates [ cloudflaredUpdateService ] ,
systemdAllTemplates [ cloudflaredUpdateTimer ] ,
}
} else {
systemdTemplates = [ ] ServiceTemplate {
systemdAllTemplates [ cloudflaredService ] ,
}
}
2018-05-01 23:45:06 +00:00
for _ , serviceTemplate := range systemdTemplates {
err := serviceTemplate . Generate ( templateArgs )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error generating service template" )
2018-05-01 23:45:06 +00:00
return err
}
}
2022-03-25 10:51:15 +00:00
if err := runCommand ( "systemctl" , "enable" , cloudflaredService ) ; err != nil {
log . Err ( err ) . Msgf ( "systemctl enable %s error" , cloudflaredService )
2018-05-01 23:45:06 +00:00
return err
}
2023-07-18 12:02:46 +00:00
if autoUpdate {
if err := runCommand ( "systemctl" , "start" , cloudflaredUpdateTimer ) ; err != nil {
log . Err ( err ) . Msgf ( "systemctl start %s error" , cloudflaredUpdateTimer )
return err
}
2018-05-01 23:45:06 +00:00
}
2023-07-18 12:02:46 +00:00
2022-03-25 10:51:15 +00:00
if err := runCommand ( "systemctl" , "daemon-reload" ) ; err != nil {
log . Err ( err ) . Msg ( "systemctl daemon-reload error" )
return err
}
return runCommand ( "systemctl" , "start" , cloudflaredService )
2018-05-01 23:45:06 +00:00
}
2023-07-18 12:02:46 +00:00
func installSysv ( templateArgs * ServiceTemplateArgs , autoUpdate bool , log * zerolog . Logger ) error {
2018-05-01 23:45:06 +00:00
confPath , err := sysvTemplate . ResolvePath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error resolving system path" )
2018-05-01 23:45:06 +00:00
return err
}
2023-07-18 12:02:46 +00:00
if autoUpdate {
templateArgs . ExtraArgs = append ( [ ] string { "--autoupdate-freq 24h0m0s" } , templateArgs . ExtraArgs ... )
} else {
templateArgs . ExtraArgs = append ( [ ] string { "--no-autoupdate" } , templateArgs . ExtraArgs ... )
}
2018-05-01 23:45:06 +00:00
if err := sysvTemplate . Generate ( templateArgs ) ; err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error generating system template" )
2018-05-01 23:45:06 +00:00
return err
}
for _ , i := range [ ... ] string { "2" , "3" , "4" , "5" } {
if err := os . Symlink ( confPath , "/etc/rc" + i + ".d/S50et" ) ; err != nil {
continue
}
}
for _ , i := range [ ... ] string { "0" , "1" , "6" } {
if err := os . Symlink ( confPath , "/etc/rc" + i + ".d/K02et" ) ; err != nil {
continue
}
}
2022-03-25 10:51:15 +00:00
return runCommand ( "service" , "cloudflared" , "start" )
2018-05-01 23:45:06 +00:00
}
func uninstallLinuxService ( c * cli . Context ) error {
2020-11-25 06:55:13 +00:00
log := logger . CreateLoggerFromContext ( c , logger . EnableTerminalLog )
2020-04-29 20:51:32 +00:00
2022-03-18 09:42:45 +00:00
var err error
2018-05-01 23:45:06 +00:00
switch {
case isSystemd ( ) :
2020-12-28 18:10:01 +00:00
log . Info ( ) . Msg ( "Using Systemd" )
2022-03-18 09:42:45 +00:00
err = uninstallSystemd ( log )
2018-05-01 23:45:06 +00:00
default :
2020-12-28 18:10:01 +00:00
log . Info ( ) . Msg ( "Using SysV" )
2022-03-18 09:42:45 +00:00
err = uninstallSysv ( log )
}
if err == nil {
log . Info ( ) . Msg ( "Linux service for cloudflared uninstalled successfully" )
2018-05-01 23:45:06 +00:00
}
2022-03-18 09:42:45 +00:00
return err
2018-05-01 23:45:06 +00:00
}
2020-11-25 06:55:13 +00:00
func uninstallSystemd ( log * zerolog . Logger ) error {
2023-07-18 12:02:46 +00:00
// Get only the installed services
installedServices := make ( map [ string ] ServiceTemplate )
for serviceName , serviceTemplate := range systemdAllTemplates {
if err := runCommand ( "systemctl" , "status" , serviceName ) ; err == nil {
installedServices [ serviceName ] = serviceTemplate
} else {
log . Info ( ) . Msgf ( "Service '%s' not installed, skipping its uninstall" , serviceName )
}
2022-03-25 10:51:15 +00:00
}
2023-07-18 12:02:46 +00:00
if _ , exists := installedServices [ cloudflaredService ] ; exists {
if err := runCommand ( "systemctl" , "disable" , cloudflaredService ) ; err != nil {
log . Err ( err ) . Msgf ( "systemctl disable %s error" , cloudflaredService )
return err
}
if err := runCommand ( "systemctl" , "stop" , cloudflaredService ) ; err != nil {
log . Err ( err ) . Msgf ( "systemctl stop %s error" , cloudflaredService )
return err
}
2018-05-01 23:45:06 +00:00
}
2023-07-18 12:02:46 +00:00
if _ , exists := installedServices [ cloudflaredUpdateTimer ] ; exists {
if err := runCommand ( "systemctl" , "stop" , cloudflaredUpdateTimer ) ; err != nil {
log . Err ( err ) . Msgf ( "systemctl stop %s error" , cloudflaredUpdateTimer )
return err
}
2018-05-01 23:45:06 +00:00
}
2023-07-18 12:02:46 +00:00
for _ , serviceTemplate := range installedServices {
2018-05-01 23:45:06 +00:00
if err := serviceTemplate . Remove ( ) ; err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error removing service template" )
2018-05-01 23:45:06 +00:00
return err
}
}
2022-03-25 10:51:15 +00:00
if err := runCommand ( "systemctl" , "daemon-reload" ) ; err != nil {
log . Err ( err ) . Msg ( "systemctl daemon-reload error" )
return err
}
2018-05-01 23:45:06 +00:00
return nil
}
2020-11-25 06:55:13 +00:00
func uninstallSysv ( log * zerolog . Logger ) error {
2022-03-25 10:51:15 +00:00
if err := runCommand ( "service" , "cloudflared" , "stop" ) ; err != nil {
log . Err ( err ) . Msg ( "service cloudflared stop error" )
return err
}
2018-05-01 23:45:06 +00:00
if err := sysvTemplate . Remove ( ) ; err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error removing service template" )
2018-05-01 23:45:06 +00:00
return err
}
for _ , i := range [ ... ] string { "2" , "3" , "4" , "5" } {
if err := os . Remove ( "/etc/rc" + i + ".d/S50et" ) ; err != nil {
continue
}
}
for _ , i := range [ ... ] string { "0" , "1" , "6" } {
if err := os . Remove ( "/etc/rc" + i + ".d/K02et" ) ; err != nil {
continue
}
}
return nil
}