From e80aaf7d18fbedefe0666b07431ec08d9fb39c69 Mon Sep 17 00:00:00 2001 From: Paulo Truta Date: Sat, 7 Dec 2024 01:26:45 +0100 Subject: [PATCH] Release/1.3.0 (#40) * Implemented scaffolding for BrowserDev tasks and implemented taskGetBrowserDevPassword * Added SetBrowserDevPasswordFile and ReplaceTextInFile funcs * Added browserDevProxyPath * Polished code and added missing pieces * Improved Makefile with info and install + build processes * Added vscode tasks support * Added run command and reverted build-all to old logic, added run vscode task * Added GetBrowserDevStatus task and into schedules * Fixed check for tasks.GetBrowserStatus() * Reverted to no result log in Exec command --- .vscode/tasks.json | 236 ++++++++++++++++++++++++++++++++++++++ Makefile | 46 +++++++- go.mod | 1 + go.sum | 2 + internal/system/system.go | 82 +++++++++++++ internal/tasks/tasks.go | 117 +++++++++++++++++++ internal/utils/utils.go | 16 +++ 7 files changed, 496 insertions(+), 4 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..9d228ef --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,236 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "command": "make", + "args": ["build"], + "options": { + "cwd": "${workspaceFolder}" + + }, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Build All", + "type": "shell", + "command": "make", + "args": ["build-all"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build Prod", + "type": "shell", + "command": "make", + "args": ["build-prod"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build Cloud", + "type": "shell", + "command": "make", + "args": ["build-cloud"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build ARM64", + "type": "shell", + "command": "make", + "args": ["build-arm64"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build ARMHF", + "type": "shell", + "command": "make", + "args": ["build-armhf"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build AMD64", + "type": "shell", + "command": "make", + "args": ["build-amd64"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Clean", + "type": "shell", + "command": "make", + "args": ["clean"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Test", + "type": "shell", + "command": "make", + "args": ["test"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Test with Coverage", + "type": "shell", + "command": "make", + "args": ["test-with-coverage"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Run", + "type": "shell", + "command": "make", + "args": ["run"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install", + "type": "shell", + "command": "make", + "args": ["install"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install Prod", + "type": "shell", + "command": "make", + "args": ["install-prod"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install Cloud", + "type": "shell", + "command": "make", + "args": ["install-cloud"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install ARM64", + "type": "shell", + "command": "make", + "args": ["install-arm64"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install ARMHF", + "type": "shell", + "command": "make", + "args": ["install-armhf"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Install AMD64", + "type": "shell", + "command": "make", + "args": ["install-amd64"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Start", + "type": "shell", + "command": "make", + "args": ["start"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Stop", + "type": "shell", + "command": "make", + "args": ["stop"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Restart", + "type": "shell", + "command": "make", + "args": ["restart"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Status", + "type": "shell", + "command": "make", + "args": ["status"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Logs", + "type": "shell", + "command": "make", + "args": ["log"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 1e646b3..594e181 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,14 @@ GOARCH := $(shell go env GOARCH) build-all: + @echo "\n๐Ÿ—๏ธ Building all architectures for ${RELEASE} mode" + @echo "๐ŸŸก This will build all supported architectures and release combinations. It can take a while...\n" + GOOS=linux GOARCH=amd64 make build GOOS=linux GOARCH=arm make build + @echo "\n๐ŸŸข All builds completed and available at ./bin/ \n" + build-prod: GOOS=linux GOARCH=arm RELEASE=prod make build @@ -30,32 +35,54 @@ build-amd64: build: - @echo "Building ${GOOS}-${GOARCH}" + @echo "\n๐Ÿ—๏ธ Building edgeboxctl (${RELEASE} release) on ${GOOS} (${GOARCH})" + @echo "๐Ÿ“ฆ Binary will be saved in ./${BUILD_DIR}/edgeboxctl-${GOOS}-${GOARCH}\n" + GOOS=${GOOS} GOARCH=${GOARCH} go build \ -trimpath -ldflags "-s -w -X ${PROJECT}/internal/diagnostics.Version=${RELEASE} \ -X ${PROJECT}/internal/diagnostics.Commit=${COMMIT} \ -X ${PROJECT}/internal/diagnostics.BuildDate=${BUILD_DATE}" \ -o bin/edgeboxctl-${GOOS}-${GOARCH} ${PROJECT}/cmd/edgeboxctl + @echo "\n๐ŸŸข Build task completed\n" + clean: + @echo "๐Ÿงน Cleaning build directory and go cache\n" + rm -rf ${BUILD_DIR} go clean + @echo "\n๐ŸŸข Clean task completed\n" + test: go test -tags=unit -timeout=600s -v ./... test-with-coverage: go test -tags=unit -timeout=600s -v ./... -coverprofile=coverage.out +run: + @echo "\n๐Ÿš€ Running edgeboxctl\n" + ./bin/edgeboxctl-${GOOS}-${GOARCH} + install: + @echo "๐Ÿ“ฆ Installing edgeboxctl service (${RELEASE}) for ${GOOS} (${GOARCH})\n" + + @echo "๐Ÿšง Stopping edgeboxctl service if it is running" sudo systemctl stop edgeboxctl || true + + @echo "\n๐Ÿ—‘๏ธ Removing old edgeboxctl binary and service" sudo rm -rf /usr/local/bin/edgeboxctl /usr/local/sbin/edgeboctl /lib/systemd/system/edgeboxctl.service + + @echo "\n๐Ÿšš Copying edgeboxctl binary to /usr/local/bin" sudo cp ./bin/edgeboxctl-${GOOS}-${GOARCH} /usr/local/bin/edgeboxctl sudo cp ./bin/edgeboxctl-${GOOS}-${GOARCH} /usr/local/sbin/edgeboxctl + + @echo "\n๐Ÿšš Copying edgeboxctl service to /lib/systemd/system" sudo cp ./edgeboxctl.service /lib/systemd/system/edgeboxctl.service sudo systemctl daemon-reload - @echo "Edgeboxctl installed successfully" - @echo "To start edgeboxctl run: systemctl start edgeboxctl" + + @echo "\n ๐Ÿš€ To start edgeboxctl run: make start" + @echo "๐ŸŸข Edgeboxctl installed successfully\n" install-prod: build-prod install install-cloud: build-cloud install @@ -64,13 +91,24 @@ install-armhf: build-armhf install install-amd64: build-amd64 install start: + @echo "\n ๐Ÿš€ Starting edgeboxctl service\n" systemctl start edgeboxctl + @echo "\n ๐ŸŸข Edgebox service started\n" stop: + @echo "\nโœ‹ Stopping edgeboxctl service\n" systemctl stop edgeboxctl + @echo "\n ๐ŸŸข Edgebox service stopped\n" + +restart: + @echo "\n๐Ÿ’ซ Restarting edgeboxctl service\n" + systemctl restart edgeboxctl + @echo "\n ๐ŸŸข Edgebox service restarted\n" status: + @echo "\nโ„น๏ธ edgeboxctl Service Info:\n" systemctl status edgeboxctl -log: start +log: + @echo "\n๐Ÿ“ฐ edgeboxctl service logs:\n" journalctl -fu edgeboxctl diff --git a/go.mod b/go.mod index 1645bc4..8ebbfd8 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-sql-driver/mysql v1.5.0 + github.com/go-yaml/yaml v2.1.0+incompatible // indirect github.com/joho/godotenv v1.3.0 github.com/mattn/go-sqlite3 v1.14.7 // indirect github.com/shirou/gopsutil v3.21.4+incompatible // indirect diff --git a/go.sum b/go.sum index c25bdef..80280ae 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= diff --git a/internal/system/system.go b/internal/system/system.go index 83fe96d..14687af 100644 --- a/internal/system/system.go +++ b/internal/system/system.go @@ -7,6 +7,7 @@ import ( "log" "os" "io" + "errors" "os/exec" "bufio" "path/filepath" @@ -17,6 +18,7 @@ import ( "github.com/joho/godotenv" "github.com/shirou/gopsutil/host" + "github.com/go-yaml/yaml" ) type cloudflaredTunnelJson struct { @@ -516,3 +518,83 @@ func ApplyUpdates() { utils.WriteOption("UPDATING_SYSTEM", "false") } +func FetchBrowserDevPasswordFromFile() (string, error) { + fmt.Println("Executing FetchBrowserDevPasswordFromFile") + + // Read the "password" entry on the yaml file + // Read the yaml file in system.GetPath(BrowserDevPasswordFileLocation) + yamlFile, err := ioutil.ReadFile(utils.GetPath(utils.BrowserDevPasswordFileLocation)) + if err != nil { + return "", err + } + + // Parse the yaml file and get the "password" entry + var yamlFileMap yaml.MapSlice + err = yaml.Unmarshal(yamlFile, &yamlFileMap) + if err != nil { + return "", err + } + + for _, item := range yamlFileMap { + key, value := item.Key, item.Value + if key == "password" { + if pwString, ok := value.(string); ok { + return pwString, nil + } else { + return "", errors.New("password value is not a string") + } + } + } + return "", errors.New("password key not found") +} + +func SetBrowserDevPasswordFile(password string) error { + // Get current password from file + currentPassword, err := FetchBrowserDevPasswordFromFile() + if err != nil { + fmt.Println("Error fetching current password from file.") + return err + } + + // Write the new password on the file using ReplaceTextInFile + err = ReplaceTextInFile(utils.GetPath(utils.BrowserDevPasswordFileLocation), currentPassword, password) + if err != nil { + fmt.Println("Error writing new password to file.") + return err + } + + return nil +} + +func ReplaceTextInFile(filePath string, oldText string, newText string) error { + // Open the file for reading + file, err := os.OpenFile(filePath, os.O_RDWR, 0644) + if err != nil { + return err + } + + // Read the file contents + data, err := ioutil.ReadAll(file) + if err != nil { + return err + } + + // Close the file + err = file.Close() + if err != nil { + return err + } + + // Replace the text in the file + newData := strings.Replace(string(data), oldText, newText, -1) + + // Write the new data back to the file + err = ioutil.WriteFile(filePath, []byte(newData), 0644) + if err != nil { + return err + } + + return nil +} + + diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 6474821..328d181 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -108,6 +108,10 @@ type taskStartShellArgs struct { Timeout int `json:"timeout"` } +type taskSetBrowserDevPasswordArgs struct { + Password string `json:"password"` +} + const STATUS_CREATED int = 0 const STATUS_EXECUTING int = 1 @@ -291,6 +295,11 @@ func ExecuteTask(task Task) Task { taskResult := taskStopShell() task.Result = sql.NullString{String: taskResult, Valid: true} + case "activate_browser_dev": + log.Println("Activating Browser Dev Environment") + taskResult := taskActivateBrowserDev() + task.Result = sql.NullString{String: taskResult, Valid: true} + case "install_edgeapp": log.Println("Installing EdgeApp...") @@ -449,6 +458,30 @@ func ExecuteTask(task Task) Task { task.Result = sql.NullString{String: taskResult, Valid: true} } + case "set_browserdev_password": + + log.Println("Setting BrowserDev Password...") + var args taskSetBrowserDevPasswordArgs + err := json.Unmarshal([]byte(task.Args.String), &args) + if err != nil { + log.Printf("Error reading arguments of set_browserdev_password task: %s", err) + } else { + taskResult := taskSetBrowserDevPassword(args) + task.Result = sql.NullString{String: taskResult, Valid: true} + } + + case "activate_browserdev": + + log.Println("Activating BrowserDev Environment...") + taskResult := taskActivateBrowserDev() + task.Result = sql.NullString{String: taskResult, Valid: true} + + case "deactivate_browserdev": + + log.Println("Deactivating BrowserDev Environment...") + taskResult := taskDeactivateBrowserDev() + task.Result = sql.NullString{String: taskResult, Valid: true} + } } @@ -495,6 +528,10 @@ func ExecuteSchedules(tick int) { if tick == 1 { + log.Println("Fetching Browser Dev Environment Information") + taskGetBrowserDevPassword() + taskGetBrowserDevStatus() + ip := taskGetSystemIP() log.Println("System IP is: " + ip) @@ -572,7 +609,9 @@ func ExecuteSchedules(tick int) { if tick%3600 == 0 { // Executing every 3600 ticks (1 hour) + taskGetBrowserDevStatus() taskCheckSystemUpdates() + } if tick%86400 == 0 { @@ -1099,6 +1138,84 @@ func taskStopShell() string { } +func taskGetBrowserDevStatus() string { + fmt.Println("Executing taskGetBrowserDevStatus") + + // Read status from systemctl status code-server@root + browserDevStatus := utils.Exec( + utils.GetPath(utils.WsPath), + "sh", + []string{"-c", "systemctl --quiet is-active code-server@root && echo 'active' || echo 'inactive'"}, + ) + if browserDevStatus == "active" { + fmt.Println("Browser Dev Environment is running") + utils.WriteOption("BROWSERDEV_STATUS", "running") + return "{\"status\": \"running\"}" + } else { + fmt.Println("Browser Dev Environment is not running") + utils.WriteOption("BROWSERDEV_STATUS", "not_running") + return "{\"status\": \"not_running\"}" + } +} + +func taskActivateBrowserDev() string { + fmt.Println("Executing taskActivateBrowserDev") + wsPath := utils.GetPath(utils.WsPath) + + // Start the service + utils.Exec(wsPath, "systemctl", []string{"start", "code-server@root"}) + // Write run file to /home/system/components/dev/.run + utils.Exec(wsPath, "touch", []string{utils.GetPath(utils.BrowserDevProxyPath) + ".run"}) + // Rebuild WS (necessary to start the proxy) + system.StartWs() + // Write control option for API + utils.WriteOption("BROWSERDEV_STATUS", "running") + return "{\"status\": \"ok\"}" +} + +func taskDeactivateBrowserDev() string { + fmt.Println("Executing taskDeactivateBrowserDev") + wsPath := utils.GetPath(utils.WsPath) + + // Remove the run file + os.Remove(utils.GetPath(utils.BrowserDevProxyPath) + ".run") + system.StartWs() + + utils.Exec(wsPath, "systemctl", []string{"stop", "code-server@root"}) + utils.WriteOption("BROWSERDEV_STATUS", "not_running") + + return "{\"status\": \"ok\"}" +} + +func taskGetBrowserDevPassword() string { + fmt.Println("Executing taskGetBrowserDevPassword") + password := utils.ReadOption("BROWSERDEV_PASSWORD") + if password == "" { + password, err := system.FetchBrowserDevPasswordFromFile() + if err == nil { + utils.WriteOption("BROWSERDEV_PASSWORD", password) + } else { + fmt.Println("Error fetching browser dev password from file: " + err.Error()) + } + } + return password +} + +func taskSetBrowserDevPassword(args taskSetBrowserDevPasswordArgs) string { + fmt.Println("Executing taskSetBrowserDevPassword") + wsPath := utils.GetPath(utils.WsPath) + + system.SetBrowserDevPasswordFile(args.Password) + utils.WriteOption("BROWSERDEV_PASSWORD", args.Password) + + // Check if BROWSERDEV_STATUS is "running", if so, restart the service + if utils.ReadOption("BROWSERDEV_STATUS") == "running" { + utils.Exec(wsPath, "systemctl", []string{"restart", "code-server@root"}) + } + + return "{\"status\": \"ok\"}" +} + func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string { fmt.Println("Executing taskInstallEdgeApp for " + args.ID) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 8b43984..71400d6 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -112,6 +112,8 @@ const EdgeAppsPath string = "edgeAppsPath" const EdgeAppsBackupPath string = "edgeAppsBackupPath" const WsPath string = "wsPath" const LoggerPath string = "loggerPath" +const BrowserDevPasswordFileLocation string = "browserDevPasswordFileLocation" +const BrowserDevProxyPath string = "browserDevProxyPath" // GetPath : Returns either the hardcoded path, or a overwritten value via .env file at project root. Register paths here for seamless working code between dev and prod environments ;) @@ -189,6 +191,20 @@ func GetPath(pathKey string) string { targetPath = "/home/system/components/backups/pw.txt" } + case BrowserDevPasswordFileLocation: + if env["BROWSERDEV_PASSWORD_FILE_LOCATION"] != "" { + targetPath = env["BROWSERDEV_PASSWORD_FILE_LOCATION"] + } else { + targetPath = "/root/.config/code-server/config.yaml" + } + + case BrowserDevProxyPath: + if env["BROWSERDEV_PROXY_PATH"] != "" { + targetPath = env["BROWSERDEV_PROXY_PATH"] + } else { + targetPath = "/home/system/components/dev/" + } + default: log.Printf("path_key %s nonexistant in GetPath().\n", pathKey)