service-tools/createcmd.go

245 lines
8.4 KiB
Go
Raw Normal View History

2018-03-09 22:23:05 +01:00
package main
import (
"fmt"
2018-03-11 01:43:21 +01:00
"io/ioutil"
2018-03-10 22:29:11 +01:00
"os"
2018-03-11 01:43:21 +01:00
"path/filepath"
"strconv"
"strings"
2018-03-09 22:23:05 +01:00
2018-03-11 01:43:21 +01:00
"github.com/coreos/go-systemd/unit"
2018-03-12 10:10:21 +01:00
"github.com/rivo/tview"
2018-03-09 22:23:05 +01:00
"github.com/spf13/cobra"
)
type CreateOptions struct {
Type string
2018-03-10 22:29:11 +01:00
Description string
Exec string
ExecStartPre string
ExecStartPost string
ExecReload string
ExecStop string
ExecStopPost string
2018-03-09 22:23:05 +01:00
WorkingDirectory string
RootDirectory string
2018-03-10 22:29:11 +01:00
User string
Group string
Restart string
RestartSec uint64
TimeoutStartSec uint64
TimeoutStopSec uint64
2018-03-10 22:29:11 +01:00
After string
WantedBy string
2018-03-09 22:23:05 +01:00
}
var (
createOpts = CreateOptions{}
2018-03-12 10:10:21 +01:00
types = Strings{"simple", "forking", "oneshot", "dbus", "notify", "idle"}
restarts = Strings{"no", "always", "on-success", "on-failure", "on-abnormal", "on-abort", "on-watchdog"}
2018-03-09 22:23:05 +01:00
createCmd = &cobra.Command{
2018-03-11 01:43:21 +01:00
Use: "create <executable> <description> <after> <wanted-by>",
2018-03-09 22:23:05 +01:00
Short: "creates a new Unit file",
Long: `The create command creates a new systemd Unit file`,
RunE: func(cmd *cobra.Command, args []string) error {
2018-03-11 01:43:21 +01:00
ts, err := targets()
if err != nil {
return fmt.Errorf("Can't find systemd targets: %s", err)
}
2018-03-10 22:29:11 +01:00
createOpts.Type = strings.ToLower(createOpts.Type)
2018-03-12 10:10:21 +01:00
if !types.Contains(createOpts.Type) {
return fmt.Errorf("No such service type: %s", createOpts.Type)
}
createOpts.Restart = strings.ToLower(createOpts.Restart)
2018-03-12 10:10:21 +01:00
if !restarts.Contains(createOpts.Restart) {
return fmt.Errorf("No such restart type: %s", createOpts.Restart)
}
2018-03-10 22:29:11 +01:00
if len(args) >= 1 {
createOpts.Exec = args[0]
2018-03-09 22:23:05 +01:00
}
2018-03-10 22:29:11 +01:00
if len(args) >= 2 {
createOpts.Description = args[1]
2018-03-11 01:43:21 +01:00
}
if len(args) >= 3 {
createOpts.After = args[2]
}
2018-03-12 10:10:21 +01:00
if len(args) >= 4 {
createOpts.WantedBy = args[3]
2018-03-11 01:43:21 +01:00
2018-03-12 10:10:21 +01:00
validate()
return executeCreate()
}
app := tview.NewApplication()
form := tview.NewForm().
2018-03-13 03:03:12 +01:00
AddInputField("Description:", createOpts.Description, 40, nil, func(s string) {
createOpts.Description = s
}).
2018-03-13 03:03:12 +01:00
AddDropDown("Type:", types, types.IndexOf(createOpts.Type), func(s string, i int) {
createOpts.Type = s
}).
2018-03-13 03:03:12 +01:00
AddInputField("Exec on start:", createOpts.Exec, 40, nil, func(s string) {
createOpts.Exec = s
}).
AddInputField("Exec on stop:", createOpts.ExecStop, 40, nil, func(s string) {
createOpts.ExecStop = s
}).
AddInputField("Exec on reload:", createOpts.ExecReload, 40, nil, func(s string) {
createOpts.ExecReload = s
}).
AddDropDown("Restarts on:", restarts, restarts.IndexOf(createOpts.Restart), func(s string, i int) {
createOpts.Restart = s
}).
2018-03-13 03:03:12 +01:00
AddDropDown("Start after target:", ts.Strings(), Strings(ts.Strings()).IndexOf(createOpts.After), func(s string, i int) {
createOpts.After = s
}).
2018-03-13 03:03:12 +01:00
AddDropDown("Wanted by target:", ts.Strings(), Strings(ts.Strings()).IndexOf(createOpts.WantedBy), func(s string, i int) {
createOpts.WantedBy = s
})
var apperr error
form.
AddButton("Create", func() {
app.Stop()
if err := validate(); err != nil {
apperr = err
} else {
2018-03-12 10:10:21 +01:00
executeCreate()
}
}).
AddButton("Cancel", func() {
app.Stop()
})
form.SetBorder(true).SetTitle("Create new service").SetTitleAlign(tview.AlignCenter)
if err := app.SetRoot(form, true).Run(); err != nil {
return err
2018-03-11 01:43:21 +01:00
}
return apperr
2018-03-09 22:23:05 +01:00
},
}
)
2018-03-12 10:10:21 +01:00
func validate() error {
ts, err := targets()
if err != nil {
return fmt.Errorf("Can't find systemd targets: %s", err)
}
// Exec check
if len(strings.TrimSpace(createOpts.Exec)) == 0 {
return fmt.Errorf("Need an executable to create a service for")
}
2018-03-12 10:10:21 +01:00
stat, err := os.Stat(createOpts.Exec)
if os.IsNotExist(err) {
return fmt.Errorf("Could not find executable: %s is not a file", createOpts.Exec)
}
if stat.IsDir() {
return fmt.Errorf("Could not find executable: %s is a directory", createOpts.Exec)
}
if stat.Mode()&0111 == 0 {
return fmt.Errorf("%s is not executable", createOpts.Exec)
}
// Description check
if len(createOpts.Description) == 0 {
return fmt.Errorf("Description for this service can't be empty")
}
if len(createOpts.After) == 0 {
return fmt.Errorf("create needs a target after which this service will be started")
}
if !ts.Contains(createOpts.After) {
return fmt.Errorf("Could not create service: no such target")
}
if len(createOpts.WantedBy) == 0 {
return fmt.Errorf("create needs a target which this service will be wanted by")
}
if !ts.Contains(createOpts.WantedBy) {
return fmt.Errorf("Could not create service: no such target")
}
return nil
}
2018-03-09 22:23:05 +01:00
func executeCreate() error {
2018-03-11 01:43:21 +01:00
u := []*unit.UnitOption{
&unit.UnitOption{"Unit", "Description", createOpts.Description},
&unit.UnitOption{"Unit", "After", createOpts.After},
&unit.UnitOption{"Service", "Type", createOpts.Type},
&unit.UnitOption{"Service", "WorkingDirectory", createOpts.WorkingDirectory},
&unit.UnitOption{"Service", "RootDirectory", createOpts.RootDirectory},
&unit.UnitOption{"Service", "ExecStart", createOpts.Exec},
&unit.UnitOption{"Service", "ExecStartPre", createOpts.ExecStartPre},
&unit.UnitOption{"Service", "ExecStartPost", createOpts.ExecStartPost},
&unit.UnitOption{"Service", "ExecReload", createOpts.ExecReload},
&unit.UnitOption{"Service", "ExecStop", createOpts.ExecStop},
&unit.UnitOption{"Service", "ExecStopPost", createOpts.ExecStopPost},
2018-03-11 01:43:21 +01:00
&unit.UnitOption{"Service", "User", createOpts.User},
&unit.UnitOption{"Service", "Group", createOpts.Group},
&unit.UnitOption{"Service", "Restart", createOpts.Restart},
&unit.UnitOption{"Service", "RestartSec", strconv.FormatUint(createOpts.RestartSec, 10)},
&unit.UnitOption{"Service", "TimeoutStartSec", strconv.FormatUint(createOpts.TimeoutStartSec, 10)},
&unit.UnitOption{"Service", "TimeoutStopSec", strconv.FormatUint(createOpts.TimeoutStopSec, 10)},
2018-03-11 01:43:21 +01:00
&unit.UnitOption{"Install", "WantedBy", createOpts.WantedBy},
}
r := unit.Serialize(stripEmptyOptions(u))
2018-03-11 01:43:21 +01:00
b, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("Encountered error while reading output: %v", err)
2018-03-11 01:43:21 +01:00
}
filename := filepath.Base(createOpts.Exec) + ".service"
2018-03-11 01:43:21 +01:00
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("Could not create file: %s", err)
2018-03-11 01:43:21 +01:00
}
defer f.Close()
_, err = f.Write(b)
if err != nil {
return fmt.Errorf("Could not write to file: %s", err)
2018-03-11 01:43:21 +01:00
}
fmt.Printf("Generated Unit file: %s\n%s\n", filename, b)
2018-03-09 22:23:05 +01:00
return nil
}
func init() {
createCmd.PersistentFlags().StringVarP(&createOpts.Type, "type", "t", "simple", "Type of service (simple, forking, oneshot, dbus, notify or idle)")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStartPre, "execstartpre", "", "Executable to run before the service starts")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStartPost, "execstartpost", "", "Executable to run after the service started")
createCmd.PersistentFlags().StringVar(&createOpts.ExecReload, "execreload", "", "Executable to run to reload the service")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStop, "execstop", "", "Executable to run to stop the service")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStopPost, "execstoppost", "", "Executable to run after the service stopped")
createCmd.PersistentFlags().StringVarP(&createOpts.WorkingDirectory, "workingdir", "w", "", "Working-directory of the service")
createCmd.PersistentFlags().StringVar(&createOpts.RootDirectory, "rootdir", "", "Root-directory of the service")
2018-03-11 01:43:21 +01:00
createCmd.PersistentFlags().StringVarP(&createOpts.User, "user", "u", "root", "User to run service as")
createCmd.PersistentFlags().StringVarP(&createOpts.Group, "group", "g", "root", "Group to run service as")
createCmd.PersistentFlags().StringVarP(&createOpts.Restart, "restart", "r", "on-failure", "When to restart (no, always, on-success, on-failure, on-abnormal, on-abort or on-watchdog)")
2018-03-11 01:43:21 +01:00
createCmd.PersistentFlags().Uint64VarP(&createOpts.RestartSec, "restartsec", "s", 5, "How many seconds between restarts")
createCmd.PersistentFlags().Uint64Var(&createOpts.TimeoutStartSec, "timeoutstartsec", 0, "How many seconds to wait for a startup")
createCmd.PersistentFlags().Uint64Var(&createOpts.TimeoutStopSec, "timeoutstopsec", 0, "How many seconds to wait when stoping a service")
2018-03-11 01:43:21 +01:00
createCmd.PersistentFlags().StringVarP(&createOpts.After, "after", "a", "", "Target after which the service will be started")
createCmd.PersistentFlags().StringVarP(&createOpts.WantedBy, "wantedby", "b", "", "This service is wanted by this target")
2018-03-09 22:23:05 +01:00
RootCmd.AddCommand(createCmd)
}