2021-11-09 11:37:51 +00:00
//go:build darwin
2018-05-01 23:45:06 +00:00
package main
import (
"fmt"
"os"
"github.com/pkg/errors"
2020-11-15 01:49:44 +00:00
"github.com/urfave/cli/v2"
2021-03-23 14:30:43 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
2018-05-01 23:45:06 +00:00
)
const (
launchdIdentifier = "com.cloudflare.cloudflared"
)
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 launch agent" ,
2018-05-01 23:45:06 +00:00
Subcommands : [ ] * cli . Command {
{
Name : "install" ,
2022-03-18 09:42:45 +00:00
Usage : "Install cloudflared as an user launch agent" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( installLaunchd ) ,
2018-05-01 23:45:06 +00:00
} ,
{
Name : "uninstall" ,
2022-03-18 09:42:45 +00:00
Usage : "Uninstall the cloudflared launch agent" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( uninstallLaunchd ) ,
2018-05-01 23:45:06 +00:00
} ,
} ,
} )
2020-11-25 06:55:13 +00:00
_ = app . Run ( os . Args )
2018-05-01 23:45:06 +00:00
}
func newLaunchdTemplate ( installPath , stdoutPath , stderrPath string ) * ServiceTemplate {
return & ServiceTemplate {
Path : installPath ,
Content : fmt . Sprintf ( ` < ? xml version = "1.0" encoding = "UTF-8" ? >
< ! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
< plist version = "1.0" >
< dict >
< key > Label < / key >
< string > % s < / string >
< key > ProgramArguments < / key >
< array >
< string > { { . Path } } < / string >
2022-02-23 16:18:45 +00:00
{ { - range $ i , $ item := . ExtraArgs } }
< string > { { $ item } } < / string >
{ { - end } }
2018-05-01 23:45:06 +00:00
< / array >
< key > RunAtLoad < / key >
< true / >
< key > StandardOutPath < / key >
< string > % s < / string >
< key > StandardErrorPath < / key >
< string > % s < / string >
< key > KeepAlive < / key >
< dict >
< key > SuccessfulExit < / key >
< false / >
< / dict >
< key > ThrottleInterval < / key >
2020-04-28 00:55:27 +00:00
< integer > 5 < / integer >
2018-05-01 23:45:06 +00:00
< / dict >
< / plist > ` , launchdIdentifier , stdoutPath , stderrPath ) ,
}
}
func isRootUser ( ) bool {
return os . Geteuid ( ) == 0
}
func installPath ( ) ( string , error ) {
// User is root, use /Library/LaunchDaemons instead of home directory
if isRootUser ( ) {
return fmt . Sprintf ( "/Library/LaunchDaemons/%s.plist" , launchdIdentifier ) , nil
}
userHomeDir , err := userHomeDir ( )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "%s/Library/LaunchAgents/%s.plist" , userHomeDir , launchdIdentifier ) , nil
}
func stdoutPath ( ) ( string , error ) {
if isRootUser ( ) {
return fmt . Sprintf ( "/Library/Logs/%s.out.log" , launchdIdentifier ) , nil
}
userHomeDir , err := userHomeDir ( )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "%s/Library/Logs/%s.out.log" , userHomeDir , launchdIdentifier ) , nil
}
func stderrPath ( ) ( string , error ) {
if isRootUser ( ) {
return fmt . Sprintf ( "/Library/Logs/%s.err.log" , launchdIdentifier ) , nil
}
userHomeDir , err := userHomeDir ( )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "%s/Library/Logs/%s.err.log" , userHomeDir , launchdIdentifier ) , nil
}
func installLaunchd ( 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
if isRootUser ( ) {
2022-03-18 09:42:45 +00:00
log . Info ( ) . Msg ( "Installing cloudflared client as a system launch daemon. " +
"cloudflared client will run at boot" )
2018-05-01 23:45:06 +00:00
} else {
2022-03-18 09:42:45 +00:00
log . Info ( ) . Msg ( "Installing cloudflared client as an user launch agent. " +
"Note that cloudflared client will only run when the user is logged in. " +
"If you want to run cloudflared client at boot, install with root permission. " +
2024-04-02 15:29:33 +00:00
"For more information, visit https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/local-management/as-a-service/macos/" )
2018-05-01 23:45:06 +00:00
}
etPath , err := os . Executable ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "Error determining executable path" )
2018-05-01 23:45:06 +00:00
return fmt . Errorf ( "Error determining executable path: %v" , err )
}
installPath , err := installPath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "Error determining install path" )
2018-05-01 23:45:06 +00:00
return errors . Wrap ( err , "Error determining install path" )
}
2022-02-23 16:18:45 +00:00
extraArgs , err := getServiceExtraArgsFromCliArgs ( c , log )
if err != nil {
errMsg := "Unable to determine extra arguments for launch daemon"
log . Err ( err ) . Msg ( errMsg )
return errors . Wrap ( err , errMsg )
}
2018-05-01 23:45:06 +00:00
stdoutPath , err := stdoutPath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error determining stdout path" )
2018-05-01 23:45:06 +00:00
return errors . Wrap ( err , "error determining stdout path" )
}
stderrPath , err := stderrPath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error determining stderr path" )
2018-05-01 23:45:06 +00:00
return errors . Wrap ( err , "error determining stderr path" )
}
launchdTemplate := newLaunchdTemplate ( installPath , stdoutPath , stderrPath )
2022-02-23 16:18:45 +00:00
templateArgs := ServiceTemplateArgs { Path : etPath , ExtraArgs : extraArgs }
2018-05-01 23:45:06 +00:00
err = launchdTemplate . Generate ( & templateArgs )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error generating launchd template" )
2018-05-01 23:45:06 +00:00
return err
}
plistPath , err := launchdTemplate . ResolvePath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error resolving launchd template path" )
2018-05-01 23:45:06 +00:00
return err
}
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Outputs are logged to %s and %s" , stderrPath , stdoutPath )
2022-03-18 09:42:45 +00:00
err = runCommand ( "launchctl" , "load" , plistPath )
if err == nil {
log . Info ( ) . Msg ( "MacOS service for cloudflared installed successfully" )
}
return err
2018-05-01 23:45:06 +00:00
}
func uninstallLaunchd ( 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
if isRootUser ( ) {
2022-03-18 09:42:45 +00:00
log . Info ( ) . Msg ( "Uninstalling cloudflared as a system launch daemon" )
2018-05-01 23:45:06 +00:00
} else {
2022-03-18 09:42:45 +00:00
log . Info ( ) . Msg ( "Uninstalling cloudflared as a user launch agent" )
2018-05-01 23:45:06 +00:00
}
installPath , err := installPath ( )
if err != nil {
return errors . Wrap ( err , "error determining install path" )
}
stdoutPath , err := stdoutPath ( )
if err != nil {
return errors . Wrap ( err , "error determining stdout path" )
}
stderrPath , err := stderrPath ( )
if err != nil {
return errors . Wrap ( err , "error determining stderr path" )
}
launchdTemplate := newLaunchdTemplate ( installPath , stdoutPath , stderrPath )
plistPath , err := launchdTemplate . ResolvePath ( )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error resolving launchd template path" )
2018-05-01 23:45:06 +00:00
return err
}
err = runCommand ( "launchctl" , "unload" , plistPath )
if err != nil {
2022-03-18 09:42:45 +00:00
log . Err ( err ) . Msg ( "error unloading launchd" )
2018-05-01 23:45:06 +00:00
return err
}
2022-03-18 09:42:45 +00:00
err = launchdTemplate . Remove ( )
if err == nil {
log . Info ( ) . Msg ( "Launchd for cloudflared was uninstalled successfully" )
}
return err
2018-05-01 23:45:06 +00:00
}