From 47a99e35ee2ecd82fb1ab2da655d36fd3301715a Mon Sep 17 00:00:00 2001 From: Laurence Date: Fri, 13 Mar 2026 11:38:00 +0000 Subject: [PATCH] feat(installer): add default install directory with existing install detection - Default to /opt/pangolin for new installations - Check current directory and /opt/pangolin for existing installs - Prompt to use existing install if found at default location - Offer to change directory ownership when running via sudo - Create installation directory if it doesn't exist --- install/main.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/install/main.go b/install/main.go index 9de332b60..dddcb89b8 100644 --- a/install/main.go +++ b/install/main.go @@ -14,6 +14,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" "text/template" "time" @@ -90,6 +91,13 @@ func main() { var config Config var alreadyInstalled = false + // Determine installation directory + installDir := findOrSelectInstallDirectory() + if err := os.Chdir(installDir); err != nil { + fmt.Printf("Error changing to installation directory: %v\n", err) + os.Exit(1) + } + // check if there is already a config file if _, err := os.Stat("config/config.yml"); err != nil { config = collectUserInput() @@ -287,6 +295,117 @@ func main() { fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain) } +func hasExistingInstall(dir string) bool { + configPath := filepath.Join(dir, "config", "config.yml") + _, err := os.Stat(configPath) + return err == nil +} + +func findOrSelectInstallDirectory() string { + const defaultInstallDir = "/opt/pangolin" + + // Get current working directory + cwd, err := os.Getwd() + if err != nil { + fmt.Printf("Error getting current directory: %v\n", err) + os.Exit(1) + } + + // 1. Check current directory for existing install + if hasExistingInstall(cwd) { + fmt.Printf("Found existing Pangolin installation in current directory: %s\n", cwd) + return cwd + } + + // 2. Check default location (/opt/pangolin) for existing install + if cwd != defaultInstallDir && hasExistingInstall(defaultInstallDir) { + fmt.Printf("\nFound existing Pangolin installation at: %s\n", defaultInstallDir) + if readBool(fmt.Sprintf("Would you like to use the existing installation at %s?", defaultInstallDir), true) { + return defaultInstallDir + } + } + + // 3. No existing install found, prompt for installation directory + fmt.Println("\n=== Installation Directory ===") + fmt.Println("No existing Pangolin installation detected.") + + installDir := readString("Enter the installation directory", defaultInstallDir) + + // Expand ~ to home directory if present + if strings.HasPrefix(installDir, "~") { + home, err := os.UserHomeDir() + if err != nil { + fmt.Printf("Error getting home directory: %v\n", err) + os.Exit(1) + } + installDir = filepath.Join(home, installDir[1:]) + } + + // Convert to absolute path + absPath, err := filepath.Abs(installDir) + if err != nil { + fmt.Printf("Error resolving path: %v\n", err) + os.Exit(1) + } + installDir = absPath + + // Check if directory exists + if _, err := os.Stat(installDir); os.IsNotExist(err) { + // Directory doesn't exist, create it + if readBool(fmt.Sprintf("Directory %s does not exist. Create it?", installDir), true) { + if err := os.MkdirAll(installDir, 0755); err != nil { + fmt.Printf("Error creating directory: %v\n", err) + os.Exit(1) + } + fmt.Printf("Created directory: %s\n", installDir) + + // Offer to change ownership if running via sudo + changeDirectoryOwnership(installDir) + } else { + fmt.Println("Installation cancelled.") + os.Exit(0) + } + } + + fmt.Printf("Installation directory: %s\n", installDir) + return installDir +} + +func changeDirectoryOwnership(dir string) { + // Check if we're running via sudo by looking for SUDO_USER + sudoUser := os.Getenv("SUDO_USER") + if sudoUser == "" || os.Geteuid() != 0 { + return + } + + sudoUID := os.Getenv("SUDO_UID") + sudoGID := os.Getenv("SUDO_GID") + + if sudoUID == "" || sudoGID == "" { + return + } + + fmt.Printf("\nRunning as root via sudo (original user: %s)\n", sudoUser) + if readBool(fmt.Sprintf("Would you like to change ownership of %s to user '%s'? This makes it easier to manage config files without sudo.", dir, sudoUser), true) { + uid, err := strconv.Atoi(sudoUID) + if err != nil { + fmt.Printf("Warning: Could not parse SUDO_UID: %v\n", err) + return + } + gid, err := strconv.Atoi(sudoGID) + if err != nil { + fmt.Printf("Warning: Could not parse SUDO_GID: %v\n", err) + return + } + + if err := os.Chown(dir, uid, gid); err != nil { + fmt.Printf("Warning: Could not change ownership: %v\n", err) + } else { + fmt.Printf("Changed ownership of %s to %s\n", dir, sudoUser) + } + } +} + func podmanOrDocker() SupportedContainer { inputContainer := readString("Would you like to run Pangolin as Docker or Podman containers?", "docker")