Initial commit
This commit is contained in:
219
cmd/buildutils.go
Normal file
219
cmd/buildutils.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isRepoValid(name string, exclude []string) bool {
|
||||
for _, str := range exclude {
|
||||
if strings.Contains(name, str) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func deleteFolderIfExists(path string) error {
|
||||
// Check if the path exists
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
// Path does not exist, nothing to do
|
||||
return nil
|
||||
}
|
||||
// Path exists, attempt to delete it
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete repository: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
}
|
||||
|
||||
func getRepositories(server, username string) ([]Repository, error) {
|
||||
url := fmt.Sprintf("https://%s/api/v1/users/%s/repos", server, username)
|
||||
|
||||
// Create a new request using http
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send the request via a client
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the response code is OK (200)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to retrieve repositories: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON response into a slice of Repository structs
|
||||
var repos []Repository
|
||||
err = json.Unmarshal(body, &repos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// runCommand runs a given command with arguments and streams stdout and stderr in real time.
|
||||
func runCommand(cmdName string, args ...string) error {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// processFolder takes a folder path, changes to that directory, runs a series of commands, and returns to the initial directory.
|
||||
func processFolder(folder string, commands [][]string) error {
|
||||
|
||||
// Store the initial directory
|
||||
initialDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current directory: %w", err)
|
||||
}
|
||||
|
||||
// Change to the target directory
|
||||
err = os.Chdir(folder)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to change directory to %s: %w", folder, err)
|
||||
}
|
||||
|
||||
// Run the specified commands in the folder
|
||||
for _, command := range commands {
|
||||
if len(command) == 0 {
|
||||
continue
|
||||
}
|
||||
cmdName := command[0]
|
||||
cmdArgs := command[1:]
|
||||
fmt.Printf("Running command: %s %v in folder %s\n", cmdName, cmdArgs, folder)
|
||||
if err := runCommand(cmdName, cmdArgs...); err != nil {
|
||||
fmt.Printf("Command %s failed with error: %v\n", cmdName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Change back to the initial directory
|
||||
err = os.Chdir(initialDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to return to initial directory %s: %w", initialDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePortInJSONFile(inputFile string, outputFile string, newPort int) (bool, error) {
|
||||
// Read the JSON file
|
||||
file, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Unmarshal the JSON into a map[string]interface{}
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(file, &data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the "port" key exists
|
||||
if _, exists := data["port"]; exists {
|
||||
// Update the "port" key with the new value
|
||||
data["port"] = newPort
|
||||
|
||||
// Marshal the updated map back to JSON
|
||||
updatedJSON, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Write the updated JSON back to the file
|
||||
err = os.WriteFile(outputFile, updatedJSON, os.ModePerm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Return true indicating the "port" key was found and updated
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Return false if the "port" key was not found
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func writeCaddyfile(routes map[string]int, filename string) error {
|
||||
// Create or open the file
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create or open file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the global options or any other required headers
|
||||
_, err = file.WriteString(`{
|
||||
# Global options
|
||||
auto_https off
|
||||
}
|
||||
|
||||
:8080 {
|
||||
root * ./web
|
||||
file_server
|
||||
route {
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
|
||||
// Iterate over the map and generate the routes
|
||||
for path, port := range routes {
|
||||
routeConfig := fmt.Sprintf(`
|
||||
handle_path /%s/* {
|
||||
rewrite * /oc{path}
|
||||
reverse_proxy localhost:%d
|
||||
}
|
||||
`, path, port)
|
||||
|
||||
_, err = file.WriteString(routeConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write route to file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the closing braces
|
||||
_, err = file.WriteString(`
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write closing braces to file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
114
cmd/localbuild.go
Normal file
114
cmd/localbuild.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var genpdf bool
|
||||
|
||||
// localbuildCmd represents the localbuild command
|
||||
var localbuildCmd = &cobra.Command{
|
||||
Use: "localbuild",
|
||||
Short: "A brief description of your command",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("localbuild called")
|
||||
repoPath := "src"
|
||||
binPath := "bin"
|
||||
startPort := 46000
|
||||
|
||||
servicesPorts := map[string]int{}
|
||||
|
||||
server := "cloud.o-forge.io"
|
||||
username := "core"
|
||||
|
||||
err := deleteFolderIfExists(repoPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
} else {
|
||||
fmt.Println("Repository deleted successfully")
|
||||
}
|
||||
//err := deleteFolderIfExists(binPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
} else {
|
||||
fmt.Println("Binaries deleted successfully")
|
||||
}
|
||||
os.Mkdir(repoPath, 0755)
|
||||
os.Mkdir(binPath, 0755)
|
||||
|
||||
repos, err := getRepositories(server, username)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
exclude := []string{"deprecated", "oc-lib", "oc-doc", "oc-release", "oc-deploy", "oc-local", "oc-test", "oc-tools"}
|
||||
|
||||
for _, repo := range repos {
|
||||
fmt.Printf("Repository : %s\n", repo.Name)
|
||||
if isRepoValid(repo.Name, exclude) {
|
||||
processFolder(repoPath, [][]string{{"git", "clone", repo.CloneURL}})
|
||||
}
|
||||
}
|
||||
|
||||
var commands [][]string
|
||||
for _, repo := range repos {
|
||||
fmt.Printf("Repository: %s\n", repo.Name)
|
||||
if !strings.Contains(repo.Name, "deprecated") && !strings.Contains(repo.Name, "oc-lib") && !strings.Contains(repo.Name, "oc-doc") {
|
||||
if fileExists(repoPath + "/" + repo.Name + "/go.mod") {
|
||||
commands = [][]string{
|
||||
{"go", "build"},
|
||||
{getMoveCommand(), getExecutableName(repo.Name), "../../bin/"},
|
||||
}
|
||||
fmt.Println("looking for config file: " + repoPath + "/" + repo.Name + "/" + repo.Name[3:] + ".json")
|
||||
if fileExists(repoPath + "/" + repo.Name + "/" + repo.Name[3:] + ".json") {
|
||||
isService, err := updatePortInJSONFile(repoPath+"/"+repo.Name+"/"+repo.Name[3:]+".json", binPath+"/"+repo.Name[3:]+".json", startPort)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
if isService {
|
||||
fmt.Println(repo.Name[3:] + " is a service, setting port.")
|
||||
servicesPorts[repo.Name[3:]] = startPort
|
||||
startPort += 1
|
||||
}
|
||||
if genpdf {
|
||||
commands = append(commands, []string{"swagger2pdf", "swagger/swagger.json", "../../bin/" + repo.Name[3:] + ".pdf"})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commands = [][]string{
|
||||
{"flutter", "build", "web"},
|
||||
{getMoveCommand(), "build/web", "../../bin/"},
|
||||
}
|
||||
}
|
||||
processFolder(repoPath+"/"+repo.Name, commands)
|
||||
}
|
||||
}
|
||||
writeCaddyfile(servicesPorts, binPath+"/Caddyfile")
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(localbuildCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// localbuildCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
localbuildCmd.Flags().BoolVarP(&genpdf, "pdf", "p", false, "Generate PDFs")
|
||||
}
|
||||
79
cmd/localrun.go
Normal file
79
cmd/localrun.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// localrunCmd represents the localrun command
|
||||
var localrunCmd = &cobra.Command{
|
||||
Use: "localrun",
|
||||
Short: "A brief description of your command",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
folderPath := "bin"
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
// Handle Ctrl+C (SIGINT) and other termination signals
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
if findCaddyRuntime(folderPath) == "" {
|
||||
log.Println("Caddy runtime not found")
|
||||
downloadLatestCaddy(folderPath)
|
||||
}
|
||||
|
||||
// Start the goroutines to run programs
|
||||
walkFolder(folderPath, ctx, wg)
|
||||
|
||||
go func() {
|
||||
// Wait for a termination signal (like Ctrl+C)
|
||||
<-signalChannel
|
||||
|
||||
log.Println("Received termination signal. Stopping all processes...")
|
||||
|
||||
// Call cancel to stop all the processes gracefully
|
||||
cancel()
|
||||
|
||||
// Force kill all processes
|
||||
stopAllProcesses()
|
||||
|
||||
// Exit the program
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// Wait for all processes to finish
|
||||
wg.Wait()
|
||||
|
||||
log.Println("All processes stopped.")
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(localrunCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// localrunCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// localrunCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
25
cmd/os.go
Normal file
25
cmd/os.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package cmd
|
||||
|
||||
import "runtime"
|
||||
|
||||
func getOsName() string {
|
||||
return runtime.GOOS
|
||||
}
|
||||
|
||||
func getOsArch() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func getExecutableName(exeName string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return exeName + ".exe"
|
||||
}
|
||||
return exeName
|
||||
}
|
||||
|
||||
func getMoveCommand() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "move"
|
||||
}
|
||||
return "mv"
|
||||
}
|
||||
73
cmd/root.go
Normal file
73
cmd/root.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "oc-local",
|
||||
Short: "A brief description of your application",
|
||||
Long: `A longer description that spans multiple lines and likely contains
|
||||
examples and usage of using your application. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.oc-local.yaml)")
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := os.UserHomeDir()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Search config in home directory with name ".oc-local" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName(".oc-local")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
221
cmd/runtutils.go
Normal file
221
cmd/runtutils.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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-") {
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startProcess runs the given program and stores a reference to it for later control
|
||||
func startProcess(ctx context.Context, program string, wg *sync.WaitGroup, args ...string) {
|
||||
defer wg.Done()
|
||||
|
||||
cmd := exec.CommandContext(ctx, program, args...)
|
||||
|
||||
mu.Lock()
|
||||
processes = append(processes, cmd)
|
||||
mu.Unlock()
|
||||
|
||||
var outBuf, errBuf bytes.Buffer
|
||||
cmd.Stdout = &outBuf
|
||||
cmd.Stderr = &errBuf
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Printf("Failed to start process: %s", program)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Started process: %s", program)
|
||||
|
||||
fmt.Print(outBuf.String())
|
||||
fmt.Print(errBuf.String())
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
log.Printf("Process finished with error: %v", err)
|
||||
} else {
|
||||
log.Printf("Process finished successfully: %s", program)
|
||||
}
|
||||
fmt.Print(outBuf.String())
|
||||
fmt.Print(errBuf.String())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user