Files
pmg/cmd/setup/setup.go
T
Sahilb315andCursor 8f6d0fcce0 feat: add Linux system-wide setup
Install shared shims, managed configuration, and login-shell PATH integration so golden images and multi-user hosts can protect package installs for every user.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 02:19:59 +05:30

233 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package setup
import (
"errors"
"fmt"
"os"
"runtime"
"github.com/safedep/dry/usefulerror"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/errcodes"
"github.com/safedep/pmg/internal/alias"
"github.com/safedep/pmg/internal/shim"
"github.com/safedep/pmg/internal/ui"
"github.com/safedep/pmg/internal/version"
"github.com/spf13/cobra"
)
var (
setupRemoveConfigFile bool
setupInstallSystem bool
setupRemoveSystem bool
)
var setupGeteuid = os.Geteuid
func NewSetupCommand() *cobra.Command {
setupCmd := &cobra.Command{
Use: "setup",
Short: "Manage PMG shell integration (aliases and shims)",
Long: "Setup and manage PMG config, shell aliases and PATH shims that allow you to use package manager commands with security guardrails.",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
setupCmd.AddCommand(NewInstallCommand())
setupCmd.AddCommand(NewRemoveCommand())
setupCmd.AddCommand(NewInfoCommand())
setupCmd.AddCommand(NewDoctorCommand())
setupCmd.AddCommand(NewCertCommand())
setupCmd.AddCommand(NewCacheCommand())
return setupCmd
}
func NewInstallCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "install",
Short: "Setup PMG config, aliases, and shims for package managers (npm, pnpm, pip, and more)",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Print(ui.GeneratePMGBanner(version.Version, version.Commit))
return install()
},
}
cmd.Flags().BoolVar(&setupInstallSystem, "system", false, "Install system-wide for all users (Linux, requires root)")
return cmd
}
func install() error {
if setupInstallSystem {
return installSystem()
}
if setupGeteuid() == 0 {
fmt.Printf("%s %s\n", ui.Colors.Yellow("⚠"),
"Running as root without --system configures only root's home. Use `pmg setup install --system` so all users are covered.")
}
if err := config.WriteTemplateConfig(); err != nil {
return fmt.Errorf("failed to write template config: %w", err)
}
if config.Get().IsManaged() {
fmt.Printf("%s %s\n", ui.Colors.Dim(""),
fmt.Sprintf("Using globally managed config: %s", config.Get().ConfigFilePath()))
}
if runtime.GOOS == "windows" {
fmt.Printf("%s %s\n", ui.Colors.Green("✓"), "PMG config written successfully")
fmt.Printf(" %s\n", ui.Colors.Dim(fmt.Sprintf("Config: %s", config.Get().ConfigDir())))
fmt.Printf("\n%s Shell aliases and PATH shims are not supported on Windows. Use WSL for full shell integration.\n",
ui.Colors.Yellow("⚠"))
return nil
}
cfg := alias.DefaultConfig()
rcFileManager, err := alias.NewDefaultRcFileManager(cfg.RcFileName)
if err != nil {
return fmt.Errorf("failed to create alias manager: %w", err)
}
aliasManager := alias.New(cfg, rcFileManager)
if err := aliasManager.Install(); err != nil {
return fmt.Errorf("failed to install aliases: %w", err)
}
shimMgr, err := shim.NewDefaultShimManager()
if err != nil {
return fmt.Errorf("failed to create shim manager: %w", err)
}
if err := shimMgr.Install(); err != nil {
return fmt.Errorf("failed to install shims: %w", err)
}
ui.PrintSetupInstallCmdInfo(aliasManager.GetRcPath(), shimMgr.GetBinDir(), config.Get().ConfigDir())
return nil
}
func installSystem() error {
if err := errIfSystemInstallAllowed(); err != nil {
return err
}
if err := config.WriteSystemTemplateConfig(); err != nil {
return fmt.Errorf("failed to write system config: %w", err)
}
shimMgr, err := shim.NewSystemShimManager()
if err != nil {
return fmt.Errorf("failed to create system shim manager: %w", err)
}
if err := shimMgr.Install(); err != nil {
return fmt.Errorf("failed to install system shims: %w", err)
}
ui.PrintSetupSystemInstallCmdInfo(shimMgr.GetBinDir(), config.SystemConfigDir(), shim.SystemProfilePath())
return nil
}
func NewRemoveCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "remove",
Short: "Removes pmg aliases and shims from the user's shell config.",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Print(ui.GeneratePMGBanner(version.Version, version.Commit))
return remove()
},
}
cmd.Flags().BoolVar(&setupRemoveConfigFile, "config-file", false, "Remove the config file")
cmd.Flags().BoolVar(&setupRemoveSystem, "system", false, "Remove system-wide install (Linux, requires root)")
return cmd
}
func remove() error {
if setupRemoveSystem {
return removeSystem()
}
if setupRemoveConfigFile {
// Only ever remove the per-user file; the globally managed
// config is not ours to delete from a per-user uninstall.
if err := config.RemoveUserConfigFile(); err != nil {
return err
}
}
if runtime.GOOS == "windows" {
fmt.Printf("%s %s\n", ui.Colors.Green("✓"), "PMG config removed. No aliases or shims to clean up on Windows.")
return nil
}
cfg := alias.DefaultConfig()
rcFileManager, err := alias.NewDefaultRcFileManager(cfg.RcFileName)
if err != nil {
return err
}
aliasManager := alias.New(cfg, rcFileManager)
if err := aliasManager.Remove(); err != nil {
return fmt.Errorf("failed to remove aliases: %w", err)
}
shimMgr, err := shim.NewDefaultShimManager()
if err != nil {
return fmt.Errorf("failed to create shim manager: %w", err)
}
if err := shimMgr.Remove(); err != nil {
return fmt.Errorf("failed to remove shims: %w", err)
}
fmt.Printf("%s %s\n", ui.Colors.Green("✓"), "PMG aliases and shims removed. Restart your terminal for changes to take effect")
return nil
}
func removeSystem() error {
if err := errIfSystemInstallAllowed(); err != nil {
return err
}
if setupRemoveConfigFile {
if err := config.RemoveSystemConfigFile(); err != nil {
return err
}
}
shimMgr, err := shim.NewSystemShimManager()
if err != nil {
return fmt.Errorf("failed to create system shim manager: %w", err)
}
if err := shimMgr.Remove(); err != nil {
return fmt.Errorf("failed to remove system shims: %w", err)
}
fmt.Printf("%s %s\n", ui.Colors.Green("✓"), "PMG system install removed")
return nil
}
func errIfSystemInstallAllowed() error {
if runtime.GOOS != "linux" {
return usefulerror.NewUsefulError().
WithCode(errcodes.UnsupportedPlatform).
WithHumanError("system install is only supported on Linux").
WithHelp("Use `pmg setup install` without --system for per-user setup, or run on Linux").
Wrap(errors.New("unsupported platform for --system"))
}
if setupGeteuid() != 0 {
return usefulerror.NewUsefulError().
WithCode(errcodes.PermissionDenied).
WithHumanError("system install requires root").
WithHelp("Re-run as root, e.g. `sudo pmg setup install --system`").
Wrap(errors.New("not root"))
}
return nil
}