Added Edgeapps Options Feature
							parent
							
								
									445bc41d0e
								
							
						
					
					
						commit
						ca7f4af7fe
					
				|  | @ -6,7 +6,7 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 	 | ||||||
| 	"github.com/joho/godotenv" | 	"github.com/joho/godotenv" | ||||||
| 
 | 
 | ||||||
| 	"github.com/edgebox-iot/edgeboxctl/internal/system" | 	"github.com/edgebox-iot/edgeboxctl/internal/system" | ||||||
|  | @ -23,6 +23,8 @@ type EdgeApp struct { | ||||||
| 	InternetAccessible bool             `json:"internet_accessible"` | 	InternetAccessible bool             `json:"internet_accessible"` | ||||||
| 	NetworkURL         string           `json:"network_url"` | 	NetworkURL         string           `json:"network_url"` | ||||||
| 	InternetURL        string           `json:"internet_url"` | 	InternetURL        string           `json:"internet_url"` | ||||||
|  | 	Options			   []EdgeAppOption  `json:"options"` | ||||||
|  | 	NeedsConfig		   bool             `json:"needs_config"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MaybeEdgeApp : Boolean flag for validation of edgeapp existance
 | // MaybeEdgeApp : Boolean flag for validation of edgeapp existance
 | ||||||
|  | @ -43,8 +45,20 @@ type EdgeAppService struct { | ||||||
| 	IsRunning bool   `json:"is_running"` | 	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 configFilename = "/edgebox-compose.yml" | ||||||
| const envFilename = "/edgebox.env" | const envFilename = "/edgebox.env" | ||||||
|  | const optionsTemplateFilename = "/edgeapp.template.env" | ||||||
|  | const optionsEnvFilename = "/edgeapp.env" | ||||||
| const runnableFilename = "/.run" | const runnableFilename = "/.run" | ||||||
| const myEdgeAppServiceEnvFilename = "/myedgeapp.env" | const myEdgeAppServiceEnvFilename = "/myedgeapp.env" | ||||||
| const defaultContainerOperationSleepTime time.Duration = time.Second * 10 | const defaultContainerOperationSleepTime time.Duration = time.Second * 10 | ||||||
|  | @ -63,6 +77,7 @@ func GetEdgeApp(ID string) MaybeEdgeApp { | ||||||
| 
 | 
 | ||||||
| 		edgeAppName := ID | 		edgeAppName := ID | ||||||
| 		edgeAppDescription := "" | 		edgeAppDescription := "" | ||||||
|  | 		edgeAppOptions := []EdgeAppOption{} | ||||||
| 
 | 
 | ||||||
| 		edgeAppEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + envFilename) | 		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 | 		edgeAppInternetAccessible := false | ||||||
| 		edgeAppInternetURL := "" | 		edgeAppInternetURL := "" | ||||||
| 
 | 
 | ||||||
|  | @ -100,6 +212,8 @@ func GetEdgeApp(ID string) MaybeEdgeApp { | ||||||
| 				InternetAccessible: edgeAppInternetAccessible, | 				InternetAccessible: edgeAppInternetAccessible, | ||||||
| 				NetworkURL:         ID + "." + system.GetHostname() + ".local", | 				NetworkURL:         ID + "." + system.GetHostname() + ".local", | ||||||
| 				InternetURL:        edgeAppInternetURL, | 				InternetURL:        edgeAppInternetURL, | ||||||
|  | 				Options: 		    edgeAppOptions, | ||||||
|  | 				NeedsConfig:        needsConfig, | ||||||
| 			}, | 			}, | ||||||
| 			Valid: true, | 			Valid: true, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -100,6 +100,29 @@ func SetupCloudOptions() { | ||||||
| 	utils.Exec("/", "rm", []string{cloudEnvFileLocationPath}) | 	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
 | // StartWs: Starts the webserver service for Edgeapps
 | ||||||
| func StartWs() { | func StartWs() { | ||||||
| 	wsPath := utils.GetPath(utils.WsPath) | 	wsPath := utils.GetPath(utils.WsPath) | ||||||
|  |  | ||||||
|  | @ -33,6 +33,12 @@ type Task struct { | ||||||
| 	Updated string         `json:"updated"` | 	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 { | type taskSetupTunnelArgs struct { | ||||||
| 	DomainName string `json:"domain_name"` | 	DomainName string `json:"domain_name"` | ||||||
| } | } | ||||||
|  | @ -53,6 +59,12 @@ type taskStopEdgeAppArgs struct { | ||||||
| 	ID string `json:"id"` | 	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 { | type taskEnableOnlineArgs struct { | ||||||
| 	ID          string `json:"id"` | 	ID          string `json:"id"` | ||||||
| 	InternetURL string `json:"internet_url"` | 	InternetURL string `json:"internet_url"` | ||||||
|  | @ -262,6 +274,19 @@ func ExecuteTask(task Task) Task { | ||||||
| 				task.Result = sql.NullString{String: taskResult, Valid: true} | 				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": | 		case "enable_online": | ||||||
| 
 | 
 | ||||||
| 			log.Println("Enabling online access to EdgeApp...") | 			log.Println("Enabling online access to EdgeApp...") | ||||||
|  | @ -376,7 +401,7 @@ func ExecuteSchedules(tick int) { | ||||||
| 		log.Println(taskGetStorageDevices()) | 		log.Println(taskGetStorageDevices()) | ||||||
| 		taskStartWs() | 		taskStartWs() | ||||||
| 		log.Println(taskGetEdgeApps()) | 		log.Println(taskGetEdgeApps()) | ||||||
| 
 | 		taskUpdateSystemLoggerServices() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if tick%5 == 0 { | 	if tick%5 == 0 { | ||||||
|  | @ -388,6 +413,7 @@ func ExecuteSchedules(tick int) { | ||||||
| 	if tick%30 == 0 { | 	if tick%30 == 0 { | ||||||
| 		// Executing every 30 ticks
 | 		// Executing every 30 ticks
 | ||||||
| 		log.Println(taskGetEdgeApps()) | 		log.Println(taskGetEdgeApps()) | ||||||
|  | 		taskUpdateSystemLoggerServices() | ||||||
| 		// RESET SOME VARIABLES HERE IF NEEDED, SINCE SYSTEM IS UNBLOCKED
 | 		// RESET SOME VARIABLES HERE IF NEEDED, SINCE SYSTEM IS UNBLOCKED
 | ||||||
| 		utils.WriteOption("BACKUP_IS_WORKING", "0") | 		utils.WriteOption("BACKUP_IS_WORKING", "0") | ||||||
| 
 | 
 | ||||||
|  | @ -909,6 +935,57 @@ func taskStopEdgeApp(args taskStopEdgeAppArgs) string { | ||||||
| 	return string(resultJSON) | 	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/<app_id>/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 { | func taskEnableOnline(args taskEnableOnlineArgs) string { | ||||||
| 	fmt.Println("Executing taskEnableOnline for " + args.ID) | 	fmt.Println("Executing taskEnableOnline for " + args.ID) | ||||||
| 
 | 
 | ||||||
|  | @ -961,6 +1038,35 @@ func taskSetReleaseVersion() string { | ||||||
| 	return diagnostics.Version | 	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 { | func taskGetEdgeApps() string { | ||||||
| 	fmt.Println("Executing taskGetEdgeApps") | 	fmt.Println("Executing taskGetEdgeApps") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -111,6 +111,8 @@ const ApiPath string = "apiPath" | ||||||
| const EdgeAppsPath string = "edgeAppsPath" | const EdgeAppsPath string = "edgeAppsPath" | ||||||
| const EdgeAppsBackupPath string = "edgeAppsBackupPath" | const EdgeAppsBackupPath string = "edgeAppsBackupPath" | ||||||
| const WsPath string = "wsPath" | 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 ;)
 | // 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 { | func GetPath(pathKey string) string { | ||||||
|  | @ -172,6 +174,13 @@ func GetPath(pathKey string) string { | ||||||
| 			targetPath = "/home/system/components/ws/" | 			targetPath = "/home/system/components/ws/" | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 	case LoggerPath: | ||||||
|  | 		if env["LOGGER_PATH"] != "" { | ||||||
|  | 			targetPath = env["LOGGER_PATH"] | ||||||
|  | 		} else { | ||||||
|  | 			targetPath = "/home/system/components/logger/" | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 	case BackupPasswordFileLocation: | 	case BackupPasswordFileLocation: | ||||||
| 
 | 
 | ||||||
| 		if env["BACKUP_PASSWORD_FILE_LOCATION"] != "" { | 		if env["BACKUP_PASSWORD_FILE_LOCATION"] != "" { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue