commit 548390dc6c1f9989530a67393ccd597e729a3002 Author: Stefan Friese Date: Sun Dec 15 14:07:44 2024 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc15086 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +./kilt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e78312a --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +# Define variables +APP_NAME = kilt +GO_CMD = go +GO_BUILD = $(GO_CMD) build +GO_INSTALL = $(GO_CMD) install +GO_CLEAN = $(GO_CMD) clean +BUILD_DIR = build +BINARY = $(BUILD_DIR)/$(APP_NAME) + +all: build + +build: + @echo "Building $(APP_NAME)..." + $(GO_BUILD) -o $(BINARY) + +install: build + @echo "Installing $(APP_NAME)..." + $(GO_INSTALL) + +clean: + @echo "Cleaning up build artifacts..." + $(GO_CLEAN) + rm -f $(BINARY) + +help: + @echo "Makefile for $(APP_NAME)" + @echo "Usage:" + @echo " make build - Build the application" + @echo " make install - Install the application" + @echo " make clean - Remove build artifacts" + @echo " make - Default target (build)" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9dbb0aa --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# kilt -- Stop processes in a timely manner + +This application kills the provided process after a given time. + +## Usage + +The time is set in seconds followed by the binary which will be executed and killed after. + +Examples: + +```sh +kilt timer 30 yes +kilt timer 10 watch lscpu +``` + +## Installation + +Use the `Makefile` provided to build, install or clean the application. + +```sh +Makefile for kilt +Usage: + make build - Build the application + make install - Install the application + make clean - Remove build artifacts + make - Default target (build) +``` diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..4d3cd51 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "github.com/spf13/cobra" + // "github.com/spf13/viper" +) + +var ( + + // cfgFile string + // userLicense string + + rootCmd = &cobra.Command { + Use: "kilt", + Short: "Kills a process after a set duration", + Long: "Kill a process after a set duration.\nSet a duration in seconds, followed by the binary and its parameters and arguments.", + } +) + +func Execute() error { + return rootCmd.Execute() +} + +func init() { + // cobra.OnInitialize(initConfig) + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + // rootCmd.PersistentFlags().StringP("author", "a", "Your Name", "author for copyright attribution") + // rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project") + // rootCmd.PersistentFlags().Bool("viper", true, "user Viper for configuration") + // viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + // viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) + // viper.SetDefault("author", "Stefan Etringer stefanfriese@stefanfriese.link") + // viper.SetDefault("license", "Good luck with that shit!") + + // rootCmd.AddCommand(versionCmd) + // rootCmd.AddCommand(initCmd) + rootCmd.CompletionOptions.DisableDefaultCmd = true +} + +// func initConfig() { +// if cfgFile != "" { +// viper.SetConfigFile(cfgFile) +// }else{ +// home, err := os.UserHomeDir() +// cobra.CheckErr(err) + +// viper.AddConfigPath(home) +// viper.SetConfigType("yaml") +// viper.SetConfigName(".cobra") +// } + + // viper.AutomaticEnv() + + // if err := viper.ReadInConfig(); err == nil { + // fmt.Println("Using config file:", viper.ConfigFileUsed()) + // } +// } diff --git a/cmd/timer.go b/cmd/timer.go new file mode 100644 index 0000000..c5c2f95 --- /dev/null +++ b/cmd/timer.go @@ -0,0 +1,115 @@ +package cmd + +import ( + "fmt" + "strings" + "io" + "strconv" + "time" + "os" + "os/exec" + "os/signal" + "github.com/creack/pty" + "golang.org/x/term" + "syscall" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(timeCmd) +} + +var timeCmd = &cobra.Command{ + Use: "timer", + Short: "set the duration followed by the binary to be executed", + Long: "Set the timer duration after which the kill signal will be sent followed by the binary which should be executed.\n\nExamples:\nkilt timer 30 watch lscpu\nkilt timer 10 less /etc/passwd\nkilt timer 20 yes\nkilt timer 60 htop", + Args: cobra.MinimumNArgs(2), // Set at least and + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + fmt.Println("Usage: timer [arguments...]") + return + } + fmt.Println("Timer set to: " + strings.Join(args, " ")) + if err:= runBinaryWithTimeout(args); err !=nil { + fmt.Println("Error:", err) + } + }, + // Do not parse the flags which are meant for the started process, so that they are not interpreted as an arguemnt for kilt + DisableFlagParsing: true, +} + +func resetTerminal() { + _ = exec.Command("stty", "sane").Run() +} + +func runBinaryWithTimeout(args []string) error { + var durationStr string = args[0] + duration, err := strconv.Atoi(durationStr) + if err != nil { + return fmt.Errorf("Invalid duration: %w", err) + } + + binaryPath := args[1] + binaryArgs := args[2:] + + cmd := exec.Command(binaryPath, binaryArgs...) + + oldState, err := term.MakeRaw(int(syscall.Stdin)) + if err != nil { + return fmt.Errorf("Failed to set terminal to raw mode: %w", err) + } + + defer term.Restore(int(syscall.Stdin), oldState) + + + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + // if err := cmd.Start(); err != nil { + // return fmt.Errorf("Failed to start binary: %w", err) + // } + + ptyFile, err := pty.Start(cmd) + if err != nil { + return fmt.Errorf("Failed to start pty: %w", err) + } + + defer func() { + _ = ptyFile.Close() + term.Restore(int(syscall.Stdin), oldState) + resetTerminal() + }() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + go func() { + <- sigChan + _ = cmd.Process.Kill() + resetTerminal() + os.Exit(1) + }() + + go func () { + _, _ = io.Copy(os.Stdout, ptyFile) + }() + + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + select { + case <- time.After(time.Duration(duration) * time.Second): + if err := cmd.Process.Kill(); err != nil { + return fmt.Errorf("Failed to kill process: %w", err) + } + fmt.Println("Process killed as timeout has been reached.") + resetTerminal() + return nil + case err := <-done: + resetTerminal() + return err + } +} + diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..ba2d082 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of kilt", + Long: `All software has versions. This is kilt's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("kilt the process killer v0.1 -- HEAD") + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..54be983 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module kilt + +go 1.18 + +require ( + github.com/creack/pty v1.1.23 + github.com/spf13/cobra v1.8.1 + golang.org/x/term v0.25.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.26.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f4424de --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= +github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1290e04 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "kilt/cmd" +) + +func main () { + cmd.Execute() +} diff --git a/rename.sh b/rename.sh new file mode 100755 index 0000000..f636780 --- /dev/null +++ b/rename.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# +go mod edit -module "${2}" +find . -type f -name '*.go' -exec sed -i -e "s,\"${1}/,\"${2}/,g" {} \; +find . -type f -name '*.go' -exec sed -i -e "s,\"${1}\",\"${2}\",g" {} \;