Skip to content

Commit

Permalink
add restart (#13)
Browse files Browse the repository at this point in the history
* no need to pass env to sudo ; use pkexec when not launched by terminal
* let desktop pattern define env vars to pass along
* add --config and use it to point viper at correct file instead of setting home
* allow only one menu process running
* let menu listen for hup to accept new restart request
* move pidpaths under menu/wait and make them consistent and only set once
  • Loading branch information
bradrf authored Jun 12, 2022
1 parent 98abd3a commit 32a4966
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 142 deletions.
2 changes: 1 addition & 1 deletion cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var getCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
pattern := patterns.GetRunning()
if pattern == nil {
if pidPath.IsRunning() && !pidPath.IsOurs() {
if waitPidPath.IsRunning() && !waitPidPath.IsOurs() {
return sendViaIPC(cmd)
}
} else {
Expand Down
94 changes: 82 additions & 12 deletions cmd/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,59 @@ import (
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"

"github.com/BitPonyLLC/huekeys/buildinfo"
"github.com/BitPonyLLC/huekeys/internal/menu"
"github.com/BitPonyLLC/huekeys/pkg/patterns"
"github.com/BitPonyLLC/huekeys/pkg/pidpath"
"github.com/BitPonyLLC/huekeys/pkg/util"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var patternName string
var menuPidPath *pidpath.PidPath
var restarting = false

func init() {
menuCmd.Flags().StringVarP(&patternName, "pattern", "p", patternName, "name of pattern to run at start")
viper.BindPFlag("menu.pattern", menuCmd.Flags().Lookup("pattern"))

defaultPidPath := filepath.Join(os.TempDir(), buildinfo.App.Name+"-menu.pid")
menuCmd.Flags().String("pidpath", defaultPidPath, "pathname of the menu pidfile")
viper.BindPFlag("menu.pidpath", menuCmd.Flags().Lookup("pidpath"))

rootCmd.AddCommand(menuCmd)
}

var menuCmd = &cobra.Command{
Use: "menu",
Short: "Display a menu in the system tray",
PreRunE: ensureWaitRunning,
Use: "menu",
Short: "Display a menu in the system tray",
PreRunE: func(cmd *cobra.Command, _ []string) error {
err := menuPidPath.CheckAndSet()
if err != nil {
return err
}

return ensureWaitRunning(cmd)
},
RunE: func(cmd *cobra.Command, _ []string) error {
restart := make(chan os.Signal, 1)
signal.Notify(restart, syscall.SIGHUP)
go func() {
sig := <-restart
restarting = true
log.Info().Str("signal", sig.String()).Msg("restarting")
cancelFunc()
}()

args := []string{}
for c := runCmd; c != rootCmd; c = c.Parent() {
args = append([]string{c.Name()}, args...)
Expand All @@ -45,7 +74,24 @@ var menuCmd = &cobra.Command{
}
}

return menu.Show(cmd.Context(), &log.Logger, viper.GetString("sockpath"))
return menu.Show(cmd.Context(), &log.Logger, waitSockPath())
},
PostRun: func(_ *cobra.Command, _ []string) {
if menuPidPath != nil {
menuPidPath.Release()
}

if !restarting {
return
}

mCmd := exec.Command(os.Args[0], os.Args[1:]...)
err := mCmd.Start()
if err != nil {
log.Error().Err(err).Msg("failed to restart menu")
}

log.Info().Str("cmd", mCmd.String()).Interface("menu", mCmd.Process).Msg("new menu process started")
},
Args: func(cmd *cobra.Command, args []string) error {
if patternName == "" {
Expand All @@ -62,8 +108,8 @@ var menuCmd = &cobra.Command{
},
}

func ensureWaitRunning(cmd *cobra.Command, args []string) error {
if !pidPath.IsOurs() && pidPath.IsRunning() {
func ensureWaitRunning(cmd *cobra.Command) error {
if !waitPidPath.IsOurs() && waitPidPath.IsRunning() {
// wait is already executing in the background
return nil
}
Expand All @@ -73,19 +119,43 @@ func ensureWaitRunning(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unable to determine executable pathname: %w", err)
}

// use sh exec to remove sudo parent processes hanging around
hkCmd := "exec " + exe + " run wait &"
execArgs := []string{"sudo", "-E", "sh", "-c", hkCmd}
execStr := strings.Join(execArgs, " ")
dpEnv, err := patterns.DesktopPatternEnv()
if err != nil {
if util.IsTTY(os.Stderr) {
cmd.PrintErrln(err)
} else {
log.Warn().Err(err).Msg("")
}
}

var execName string
var execArgs []string

// checking only stdin isn't enough: it's attached when launched from gnome!
if util.IsTTY(os.Stdin) && util.IsTTY(os.Stdout) {
execName = "sudo"
execArgs = []string{}
} else {
// need to open a dialog for permission...
execName = "pkexec"
execArgs = []string{"--user", "root"}
}

// use sh exec to let parent processes exit
hkCmd := fmt.Sprint("export ", dpEnv, "; exec ", exe,
" --config ", viper.GetViper().ConfigFileUsed(), " run wait &")
execArgs = append(execArgs, "sh", "-c", hkCmd)

execStr := fmt.Sprint(execName, " ", strings.Join(execArgs, " "))
log.Debug().Str("cmd", execStr).Msg("")

err = exec.Command("sudo", "-E", "sh", "-c", hkCmd).Run()
err = exec.Command(execName, execArgs...).Run()
if err != nil {
return fmt.Errorf("unable to run %s: %w", execStr, err)
}

// wait a second for socket to be ready...
sockPath := viper.GetString("sockpath")
sockPath := waitSockPath()
for i := 0; i < 10; i += 1 {
time.Sleep(50 * time.Millisecond)
_, err := os.Stat(sockPath)
Expand Down
73 changes: 47 additions & 26 deletions cmd/pattern.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package cmd

import (
"os"
"path/filepath"

"github.com/BitPonyLLC/huekeys/buildinfo"
"github.com/BitPonyLLC/huekeys/pkg/patterns"
"github.com/BitPonyLLC/huekeys/pkg/pidpath"
"github.com/BitPonyLLC/huekeys/pkg/util"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var waitPidPath *pidpath.PidPath

var runCmd = &cobra.Command{
Use: "run",
Short: "runs a backlight pattern",
Expand All @@ -20,20 +27,45 @@ func init() {
rootCmd.AddCommand(runCmd)

//----------------------------------------
addPatternCmd("wait for remote commands", patterns.Get("wait"))
addPatternCmd("pulse the keyboard brightness up and down", patterns.Get("pulse"))
addPatternCmd("loop through all the colors of the rainbow", patterns.Get("rainbow"))
addPatternCmd("constantly change the color to a random selection", patterns.Get("random"))
addPatternCmd("change the color according to CPU utilization (cold to hot)", patterns.Get("cpu"))
addPatternCmd("monitor the desktop picture and change the keyboard color to match", patterns.Get("desktop"))

//----------------------------------------
waitCmd := addPatternCmd("wait for remote commands", patterns.Get("wait"))
// wait needs to manage the pidpath and start the IPC server...
waitCmd.PreRunE = func(cmd *cobra.Command, args []string) error {
if err := commonPreRunE(cmd, args); err != nil {
return err
}
if err := waitPidPath.CheckAndSet(); err != nil {
return fail(11, err)
}
return ipcServer.Start(cmd.Context(), &log.Logger, waitSockPath(), rootCmd)
}
waitCmd.PostRun = func(cmd *cobra.Command, args []string) {
if waitPidPath != nil {
waitPidPath.Release()
}
}

defaultPidPath := filepath.Join(os.TempDir(), buildinfo.App.Name+"-wait.pid")
waitCmd.Flags().String("pidpath", defaultPidPath, "pathname of the wait pidfile")
viper.BindPFlag("wait.pidpath", waitCmd.Flags().Lookup("pidpath"))

defaultSockPath := filepath.Join(os.TempDir(), buildinfo.App.Name+"-wait.sock")
waitCmd.Flags().String("sockpath", defaultSockPath, "pathname of the wait sockfile")
viper.BindPFlag("wait.sockpath", waitCmd.Flags().Lookup("sockpath"))

//----------------------------------------
watchPattern := patterns.Get("watch").(*patterns.WatchPattern)
watchCmd := addPatternCmd("watch and report color, brightness, and pattern changes", watchPattern)
// watch needs to behave differently from others when run...
watchCmd.RunE = func(cmd *cobra.Command, _ []string) error {
if pidPath.IsRunning() && !pidPath.IsOurs() {
return sendViaIPCForeground(cmd, true)
if waitPidPath.IsRunning() && !waitPidPath.IsOurs() {
return sendViaIPCForeground(cmd, true, "")
}
// there may be multiple watch patterns running (i.e. multiple watch
// clients) so each one needs to maintain its own Out writer!
Expand Down Expand Up @@ -63,33 +95,14 @@ func init() {
}

func addPatternCmd(short string, pattern patterns.Pattern) *cobra.Command {
priority := viper.GetInt("nice")
basePattern := pattern.GetBase()

cmd := &cobra.Command{
Use: pattern.GetBase().Name,
Short: short,
PreRunE: func(cmd *cobra.Command, _ []string) error {
if err := pidPath.CheckAndSet(); err != nil {
if pidPath.IsRunning() {
if cmd.Name() == "wait" {
return err
}
log.Debug().Err(err).Msg("ignoring")
return nil
}
return fail(11, err)
}
if err := util.BeNice(priority); err != nil {
return fail(12, err)
}
if _, ok := pattern.(*patterns.WaitPattern); ok {
return ipcServer.Start(cmd.Context(), &log.Logger, viper.GetString("sockpath"), rootCmd)
}
return nil
},
Use: pattern.GetBase().Name,
Short: short,
PreRunE: commonPreRunE,
RunE: func(cmd *cobra.Command, _ []string) error {
if pidPath.IsRunning() && !pidPath.IsOurs() {
if waitPidPath.IsRunning() && !waitPidPath.IsOurs() {
return sendViaIPC(cmd)
}
return pattern.Run(cmd.Context(), &log.Logger)
Expand All @@ -106,3 +119,11 @@ func addPatternCmd(short string, pattern patterns.Pattern) *cobra.Command {
runCmd.AddCommand(cmd)
return cmd
}

func commonPreRunE(cmd *cobra.Command, _ []string) error {
return util.BeNice(viper.GetInt("nice"))
}

func waitSockPath() string {
return viper.GetString("wait.sockpath")
}
4 changes: 2 additions & 2 deletions cmd/quit.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ var quitCmd = &cobra.Command{
Use: "quit",
Short: "Tells remote process to quit",
RunE: func(cmd *cobra.Command, args []string) error {
if pidPath.IsRunning() {
if pidPath.IsOurs() {
if waitPidPath.IsRunning() {
if waitPidPath.IsOurs() {
log.Info().Msg("received request to quit")
cancelFunc()
return nil
Expand Down
18 changes: 12 additions & 6 deletions cmd/remote_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ import (

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func sendViaIPC(cmd *cobra.Command) error {
return sendViaIPCForeground(cmd, false)
return sendViaIPCForeground(cmd, false, "")
}

func sendViaIPCForeground(cmd *cobra.Command, foreground bool) error {
msg := strings.Join(os.Args[1:], " ")
log.Debug().Int("pid", pidPath.Getpid()).Str("cmd", msg).Msg("sending")
func sendMsgViaIPC(cmd *cobra.Command, msg string) error {
return sendViaIPCForeground(cmd, false, msg)
}

func sendViaIPCForeground(cmd *cobra.Command, foreground bool, msg string) error {
if msg == "" {
msg = strings.Join(os.Args[1:], " ")
}

log.Debug().Int("pid", waitPidPath.Getpid()).Str("cmd", msg).Msg("sending")

client := &ipc.Client{
Foreground: foreground,
Expand All @@ -34,7 +40,7 @@ func sendViaIPCForeground(cmd *cobra.Command, foreground bool) error {
}()
}

err := client.Send(viper.GetString("sockpath"), msg)
err := client.Send(waitSockPath(), msg)
if err != nil {
return err
}
Expand Down
46 changes: 46 additions & 0 deletions cmd/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmd

import (
"errors"
"fmt"
"syscall"

"github.com/BitPonyLLC/huekeys/pkg/pidpath"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var restartCmd = &cobra.Command{
Use: "restart",
Short: "Tells remote process to restart",
RunE: func(cmd *cobra.Command, args []string) error {
if !waitPidPath.IsRunning() {
return errors.New("no remote process found")
}

if waitPidPath.IsOurs() {
log.Info().Msg("received request to restart")
cancelFunc()
return nil
}

menuPidPath = pidpath.NewPidPath(viper.GetString("menu.pidpath"), 0666)
if !menuPidPath.IsRunning() {
return sendMsgViaIPC(cmd, "quit")
}

log.Info().Str("menu", menuPidPath.String()).Msg("sending restart signal to menu")
err := syscall.Kill(menuPidPath.Getpid(), syscall.SIGHUP)
if err != nil {
return fmt.Errorf("unable to kill menu process: %w", err)
}

return nil
},
}

func init() {
rootCmd.AddCommand(restartCmd)
}
Loading

0 comments on commit 32a4966

Please sign in to comment.