diff --git a/internal/edgeapps/edgeapps.go b/internal/edgeapps/edgeapps.go index e0b66d5..67bce80 100644 --- a/internal/edgeapps/edgeapps.go +++ b/internal/edgeapps/edgeapps.go @@ -6,7 +6,7 @@ import ( "os" "strings" "time" - + "github.com/joho/godotenv" "github.com/edgebox-iot/edgeboxctl/internal/system" @@ -23,6 +23,8 @@ type EdgeApp struct { InternetAccessible bool `json:"internet_accessible"` NetworkURL string `json:"network_url"` InternetURL string `json:"internet_url"` + Options []EdgeAppOption `json:"options"` + NeedsConfig bool `json:"needs_config"` } // MaybeEdgeApp : Boolean flag for validation of edgeapp existance @@ -43,8 +45,20 @@ type EdgeAppService struct { IsRunning bool `json:"is_running"` } +type EdgeAppOption struct { + Key string `json:"key"` + Value string `json:"value"` + DefaultValue string `json:"default_value"` + Format string `json:"format"` + Description string `json:"description"` + IsSecret bool `json:"is_secret"` + IsInstallLocked bool `json:"is_install_locked"` +} + const configFilename = "/edgebox-compose.yml" const envFilename = "/edgebox.env" +const optionsTemplateFilename = "/edgeapp.template.env" +const optionsEnvFilename = "/edgeapp.env" const runnableFilename = "/.run" const myEdgeAppServiceEnvFilename = "/myedgeapp.env" const defaultContainerOperationSleepTime time.Duration = time.Second * 10 @@ -63,6 +77,7 @@ func GetEdgeApp(ID string) MaybeEdgeApp { edgeAppName := ID edgeAppDescription := "" + edgeAppOptions := []EdgeAppOption{} edgeAppEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + envFilename) @@ -77,6 +92,103 @@ func GetEdgeApp(ID string) MaybeEdgeApp { } } + needsConfig := false + hasFilledOptions := false + edgeAppOptionsTemplate, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + optionsTemplateFilename) + if err != nil { + log.Println("Error loading options template file for edgeapp " + edgeAppName) + } else { + // Try to read the edgeAppOptionsEnv file + edgeAppOptionsEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + optionsEnvFilename) + if err != nil { + log.Println("Error loading options env file for edgeapp " + edgeAppName) + } else { + hasFilledOptions = true + } + + for key, value := range edgeAppOptionsTemplate { + + optionFilledValue := "" + if hasFilledOptions { + // Check if key exists in edgeAppOptionsEnv + optionFilledValue = edgeAppOptionsEnv[key] + } + + format := "" + defaultValue := "" + description := "" + installLocked := false + + // Parse value to separate by | and get the format, installLocked, description and default value + // Format is the first element + // InstallLocked is the second element + // Description is the third element + // Default value is the fourth element + + valueSlices := strings.Split(value, "|") + if len(valueSlices) > 0 { + format = valueSlices[0] + } + if len(valueSlices) > 1 { + installLocked = valueSlices[1] == "true" + } + if len(valueSlices) > 2 { + description = valueSlices[2] + } + if len(valueSlices) > 3 { + defaultValue = valueSlices[3] + } + + // // If value contains ">|", then get everything that is to the right of it as the description + // // and get everything between "<>" as the format + // if strings.Contains(value, ">|") { + // description = strings.Split(value, ">|")[1] + // // Check if description has default value. That would be everything that is to the right of the last "|" + // if strings.Contains(description, "|") { + // defaultValue = strings.Split(description, "|")[1] + // description = strings.Split(description, "|")[0] + // } + + // value = strings.Split(value, ">|")[0] + // // Remove the initial < from value + // value = strings.TrimPrefix(value, "<") + // } else { + // // Trim initial < and final > from value + // value = strings.TrimPrefix(value, "<") + // value = strings.TrimSuffix(value, ">") + // } + + isSecret := false + + // Check if the lowercased key string contains the letters "pass", "secret", "key" + lowercaseKey := strings.ToLower(key) + // check if lowercaseInput contains "pass", "key", or "secret", or "token" + if strings.Contains(lowercaseKey, "pass") || + strings.Contains(lowercaseKey, "key") || + strings.Contains(lowercaseKey, "secret") || + strings.Contains(lowercaseKey, "token") { + isSecret = true + } + + currentOption := EdgeAppOption{ + Key: key, + Value: optionFilledValue, + DefaultValue: defaultValue, + Description: description, + Format: format, + IsSecret: isSecret, + IsInstallLocked: installLocked, + } + edgeAppOptions = append(edgeAppOptions, currentOption) + + if optionFilledValue == "" { + needsConfig = true + } + + } + } + + edgeAppInternetAccessible := false edgeAppInternetURL := "" @@ -100,6 +212,8 @@ func GetEdgeApp(ID string) MaybeEdgeApp { InternetAccessible: edgeAppInternetAccessible, NetworkURL: ID + "." + system.GetHostname() + ".local", InternetURL: edgeAppInternetURL, + Options: edgeAppOptions, + NeedsConfig: needsConfig, }, Valid: true, } diff --git a/internal/system/system.go b/internal/system/system.go index 82cb34d..42fe285 100644 --- a/internal/system/system.go +++ b/internal/system/system.go @@ -100,6 +100,29 @@ func SetupCloudOptions() { utils.Exec("/", "rm", []string{cloudEnvFileLocationPath}) } +func StartSystemLogger() { + fmt.Println("Starting system logger") + loggerPath := utils.GetPath(utils.LoggerPath) + utils.Exec(loggerPath, "make", []string{"start"}) +} + +// UpdateSystemLoggerServices: Updates the services.txt file with the services that are currently running +func UpdateSystemLoggerServices(services []string) { + fmt.Println("Updating system logger services:") + fmt.Println(services) + loggerPath := utils.GetPath(utils.LoggerPath) + + utils.Exec(loggerPath, "bash", []string{"-c", "rm services.txt && touch services.txt"}) + + for _, service := range services { + fmt.Println("Adding " + service + " to services.txt") + utils.Exec(loggerPath, "bash", []string{"-c", "echo " + service + " >> services.txt"}) + } + + // Add empty line at the end of file (best practice) + utils.Exec(loggerPath, "bash", []string{"-c", "echo '' >> services.txt"}) +} + // StartWs: Starts the webserver service for Edgeapps func StartWs() { wsPath := utils.GetPath(utils.WsPath) diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 051cdfb..e98720f 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -33,6 +33,12 @@ type Task struct { Updated string `json:"updated"` } +// TaskOption: Struct for Task Options (kv pair) +type TaskOption struct { + Key string `json:"key"` + Value string `json:"value"` +} + type taskSetupTunnelArgs struct { DomainName string `json:"domain_name"` } @@ -53,6 +59,12 @@ type taskStopEdgeAppArgs struct { ID string `json:"id"` } +type taskSetEdgeAppOptionsArgs struct { + ID string `json:"id"` + // Options should be an array of "key":"value" pairs + Options []TaskOption `json:"options"` +} + type taskEnableOnlineArgs struct { ID string `json:"id"` InternetURL string `json:"internet_url"` @@ -262,6 +274,19 @@ func ExecuteTask(task Task) Task { task.Result = sql.NullString{String: taskResult, Valid: true} } + case "set_edgeapp_options": + + log.Println("Setting EdgeApp Options...") + var args taskSetEdgeAppOptionsArgs + // {"id":"podgrab","options":{"PODGRAB_PASSWORD":"fumarmata"}} + err := json.Unmarshal([]byte(task.Args.String), &args) + if err != nil { + log.Printf("Error reading arguments of set_edgeapp_options task: %s", err) + } else { + taskResult := taskSetEdgeAppOptions(args) + task.Result = sql.NullString{String: taskResult, Valid: true} + } + case "enable_online": log.Println("Enabling online access to EdgeApp...") @@ -376,7 +401,7 @@ func ExecuteSchedules(tick int) { log.Println(taskGetStorageDevices()) taskStartWs() log.Println(taskGetEdgeApps()) - + taskUpdateSystemLoggerServices() } if tick%5 == 0 { @@ -388,6 +413,7 @@ func ExecuteSchedules(tick int) { if tick%30 == 0 { // Executing every 30 ticks log.Println(taskGetEdgeApps()) + taskUpdateSystemLoggerServices() // RESET SOME VARIABLES HERE IF NEEDED, SINCE SYSTEM IS UNBLOCKED utils.WriteOption("BACKUP_IS_WORKING", "0") @@ -909,6 +935,57 @@ func taskStopEdgeApp(args taskStopEdgeAppArgs) string { return string(resultJSON) } +func taskSetEdgeAppOptions(args taskSetEdgeAppOptionsArgs) string { + // Id is the edgeapp id + appID := args.ID + + + // Open the file to write the options, + // it is an env file in /home/system/components/apps//edgeapp.env + + // Get the path to the edgeapp.env file + edgeappEnvPath := "/home/system/components/apps/" + appID + "/edgeapp.env" + + // If the file does not exist, create it + if _, err := os.Stat(edgeappEnvPath); os.IsNotExist(err) { + // Create the file + _, err := os.Create(edgeappEnvPath) + if err != nil { + log.Printf("Error creating edgeapp.env file: %s", err) + } + } + + // It is an env file, so we can use go-dotenv to write the options + // Open the file + edgeappEnvFile, err := os.OpenFile(edgeappEnvPath, os.O_WRONLY, 0600) + if err != nil { + log.Printf("Error opening edgeapp.env file: %s", err) + } + + // Write the options to the file + for _, value := range args.Options { + // Write the option to the file + _, err := edgeappEnvFile.WriteString(value.Key + "=" + value.Value + "\n") + if err != nil { + log.Printf("Error writing option to edgeapp.env file: %s", err) + } + } + + // Close the file + err = edgeappEnvFile.Close() + if err != nil { + log.Printf("Error closing edgeapp.env file: %s", err) + } + + result := edgeapps.GetEdgeAppStatus(appID) + resultJSON, _ := json.Marshal(result) + + system.StartWs() + taskGetEdgeApps() // This task will imediatelly update the entry in the api database. + + return string(resultJSON) +} + func taskEnableOnline(args taskEnableOnlineArgs) string { fmt.Println("Executing taskEnableOnline for " + args.ID) @@ -961,6 +1038,35 @@ func taskSetReleaseVersion() string { return diagnostics.Version } +func taskUpdateSystemLoggerServices() string { + fmt.Println("Executing taskUpdateSystemLoggerServices") + // The input is an array of strings + // Each string is a service name to be logged + var input []string + + // Get the services + edgeAppsList := utils.ReadOption("EDGEAPPS_LIST") + var edgeApps []edgeapps.EdgeApp + err := json.Unmarshal([]byte(edgeAppsList), &edgeApps) + if err != nil { + log.Fatalf("failed to unmarshal EDGEAPPS_LIST: %v", err) + } + + for _, edgeApp := range edgeApps { + for _, service := range edgeApp.Services { + input = append(input, service.ID) + } + } + + input = append(input, "edgeboxctl") + input = append(input, "tunnel") + + // Run the system logger + system.UpdateSystemLoggerServices(input) + + return "{\"status\": \"ok\"}" +} + func taskGetEdgeApps() string { fmt.Println("Executing taskGetEdgeApps") diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 16f1d87..6d336f9 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -111,6 +111,8 @@ const ApiPath string = "apiPath" const EdgeAppsPath string = "edgeAppsPath" const EdgeAppsBackupPath string = "edgeAppsBackupPath" const WsPath string = "wsPath" +const LoggerPath string = "loggerPath" + // 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 ;) func GetPath(pathKey string) string { @@ -172,6 +174,13 @@ func GetPath(pathKey string) string { targetPath = "/home/system/components/ws/" } + case LoggerPath: + if env["LOGGER_PATH"] != "" { + targetPath = env["LOGGER_PATH"] + } else { + targetPath = "/home/system/components/logger/" + } + case BackupPasswordFileLocation: if env["BACKUP_PASSWORD_FILE_LOCATION"] != "" {