Author SHA1 Message Date
Malachi Soord f0bfc92295 Enable codecov 2022-10-09 15:56:29 +02:00
19 changed files with 239 additions and 1620 deletions
-10
View File
@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+7 -7
View File
@@ -13,16 +13,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v2
with:
go-version: '1.20.2'
go-version: ^1.15
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Build
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
-3
View File
@@ -21,6 +21,3 @@ main
# Build
/bin
# Logs from executed scripts
/scripts/output.log
+5 -3
View File
@@ -1,9 +1,11 @@
FROM golang:1.20.2
FROM golang:latest
WORKDIR /app
COPY ./ /app
RUN go install github.com/githubnemo/CompileDaemon@latest
RUN go mod download
ENTRYPOINT CompileDaemon --build="make build" --command=./bin/edgeboxctl-$(go env GOOS)-$(go env GOARCH)
RUN go get github.com/githubnemo/CompileDaemon
ENTRYPOINT CompileDaemon --build="make build" --command=./bin/edgeboxctl
+9 -28
View File
@@ -1,18 +1,19 @@
.DEFAULT_GOAL := build
PROJECT?=github.com/edgebox-iot/edgeboxctl
PROJECT ?= github.com/edgebox-iot/edgeboxctl
RELEASE ?= dev
COMMIT := $(shell git rev-parse --short HEAD)
BUILD_DATE := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
BUILD_DIR = bin
GOOS := $(shell go env GOOS)
GOARCH := $(shell go env GOARCH)
build-all: clean
build-all:
GOOS=linux GOARCH=amd64 make build
GOOS=linux GOARCH=arm make build
GOOS=linux GOARCH=arm64 make build
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}"
@@ -21,6 +22,7 @@ build:
-X ${PROJECT}/internal/diagnostics.Commit=${COMMIT} \
-X ${PROJECT}/internal/diagnostics.BuildDate=${BUILD_DATE}" \
-o bin/edgeboxctl-${GOOS}-${GOARCH} ${PROJECT}/cmd/edgeboxctl
cp ./bin/edgeboxctl-${GOOS}-${GOARCH} ./bin/edgeboxctl
clean:
rm -rf ${BUILD_DIR}
@@ -31,24 +33,3 @@ test:
test-with-coverage:
go test -tags=unit -timeout=600s -v ./... -coverprofile=coverage.out
install:
sudo systemctl stop edgeboxctl || true
sudo rm -rf /usr/local/bin/edgeboxctl /usr/local/sbin/edgeboctl /lib/systemd/system/edgeboxctl.service
sudo cp ./bin/edgeboxctl-${GOOS}-${GOARCH} /usr/local/bin/edgeboxctl
sudo cp ./bin/edgeboxctl-${GOOS}-${GOARCH} /usr/local/sbin/edgeboxctl
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"
build-install: build install
start:
systemctl start edgeboxctl
stop:
systemctl stop edgeboxctl
log: start
journalctl -fu edgeboxctl
+49 -8
View File
@@ -1,3 +1,21 @@
<!-- PROJECT SHIELDS -->
<!--
*** Using markdown "reference style" links for readability.
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
*** See the bottom of this document for the declaration of the reference variables
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
-->
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
[![LinkedIn][linkedin-shield]][linkedin-url]
<!-- PROJECT LOGO -->
<br />
<p align="center">
@@ -8,7 +26,9 @@
<h3 align="center">Edgebox Control Module</h3>
<p align="center">
A System Control module written in Go. Its responsibility is to configure dependencies and perform system tasks, automatically on a schedule, or on demand.
A System Control module written in Go. Its resonsability is to configure dependencies and perform system tasks, automatically in a schedule, or by command.
<br />
<a href="https://github.com/github_username/edgeboxctl"><strong>Explore the docs »</strong></a>
<br />
<br />
<a href="https://github.com/github_username/edgeboxctl/issues">Report Bug</a>
@@ -38,7 +58,9 @@
<!-- ABOUT THE PROJECT -->
## About The Project
Edgebox is an easy to configure and use system. It brings powerful features that go alongside or can even completely replace various services that you already use in the day-to-day.
[![Edgebox Screen Shot][product-screenshot]](https://edgebox.co)
Edgebox is a small box that connects alongside your home internet router. It brings powerful features that go alongside or can even completely replace various services that you already use in the day-to-day.
### Built With
@@ -56,15 +78,18 @@ To get a local copy up and running follow these simple steps.
### Prerequisites
If you're running for development purposes, a docker container suffices, so make sure you have:
If you're installing this to run natively in the system, you better be doing it inside of the [Development Virtual Machine](https://github.com/edgebox-iot/devm). This software can do destructive action in the system is is running. You've been warned.
* docker
* docker compose
If you're running for development purposes, a Docker container suffices, so make sure you have:
* Docker
* Docker Compose
```sh
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:
Aditionally, edgeboxctl needs the following bash commands available wherever it runs:
* `arm-linux-gnueabi-gcc` (`sudo apt-get install gcc-arm*`)
* `sh`
* `rm`
@@ -116,4 +141,20 @@ Contributions are what make the open source community such an amazing place to b
<!-- LICENSE -->
## License
Distributed under the Elastic License 2.0. See `LICENSE` for more information.
Distributed under the MIT License. See `LICENSE` for more information.
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[contributors-shield]: https://img.shields.io/github/contributors/edgebox-iot/edgeboxctl.svg?style=flat-square
[contributors-url]: https://github.com/edgebox-iot/edgeboxctl/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/edgebox-iot/edgeboxctl.svg?style=flat-square
[forks-url]: https://github.com/edgebox-iot/edgeboxctl/network/members
[stars-shield]: https://img.shields.io/github/stars/edgebox-iot/edgeboxctl.svg?style=flat-square
[stars-url]: https://github.com/edgebox-iot/edgeboxctl/stargazers
[issues-shield]: https://img.shields.io/github/issues/edgebox-iot/edgeboxctl.svg?style=flat-square
[issues-url]: https://github.com/edgebox-iot/edgeboxctl/issues
[license-shield]: https://img.shields.io/github/license/edgebox-iot/edgeboxctl.svg?style=flat-square
[license-url]: https://github.com/edgebox-iot/edgeboxctl/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/edgebox-iot
+2 -3
View File
@@ -1,4 +1,3 @@
codecov:
require_ci_to_pass: no
comment: false
github_checks:
annotations: false
-29
View File
@@ -1,29 +0,0 @@
[Unit]
Description=Edgebox Control Module Service
ConditionPathExists=/home/system/
After=network.target
[Service]
Type=simple
User=root
Group=root
LimitNOFILE=1024
Restart=on-failure
RestartSec=10
startLimitIntervalSec=60
WorkingDirectory=/home/system/components/edgeboxctl
ExecStart=edgeboxctl --name=edgebox-cloud
# make sure log directory exists and owned by syslog
PermissionsStartOnly=true
ExecStartPre=/bin/mkdir -p /var/log/edgeboxctl
ExecStartPre=/bin/chown root:root /var/log/edgeboxctl
ExecStartPre=/bin/chmod 755 /var/log/edgeboxctl
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=edgeboxctl
[Install]
WantedBy=multi-user.target
-28
View File
@@ -1,28 +0,0 @@
package backups
// import (
// "fmt"
// "os"
// "path/filepath"
// "strings"
// "github.com/edgebox-iot/edgeboxctl/internal/diagnostics"
// "github.com/edgebox-iot/edgeboxctl/internal/utils"
// "github.com/shirou/gopsutil/disk"
// )
// Repository : Struct representing the backup repository of a device in the system
type Repository struct {
ID string `json:"id"`
FileCount int64 `json:"file_count"`
Size string `json:"size"`
Snapshots []Snapshot `json:"snapshots"`
Status string `json:"status"`
// UsageStat UsageStat `json:"usage_stat"`
}
// Snapshot : Struct representing a single snapshot in the backup repository
type Snapshot struct {
ID string `json:"id"`
time string `json:"time"`
}
+13
View File
@@ -1,5 +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)
}
+2 -202
View File
@@ -9,7 +9,6 @@ import (
"github.com/joho/godotenv"
"github.com/edgebox-iot/edgeboxctl/internal/system"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
)
@@ -23,9 +22,6 @@ 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"`
Login EdgeAppLogin `json:"login"`
}
// MaybeEdgeApp : Boolean flag for validation of edgeapp existance
@@ -46,29 +42,9 @@ 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"`
}
type EdgeAppLogin struct {
Enabled bool `json:"enabled"`
Username string `json:"username"`
Password string `json:"password"`
}
const configFilename = "/edgebox-compose.yml"
const envFilename = "/edgebox.env"
const optionsTemplateFilename = "/edgeapp.template.env"
const optionsEnvFilename = "/edgeapp.env"
const authEnvFilename = "/auth.env"
const runnableFilename = "/.run"
const appdataFoldername = "/appdata"
const myEdgeAppServiceEnvFilename = "/myedgeapp.env"
const defaultContainerOperationSleepTime time.Duration = time.Second * 10
@@ -86,7 +62,6 @@ func GetEdgeApp(ID string) MaybeEdgeApp {
edgeAppName := ID
edgeAppDescription := ""
edgeAppOptions := []EdgeAppOption{}
edgeAppEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + envFilename)
@@ -101,103 +76,6 @@ 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 := ""
@@ -211,21 +89,6 @@ func GetEdgeApp(ID string) MaybeEdgeApp {
}
}
edgeAppBasicAuthEnabled := false
edgeAppBasicAuthUsername := ""
edgeAppBasicAuthPassword := ""
edgeAppAuthEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + authEnvFilename)
if err != nil {
log.Println("No auth.env file found. Login status is disabled.")
} else {
if edgeAppAuthEnv["USERNAME"] != "" && edgeAppAuthEnv["PASSWORD"] != "" {
edgeAppBasicAuthEnabled = true
edgeAppBasicAuthUsername = edgeAppAuthEnv["USERNAME"]
edgeAppBasicAuthPassword = edgeAppAuthEnv["PASSWORD"]
}
}
result = MaybeEdgeApp{
EdgeApp: EdgeApp{
ID: ID,
@@ -234,12 +97,8 @@ func GetEdgeApp(ID string) MaybeEdgeApp {
Status: GetEdgeAppStatus(ID),
Services: GetEdgeAppServices(ID),
InternetAccessible: edgeAppInternetAccessible,
NetworkURL: ID + "." + system.GetHostname() + ".local",
NetworkURL: ID + ".edgebox.local",
InternetURL: edgeAppInternetURL,
Options: edgeAppOptions,
NeedsConfig: needsConfig,
Login: EdgeAppLogin{edgeAppBasicAuthEnabled, edgeAppBasicAuthUsername, edgeAppBasicAuthPassword},
},
Valid: true,
}
@@ -294,40 +153,11 @@ func SetEdgeAppInstalled(ID string) bool {
func SetEdgeAppNotInstalled(ID string) bool {
// Stop the app first
StopEdgeApp(ID)
// Now remove any files
result := true
err := os.Remove(utils.GetPath(utils.EdgeAppsPath) + ID + runnableFilename)
if err != nil {
result = false
log.Println(err)
}
err = os.Remove(utils.GetPath(utils.EdgeAppsPath) + ID + authEnvFilename)
if err != nil {
result = false
log.Println(err)
}
err = os.RemoveAll(utils.GetPath(utils.EdgeAppsPath) + ID + appdataFoldername)
if err != nil {
result = false
log.Println(err)
}
err = os.Remove(utils.GetPath(utils.EdgeAppsPath) + ID + myEdgeAppServiceEnvFilename)
if err != nil {
result = false
log.Println(err)
}
err = os.Remove(utils.GetPath(utils.EdgeAppsPath) + ID + optionsEnvFilename)
if err != nil {
result = false
log.Println(err)
log.Fatal(err)
}
buildFrameworkContainers()
@@ -466,36 +296,6 @@ func StopEdgeApp(ID string) EdgeAppStatus {
}
// StopAllEdgeApps: Stops all EdgeApps and returns a count of how many were stopped
func StopAllEdgeApps() int {
edgeApps := GetEdgeApps()
appCount := 0
for _, edgeApp := range edgeApps {
StopEdgeApp(edgeApp.ID)
appCount++
}
return appCount
}
// StartAllEdgeApps: Starts all EdgeApps and returns a count of how many were started
func StartAllEdgeApps() int {
edgeApps := GetEdgeApps()
appCount := 0
for _, edgeApp := range edgeApps {
RunEdgeApp(edgeApp.ID)
appCount++
}
return appCount
}
func RestartEdgeAppsService() {
buildFrameworkContainers()
}
// EnableOnline : Write environment file and rebuild the necessary containers. Rebuilds containers in project (in case of change only)
func EnableOnline(ID string, InternetURL string) MaybeEdgeApp {
+21 -2
View File
@@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
"github.com/edgebox-iot/edgeboxctl/internal/diagnostics"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
"github.com/shirou/gopsutil/disk"
)
@@ -74,9 +75,20 @@ const (
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(release_version diagnostics.ReleaseVersion) []Device {
// GetDevices : Returns a list of all available storage devices in structs filled with information
func GetDevices() []Device {
var devices []Device
cmdArgs := []string{"--raw", "--bytes", "--noheadings"}
@@ -88,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"
@@ -139,9 +153,14 @@ func GetDevices() []Device {
Status: DeviceStatus{ID: 1, Description: "healthy"},
}
if device.ID == mainDiskID {
device.MainDevice = true
}
currentDevice = device
} else if isPartition {
mountpoint := ""
if len(deviceRawInfo) >= 7 {
mountpoint = deviceRawInfo[6]
+8 -1
View File
@@ -7,7 +7,14 @@ import (
)
func TestGetDevices(t *testing.T) {
assertGetDevices(GetDevices(), t)
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) {
-341
View File
@@ -4,14 +4,6 @@ import (
"fmt"
"strconv"
"strings"
"log"
"os"
"io"
"os/exec"
"bufio"
"path/filepath"
"io/ioutil"
"encoding/json"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
@@ -19,12 +11,6 @@ import (
"github.com/shirou/gopsutil/host"
)
type cloudflaredTunnelJson struct {
AccountTag string `json:"AccountTag"`
TunnelSecret string `json:"TunnelSecret"`
TunnelID string `json:"TunnelID"`
}
// GetUptimeInSeconds: Returns a value (as string) of the total system uptime
func GetUptimeInSeconds() string {
uptime, _ := host.Uptime()
@@ -82,7 +68,6 @@ func SetupCloudOptions() {
if err != nil {
fmt.Println("Error loading .env file for cloud version setup")
return
}
if cloudEnv["NAME"] != "" {
@@ -93,22 +78,6 @@ func SetupCloudOptions() {
utils.WriteOption("EMAIL", cloudEnv["EMAIL"])
}
if cloudEnv["USERNAME"] != "" {
utils.WriteOption("USERNAME", cloudEnv["USERNAME"])
}
if cloudEnv["CLUSTER"] != "" {
utils.WriteOption("CLUSTER", cloudEnv["CLUSTER"])
}
if cloudEnv["CLUSTER_IP"] != "" {
utils.WriteOption("CLUSTER_IP", cloudEnv["CLUSTER_IP"])
}
if cloudEnv["CLUSTER_SSH_PORT"] != "" {
utils.WriteOption("CLUSTER_SSH_PORT", cloudEnv["CLUSTER_SSH_PORT"])
}
if cloudEnv["EDGEBOXIO_API_TOKEN"] != "" {
utils.WriteOption("EDGEBOXIO_API_TOKEN", cloudEnv["EDGEBOXIO_API_TOKEN"])
}
@@ -116,313 +85,3 @@ func SetupCloudOptions() {
// 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{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)
fmt.Println("Starting WS")
cmdargs := []string{"-b"}
utils.Exec(wsPath, "./ws", cmdargs)
}
// StartService: Starts a service
func StartService(serviceID string) {
wsPath := utils.GetPath(utils.WsPath)
fmt.Println("Starting" + serviceID + "service")
cmdargs := []string{"start", serviceID}
utils.Exec(wsPath, "systemctl", cmdargs)
}
// StopService: Stops a service
func StopService(serviceID string) {
wsPath := utils.GetPath(utils.WsPath)
fmt.Println("Stopping" + serviceID + "service")
cmdargs := []string{"stop", "cloudflared"}
utils.Exec(wsPath, "systemctl", cmdargs)
}
// RestartService: Restarts a service
func RestartService(serviceID string) {
wsPath := utils.GetPath(utils.WsPath)
fmt.Println("Restarting" + serviceID + "service")
cmdargs := []string{"restart", serviceID}
utils.Exec(wsPath, "systemctl", cmdargs)
}
// GetServiceStatus: Returns the status output of a service
func GetServiceStatus(serviceID string) string {
wsPath := utils.GetPath(utils.WsPath)
cmdargs := []string{"status", serviceID}
return utils.Exec(wsPath, "systemctl", cmdargs)
}
func CreateBackupsPasswordFile(password string) {
// Create a password file for backups
backupPasswordFile := utils.GetPath(utils.BackupPasswordFileLocation)
backupPasswordFileDir := filepath.Dir(backupPasswordFile)
if _, err := os.Stat(backupPasswordFileDir); os.IsNotExist(err) {
os.MkdirAll(backupPasswordFileDir, 0755)
}
// Write the password to the file, overriting an existing file
err := ioutil.WriteFile(backupPasswordFile, []byte(password), 0644)
if err != nil {
panic(err)
}
}
// CreateTunnel: Creates a tunnel via cloudflared, needs to be authenticated first
func CreateTunnel(configDestination string) {
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())
}
// 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)
}
var jsonFile os.DirEntry
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")
}
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)
}
fmt.Println("Parsing JSON file.")
var data cloudflaredTunnelJson
err = json.Unmarshal(jsonBytes, &data)
if err != nil {
log.Printf("Error reading tunnel JSON file: %s", err)
}
fmt.Println("Tunnel ID is:" + data.TunnelID)
// create the config.yml file with the following content in each line:
// "url": "http://localhost:80"
// "tunnel": "<TunnelID>"
// "credentials-file": "/root/.cloudflared/<tunnelID>.json"
file := configDestination
f, err := os.Create(file)
if err != nil {
panic(err)
}
defer f.Close()
_, err = f.WriteString("url: http://localhost:80\ntunnel: " + data.TunnelID + "\ncredentials-file: " + jsonFilePath)
if err != nil {
panic(err)
}
}
// DeleteTunnel: Deletes a tunnel via cloudflared, this does not remove the service
func DeleteTunnel() {
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 {
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())
}
}
// InstallTunnelService: Installs the tunnel service
func InstallTunnelService(config string) {
fmt.Println("Installing cloudflared service.")
cmd := exec.Command("cloudflared", "--config", config, "service", "install")
cmd.Start()
cmd.Wait()
}
// RemoveTunnelService: Removes the tunnel service
func RemoveTunnelService() {
wsPath := utils.GetPath(utils.WsPath)
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)
}
func CopyDir(src string, dest string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if !srcInfo.IsDir() {
return fmt.Errorf("%s is not a directory", src)
}
err = os.MkdirAll(dest, srcInfo.Mode())
if err != nil {
return err
}
items, err := ioutil.ReadDir(src)
if err != nil {
return err
}
for _, item := range items {
srcPath := filepath.Join(src, item.Name())
destPath := filepath.Join(dest, item.Name())
if item.IsDir() {
err = CopyDir(srcPath, destPath)
if err != nil {
fmt.Printf("error copying directory %s to %s: %s\n", srcPath, destPath, err.Error())
}
} else {
err = CopyFile(srcPath, destPath)
if err != nil {
fmt.Printf("error copying file %s to %s: %s\n", srcPath, destPath, err.Error())
}
}
}
return nil
}
func CopyFile(src string, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
err = destFile.Sync()
if err != nil {
return err
}
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
err = os.Chmod(dest, srcInfo.Mode())
if err != nil {
return err
}
return nil
}
+24 -762
View File
@@ -7,17 +7,12 @@ import (
"log"
"strconv"
"time"
"os/exec"
"strings"
"os"
"bufio"
"github.com/edgebox-iot/edgeboxctl/internal/diagnostics"
"github.com/edgebox-iot/edgeboxctl/internal/edgeapps"
"github.com/edgebox-iot/edgeboxctl/internal/storage"
"github.com/edgebox-iot/edgeboxctl/internal/system"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
_ "github.com/go-sql-driver/mysql" // Mysql Driver
_ "github.com/mattn/go-sqlite3" // SQlite Driver
)
@@ -33,19 +28,11 @@ 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 TaskBasicAuth struct {
Username string `json:"username"`
Password string `json:"password"`
}
type taskSetupTunnelArgs struct {
DomainName string `json:"domain_name"`
BootnodeAddress string `json:"bootnode_address"`
BootnodeToken string `json:"bootnode_token"`
AssignedAddress string `json:"assigned_address"`
NodeName string `json:"node_name"`
}
type taskStartEdgeAppArgs struct {
@@ -64,21 +51,6 @@ 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 taskSetEdgeAppBasicAuthArgs struct {
ID string `json:"id"`
Login TaskBasicAuth `json:"login"`
}
type taskRemoveEdgeAppBasicAuthArgs struct {
ID string `json:"id"`
}
type taskEnableOnlineArgs struct {
ID string `json:"id"`
InternetURL string `json:"internet_url"`
@@ -92,15 +64,6 @@ type taskEnablePublicDashboardArgs struct {
InternetURL string `json:"internet_url"`
}
type taskSetupBackupsArgs struct {
Service string `json:"service"`
AccessKeyID string `json:"access_key_id"`
SecretAccessKey string `json:"secret_access_key"`
RepositoryName string `json:"repository_name"`
RepositoryPassword string `json:"repository_password"`
}
const STATUS_CREATED int = 0
const STATUS_EXECUTING int = 1
const STATUS_FINISHED int = 2
@@ -158,85 +121,29 @@ func ExecuteTask(task Task) Task {
formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now())
fmt.Println("Changing task status to executing: " + task.Task)
_, err = statement.Exec(STATUS_EXECUTING, formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
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_backups":
log.Println("Setting up Backups Destination...")
var args taskSetupBackupsArgs
err := json.Unmarshal([]byte(task.Args.String), &args)
if err != nil {
log.Println("Error reading arguments of setup_backups task.")
} else {
taskResult := taskSetupBackups(args)
taskResultBool := true
// Check if returned taskResult string contains "error"
if strings.Contains(taskResult, "error") {
taskResultBool = false
}
task.Result = sql.NullString{String: taskResult, Valid: taskResultBool}
}
case "start_backup":
log.Println("Backing up Edgebox...")
taskResult := taskBackup()
taskResultBool := true
// Check if returned taskResult string contains "error"
if strings.Contains(taskResult, "error") {
taskResultBool = false
}
task.Result = sql.NullString{String: taskResult, Valid: taskResultBool}
case "restore_backup":
log.Println("Attempting to Restore Last Backup to Edgebox")
taskResult := taskRestoreBackup()
taskResultBool := true
// Check if returned taskResult string contains "error"
if strings.Contains(taskResult, "error") {
taskResultBool = false
}
task.Result = sql.NullString{String: taskResult, Valid: taskResultBool}
case "setup_tunnel":
log.Println("Setting up Cloudflare Tunnel...")
log.Println("Setting up bootnode connection...")
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)
log.Printf("Error reading arguments of setup_bootnode task: %s", err)
} else {
taskResult := taskSetupTunnel(args)
task.Result = sql.NullString{String: taskResult, Valid: true}
}
case "start_tunnel":
log.Println("Starting Cloudflare Tunnel...")
taskResult := taskStartTunnel()
task.Result = sql.NullString{String: taskResult, Valid: true}
case "stop_tunnel":
log.Println("Stopping Cloudflare Tunnel...")
taskResult := taskStopTunnel()
task.Result = sql.NullString{String: taskResult, Valid: true}
case "disable_tunnel":
log.Println("Disabling Cloudflare Tunnel...")
taskResult := taskDisableTunnel()
task.Result = sql.NullString{String: taskResult, Valid: true}
case "install_edgeapp":
log.Println("Installing EdgeApp...")
@@ -285,43 +192,6 @@ 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 "set_edgeapp_basic_auth":
log.Println("Settig EdgeApp Basic Authentication...")
var args taskSetEdgeAppBasicAuthArgs
err := json.Unmarshal([]byte(task.Args.String), &args)
if err != nil {
log.Printf("Error reading arguments of set_edgeapp_basic_auth task: %s", err)
} else {
taskResult := taskSetEdgeAppBasicAuth(args)
task.Result = sql.NullString{String: taskResult, Valid: true}
}
case "remove_edgeapp_basic_auth":
log.Println("Removing EdgeApp Basic Authentication...")
var args taskRemoveEdgeAppBasicAuthArgs
err := json.Unmarshal([]byte(task.Args.String), &args)
if err != nil {
log.Printf("Error reading arguments of remove_edgeapp_basic_auth task: %s", err)
} else {
taskResult := taskRemoveEdgeAppBasicAuth(args)
task.Result = sql.NullString{String: taskResult, Valid: true}
}
case "enable_online":
log.Println("Enabling online access to EdgeApp...")
@@ -363,8 +233,10 @@ func ExecuteTask(task Task) Task {
log.Println("Disabling online access to Dashboard...")
taskResult := taskDisablePublicDashboard()
task.Result = sql.NullString{String: taskResult, Valid: true}
}
}
statement, err = db.Prepare("Update task SET status = ?, result = ?, updated = ? WHERE ID = ?;") // Prepare SQL Statement
if err != nil {
@@ -377,13 +249,11 @@ func ExecuteTask(task Task) Task {
formatedDatetime = utils.GetSQLiteFormattedDateTime(time.Now())
if task.Result.Valid {
fmt.Println("Task Result: " + task.Result.String)
_, err = statement.Exec(STATUS_FINISHED, task.Result.String, formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with result info
if err != nil {
log.Fatal(err.Error())
}
} else {
fmt.Println("Error executing task with result: " + task.Result.String)
_, err = statement.Exec(STATUS_ERROR, "Error", formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with Error info
if err != nil {
log.Fatal(err.Error())
@@ -422,18 +292,18 @@ func ExecuteSchedules(tick int) {
// })
// }
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() + ")")
log.Println(taskGetStorageDevices())
taskStartWs()
log.Println(taskGetEdgeApps())
taskUpdateSystemLoggerServices()
}
if tick%5 == 0 {
@@ -445,32 +315,6 @@ 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")
// Check is Last Backup time (in unix time) is older than 1 h
lastBackup := utils.ReadOption("BACKUP_LAST_RUN")
if lastBackup != "" {
lastBackupTime, err := strconv.ParseInt(lastBackup, 10, 64)
if err != nil {
log.Println("Error parsing last backup time: " + err.Error())
} else {
secondsSinceLastBackup := time.Now().Unix() - lastBackupTime
if secondsSinceLastBackup > 3600 {
// If last backup is older than 1 hour, set BACKUP_IS_WORKING to 0
log.Println("Last backup was older than 1 hour, performing auto backup...")
log.Println(taskAutoBackup())
} else {
log.Println("Last backup is " + fmt.Sprint(secondsSinceLastBackup) + " seconds old (less than 1 hour ago), skipping auto backup...")
}
}
} else {
log.Println("Last backup time not found, skipping performing auto backup...")
}
}
if tick%60 == 0 {
@@ -478,451 +322,25 @@ func ExecuteSchedules(tick int) {
log.Println("System IP is: " + ip)
}
if tick%3600 == 0 {
// Executing every 3600 ticks (1 hour)
}
if tick%86400 == 0 {
// Executing every 86400 ticks (+/1 day)
// Ensuring we run a normal build, setting up avahi domain names fresh in the network
taskStartWs()
}
// Just add a schedule here if you need a custom one (every "tick hour", every "tick day", etc...)
}
func taskSetupBackups(args taskSetupBackupsArgs) string {
fmt.Println("Executing taskSetupBackups" + args.Service)
// ...
service_url := ""
key_id_name := "AWS_ACCESS_KEY_ID"
key_secret_name := "AWS_SECRET_ACCESS_KEY"
repo_location := "/home/system/components/apps/"
service_found := false
switch args.Service {
case "s3":
service_url = "s3.amazonaws.com/"
service_found = true
case "b2":
service_url = ""
key_id_name = "B2_ACCOUNT_ID"
key_secret_name = "B2_ACCOUNT_KEY"
service_found = true
case "wasabi":
service_found = true
service_url = "s3.wasabisys.com/"
}
if !service_found {
fmt.Println("Service not found")
return "{\"status\": \"error\", \"message\": \"Service not found\"}"
}
fmt.Println("Creating env vars for authentication with backup service")
os.Setenv(key_id_name, args.AccessKeyID)
os.Setenv(key_secret_name, args.SecretAccessKey)
fmt.Println("Creating restic password file")
system.CreateBackupsPasswordFile(args.RepositoryPassword)
fmt.Println("Initializing restic repository")
utils.WriteOption("BACKUP_IS_WORKING", "1")
cmdArgs := []string{"-r", args.Service + ":" + service_url + args.RepositoryName + ":" + repo_location, "init", "--password-file", utils.GetPath(utils.BackupPasswordFileLocation), "--verbose=3"}
result := utils.ExecAndStream(repo_location, "restic", cmdArgs)
utils.WriteOption("BACKUP_IS_WORKING", "0")
// Write backup settings to table
utils.WriteOption("BACKUP_SERVICE", args.Service)
utils.WriteOption("BACKUP_SERVICE_URL", service_url)
utils.WriteOption("BACKUP_REPOSITORY_NAME", args.RepositoryName)
utils.WriteOption("BACKUP_REPOSITORY_PASSWORD", args.RepositoryPassword)
utils.WriteOption("BACKUP_REPOSITORY_ACCESS_KEY_ID", args.AccessKeyID)
utils.WriteOption("BACKUP_REPOSITORY_SECRET_ACCESS_KEY", args.SecretAccessKey)
utils.WriteOption("BACKUP_REPOSITORY_LOCATION", repo_location)
// See if result contains the substring "Fatal:"
if strings.Contains(result, "Fatal:") {
fmt.Println("Error initializing restic repository")
utils.WriteOption("BACKUP_STATUS", "error")
utils.WriteOption("BACKUP_ERROR_MESSAGE", result)
return "{\"status\": \"error\", \"message\": \"" + result + "\"}"
}
// Save options to database
utils.WriteOption("BACKUP_STATUS", "initiated")
// Populate Stats right away
taskGetBackupStatus()
return "{\"status\": \"ok\"}"
}
func taskRemoveBackups() string {
fmt.Println("Executing taskRemoveBackups")
// ... This deletes the restic repository
// cmdArgs := []string{"-r", "s3:https://s3.amazonaws.com/edgebox-backups:/home/system/components/apps/", "forget", "latest", "--password-file", utils.GetPath(utils.BackupPasswordFileLocation), "--verbose=3"}
utils.WriteOption("BACKUP_STATUS", "")
utils.WriteOption("BACKUP_IS_WORKING", "0")
return "{\"status\": \"ok\"}"
}
func taskBackup() string {
fmt.Println("Executing taskBackup")
// Load Backup Options
backup_service := utils.ReadOption("BACKUP_SERVICE")
backup_service_url := utils.ReadOption("BACKUP_SERVICE_URL")
backup_repository_name := utils.ReadOption("BACKUP_REPOSITORY_NAME")
// backup_repository_password := utils.ReadOption("BACKUP_REPOSITORY_PASSWORD")
backup_repository_access_key_id := utils.ReadOption("BACKUP_REPOSITORY_ACCESS_KEY_ID")
backup_repository_secret_access_key := utils.ReadOption("BACKUP_REPOSITORY_SECRET_ACCESS_KEY")
backup_repository_location := utils.ReadOption("BACKUP_REPOSITORY_LOCATION")
key_id_name := "AWS_ACCESS_KEY_ID"
key_secret_name := "AWS_SECRET_ACCESS_KEY"
service_found := false
switch backup_service {
case "s3":
service_found = true
case "b2":
key_id_name = "B2_ACCOUNT_ID"
key_secret_name = "B2_ACCOUNT_KEY"
service_found = true
case "wasabi":
service_found = true
}
if !service_found {
fmt.Println("Service not found")
return "{\"status\": \"error\", \"message\": \"Backup Service not found\"}"
}
fmt.Println("Creating env vars for authentication with backup service")
fmt.Println(key_id_name)
os.Setenv(key_id_name, backup_repository_access_key_id)
fmt.Println(key_secret_name)
os.Setenv(key_secret_name, backup_repository_secret_access_key)
utils.WriteOption("BACKUP_IS_WORKING", "1")
// ... This backs up the restic repository
cmdArgs := []string{"-r", backup_service + ":" + backup_service_url + backup_repository_name + ":" + backup_repository_location, "backup", backup_repository_location, "--password-file", utils.GetPath(utils.BackupPasswordFileLocation), "--verbose=3"}
result := utils.ExecAndStream(backup_repository_location, "restic", cmdArgs)
utils.WriteOption("BACKUP_IS_WORKING", "0")
// Write as Unix timestamp
utils.WriteOption("BACKUP_LAST_RUN", strconv.FormatInt(time.Now().Unix(), 10))
// See if result contains the substring "Fatal:"
if strings.Contains(result, "Fatal:") {
fmt.Println("Error backing up")
utils.WriteOption("BACKUP_STATUS", "error")
utils.WriteOption("BACKUP_ERROR_MESSAGE", result)
return "{\"status\": \"error\", \"message\": \"" + result + "\"}"
}
utils.WriteOption("BACKUP_STATUS", "working")
taskGetBackupStatus()
return "{\"status\": \"ok\"}"
}
func taskRestoreBackup() string {
fmt.Println("Executing taskRestoreBackup")
// Load Backup Options
backup_service := utils.ReadOption("BACKUP_SERVICE")
backup_service_url := utils.ReadOption("BACKUP_SERVICE_URL")
backup_repository_name := utils.ReadOption("BACKUP_REPOSITORY_NAME")
// backup_repository_password := utils.ReadOption("BACKUP_REPOSITORY_PASSWORD")
backup_repository_access_key_id := utils.ReadOption("BACKUP_REPOSITORY_ACCESS_KEY_ID")
backup_repository_secret_access_key := utils.ReadOption("BACKUP_REPOSITORY_SECRET_ACCESS_KEY")
backup_repository_location := utils.ReadOption("BACKUP_REPOSITORY_LOCATION")
key_id_name := "AWS_ACCESS_KEY_ID"
key_secret_name := "AWS_SECRET_ACCESS_KEY"
service_found := false
switch backup_service {
case "s3":
service_found = true
case "b2":
key_id_name = "B2_ACCOUNT_ID"
key_secret_name = "B2_ACCOUNT_KEY"
service_found = true
case "wasabi":
service_found = true
}
if !service_found {
fmt.Println("Service not found")
return "{\"status\": \"error\", \"message\": \"Backup Service not found\"}"
}
fmt.Println("Creating env vars for authentication with backup service")
fmt.Println(key_id_name)
os.Setenv(key_id_name, backup_repository_access_key_id)
fmt.Println(key_secret_name)
os.Setenv(key_secret_name, backup_repository_secret_access_key)
utils.WriteOption("BACKUP_IS_WORKING", "1")
fmt.Println("Stopping All EdgeApps")
// Stop All EdgeApps
edgeapps.StopAllEdgeApps()
// Copy all files in /home/system/components/apps/ to a backup folder
fmt.Println("Copying all files in /home/system/components/apps/ to a backup folder")
os.MkdirAll(utils.GetPath(utils.EdgeAppsBackupPath + "temp/"), 0777)
system.CopyDir(utils.GetPath(utils.EdgeAppsPath), utils.GetPath(utils.EdgeAppsBackupPath + "temp/"))
fmt.Println("Removing all files in /home/system/components/apps/")
os.RemoveAll(utils.GetPath(utils.EdgeAppsPath))
// Create directory /home/system/components/apps/
fmt.Println("Creating directory /home/system/components/apps/")
os.MkdirAll(utils.GetPath(utils.EdgeAppsPath), 0777)
// ... This restores up the restic repository
cmdArgs := []string{"-r", backup_service + ":" + backup_service_url + backup_repository_name + ":" + backup_repository_location, "restore", "latest", "--target", "/", "--path", backup_repository_location, "--password-file", utils.GetPath(utils.BackupPasswordFileLocation), "--verbose=3"}
result := utils.ExecAndStream(backup_repository_location, "restic", cmdArgs)
taskGetBackupStatus()
edgeapps.RestartEdgeAppsService()
utils.WriteOption("BACKUP_IS_WORKING", "0")
// See if result contains the substring "Fatal:"
if strings.Contains(result, "Fatal:") {
// Copy all files from backup folder to /home/system/components/apps/
os.MkdirAll(utils.GetPath(utils.EdgeAppsPath), 0777)
system.CopyDir(utils.GetPath(utils.EdgeAppsBackupPath + "temp/"), utils.GetPath(utils.EdgeAppsPath))
fmt.Println("Error restoring backup: ")
utils.WriteOption("BACKUP_STATUS", "error")
utils.WriteOption("BACKUP_ERROR_MESSAGE", result)
return "{\"status\": \"error\", \"message\": \"" + result + "\"}"
}
utils.WriteOption("BACKUP_STATUS", "working")
taskGetBackupStatus()
return "{\"status\": \"ok\"}"
}
func taskAutoBackup() string {
fmt.Println("Executing taskAutoBackup")
// Get Backup Status
backup_status := utils.ReadOption("BACKUP_STATUS")
// We only backup is the status is "working"
if backup_status == "working" {
return taskBackup()
} else {
fmt.Println("Backup status is not working... skipping")
return "{\"status\": \"skipped\"}"
}
}
func taskGetBackupStatus() string {
fmt.Println("Executing taskGetBackupStatus")
// Load Backup Options
backup_service := utils.ReadOption("BACKUP_SERVICE")
backup_service_url := utils.ReadOption("BACKUP_SERVICE_URL")
backup_repository_name := utils.ReadOption("BACKUP_REPOSITORY_NAME")
// backup_repository_password := utils.ReadOption("BACKUP_REPOSITORY_PASSWORD")
backup_repository_access_key_id := utils.ReadOption("BACKUP_REPOSITORY_ACCESS_KEY_ID")
backup_repository_secret_access_key := utils.ReadOption("BACKUP_REPOSITORY_SECRET_ACCESS_KEY")
backup_repository_location := utils.ReadOption("BACKUP_REPOSITORY_LOCATION")
key_id_name := "AWS_ACCESS_KEY_ID"
key_secret_name := "AWS_SECRET_ACCESS_KEY"
service_found := false
switch backup_service {
case "s3":
service_found = true
case "b2":
key_id_name = "B2_ACCOUNT_ID"
key_secret_name = "B2_ACCOUNT_KEY"
service_found = true
case "wasabi":
service_found = true
}
if !service_found {
fmt.Println("Service not found")
return "{\"status\": \"error\", \"message\": \"Backup Service not found\"}"
}
fmt.Println("Creating env vars for authentication with backup service")
os.Setenv(key_id_name, backup_repository_access_key_id)
os.Setenv(key_secret_name, backup_repository_secret_access_key)
// ... This gets the restic repository status
cmdArgs := []string{"-r", backup_service + ":" + backup_service_url + backup_repository_name + ":" + backup_repository_location, "stats", "--password-file", utils.GetPath(utils.BackupPasswordFileLocation), "--verbose=3"}
utils.WriteOption("BACKUP_STATS", utils.ExecAndStream(backup_repository_location, "restic", cmdArgs))
return "{\"status\": \"ok\"}"
}
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)
// Stop a the service if it is running
system.StopService("cloudflared")
cmdargs = []string{"start", "tinc@dnet"}
utils.Exec(wsPath, "systemctl", cmdargs)
// Uninstall the service if it is installed
system.RemoveTunnelService()
cmdargs = []string{"enable", "tinc@dnet"}
utils.Exec(wsPath, "systemctl", cmdargs)
fmt.Println("Creating cloudflared folder")
cmdargs := []string{"/home/system/.cloudflared"}
utils.Exec(wsPath, "mkdir", cmdargs)
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()
// 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)
// Remove old tunnel if it exists, and create from scratch
system.DeleteTunnel()
// Create new tunnel (destination config file is param)
system.CreateTunnel("/home/system/.cloudflared/config.yml")
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", args.DomainName)
cmd.Start()
err = cmd.Wait()
if err != nil {
log.Fatal(err)
}
domainNameInfo := args.DomainName
utils.WriteOption("DOMAIN_NAME", domainNameInfo)
// Install service with given config file
system.InstallTunnelService("/home/system/.cloudflared/config.yml")
// Start the service
system.StartService("cloudflared")
if err != nil {
fmt.Println("Tunnel auth setup finished with errors.")
status := "{\"status\": \"error\", \"login_link\": \"" + url + "\"}"
utils.WriteOption("TUNNEL_STATUS", status)
log.Fatal(err)
} else {
fmt.Println("Tunnel auth setup finished without errors.")
status := "{\"status\": \"connected\", \"login_link\": \"" + url + "\", \"domain\": \"" + args.DomainName + "\"}"
utils.WriteOption("TUNNEL_STATUS", status)
}
fmt.Println("Finished running async")
}()
return "{\"url\": \"" + url + "\"}"
}
func taskStartTunnel() string {
fmt.Println("Executing taskStartTunnel")
system.StartService("cloudflared")
domainName := utils.ReadOption("DOMAIN_NAME")
status := "{\"status\": \"connected\", \"domain\": \"" + domainName + "\"}"
utils.WriteOption("TUNNEL_STATUS", status)
return "{\"status\": \"ok\"}"
}
func taskStopTunnel() string {
fmt.Println("Executing taskStopTunnel")
system.StopService("cloudflared")
domainName := utils.ReadOption("DOMAIN_NAME")
status := "{\"status\": \"stopped\", \"domain\": \"" + domainName + "\"}"
utils.WriteOption("TUNNEL_STATUS", status)
return "{\"status\": \"ok\"}"
}
func taskDisableTunnel() string {
fmt.Println("Executing taskDisableTunnel")
system.StopService("cloudflared")
system.DeleteTunnel()
system.RemoveTunnelService()
utils.DeleteOption("DOMAIN_NAME")
utils.DeleteOption("TUNNEL_STATUS")
return "{\"status\": \"ok\"}"
output := "OK" // Better check / logging of command execution result.
return output
}
func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string {
@@ -967,128 +385,6 @@ 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/<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 taskSetEdgeAppBasicAuth(args taskSetEdgeAppBasicAuthArgs) 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>/auth.env
// Get the path to the auth.env file
edgeappAuthEnvPath := "/home/system/components/apps/" + appID + "/auth.env"
// If the file does not exist, create it
if _, err := os.Stat(edgeappAuthEnvPath); os.IsNotExist(err) {
// Create the file
_, err := os.Create(edgeappAuthEnvPath)
if err != nil {
log.Printf("Error creating auth.env file: %s", err)
}
}
// It is an env file, so we can use go-dotenv to write the options
// Open the file
edgeappAuthEnvFile, err := os.OpenFile(edgeappAuthEnvPath, os.O_WRONLY, 0600)
if err != nil {
log.Printf("Error opening auth.env file: %s", err)
}
// Write the login values to the file
_, err = edgeappAuthEnvFile.WriteString("USERNAME=" + args.Login.Username + "\n" + "PASSWORD=" + args.Login.Password + "\n")
if err != nil {
log.Printf("Error writing credentials to auth.env file: %s", err)
}
// Close the file
err = edgeappAuthEnvFile.Close()
if err != nil {
log.Printf("Error closing auth.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 taskRemoveEdgeAppBasicAuth(args taskRemoveEdgeAppBasicAuthArgs) string {
// Id is the edgeapp id
appID := args.ID
// Get the path to the auth.env file
edgeappAuthEnvFile := "/auth.env"
fmt.Println("Removing auth.env file" + edgeappAuthEnvFile)
err := os.Remove(utils.GetPath(utils.EdgeAppsPath) + args.ID + edgeappAuthEnvFile)
if err != nil {
log.Fatal(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)
@@ -1141,35 +437,6 @@ 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")
@@ -1190,7 +457,7 @@ func taskGetSystemUptime() string {
func taskGetStorageDevices() string {
fmt.Println("Executing taskGetStorageDevices")
devices := storage.GetDevices()
devices := storage.GetDevices(diagnostics.GetReleaseVersion())
devicesJSON, _ := json.Marshal(devices)
utils.WriteOption("STORAGE_DEVICES_LIST", string(devicesJSON))
@@ -1216,8 +483,3 @@ func taskSetupCloudOptions() {
fmt.Println("Executing taskSetupCloudOptions")
system.SetupCloudOptions()
}
func taskStartWs() {
fmt.Println("Executing taskStartWs")
system.StartWs()
}
+3 -77
View File
@@ -16,7 +16,7 @@ import (
)
// ExecAndStream : Runs a terminal command, but streams progress instead of outputting. Ideal for long lived process that need to be logged.
func ExecAndStream(path string, command string, args []string) string {
func ExecAndStream(path string, command string, args []string) {
cmd := exec.Command(command, args...)
@@ -27,17 +27,13 @@ func ExecAndStream(path string, command string, args []string) string {
err := cmd.Run()
outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes())
returnVal := outStr
if err != nil {
fmt.Printf("cmd.Run() failed with %s\n", err)
returnVal = errStr
}
outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes())
fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
return returnVal
}
// Exec : Runs a terminal Command, catches and logs errors, returns the result.
@@ -104,15 +100,11 @@ func GetSQLiteFormattedDateTime(t time.Time) string {
return formatedDatetime
}
const BackupPasswordFileLocation string = "backupPasswordFileLocation"
const CloudEnvFileLocation string = "cloudEnvFileLocation"
const ApiEnvFileLocation string = "apiEnvFileLocation"
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 {
@@ -132,7 +124,7 @@ func GetPath(pathKey string) string {
if env["CLOUD_ENV_FILE_LOCATION"] != "" {
targetPath = env["CLOUD_ENV_FILE_LOCATION"]
} else {
targetPath = "/home/system/components/api/cloud.env"
targetPath = "/home/system/components/edgeboxctl/cloud.env"
}
case ApiEnvFileLocation:
@@ -159,13 +151,6 @@ func GetPath(pathKey string) string {
targetPath = "/home/system/components/apps/"
}
case EdgeAppsBackupPath:
if env["EDGEAPPS_BACKUP_PATH"] != "" {
targetPath = env["EDGEAPPS_BACKUP_PATH"]
} else {
targetPath = "/home/system/components/backups/"
}
case WsPath:
if env["WS_PATH"] != "" {
@@ -174,21 +159,6 @@ 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"] != "" {
targetPath = env["BACKUP_PASSWORD_FILE_LOCATION"]
} else {
targetPath = "/home/system/components/backups/pw.txt"
}
default:
log.Printf("path_key %s nonexistant in GetPath().\n", pathKey)
@@ -222,47 +192,3 @@ func WriteOption(optionKey string, optionValue string) {
db.Close()
}
// ReadOption : Reads a key value pair option from the api shared database
func ReadOption(optionKey string) string {
db, err := sql.Open("sqlite3", GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
var optionValue string
err = db.QueryRow("SELECT value FROM option WHERE name = ?", optionKey).Scan(&optionValue)
if err != nil {
log.Println(err.Error())
}
db.Close()
return optionValue
}
// DeleteOption : Deletes a key value pair option from the api shared database
func DeleteOption(optionKey string) {
db, err := sql.Open("sqlite3", GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
statement, err := db.Prepare("DELETE FROM option WHERE name = ?;") // Prepare SQL Statement
if err != nil {
log.Fatal(err.Error())
}
_, err = statement.Exec(optionKey) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
db.Close()
}
-6
View File
@@ -1,6 +0,0 @@
#!/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
-7
View File
@@ -1,7 +0,0 @@
#!/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/output.log
echo "sleeping 5 seconds"
sleep 5
-7
View File
@@ -1,7 +0,0 @@
#!/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/output.log &
echo "sleeping 5 seconds"
sleep 5