Skip to content

Commit

Permalink
feat: add launchctl service support for macos (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
artemijspavlovs authored Sep 16, 2024
1 parent 7668320 commit f518849
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 61 deletions.
7 changes: 4 additions & 3 deletions cmd/binaries/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"strings"
"time"

"github.com/pterm/pterm"
"github.com/spf13/cobra"

"github.com/dymensionxyz/roller/cmd/consts"
"github.com/dymensionxyz/roller/utils/bash"
"github.com/dymensionxyz/roller/utils/dependencies"
"github.com/dymensionxyz/roller/utils/dependencies/types"
"github.com/dymensionxyz/roller/utils/rollapp"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
)

func Cmd() *cobra.Command {
Expand Down Expand Up @@ -42,7 +43,7 @@ func Cmd() *cobra.Command {
dymdBinaryOptions := types.Dependency{
Name: "dymension",
Repository: "https://github.com/artemijspavlovs/dymension",
Release: "3.1.0-pg04",
Release: "v3.1.0-pg06",
Binaries: []types.BinaryPathPair{
{
Binary: "dymd",
Expand Down
2 changes: 1 addition & 1 deletion cmd/rollapp/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Cmd() *cobra.Command {
dymdBinaryOptions := types.Dependency{
Name: "dymension",
Repository: "https://github.com/artemijspavlovs/dymension",
Release: "v3.2.0-pg-roller-rc05-1",
Release: "v3.1.0-pg06",
Binaries: []types.BinaryPathPair{
{
Binary: "dymd",
Expand Down
188 changes: 159 additions & 29 deletions cmd/services/load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,73 @@ func Cmd(services []string, module string) *cobra.Command {
Use: "load",
Short: "Loads the different RollApp services on the local machine",
Run: func(cmd *cobra.Command, args []string) {
if runtime.GOOS != "linux" {
pterm.Error.Printf(
"the %s commands are only available on linux machines\n",
pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()).
Sprintf("'services'"),
)

return
}
if runtime.GOOS == "darwin" {
for _, service := range services {
serviceData := ServiceTemplateData{
Name: service,
ExecPath: consts.Executables.Roller,
UserName: os.Getenv("USER"),
}
tpl, err := generateLaunchctlServiceTemplate(serviceData)
if err != nil {
pterm.Error.Println("failed to generate template", err)
return
}
err = writeLaunchctlServiceFile(tpl, service)
if err != nil {
pterm.Error.Println("failed to write launchctl file", err)
return
}
errorhandling.PrettifyErrorIfExists(err)
filePath := filepath.Join(
"/Library/LaunchDaemons/",
fmt.Sprintf("xyz.dymension.roller.%s.plist", service),
)

_, err = bash.ExecCommandWithStdout(
exec.Command(
"sudo",
"launchctl",
"load",
filePath,
),
)

for _, service := range services {
serviceData := ServiceTemplateData{
Name: service,
ExecPath: consts.Executables.Roller,
UserName: os.Getenv("USER"),
errorhandling.PrettifyErrorIfExists(err)
}
tpl, err := generateServiceTemplate(serviceData)
errorhandling.PrettifyErrorIfExists(err)
err = writeServiceFile(tpl, service)

return
} else if runtime.GOOS == "linux" {
for _, service := range services {
serviceData := ServiceTemplateData{
Name: service,
ExecPath: consts.Executables.Roller,
UserName: os.Getenv("USER"),
}
tpl, err := generateSystemdServiceTemplate(serviceData)
errorhandling.PrettifyErrorIfExists(err)
err = writeSystemdServiceFile(tpl, service)
errorhandling.PrettifyErrorIfExists(err)
}

_, err := bash.ExecCommandWithStdout(
exec.Command("sudo", "systemctl", "daemon-reload"),
)
errorhandling.PrettifyErrorIfExists(err)
}

_, err := bash.ExecCommandWithStdout(
exec.Command("sudo", "systemctl", "daemon-reload"),
)
errorhandling.PrettifyErrorIfExists(err)
pterm.Success.Printf(
"💈 Services %s been loaded successfully.\n",
strings.Join(services, ", "),
)

pterm.Success.Printf(
"💈 Services %s been loaded successfully.\n",
strings.Join(services, ", "),
)
} else {
pterm.Info.Printf(
"the %s commands currently support only darwin and linux operating systems",
cmd.Use,
)
return
}

pterm.Info.Println("next steps:")
pterm.Info.Printf(
Expand All @@ -77,7 +113,31 @@ func Cmd(services []string, module string) *cobra.Command {
return cmd
}

func writeServiceFile(serviceTxt *bytes.Buffer, serviceName string) error {
// TODO: refactor into generic service functions that handle different operating systems
func writeLaunchctlServiceFile(serviceTxt *bytes.Buffer, serviceName string) error {
filePath := filepath.Join(
"/Library/LaunchDaemons/",
fmt.Sprintf("xyz.dymension.roller.%s.plist", serviceName),
)
cmd := exec.Command(
"bash", "-c", fmt.Sprintf(
"echo '%s' | sudo tee %s",
serviceTxt.String(), filePath,
),
)
// Need to start and wait instead of run to allow sudo to prompt for password
err := cmd.Start()
if err != nil {
return err
}
err = cmd.Wait()
if err != nil {
return err
}
return nil
}

func writeSystemdServiceFile(serviceTxt *bytes.Buffer, serviceName string) error {
filePath := filepath.Join("/etc/systemd/system/", fmt.Sprintf("%s.service", serviceName))
cmd := exec.Command(
"bash", "-c", fmt.Sprintf(
Expand All @@ -97,15 +157,85 @@ func writeServiceFile(serviceTxt *bytes.Buffer, serviceName string) error {
return nil
}

func generateServiceTemplate(serviceData ServiceTemplateData) (*bytes.Buffer, error) {
func generateLaunchctlServiceTemplate(
serviceData ServiceTemplateData,
) (*bytes.Buffer, error) {
tmpl := `<?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>xyz.dymension.roller.{{.Name}}</string>
<key>ProgramArguments</key>
<array>
<string>{{.ExecPath}}</string>
<string>{{.Name}}</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>10</integer>
<key>UserName</key>
<string>{{.UserName}}</string>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65535</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65535</integer>
</dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin/roller_bins:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
`
serviceTemplate, err := template.New("service").Parse(tmpl)
if err != nil {
pterm.Println("failed to create template")
return nil, err
}
var tpl bytes.Buffer
err = serviceTemplate.Execute(&tpl, serviceData)
if err != nil {
pterm.Println("failed to generate template")
return nil, err
}
return &tpl, nil
}

func generateSystemdServiceTemplate(serviceData ServiceTemplateData) (*bytes.Buffer, error) {
tmpl := `[Unit]
Description=Roller {{.Name}} service
After=network.target
[Service]
ExecStart={{.ExecPath}} {{.Name}} start
Restart=always
RestartSec=3s
Restart=on-failure
RestartSec=10
MemoryHigh=65%
MemoryMax=70%
User={{.UserName}}
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
Expand Down
37 changes: 25 additions & 12 deletions cmd/services/restart/restart.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package restart

import (
"errors"
"fmt"
"runtime"
"strings"
Expand All @@ -27,20 +28,32 @@ func Cmd(services []string) *cobra.Command {
}

func restartSystemdServices(services []string) error {
if runtime.GOOS != "linux" {
return fmt.Errorf(
"the services commands are only available on linux machines",
if runtime.GOOS == "linux" {
for _, service := range services {
err := servicemanager.RestartSystemdService(fmt.Sprintf("%s.service", service))
if err != nil {
return fmt.Errorf("failed to restart %s systemd service: %v", service, err)
}
}
pterm.Success.Printf(
"💈 Services %s restarted successfully.\n",
strings.Join(services, ", "),
)
}
for _, service := range services {
err := servicemanager.RestartSystemdService(fmt.Sprintf("%s.service", service))
if err != nil {
return fmt.Errorf("failed to restart %s systemd service: %v", service, err)
} else if runtime.GOOS == "darwin" {
if runtime.GOOS == "linux" {
for _, service := range services {
err := servicemanager.RestartLaunchctlService(service)
if err != nil {
return fmt.Errorf("failed to restart %s systemd service: %v", service, err)
}
}
pterm.Success.Printf(
"💈 Services %s restarted successfully.\n",
strings.Join(services, ", "),
)
}
} else {
return errors.New("os not supported")
}
pterm.Success.Printf(
"💈 Services %s restarted successfully.\n",
strings.Join(services, ", "),
)
return nil
}
39 changes: 33 additions & 6 deletions cmd/services/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,25 @@ func RollappCmd() *cobra.Command {
Use: "start",
Short: "Start the systemd services on local machine",
Run: func(cmd *cobra.Command, args []string) {
err := startSystemdServices(consts.RollappSystemdServices)
if err != nil {
pterm.Error.Println("failed to start systemd services:", err)
if runtime.GOOS == "darwin" {
err := startLaunchctlServices(consts.RollappSystemdServices)
if err != nil {
pterm.Error.Println("failed to start launchd services:", err)
return
}
} else if runtime.GOOS == "linux" {
err := startSystemdServices(consts.RollappSystemdServices)
if err != nil {
pterm.Error.Println("failed to start systemd services:", err)
return
}
} else {
pterm.Info.Printf(
"the %s commands currently support only darwin and linux operating systems",
cmd.Use,
)
return
}

pterm.Info.Println("next steps:")
pterm.Info.Printf(
"run %s to set up IBC channels and start relaying packets\n",
Expand Down Expand Up @@ -94,8 +107,6 @@ func startSystemdServices(services []string) error {
for _, service := range services {
err := servicemanager.StartSystemdService(
fmt.Sprintf("%s.service", service),
"--show-sequencer-balance",
"false",
)
if err != nil {
return fmt.Errorf("failed to start %s systemd service: %v", service, err)
Expand All @@ -107,3 +118,19 @@ func startSystemdServices(services []string) error {
)
return nil
}

func startLaunchctlServices(services []string) error {
for _, service := range services {
err := servicemanager.StartLaunchctlService(
service,
)
if err != nil {
return fmt.Errorf("failed to start %s launchctl service: %v", service, err)
}
}
pterm.Success.Printf(
"💈 Services %s started successfully.\n",
strings.Join(services, ", "),
)
return nil
}
Loading

0 comments on commit f518849

Please sign in to comment.