// Package zsh installs zsh, oh-my-zsh, and configured plugins. package zsh import ( "context" "fmt" "os" "path/filepath" "gitlab.hive-brain.de/MDietermann/Archduino/internal/config" "gitlab.hive-brain.de/MDietermann/Archduino/internal/module" "gitlab.hive-brain.de/MDietermann/Archduino/internal/platform" ) type Module struct { cfg config.ZshConfig pm platform.PackageManager git platform.GitClient } func New(cfg config.ZshConfig, pm platform.PackageManager, git platform.GitClient) Module { return Module{cfg: cfg, pm: pm, git: git} } type state struct { ZshInstalled bool OMZInstalled bool MissingPlugins []config.ZshPlugin } func omzHome() (string, error) { h, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(h, ".oh-my-zsh"), nil } func (Module) Meta() module.Meta { return module.Meta{ID: "zsh", Name: "Zsh + oh-my-zsh", Description: "Install zsh, oh-my-zsh, and plugins."} } func (m Module) Detect(ctx context.Context) (module.State, error) { var s state ok, err := m.pm.IsInstalled(ctx, "zsh") if err != nil { return nil, err } s.ZshInstalled = ok omz, err := omzHome() if err != nil { return nil, err } if _, err := os.Stat(omz); err == nil { s.OMZInstalled = true } for _, p := range m.cfg.Plugins { path := filepath.Join(omz, "custom", "plugins", p.Name, ".git") if _, err := os.Stat(path); err != nil { s.MissingPlugins = append(s.MissingPlugins, p) } } return s, nil } func (m Module) Plan(ctx context.Context, cur module.State) (module.Plan, error) { s, _ := cur.(state) var actions []module.Action if !s.ZshInstalled { actions = append(actions, module.Action{ Description: "pacman install zsh", Kind: module.ActionPackageInstall, Exec: func(ctx context.Context) error { return m.pm.Install(ctx, []string{"zsh"}) }, }) } if !s.OMZInstalled { actions = append(actions, module.Action{ Description: "clone oh-my-zsh from " + m.cfg.OhMyZshRepo, Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } return m.git.EnsureRepo(ctx, m.cfg.OhMyZshRepo, omz) }, }) } for _, p := range s.MissingPlugins { p := p // capture actions = append(actions, module.Action{ Description: "clone plugin " + p.Name, Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } return m.git.EnsureRepo(ctx, p.Repo, filepath.Join(omz, "custom", "plugins", p.Name)) }, }) } return module.Plan{Actions: actions}, nil } func (m Module) Apply(ctx context.Context, p module.Plan, r module.Reporter) error { for i, a := range p.Actions { r.Progress(m.Meta().ID, i+1, len(p.Actions), a.Description) if err := a.Exec(ctx); err != nil { return err } } return nil } func (m Module) Verify(ctx context.Context) error { st, err := m.Detect(ctx) if err != nil { return err } s := st.(state) if !s.ZshInstalled { return fmt.Errorf("zsh not installed") } if !s.OMZInstalled { return fmt.Errorf("oh-my-zsh not installed") } if len(s.MissingPlugins) > 0 { return fmt.Errorf("plugins missing: %v", s.MissingPlugins) } return nil } func (m Module) PlanForce(ctx context.Context) (module.Plan, error) { actions := []module.Action{{ Description: "pacman -S (force) zsh", Kind: module.ActionPackageInstall, Exec: func(ctx context.Context) error { return m.pm.Reinstall(ctx, []string{"zsh"}) }, }} actions = append(actions, module.Action{ Description: "reclone oh-my-zsh from " + m.cfg.OhMyZshRepo + " (force)", Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } if err := os.RemoveAll(omz); err != nil { return err } return m.git.Clone(ctx, m.cfg.OhMyZshRepo, omz) }, }) for _, p := range m.cfg.Plugins { p := p actions = append(actions, module.Action{ Description: "reclone plugin " + p.Name + " (force)", Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } dest := filepath.Join(omz, "custom", "plugins", p.Name) if err := os.RemoveAll(dest); err != nil { return err } return m.git.Clone(ctx, p.Repo, dest) }, }) } return module.Plan{Actions: actions}, nil }