package cmd import ( "fmt" "io" "os" "github.com/spf13/cobra" "github.com/warkanum/unitdore/config" "github.com/warkanum/unitdore/initsys" ) var dryRun, installAllFlag bool var installCmd = &cobra.Command{ Use: "install | --all", Short: "Generate and install the service file for one unit (or all with --all)", Long: `Install generates the service file for the named unit using the configured init system. It does not enable or start it. The unit must be enabled in the config. With --all it installs every enabled unit (same as 'installall'); disabled units that are installed are stopped, disabled and removed. Use --dry-run to preview the generated files without writing anything.`, Args: nameOrAll(&installAllFlag), RunE: runInstall, } var installallCmd = &cobra.Command{ Use: "installall", Short: "Generate and install service files for all enabled units", Long: `Installall generates service files for all enabled units using the configured init system. It does not enable or start them. Units that are disabled in the config are stopped, disabled and their service files removed. Use --dry-run to preview the generated files without writing anything.`, RunE: runInstallall, } func init() { installCmd.Flags().BoolVar(&installAllFlag, "all", false, "install all enabled units") for _, c := range []*cobra.Command{installCmd, installallCmd} { c.Flags().BoolVar(&dryRun, "dry-run", false, "preview service files without writing") rootCmd.AddCommand(c) } } func runInstall(cmd *cobra.Command, args []string) error { cfg, err := config.Load(configPath) if err != nil { return err } ini, err := initsys.New(cfg) if err != nil { return err } if installAllFlag { installAll(cfg, ini, dryRun, os.Stdout) return nil } return installOne(cfg, ini, args[0], dryRun, os.Stdout) } func runInstallall(cmd *cobra.Command, args []string) error { cfg, err := config.Load(configPath) if err != nil { return err } ini, err := initsys.New(cfg) if err != nil { return err } installAll(cfg, ini, dryRun, os.Stdout) return nil } // installOne installs a single enabled unit. Disabled units are an error: // removing files is installall's and update's job. func installOne(cfg *config.Config, ini initsys.Init, name string, dry bool, w io.Writer) error { u := cfg.FindUnit(name) if u == nil { return fmt.Errorf("unit %q not found", name) } if !u.Enabled { return fmt.Errorf("unit %q is disabled — run 'unitdore update %s --enable' first", name, name) } prefix, suffix := cfg.Prefix, cfg.Suffix path, err := ini.UnitPath(*u, prefix, suffix) if err != nil { return err } if dry { content, err := ini.Generate(*u, prefix, suffix) if err != nil { return err } fmt.Fprintf(w, "\n--- %s ---\n%s\n", path, content) return nil } if err := ini.Install(*u, prefix, suffix); err != nil { return err } fmt.Fprintf(w, " ✓ installed: %s\n", path) lingerHint(w, ini, map[string]bool{}, *u) return nil } func installAll(cfg *config.Config, ini initsys.Init, dry bool, w io.Writer) { prefix, suffix := cfg.Prefix, cfg.Suffix installed, skipped, removed := 0, 0, 0 lingerSeen := map[string]bool{} for _, u := range cfg.Units { name := ini.ServiceName(u, prefix, suffix) if !u.Enabled { if !ini.IsInstalled(u, prefix, suffix) { skipped++ continue } if dry { fmt.Fprintf(w, " ~ would remove: %s\n", name) continue } // Stop and disable first so the service doesn't outlive its file if err := ini.Disable(u, prefix, suffix); err != nil { fmt.Fprintf(w, " ✗ failed to disable %s: %v\n", u.Name, err) continue } if err := ini.Uninstall(u, prefix, suffix); err != nil { fmt.Fprintf(w, " ✗ failed to remove %s: %v\n", u.Name, err) continue } fmt.Fprintf(w, " - removed: %s (disabled)\n", name) removed++ continue } if dry { content, err := ini.Generate(u, prefix, suffix) if err != nil { fmt.Fprintf(w, " ✗ %s: %v\n", u.Name, err) continue } path, err := ini.UnitPath(u, prefix, suffix) if err != nil { fmt.Fprintf(w, " ✗ %s: %v\n", u.Name, err) continue } fmt.Fprintf(w, "\n--- %s ---\n%s\n", path, content) installed++ continue } if err := ini.Install(u, prefix, suffix); err != nil { fmt.Fprintf(w, " ✗ failed: %s: %v\n", u.Name, err) continue } path, _ := ini.UnitPath(u, prefix, suffix) fmt.Fprintf(w, " ✓ installed: %s\n", path) lingerHint(w, ini, lingerSeen, u) installed++ } verb := "Installed" if dry { verb = "Would install" } fmt.Fprintf(w, "\nDone. %s: %d Removed: %d Skipped: %d\n", verb, installed, removed, skipped) }