From 7f5638bc05348c39950f74d72a642ffbc9f63ee8 Mon Sep 17 00:00:00 2001 From: Paulo Truta Date: Sat, 18 Mar 2023 14:56:47 +0000 Subject: [PATCH] Applied shwrap pattern to login, create and delete tunnel actions, many fixes --- Makefile | 3 +- internal/tasks/tasks.go | 299 +++++++++++++++++---------- scripts/cloudflared_login.sh | 6 + scripts/cloudflared_tunnel_create.sh | 7 + scripts/cloudflared_tunnel_delete.sh | 7 + 5 files changed, 209 insertions(+), 113 deletions(-) create mode 100644 scripts/cloudflared_login.sh create mode 100755 scripts/cloudflared_tunnel_create.sh create mode 100755 scripts/cloudflared_tunnel_delete.sh diff --git a/Makefile b/Makefile index bf0fc2e..c09a58b 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,8 @@ install-cloud: build-cloud @echo "To start edgeboxctl run: systemctl start edgeboxctl" install-prod: build-prod - sudo systemctl stop edgeboxctl + -sudo systemctl stop edgeboxctl + sudo rm -rf /usr/local/bin/edgeboxctl /lib/systemd/system/edgeboxctl.service sudo cp ./bin/edgeboxctl /usr/local/bin/edgeboxctl sudo cp ./edgeboxctl.service /lib/systemd/system/edgeboxctl.service sudo systemctl daemon-reload diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 4a1efe6..6af6651 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -10,8 +10,9 @@ import ( "os/exec" "strings" "os" - "path/filepath" "io/ioutil" + "bufio" + "path/filepath" "github.com/edgebox-iot/edgeboxctl/internal/diagnostics" "github.com/edgebox-iot/edgeboxctl/internal/edgeapps" @@ -34,10 +35,7 @@ type Task struct { } type taskSetupTunnelArgs struct { - BootnodeAddress string `json:"bootnode_address"` - BootnodeToken string `json:"bootnode_token"` - AssignedAddress string `json:"assigned_address"` - NodeName string `json:"node_name"` + DomainName string `json:"domain_name"` } type taskStartEdgeAppArgs struct { @@ -69,6 +67,13 @@ type taskEnablePublicDashboardArgs struct { InternetURL string `json:"internet_url"` } +type cloudflaredTunnelJson struct { + AccountTag string `json:"AccountTag"` + TunnelSecret string `json:"TunnelSecret"` + TunnelID string `json:"TunnelID"` +} + + const STATUS_CREATED int = 0 const STATUS_EXECUTING int = 1 const STATUS_FINISHED int = 2 @@ -139,15 +144,17 @@ func ExecuteTask(task Task) Task { switch task.Task { case "setup_tunnel": - log.Println("Setting up Cloudflare connection...") - // var args taskSetupTunnelArgs - // err := json.Unmarshal([]byte(task.Args.String), &args) - // if err != nil { - // log.Printf("Error reading arguments of setup_bootnode task: %s", err) - // } else { - taskResult := taskSetupTunnel() - task.Result = sql.NullString{String: taskResult, Valid: true} - // } + log.Println("Setting up Cloudflare Tunnel...") + var args taskSetupTunnelArgs + err := json.Unmarshal([]byte(task.Args.String), &args) + if err != nil { + log.Printf("Error reading arguments of setup_tunnel task: %s", err) + status := "{\"status\": \"error\", \"message\": \"The Domain Name you are going to Authorize must be provided beforehand! Please insert a domain name and try again.\"}" + utils.WriteOption("TUNNEL_STATUS", status) + } else { + taskResult := taskSetupTunnel(args) + task.Result = sql.NullString{String: taskResult, Valid: true} + } case "install_edgeapp": @@ -331,64 +338,138 @@ func ExecuteSchedules(tick int) { } -func taskSetupTunnel() string { +func taskSetupTunnel(args taskSetupTunnelArgs) string { fmt.Println("Executing taskSetupTunnel") - - wsPath := utils.GetPath(utils.WsPath) - // cmdargs := []string{"gen", "--name", args.NodeName, "--token", args.BootnodeToken, args.BootnodeAddress + ":8655", "--prefix", args.AssignedAddress} - // utils.Exec(wsPath, "tinc-boot", cmdargs) - - // cmdargs = []string{"start", "tinc@dnet"} - // utils.Exec(wsPath, "systemctl", cmdargs) - - // cmdargs = []string{"enable", "tinc@dnet"} - // utils.Exec(wsPath, "systemctl", cmdargs) + wsPath := utils.GetPath(utils.WsPath) // Stop a the service if it is running + fmt.Println("Stopping cloudflared service") cmdargs := []string{"stop", "cloudflared"} utils.Exec(wsPath, "systemctl", cmdargs) + fmt.Println("Removing possibly previous service install.") + cmd := exec.Command("cloudflared", "service", "uninstall") + cmd.Start() + cmd.Wait() + + fmt.Println("Removing cloudflared files") cmdargs = []string{"-rf", "/home/system/.cloudflared"} utils.Exec(wsPath, "rm", cmdargs) + cmdargs = []string{"-rf", "/etc/cloudflared/config.yml"} + utils.Exec(wsPath, "rm", cmdargs) + cmdargs = []string{"-rf", "/root/.cloudflared/cert.pem"} + utils.Exec(wsPath, "rm", cmdargs) + fmt.Println("Creating cloudflared folder") cmdargs = []string{"/home/system/.cloudflared"} utils.Exec(wsPath, "mkdir", cmdargs) - // The cloudflared command should run in the background. We want to extract the immediate output but leave the command running in the background - // to download the certificate. - - cmd := exec.Command("cloudflared", "tunnel", "login", "2>&1", "|", "tee", "/home/system/tunnel_out.txt") - - var status string - var url string - - cmd.Start() + cmd = exec.Command("sh", "/home/system/components/edgeboxctl/scripts/cloudflared_login.sh") + stdout, err := cmd.StdoutPipe() + if err != nil { + panic(err) + } + scanner := bufio.NewScanner(stdout) + err = cmd.Start() + if err != nil { + panic(err) + } + url := "" + for scanner.Scan() { + fmt.Println(scanner.Text()) + text := scanner.Text() + if strings.Contains(text, "https://") { + url = text + fmt.Println("Tunnel setup is requesting auth with URL: " + url) + status := "{\"status\": \"waiting\", \"login_link\": \"" + url + "\"}" + utils.WriteOption("TUNNEL_STATUS", status) + break + } + } + if scanner.Err() != nil { + cmd.Process.Kill() + cmd.Wait() + panic(scanner.Err()) + } go func() { + fmt.Println("Running async") + cmd.Wait() - fmt.Println("Waiting for cloudflared tunnel login to finish...") - err := cmd.Wait() - if err != nil { - log.Fatal(err) + // Keep retrying to read cert.pem file until it is created + // When running as a service, the cert is saved to a different folder, + // so we check both :) + for { + _, err := os.Stat("/home/system/.cloudflared/cert.pem") + _, err2 := os.Stat("/root/.cloudflared/cert.pem") + if err == nil || err2 == nil { + fmt.Println("cert.pem file detected") + break + } + time.Sleep(1 * time.Second) + fmt.Println("Waiting for cert.pem file to be created") } fmt.Println("Tunnel auth setup finished without errors.") status := "{\"status\": \"starting\", \"login_link\": \"" + url + "\"}" utils.WriteOption("TUNNEL_STATUS", status) - - cmd = exec.Command("cloudflared", "tunnel", "delete", "edgebox") - cmd.Start() - err = cmd.Wait() + + // fmt.Println("Moving certificate to global folder.") + // cmdargs = []string{"/home/system/.cloudflared/cert.pem", "/etc/cloudflared/cert.pem"} + // utils.Exec(wsPath, "cp", cmdargs) + + fmt.Println("Deleting possible previous tunnel.") + // Configure the service and start it + cmd := exec.Command("sh", "/home/system/components/edgeboxctl/scripts/cloudflared_tunnel_delete.sh") + stdout, err := cmd.StdoutPipe() if err != nil { - log.Fatal(err) + panic(err) + } + scanner := bufio.NewScanner(stdout) + err = cmd.Start() + if err != nil { + panic(err) + } + for scanner.Scan() { + fmt.Println(scanner.Text()) + text := scanner.Text() + fmt.Println(text) + } + if scanner.Err() != nil { + cmd.Process.Kill() + cmd.Wait() + panic(scanner.Err()) + } + + + fmt.Println("Creating Tunnel for Edgebox.") + cmd = exec.Command("sh", "/home/system/components/edgeboxctl/scripts/cloudflared_tunnel_create.sh") + stdout, err = cmd.StdoutPipe() + if err != nil { + panic(err) + } + scanner = bufio.NewScanner(stdout) + err = cmd.Start() + if err != nil { + panic(err) + } + for scanner.Scan() { + fmt.Println(scanner.Text()) + text := scanner.Text() + fmt.Println(text) + } + if scanner.Err() != nil { + cmd.Process.Kill() + cmd.Wait() + panic(scanner.Err()) } - cmd = exec.Command("cloudflared", "tunnel", "create", "edgebox") - cmd.Start() - - err = cmd.Wait() + // This also needs to be executed in root and non root variants + fmt.Println("Reading cloudflared folder to get the JSON file.") + isRoot := false dir := "/home/system/.cloudflared/" + dir2 := "/root/.cloudflared/" files, err := os.ReadDir(dir) if err != nil { panic(err) @@ -398,31 +479,54 @@ func taskSetupTunnel() string { for _, file := range files { // check if file has json extension if filepath.Ext(file.Name()) == ".json" { + fmt.Println("Non-Root JSON file found: " + file.Name()) jsonFile = file } } + // If the files are not in the home folder, try the root folder + if jsonFile == nil { + files, err = os.ReadDir(dir2) + if err != nil { + panic(err) + } + for _, file := range files { + // check if file has json extension + if filepath.Ext(file.Name()) == ".json" { + fmt.Println("Root JSON file found: " + file.Name()) + jsonFile = file + isRoot = true + } + } + } + if jsonFile == nil { panic("No JSON file found in directory") } - jsonFilePath := filepath.Join(dir, jsonFile.Name()) + fmt.Println("Reading JSON file.") + targetDir := "/home/system/.cloudflared/" + if isRoot { + targetDir = "/root/.cloudflared/" + } + jsonFilePath := filepath.Join(targetDir, jsonFile.Name()) jsonBytes, err := ioutil.ReadFile(jsonFilePath) if err != nil { panic(err) } - var data interface{} + fmt.Println("Parsing JSON file.") + var data cloudflaredTunnelJson err = json.Unmarshal(jsonBytes, &data) if err != nil { - panic(err) + log.Printf("Error reading tunnel JSON file: %s", err) } fmt.Println(data) // print propertie tunnel_id from json file - fmt.Println(data.(map[string]interface{})["tunnelID"]) + fmt.Println("Tunnel ID is:" + data.TunnelID) // create the config.yml file with the following content in each line: // "url": "http://localhost:80" @@ -437,33 +541,56 @@ func taskSetupTunnel() string { defer f.Close() - _, err = f.WriteString("url: http://localhost:80\ntunnel: " + data.(map[string]interface{})["tunnelID"].(string) + "\ncredentials-file: " + jsonFilePath) + _, err = f.WriteString("url: http://localhost:80\ntunnel: " + data.TunnelID + "\ncredentials-file: " + jsonFilePath) if err != nil { panic(err) } - cmd = exec.Command("cloudflared", "tunnel", "route", "dns", "-f" ,"edgebox", "*.myedge.app") + fmt.Println("Creating DNS Routes for @ and *.") + cmd = exec.Command("cloudflared", "tunnel", "route", "dns", "-f" ,"edgebox", "*." + args.DomainName) cmd.Start() err = cmd.Wait() if err != nil { log.Fatal(err) } - cmd = exec.Command("cloudflared", "tunnel", "route", "dns", "-f" ,"edgebox", "myedge.app") + domainNameInfo := args.DomainName + utils.WriteOption("DOMAIN_NAME", domainNameInfo) + + cmd = exec.Command("cloudflared", "tunnel", "route", "dns", "-f" ,"edgebox", args.DomainName) cmd.Start() err = cmd.Wait() if err != nil { log.Fatal(err) } - cmd = exec.Command("cloudflared", "service", "install") + fmt.Println("Installing systemd service.") + cmd = exec.Command("cloudflared", "--config", "/home/system/.cloudflared/config.yml", "service", "install") cmd.Start() cmd.Wait() + fmt.Println("Starting tunnel.") cmd = exec.Command("systemctl", "start", "cloudflared") - cmd.Start() - err = cmd.Wait() + stdout, err = cmd.StdoutPipe() + if err != nil { + panic(err) + } + scanner = bufio.NewScanner(stdout) + err = cmd.Start() + if err != nil { + panic(err) + } + for scanner.Scan() { + fmt.Println(scanner.Text()) + text := scanner.Text() + fmt.Println(text) + } + if scanner.Err() != nil { + cmd.Process.Kill() + cmd.Wait() + panic(scanner.Err()) + } if err != nil { fmt.Println("Tunnel auth setup finished with errors.") @@ -472,66 +599,14 @@ func taskSetupTunnel() string { log.Fatal(err) } else { fmt.Println("Tunnel auth setup finished without errors.") - status := "{\"status\": \"connected\", \"login_link\": \"" + url + "\"}" + status := "{\"status\": \"connected\", \"login_link\": \"" + url + "\", \"domain\": \"" + args.DomainName + "\"}" utils.WriteOption("TUNNEL_STATUS", status) } + fmt.Println("Finished running async") }() - // Wait a couple seconds... - time.Sleep(10 * time.Second) - fmt.Println("Waited 10 secs for buffer... Attempting to read out file...") - - // try to read the tunnel_out.txt file into a string - // if the file does not exist, keep trying each 5 seconds - // until the file exists - for { - _, err := os.Stat("/home/system/tunnel_out.txt") - if err == nil { - break - } - // print the error - fmt.Println(err) - time.Sleep(5 * time.Second) - fmt.Println("Did not find file, trying again...") - } - - b, err := os.ReadFile("/home/system/tunnel_out.txt") // just pass the file name - if err != nil { - log.Fatal(err) - } - - fmt.Println("File contents: \n" + string(b)) - - // Splitting the result into lines. - lines := strings.Split(string(b), "\n") - - // Finding the line with the URL. - for _, line := range lines { - if strings.Contains(line, "https://") { - url = line - fmt.Println("Tunnel setup is requesting auth with URL: " + url) - } - } - - // initialOutput := make([]byte, 0) - - // The program can continue while the command runs in the background. - // ... - - // Wait for the goroutine to finish before exiting the program - if err == nil { - status = "{\"status\": \"waiting\", \"login_link\": \"" + url + "\"}" - } else { - status = "{\"status\": \"error\", \"message\": \"" + err.Error() + "\"}" - } - - - utils.WriteOption("TUNNEL_STATUS", status) - return "{\"url\": \"" + url + "\"}" - - // Returning the URL (or error) in the status output. - return status + return "{\"url\": \"" + url + "\"}" } func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string { diff --git a/scripts/cloudflared_login.sh b/scripts/cloudflared_login.sh new file mode 100644 index 0000000..12e985e --- /dev/null +++ b/scripts/cloudflared_login.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +echo "Starting script login" +cloudflared tunnel login 2>&1 | tee /home/system/components/edgeboxctl/scripts/output.log & +echo "sleeping 5 seconds" +sleep 5 \ No newline at end of file diff --git a/scripts/cloudflared_tunnel_create.sh b/scripts/cloudflared_tunnel_create.sh new file mode 100755 index 0000000..136e815 --- /dev/null +++ b/scripts/cloudflared_tunnel_create.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +echo "Starting script create" +# script -q -c "cloudflared tunnel login 2>&1 | tee /app/output.log" & +cloudflared tunnel create edgebox 2>&1 | tee /home/system/components/edgeboxctl/scripts/create_output.log +echo "sleeping 5 seconds" +sleep 5 \ No newline at end of file diff --git a/scripts/cloudflared_tunnel_delete.sh b/scripts/cloudflared_tunnel_delete.sh new file mode 100755 index 0000000..33594ea --- /dev/null +++ b/scripts/cloudflared_tunnel_delete.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +echo "Starting script delete" +TUNNEL_ORIGIN_CERT=/home/system/.cloudflared/cert.pem +cloudflared tunnel delete edgebox 2>&1 | tee /home/system/components/edgeboxctl/scripts/delete_output.log & +echo "sleeping 5 seconds" +sleep 5 \ No newline at end of file