diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d8b2f24..debdacc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,7 +22,7 @@ jobs: run: make build - name: Test run: make test-with-coverage - - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: coverage.out + # - uses: codecov/codecov-action@v1 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # files: coverage.out diff --git a/Makefile b/Makefile index 53e3cb4..bcabf7f 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,9 @@ build-all: build-prod: GOOS=linux GOARCH=arm RELEASE=prod make build +build-cloud: + GOOS=linux GOARCH=amd64 RELEASE=cloud make build + build: @echo "Building ${GOOS}-${GOARCH}" GOOS=${GOOS} GOARCH=${GOARCH} go build \ diff --git a/README.md b/README.md index b264102..f510ae1 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ sudo apt-get install docker docker-compose Check the following links for more info on [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/). Aditionally, edgeboxctl needs the following bash commands available wherever it runs: +* `arm-linux-gnueabi-gcc` (`sudo apt-get install gcc-arm*`) * `sh` * `rm` * `systemctl` diff --git a/internal/diagnostics/diagnostics.go b/internal/diagnostics/diagnostics.go index bd66329..ff032f8 100644 --- a/internal/diagnostics/diagnostics.go +++ b/internal/diagnostics/diagnostics.go @@ -1,7 +1,18 @@ package diagnostics +type ReleaseVersion string + var Version string - var Commit string - var BuildDate string + +const ( + DEV_VERSION ReleaseVersion = "dev" + PROD_VERSION ReleaseVersion = "prod" + CLOUD_VERSION ReleaseVersion = "cloud" + OTHER_VERSION ReleaseVersion = "other" +) + +func GetReleaseVersion() ReleaseVersion { + return ReleaseVersion(Version) +} diff --git a/internal/edgeapps/edgeapps.go b/internal/edgeapps/edgeapps.go index b5d99a7..79ae78e 100644 --- a/internal/edgeapps/edgeapps.go +++ b/internal/edgeapps/edgeapps.go @@ -335,6 +335,37 @@ func DisableOnline(ID string) MaybeEdgeApp { } +func EnablePublicDashboard(InternetURL string) bool { + + envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename + env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL) + _ = godotenv.Write(env, envFilePath) + + buildFrameworkContainers() + + return true + +} + +func DisablePublicDashboard() bool { + envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename + if !IsPublicDashboard() { + log.Println("myedge.app environment file for the dashboard / api not found. No need to delete.") + return false + } + + cmdArgs := []string{envFilePath} + utils.Exec(utils.GetPath("apiPath"), "rm", cmdArgs) + buildFrameworkContainers() + return true +} + +func IsPublicDashboard() bool { + envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename + _, err := godotenv.Read(envFilePath) + return err == nil +} + func buildFrameworkContainers() { cmdArgs := []string{utils.GetPath("wsPath") + "ws", "--build"} diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 58628db..8ff3f25 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -6,24 +6,25 @@ import ( "path/filepath" "strings" + "github.com/edgebox-iot/edgeboxctl/internal/diagnostics" "github.com/edgebox-iot/edgeboxctl/internal/utils" "github.com/shirou/gopsutil/disk" ) // Device : Struct representing a storage device in the system type Device struct { - ID string `json:"id"` - Name string `json:"name"` - Size string `json:"size"` - InUse bool `json:"in_use"` - MainDevice bool `json:"main_device"` - MAJ string `json:"maj"` - MIN string `json:"min"` - RM string `json:"rm"` - RO string `json:"ro"` - Partitions []Partition `json:"partitions"` - Status DeviceStatus `json:"status"` - UsageStat UsageStat `json:"usage_stat"` + ID DeviceIdentifier `json:"id"` + Name string `json:"name"` + Size string `json:"size"` + InUse bool `json:"in_use"` + MainDevice bool `json:"main_device"` + MAJ string `json:"maj"` + MIN string `json:"min"` + RM string `json:"rm"` + RO string `json:"ro"` + Partitions []Partition `json:"partitions"` + Status DeviceStatus `json:"status"` + UsageStat UsageStat `json:"usage_stat"` } // DeviceStatus : Struct representing possible storage device statuses (code + description) @@ -66,10 +67,27 @@ type Partition struct { UsageStat UsageStat `json:"usage_stat"` } -const mainDiskID = "mmcblk0" +type DeviceIdentifier string + +const ( + DISK_TYPE_SDA DeviceIdentifier = "sda" + DISK_TYPE_MCBLK DeviceIdentifier = "mmcblk0" + DISK_TYPE_VDA DeviceIdentifier = "vda" +) + +func GetDeviceIdentifier(release_version diagnostics.ReleaseVersion) DeviceIdentifier { + switch release_version { + case diagnostics.CLOUD_VERSION: + return DISK_TYPE_VDA + case diagnostics.PROD_VERSION: + return DISK_TYPE_MCBLK + } + + return DISK_TYPE_SDA +} // GetDevices : Returns a list of all available sotrage devices in structs filled with information -func GetDevices() []Device { +func GetDevices(release_version diagnostics.ReleaseVersion) []Device { var devices []Device @@ -82,6 +100,8 @@ func GetDevices() []Device { firstDevice := true currentDeviceInUseFlag := false + mainDiskID := GetDeviceIdentifier(release_version) + for scanner.Scan() { // 1 Device is represented here. Extract words in order for filling a Device struct // Example deviceRawInfo: "mmcblk0 179:0 0 29.7G 0 disk" @@ -122,7 +142,7 @@ func GetDevices() []Device { mainDevice := false device := Device{ - ID: deviceRawInfo[0], + ID: DeviceIdentifier(deviceRawInfo[0]), Name: deviceRawInfo[0], Size: deviceRawInfo[3], MainDevice: mainDevice, diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go index a0893bb..05b5dee 100644 --- a/internal/storage/storage_test.go +++ b/internal/storage/storage_test.go @@ -7,25 +7,37 @@ import ( ) func TestGetDevices(t *testing.T) { - result := GetDevices() - if len(result) == 0 { + t.Log("Testing with release version dev") + assertGetDevices(GetDevices("dev"), t) + t.Log("Testing with release version prod") + assertGetDevices(GetDevices("prod"), t) + t.Log("Testing with release version cloud") + assertGetDevices(GetDevices("cloud"), t) + +} + +func assertGetDevices(devices []Device, t *testing.T) { + + if len(devices) == 0 { t.Log("Expecting at least 1 block device, 0 elements found in slice") t.Fail() } foundDevice := false - t.Log("Looking for a mmcblk0 or sda device") - for _, device := range result { - if device.ID == "mmcblk0" || device.ID == "sda" { + t.Log("Looking for a mmcblk0, sda or dva device") + for _, device := range devices { + + if device.ID == "mmcblk0" || device.ID == "sda" || device.ID == "vda" { t.Log("Found target device", device.ID) foundDevice = true } + } if !foundDevice { - t.Log("Expected to find device mmcblk0 but did not. Devices:", result) + t.Log("Expected to find device mmcblk0, sda or dva but did not. Devices:", devices) t.Fail() } } diff --git a/internal/system/system.go b/internal/system/system.go index f8c2610..079ab47 100644 --- a/internal/system/system.go +++ b/internal/system/system.go @@ -3,16 +3,23 @@ package system import ( "fmt" "strconv" + "strings" + + "github.com/edgebox-iot/edgeboxctl/internal/utils" "github.com/shirou/gopsutil/host" + "github.com/joho/godotenv" + ) +// GetUptimeInSeconds: Returns a value (as string) of the total system uptime func GetUptimeInSeconds() string { uptime, _ := host.Uptime() return strconv.FormatUint(uptime, 10) } +// GetUptimeFormatted: Returns a humanized version that can be useful for logging func GetUptimeFormatted() string { uptime, _ := host.Uptime() @@ -21,3 +28,61 @@ func GetUptimeFormatted() string { minutes := ((uptime - (days * 60 * 60 * 24)) - (hours * 60 * 60)) / 60 return fmt.Sprintf("%d days, %d hours, %d minutes", days, hours, minutes) } + +// GetIP: Returns the ip address of the instance +func GetIP() string { + ip := "" + + // Trying to find a valid IP (For direct connection, not tunneled) + ethResult := utils.ExecAndGetLines("/", "ip", []string{"-o", "-4", "addr", "list", "eth0"}) + for ethResult.Scan() { + adapterRawInfo := strings.Fields(ethResult.Text()) + if ip == "" { + ip = strings.Split(adapterRawInfo[3], "/")[0] + } + } + + // If no IP was found yet, try wlan0 + if ip == "" { + wlanResult := utils.ExecAndGetLines("/", "ip", []string{"-o", "-4", "addr", "list", "wlan0"}) + for wlanResult.Scan() { + adapterRawInfo := strings.Fields(wlanResult.Text()) + if ip == "" { + ip = strings.Split(adapterRawInfo[3], "/")[0] + } + } + } + + return ip +} + +func GetHostname() string { + return utils.Exec("/", "hostname", []string{}) +} + +// SetupCloudOptions: Reads the designated env file looking for options to write into the options table. Meant to be used on initial setup. Deletes source env file after operation. +func SetupCloudOptions() { + + var cloudEnv map[string]string + cloudEnv, err := godotenv.Read(utils.GetPath("cloudEnvFileLocation")) + + if err != nil { + fmt.Println("Error loading .env file for cloud version setup") + } + + if cloudEnv["NAME"] != "" { + utils.WriteOption("NAME", cloudEnv["NAME"]) + } + + if cloudEnv["EMAIL"] != "" { + utils.WriteOption("EMAIL", cloudEnv["EMAIL"]) + } + + if cloudEnv["EDGEBOXIO_API_TOKEN"] != "" { + utils.WriteOption("EDGEBOXIO_API_TOKEN", cloudEnv["EDGEBOXIO_API_TOKEN"]) + } + + // In the end of this operation takes place, remove the env file as to not overwrite any options once they are set. + utils.Exec("/", "rm", []string{utils.GetPath("cloudEnvFileLocation")}) + +} diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index e6d5ea2..ce465b8 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -60,6 +60,10 @@ type taskDisableOnlineArgs struct { ID string `json:"id"` } +type taskEnablePublicDashboardArgs struct { + InternetURL string `json:"internet_url"` +} + const STATUS_CREATED int = 0 const STATUS_EXECUTING int = 1 const STATUS_FINISHED int = 2 @@ -122,10 +126,11 @@ func ExecuteTask(task Task) Task { log.Fatal(err.Error()) } - if diagnostics.Version == "dev" { + if diagnostics.GetReleaseVersion() == diagnostics.DEV_VERSION { log.Printf("Dev environemnt. Not executing tasks.") } else { log.Println("Task: " + task.Task) + log.Println("Args: " + task.Args.String) switch task.Task { case "setup_tunnel": @@ -211,6 +216,24 @@ func ExecuteTask(task Task) Task { task.Result = sql.NullString{String: taskResult, Valid: true} } + case "enable_public_dashboard": + + log.Println("Enabling online access to Dashboard...") + var args taskEnablePublicDashboardArgs + err := json.Unmarshal([]byte(task.Args.String), &args) + if err != nil { + log.Printf("Error reading arguments of enable_public_dashboard task: %s", err) + } else { + taskResult := taskEnablePublicDashboard(args) + task.Result = sql.NullString{String: taskResult, Valid: true} + } + + case "disable_public_dashboard": + + log.Println("Disabling online access to Dashboard...") + taskResult := taskDisablePublicDashboard() + task.Result = sql.NullString{String: taskResult, Valid: true} + } } @@ -230,7 +253,6 @@ func ExecuteTask(task Task) Task { if err != nil { log.Fatal(err.Error()) } - } else { _, err = statement.Exec(STATUS_ERROR, "Error", formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with Error info if err != nil { @@ -255,6 +277,26 @@ func ExecuteSchedules(tick int) { if tick == 1 { + ip := taskGetSystemIP() + log.Println("System IP is: " + ip) + + release := taskSetReleaseVersion() + log.Println("Setting api option flag for Edgeboxctl (" + release + " version)") + + hostname := taskGetHostname() + log.Println("Hostname is " + hostname) + + // if diagnostics.Version == "cloud" && !edgeapps.IsPublicDashboard() { + // taskEnablePublicDashboard(taskEnablePublicDashboardArgs{ + // InternetURL: hostname + ".myedge.app", + // }) + // } + + if diagnostics.GetReleaseVersion() == diagnostics.CLOUD_VERSION { + log.Println("Setting up cloud version options (name, email, api token)") + taskSetupCloudOptions() + } + // Executing on startup (first tick). Schedules run before tasks in the SystemIterator uptime := taskGetSystemUptime() log.Println("Uptime is " + uptime + " seconds (" + system.GetUptimeFormatted() + ")") @@ -266,18 +308,18 @@ func ExecuteSchedules(tick int) { if tick%5 == 0 { // Executing every 5 ticks + taskGetSystemUptime() log.Println(taskGetStorageDevices()) } if tick%30 == 0 { // Executing every 30 ticks log.Println(taskGetEdgeApps()) - } if tick%60 == 0 { - // Every 60 ticks... - + ip := taskGetSystemIP() + log.Println("System IP is: " + ip) } // Just add a schedule here if you need a custom one (every "tick hour", every "tick day", etc...) @@ -285,7 +327,6 @@ func ExecuteSchedules(tick int) { } func taskSetupTunnel(args taskSetupTunnelArgs) string { - fmt.Println("Executing taskSetupTunnel") cmdargs := []string{"gen", "--name", args.NodeName, "--token", args.BootnodeToken, args.BootnodeAddress + ":8655", "--prefix", args.AssignedAddress} @@ -299,181 +340,145 @@ func taskSetupTunnel(args taskSetupTunnelArgs) string { output := "OK" // Better check / logging of command execution result. return output - } func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string { - fmt.Println("Executing taskInstallEdgeApp for " + args.ID) result := edgeapps.SetEdgeAppInstalled(args.ID) - resultJSON, _ := json.Marshal(result) taskGetEdgeApps() - return string(resultJSON) - } func taskRemoveEdgeApp(args taskRemoveEdgeAppArgs) string { - fmt.Println("Executing taskRemoveEdgeApp for " + args.ID) // Making sure the application is stopped before setting it as removed. edgeapps.StopEdgeApp(args.ID) result := edgeapps.SetEdgeAppNotInstalled(args.ID) - resultJSON, _ := json.Marshal(result) taskGetEdgeApps() - return string(resultJSON) - } func taskStartEdgeApp(args taskStartEdgeAppArgs) string { - fmt.Println("Executing taskStartEdgeApp for " + args.ID) result := edgeapps.RunEdgeApp(args.ID) - resultJSON, _ := json.Marshal(result) taskGetEdgeApps() // This task will imediatelly update the entry in the api database. - return string(resultJSON) - } func taskStopEdgeApp(args taskStopEdgeAppArgs) string { - fmt.Println("Executing taskStopEdgeApp for " + args.ID) result := edgeapps.StopEdgeApp(args.ID) - resultJSON, _ := json.Marshal(result) 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) result := edgeapps.EnableOnline(args.ID, args.InternetURL) - resultJSON, _ := json.Marshal(result) taskGetEdgeApps() - return string(resultJSON) - } func taskDisableOnline(args taskDisableOnlineArgs) string { - fmt.Println("Executing taskDisableOnline for " + args.ID) result := edgeapps.DisableOnline(args.ID) - resultJSON, _ := json.Marshal(result) taskGetEdgeApps() - return string(resultJSON) +} +func taskEnablePublicDashboard(args taskEnablePublicDashboardArgs) string { + fmt.Println("Enabling taskEnablePublicDashboard") + result := edgeapps.EnablePublicDashboard(args.InternetURL) + if result { + + utils.WriteOption("PUBLIC_DASHBOARD", args.InternetURL) + return "{result: true}" + + } + + return "{result: false}" +} + +func taskDisablePublicDashboard() string { + fmt.Println("Executing taskDisablePublicDashboard") + result := edgeapps.DisablePublicDashboard() + utils.WriteOption("PUBLIC_DASHBOARD", "") + if result { + return "{result: true}" + } + return "{result: false}" +} + +func taskSetReleaseVersion() string { + + fmt.Println("Executing taskSetReleaseVersion") + + utils.WriteOption("RELEASE_VERSION", diagnostics.Version) + + return diagnostics.Version } func taskGetEdgeApps() string { - fmt.Println("Executing taskGetEdgeApps") edgeApps := edgeapps.GetEdgeApps() edgeAppsJSON, _ := json.Marshal(edgeApps) - db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) - - if err != nil { - log.Fatal(err.Error()) - } - - statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now()) - - _, err = statement.Exec("EDGEAPPS_LIST", string(edgeAppsJSON), formatedDatetime, formatedDatetime) // Execute SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - db.Close() - + utils.WriteOption("EDGEAPPS_LIST", string(edgeAppsJSON)) return string(edgeAppsJSON) - } func taskGetSystemUptime() string { fmt.Println("Executing taskGetSystemUptime") - uptime := system.GetUptimeInSeconds() - - db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) - - if err != nil { - log.Fatal(err.Error()) - } - - statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now()) - - _, err = statement.Exec("SYSTEM_UPTIME", uptime, formatedDatetime, formatedDatetime) // Execute SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - db.Close() - + utils.WriteOption("SYSTEM_UPTIME", uptime) return uptime - } func taskGetStorageDevices() string { fmt.Println("Executing taskGetStorageDevices") - devices := storage.GetDevices() + devices := storage.GetDevices(diagnostics.GetReleaseVersion()) devicesJSON, _ := json.Marshal(devices) - db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) - - if err != nil { - log.Fatal(err.Error()) - } - - statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now()) - - _, err = statement.Exec("STORAGE_DEVICES_LIST", devicesJSON, formatedDatetime, formatedDatetime) // Execute SQL Statement - if err != nil { - log.Fatal(err.Error()) - } - - db.Close() + utils.WriteOption("STORAGE_DEVICES_LIST", string(devicesJSON)) return string(devicesJSON) - +} + +func taskGetSystemIP() string { + fmt.Println("Executing taskGetStorageDevices") + ip := system.GetIP() + utils.WriteOption("IP_ADDRESS", ip) + return ip +} + +func taskGetHostname() string { + fmt.Println("Executing taskGetHostname") + hostname := system.GetHostname() + utils.WriteOption("HOSTNAME", hostname) + return hostname +} + +func taskSetupCloudOptions() { + fmt.Println("Executing taskSetupCloudOptions") + system.SetupCloudOptions() } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 996c0ea..2eeaa35 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "bufio" "bytes" + "database/sql" "fmt" "io" "log" @@ -112,6 +113,14 @@ func GetPath(pathKey string) string { } switch pathKey { + case "cloudEnvFileLocation": + + if env["CLOUD_ENV_FILE_LOCATION"] != "" { + targetPath = env["CLOUD_ENV_FILE_LOCATION"] + } else { + targetPath = "/home/system/components/edgeboxctl/cloud.env" + } + case "apiEnvFileLocation": if env["API_ENV_FILE_LOCATION"] != "" { @@ -120,6 +129,14 @@ func GetPath(pathKey string) string { targetPath = "/home/system/components/api/edgebox.env" } + case "apiPath": + + if env["API_PATH"] != "" { + targetPath = env["APT_PATH"] + } else { + targetPath = "/home/system/components/api/" + } + case "edgeAppsPath": if env["EDGEAPPS_PATH"] != "" { @@ -145,3 +162,27 @@ func GetPath(pathKey string) string { return targetPath } + +// WriteOption : Writes a key value pair option into the api shared database +func WriteOption(optionKey string, optionValue string) { + + db, err := sql.Open("sqlite3", GetSQLiteDbConnectionDetails()) + + if err != nil { + log.Fatal(err.Error()) + } + + statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement + if err != nil { + log.Fatal(err.Error()) + } + + formatedDatetime := GetSQLiteFormattedDateTime(time.Now()) + + _, err = statement.Exec(optionKey, optionValue, formatedDatetime, formatedDatetime) // Execute SQL Statement + if err != nil { + log.Fatal(err.Error()) + } + + db.Close() +}