package cmd import ( "bufio" "context" "fmt" "io" "io/fs" "log" "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" ) var processes []*exec.Cmd var mu sync.Mutex // walkFolder starts processes for files starting with "az" in the given folder func walkFolder(folderPath string, ctx context.Context, wg *sync.WaitGroup) { os.Chdir(folderPath) files, err := os.ReadDir(".") if err != nil { log.Fatal(err) } for _, file := range files { // Check if filename starts with "oc-" if strings.HasPrefix(file.Name(), "oc-") && (!strings.Contains(file.Name(), ".") || strings.Contains(file.Name(), ".exe")) { // Prepare the command to start the process wg.Add(1) go startProcess(ctx, "./"+file.Name(), wg) } // find if strings.HasPrefix(strings.ToLower(file.Name()), "caddy") && strings.ToLower(file.Name()) != "caddyfile" { runtime := findCaddyRuntime(".") if runtime != "" { // Prepare the command to start the process wg.Add(1) go startProcess(ctx, "./"+runtime, wg, "run") } } } } func startProcess(ctx context.Context, program string, wg *sync.WaitGroup, args ...string) { defer wg.Done() // Create log file logFile, err := os.Create(program + ".log") if err != nil { log.Printf("Failed to create log file: %v", err) return } cmd := exec.CommandContext(ctx, program, args...) mu.Lock() processes = append(processes, cmd) mu.Unlock() // Create pipes to capture the output in real-time stdoutPipe, err := cmd.StdoutPipe() if err != nil { log.Printf("Failed to get stdout pipe: %v", err) return } stderrPipe, err := cmd.StderrPipe() if err != nil { log.Printf("Failed to get stderr pipe: %v", err) return } // Start the command if err := cmd.Start(); err != nil { log.Printf("Failed to start process: %s", program) return } log.Printf("Started process: %s", program) // Create goroutines to read stdout and stderr concurrently go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { fmt.Fprintln(logFile, scanner.Text()) // Print each line from stdout as it arrives } if err := scanner.Err(); err != nil { log.Printf("Error reading stdout: %v", err) } }() go func() { scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { fmt.Fprintln(logFile, scanner.Text()) // Print each line from stderr as it arrives } if err := scanner.Err(); err != nil { log.Printf("Error reading stderr: %v", err) } }() // Wait for the process to finish if err := cmd.Wait(); err != nil { log.Printf("Process finished with error: %v", err) } else { log.Printf("Process finished successfully: %s", program) } } // stopAllProcesses stops all the started processes func stopAllProcesses() { mu.Lock() defer mu.Unlock() for _, cmd := range processes { if cmd.Process != nil { log.Printf("Killing process with PID %d", cmd.Process.Pid) err := cmd.Process.Kill() if err != nil { log.Printf("Failed to kill process with PID %d: %v", cmd.Process.Pid, err) } } } processes = []*exec.Cmd{} } func findCaddyRuntime(folder string) string { var foundFile string err := filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } // Check if it's a file (not a directory) if !d.IsDir() { filename := d.Name() // Check if the filename starts with "caddy" (case-insensitive) and is not "CaddyFile" if strings.HasPrefix(strings.ToLower(filename), "caddy") && strings.ToLower(filename) != "caddyfile" { foundFile = filename return filepath.SkipDir // Skip further traversal once we find a match } } return nil }) if err != nil { fmt.Println("Error:", err) return "" } return foundFile } func downloadLatestCaddy(outputFolder string) error { // Define the GitHub API URL for the latest Caddy release apiURL := "https://api.github.com/repos/caddyserver/caddy/releases/latest" // Get the system's OS and architecture osName := runtime.GOOS archName := runtime.GOARCH // Make an HTTP GET request to the GitHub API resp, err := http.Get(apiURL) if err != nil { return fmt.Errorf("failed to get latest Caddy release: %v", err) } defer resp.Body.Close() // Read the response body body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read GitHub API response: %v", err) } // Find the download URL for the current platform (OS + architecture) downloadURL := "" urlPrefix := fmt.Sprintf("%s_%s", osName, archName) lines := strings.Split(string(body), "\n") for _, line := range lines { if strings.Contains(line, "browser_download_url") && strings.Contains(line, urlPrefix) { // Extract the URL parts := strings.Split(line, "\"") for _, part := range parts { if strings.HasPrefix(part, "https://") && strings.Contains(part, urlPrefix) && (strings.HasSuffix(part, ".tar.gz") || strings.HasSuffix(part, ".zip")) { downloadURL = part break } } if downloadURL != "" { break } } } if downloadURL == "" { return fmt.Errorf("failed to find a suitable Caddy binary for %s_%s", osName, archName) } // Make an HTTP GET request to download the file resp, err = http.Get(downloadURL) if err != nil { return fmt.Errorf("failed to download Caddy executable: %v", err) } defer resp.Body.Close() suffix := "" if strings.Contains(downloadURL, ".tar.gz") { suffix = ".tar.gz" } else if strings.Contains(downloadURL, ".zip") { suffix = ".zip" } // Create the output file outputPath := fmt.Sprintf("%s/caddy"+suffix, strings.TrimRight(outputFolder, "/")) outFile, err := os.Create(outputPath) if err != nil { return fmt.Errorf("failed to create output file: %v", err) } defer outFile.Close() // Write the response body to the output file _, err = io.Copy(outFile, resp.Body) if err != nil { return fmt.Errorf("failed to write to output file: %v", err) } // Extract the downloaded file if strings.Contains(downloadURL, ".tar.gz") { err = exec.Command("tar", "-xzf", outputPath, "-C", outputFolder).Run() if err != nil { return fmt.Errorf("failed to extract tar.gz file: %v", err) } } else if strings.Contains(downloadURL, ".zip") { err = exec.Command("unzip", outputPath, "-d", outputFolder).Run() if err != nil { return fmt.Errorf("failed to extract zip file: %v", err) } } // Remove the downloaded file err = os.Remove(outputPath) if err != nil { return fmt.Errorf("failed to remove: %v", err) } fmt.Printf("Caddy downloaded successfully to %s\n", outputPath) return nil }