diff --git a/cmd/start.go b/cmd/start.go new file mode 100644 index 0000000..033436a --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/warkanum/unitdore/config" + "github.com/warkanum/unitdore/systemd" +) + +var startCmd = &cobra.Command{ + Use: "start ", + Short: "Start a unit by name", + Args: cobra.ExactArgs(1), + RunE: runStart, +} + +func init() { + rootCmd.AddCommand(startCmd) +} + +func runStart(cmd *cobra.Command, args []string) error { + cfg, err := config.Load(configPath) + if err != nil { + return err + } + + name := args[0] + u := cfg.FindUnit(name) + if u == nil { + return fmt.Errorf("unit %q not found", name) + } + + prefix, suffix := cfg.Prefix, cfg.Suffix + + if !systemd.IsInstalled(*u, prefix, suffix) { + return fmt.Errorf("%s is not installed — run 'unitdore install' first", name) + } + + fmt.Printf(" ▶ starting %s...\n", systemd.ServiceName(*u, prefix, suffix)) + if err := systemd.Start(*u, prefix, suffix); err != nil { + return err + } + fmt.Printf(" ✓ started: %s\n", name) + return nil +} diff --git a/cmd/stop.go b/cmd/stop.go new file mode 100644 index 0000000..a95f23c --- /dev/null +++ b/cmd/stop.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/warkanum/unitdore/config" + "github.com/warkanum/unitdore/systemd" +) + +var stopCmd = &cobra.Command{ + Use: "stop ", + Short: "Stop a unit by name", + Args: cobra.ExactArgs(1), + RunE: runStop, +} + +func init() { + rootCmd.AddCommand(stopCmd) +} + +func runStop(cmd *cobra.Command, args []string) error { + cfg, err := config.Load(configPath) + if err != nil { + return err + } + + name := args[0] + u := cfg.FindUnit(name) + if u == nil { + return fmt.Errorf("unit %q not found", name) + } + + prefix, suffix := cfg.Prefix, cfg.Suffix + + if !systemd.IsInstalled(*u, prefix, suffix) { + return fmt.Errorf("%s is not installed", name) + } + + fmt.Printf(" ■ stopping %s...\n", systemd.ServiceName(*u, prefix, suffix)) + if err := systemd.Stop(*u, prefix, suffix); err != nil { + return err + } + fmt.Printf(" ✓ stopped: %s\n", name) + return nil +} diff --git a/cmd/uninstall.go b/cmd/uninstall.go new file mode 100644 index 0000000..1456372 --- /dev/null +++ b/cmd/uninstall.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/warkanum/unitdore/config" + "github.com/warkanum/unitdore/systemd" +) + +var uninstallCmd = &cobra.Command{ + Use: "uninstall ", + Short: "Disable, stop, and remove the service file for a unit", + Args: cobra.ExactArgs(1), + RunE: runUninstall, +} + +func init() { + rootCmd.AddCommand(uninstallCmd) +} + +func runUninstall(cmd *cobra.Command, args []string) error { + cfg, err := config.Load(configPath) + if err != nil { + return err + } + + name := args[0] + u := cfg.FindUnit(name) + if u == nil { + return fmt.Errorf("unit %q not found", name) + } + + prefix, suffix := cfg.Prefix, cfg.Suffix + + if !systemd.IsInstalled(*u, prefix, suffix) { + return fmt.Errorf("%s is not installed", name) + } + + svcName := systemd.ServiceName(*u, prefix, suffix) + + fmt.Printf(" ■ disabling %s...\n", svcName) + if err := systemd.Disable(*u, prefix, suffix); err != nil { + return fmt.Errorf("disabling unit: %w", err) + } + + path, _ := systemd.UnitPath(*u, prefix, suffix) + fmt.Printf(" ✗ removing %s...\n", path) + if err := systemd.Uninstall(*u, prefix, suffix); err != nil { + return fmt.Errorf("removing service file: %w", err) + } + + fmt.Printf(" ✓ uninstalled: %s\n", name) + return nil +} diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..def6c39 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + "github.com/warkanum/unitdore/config" + "github.com/warkanum/unitdore/systemd" +) + +var ( + updateOrder int + updateEnable bool + updateDisable bool +) + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a unit's config and recreate its service file", + Args: cobra.ExactArgs(1), + RunE: runUpdate, +} + +func init() { + updateCmd.Flags().IntVar(&updateOrder, "order", 0, "set startup order") + updateCmd.Flags().BoolVar(&updateEnable, "enable", false, "enable the unit") + updateCmd.Flags().BoolVar(&updateDisable, "disable", false, "disable the unit") + rootCmd.AddCommand(updateCmd) +} + +func runUpdate(cmd *cobra.Command, args []string) error { + if updateEnable && updateDisable { + return errors.New("--enable and --disable are mutually exclusive") + } + + cfg, err := config.Load(configPath) + if err != nil { + return err + } + + name := args[0] + u := cfg.FindUnit(name) + if u == nil { + return fmt.Errorf("unit %q not found", name) + } + + if cmd.Flags().Changed("order") { + u.Order = updateOrder + fmt.Printf(" order → %d\n", u.Order) + } + if updateEnable { + u.Enabled = true + u.DisabledReason = "" + fmt.Printf(" enabled\n") + } + if updateDisable { + u.Enabled = false + fmt.Printf(" disabled\n") + } + + if err := cfg.Save(configPath); err != nil { + return err + } + + prefix, suffix, serviceUser := cfg.Prefix, cfg.Suffix, cfg.EffectiveServiceUser() + + if systemd.IsInstalled(*u, prefix, suffix) { + if err := systemd.Install(*u, prefix, suffix, serviceUser); err != nil { + return fmt.Errorf("recreating service file: %w", err) + } + path, _ := systemd.UnitPath(*u, prefix, suffix) + fmt.Printf(" ✓ service file recreated: %s\n", path) + } + + return nil +} diff --git a/systemd/manager.go b/systemd/manager.go index dff8ba3..99fba74 100644 --- a/systemd/manager.go +++ b/systemd/manager.go @@ -73,6 +73,16 @@ func Disable(u config.Unit, prefix, suffix string) error { return systemctl(u.User, "disable", "--now", ServiceName(u, prefix, suffix)) } +// Start starts a unit without enabling it. +func Start(u config.Unit, prefix, suffix string) error { + return systemctl(u.User, "start", ServiceName(u, prefix, suffix)) +} + +// Stop stops a unit without disabling it. +func Stop(u config.Unit, prefix, suffix string) error { + return systemctl(u.User, "stop", ServiceName(u, prefix, suffix)) +} + // Status returns the ActiveState of a unit ("active", "inactive", "failed", "unknown"). func Status(u config.Unit, prefix, suffix string) string { args := []string{"show", "-p", "ActiveState", "--value", ServiceName(u, prefix, suffix)}