mirror of
https://github.com/edgebox-iot/edgeboxctl.git
synced 2026-09-30 08:35:45 +02:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a43a156abf | ||
|
|
f7823b9e20 | ||
|
|
ece500c342 | ||
|
|
161ec76eb9 | ||
|
|
d9720a48c9 | ||
|
|
1ba5a8f506 | ||
|
|
25d5ca74f8 | ||
|
|
d62cb57222 | ||
|
|
f0bfc92295 | ||
|
|
0680b7ffb9 |
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -13,16 +13,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.15
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- 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
|
||||
|
||||
@@ -21,3 +21,6 @@ main
|
||||
# Build
|
||||
|
||||
/bin
|
||||
|
||||
# Logs from executed scripts
|
||||
/scripts/output.log
|
||||
|
||||
@@ -15,6 +15,15 @@ build-prod:
|
||||
build-cloud:
|
||||
GOOS=linux GOARCH=amd64 RELEASE=cloud make build
|
||||
|
||||
build-arm64:
|
||||
GOOS=linux GOARCH=arm64 RELEASE=prod make build
|
||||
|
||||
build-armhf:
|
||||
GOOS=linux GOARCH=arm RELEASE=prod make build
|
||||
|
||||
build-amd64:
|
||||
GOOS=linux GOARCH=amd64 RELEASE=prod make build
|
||||
|
||||
build:
|
||||
@echo "Building ${GOOS}-${GOARCH}"
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} go build \
|
||||
@@ -33,3 +42,20 @@ test:
|
||||
|
||||
test-with-coverage:
|
||||
go test -tags=unit -timeout=600s -v ./... -coverprofile=coverage.out
|
||||
|
||||
install-cloud: build-cloud
|
||||
systemctl stop edgeboxctl
|
||||
cp ./bin/edgeboxctl /usr/local/bin/edgeboxctl
|
||||
cp ./edgeboxctl/edgeboxctl.service /lib/systemd/system/edgeboxctl.service
|
||||
systemctl daemon-reload
|
||||
@echo "Edgeboxctl installed successfully"
|
||||
@echo "To start edgeboxctl run: systemctl start edgeboxctl"
|
||||
|
||||
install-prod: build-prod
|
||||
-sudo systemctl stop edgeboxctl
|
||||
sudo rm -rf /usr/local/bin/edgeboxctl /lib/systemd/system/edgeboxctl.service
|
||||
sudo cp ./bin/edgeboxctl /usr/local/bin/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"
|
||||
@@ -1,21 +1,3 @@
|
||||
|
||||
<!-- 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">
|
||||
@@ -28,8 +10,6 @@
|
||||
<p align="center">
|
||||
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>
|
||||
·
|
||||
@@ -60,7 +40,7 @@
|
||||
|
||||
[![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.
|
||||
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.
|
||||
|
||||
|
||||
### Built With
|
||||
@@ -142,19 +122,3 @@ Contributions are what make the open source community such an amazing place to b
|
||||
## License
|
||||
|
||||
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
|
||||
|
||||
@@ -99,7 +99,7 @@ func printDbDetails() {
|
||||
|
||||
// IsSystemReady : Checks hability of the service to execute commands (Only after "edgebox --build" is ran at least once via SSH, or if built for distribution)
|
||||
func isSystemReady() bool {
|
||||
_, err := os.Stat("/home/system/components/ws/.ready")
|
||||
_, err := os.Stat(utils.GetPath(utils.WsPath) + ".ready")
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,4 +1,3 @@
|
||||
codecov:
|
||||
require_ci_to_pass: no
|
||||
|
||||
comment: false
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[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
|
||||
@@ -56,14 +56,14 @@ func GetEdgeApp(ID string) MaybeEdgeApp {
|
||||
Valid: false,
|
||||
}
|
||||
|
||||
_, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + configFilename)
|
||||
_, err := os.Stat(utils.GetPath(utils.EdgeAppsPath) + ID + configFilename)
|
||||
if !os.IsNotExist(err) {
|
||||
// File exists. Start digging!
|
||||
|
||||
edgeAppName := ID
|
||||
edgeAppDescription := ""
|
||||
|
||||
edgeAppEnv, err := godotenv.Read(utils.GetPath("edgeAppsPath") + ID + envFilename)
|
||||
edgeAppEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + envFilename)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error loading .env file for edgeapp " + edgeAppName)
|
||||
@@ -79,7 +79,7 @@ func GetEdgeApp(ID string) MaybeEdgeApp {
|
||||
edgeAppInternetAccessible := false
|
||||
edgeAppInternetURL := ""
|
||||
|
||||
myEdgeAppServiceEnv, err := godotenv.Read(utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename)
|
||||
myEdgeAppServiceEnv, err := godotenv.Read(utils.GetPath(utils.EdgeAppsPath) + ID + myEdgeAppServiceEnvFilename)
|
||||
if err != nil {
|
||||
log.Println("No myedge.app environment file found. Status is Network-Only")
|
||||
} else {
|
||||
@@ -113,7 +113,7 @@ func IsEdgeAppInstalled(ID string) bool {
|
||||
|
||||
result := false
|
||||
|
||||
_, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + runnableFilename)
|
||||
_, err := os.Stat(utils.GetPath(utils.EdgeAppsPath) + ID + runnableFilename)
|
||||
if !os.IsNotExist(err) {
|
||||
result = true
|
||||
}
|
||||
@@ -125,11 +125,12 @@ func IsEdgeAppInstalled(ID string) bool {
|
||||
func SetEdgeAppInstalled(ID string) bool {
|
||||
|
||||
result := true
|
||||
edgeAppPath := utils.GetPath(utils.EdgeAppsPath)
|
||||
|
||||
_, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + runnableFilename)
|
||||
_, err := os.Stat(edgeAppPath + ID + runnableFilename)
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
_, err := os.Create(utils.GetPath("edgeAppsPath") + ID + runnableFilename)
|
||||
_, err := os.Create(edgeAppPath + ID + runnableFilename)
|
||||
result = true
|
||||
|
||||
if err != nil {
|
||||
@@ -153,7 +154,7 @@ func SetEdgeAppInstalled(ID string) bool {
|
||||
func SetEdgeAppNotInstalled(ID string) bool {
|
||||
|
||||
result := true
|
||||
err := os.Remove(utils.GetPath("edgeAppsPath") + ID + runnableFilename)
|
||||
err := os.Remove(utils.GetPath(utils.EdgeAppsPath) + ID + runnableFilename)
|
||||
if err != nil {
|
||||
result = false
|
||||
log.Fatal(err)
|
||||
@@ -172,7 +173,7 @@ func GetEdgeApps() []EdgeApp {
|
||||
|
||||
// Building list of available edgeapps in the system with their status
|
||||
|
||||
files, err := ioutil.ReadDir(utils.GetPath("edgeAppsPath"))
|
||||
files, err := ioutil.ReadDir(utils.GetPath(utils.EdgeAppsPath))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -237,16 +238,16 @@ func GetEdgeAppStatus(ID string) EdgeAppStatus {
|
||||
|
||||
// GetEdgeAppServices : Returns a
|
||||
func GetEdgeAppServices(ID string) []EdgeAppService {
|
||||
|
||||
cmdArgs := []string{"-r", ".services | keys[]", utils.GetPath("edgeAppsPath") + ID + configFilename}
|
||||
servicesString := utils.Exec(utils.GetPath("wsPath"), "yq", cmdArgs)
|
||||
wsPath := utils.GetPath(utils.WsPath)
|
||||
cmdArgs := []string{"-r", ".services | keys[]", utils.GetPath(utils.EdgeAppsPath) + ID + configFilename}
|
||||
servicesString := utils.Exec(utils.GetPath(utils.WsPath), "yq", cmdArgs)
|
||||
serviceSlices := strings.Split(servicesString, "\n")
|
||||
serviceSlices = utils.DeleteEmptySlices(serviceSlices)
|
||||
var edgeAppServices []EdgeAppService
|
||||
|
||||
for _, serviceID := range serviceSlices {
|
||||
cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "exec", "-T", serviceID, "echo", "'Service Check'"}
|
||||
cmdResult := utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs)
|
||||
cmdArgs = []string{"-f", wsPath + "/docker-compose.yml", "exec", "-T", serviceID, "echo", "'Service Check'"}
|
||||
cmdResult := utils.Exec(wsPath, "docker-compose", cmdArgs)
|
||||
isRunning := false
|
||||
if cmdResult != "" {
|
||||
isRunning = true
|
||||
@@ -260,16 +261,14 @@ func GetEdgeAppServices(ID string) []EdgeAppService {
|
||||
|
||||
// RunEdgeApp : Run an EdgeApp and return its most current status
|
||||
func RunEdgeApp(ID string) EdgeAppStatus {
|
||||
|
||||
wsPath := utils.GetPath(utils.WsPath)
|
||||
services := GetEdgeAppServices(ID)
|
||||
|
||||
cmdArgs := []string{}
|
||||
|
||||
for _, service := range services {
|
||||
|
||||
cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "start", service.ID}
|
||||
utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs)
|
||||
|
||||
cmdArgs = []string{"-f", wsPath + "/docker-compose.yml", "start", service.ID}
|
||||
utils.Exec(wsPath, "docker-compose", cmdArgs)
|
||||
}
|
||||
|
||||
// Wait for it to settle up before continuing...
|
||||
@@ -281,16 +280,13 @@ func RunEdgeApp(ID string) EdgeAppStatus {
|
||||
|
||||
// StopEdgeApp : Stops an EdgeApp and return its most current status
|
||||
func StopEdgeApp(ID string) EdgeAppStatus {
|
||||
|
||||
wsPath := utils.GetPath(utils.WsPath)
|
||||
services := GetEdgeAppServices(ID)
|
||||
|
||||
cmdArgs := []string{}
|
||||
|
||||
for _, service := range services {
|
||||
|
||||
cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "stop", service.ID}
|
||||
utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs)
|
||||
|
||||
cmdArgs = []string{"-f", wsPath + "/docker-compose.yml", "stop", service.ID}
|
||||
utils.Exec(wsPath, "docker-compose", cmdArgs)
|
||||
}
|
||||
|
||||
// Wait for it to settle up before continuing...
|
||||
@@ -306,7 +302,7 @@ func EnableOnline(ID string, InternetURL string) MaybeEdgeApp {
|
||||
maybeEdgeApp := GetEdgeApp(ID)
|
||||
if maybeEdgeApp.Valid { // We're only going to do this operation if the EdgeApp actually exists.
|
||||
// Create the myedgeapp.env file and add the InternetURL entry to it
|
||||
envFilePath := utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename
|
||||
envFilePath := utils.GetPath(utils.EdgeAppsPath) + ID + myEdgeAppServiceEnvFilename
|
||||
env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL)
|
||||
_ = godotenv.Write(env, envFilePath)
|
||||
}
|
||||
@@ -320,13 +316,13 @@ func EnableOnline(ID string, InternetURL string) MaybeEdgeApp {
|
||||
// DisableOnline : Removes env files necessary for system external access config. Rebuilds containers in project (in case of change only).
|
||||
func DisableOnline(ID string) MaybeEdgeApp {
|
||||
|
||||
envFilePath := utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename
|
||||
envFilePath := utils.GetPath(utils.EdgeAppsPath) + ID + myEdgeAppServiceEnvFilename
|
||||
_, err := godotenv.Read(envFilePath)
|
||||
if err != nil {
|
||||
log.Println("myedge.app environment file for " + ID + " not found. No need to delete.")
|
||||
} else {
|
||||
cmdArgs := []string{envFilePath}
|
||||
utils.Exec(utils.GetPath("wsPath"), "rm", cmdArgs)
|
||||
utils.Exec(utils.GetPath(utils.WsPath), "rm", cmdArgs)
|
||||
}
|
||||
|
||||
buildFrameworkContainers()
|
||||
@@ -337,7 +333,7 @@ func DisableOnline(ID string) MaybeEdgeApp {
|
||||
|
||||
func EnablePublicDashboard(InternetURL string) bool {
|
||||
|
||||
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
|
||||
envFilePath := utils.GetPath(utils.ApiPath) + myEdgeAppServiceEnvFilename
|
||||
env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL)
|
||||
_ = godotenv.Write(env, envFilePath)
|
||||
|
||||
@@ -348,28 +344,29 @@ func EnablePublicDashboard(InternetURL string) bool {
|
||||
}
|
||||
|
||||
func DisablePublicDashboard() bool {
|
||||
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
|
||||
envFilePath := utils.GetPath(utils.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)
|
||||
utils.Exec(utils.GetPath(utils.ApiPath), "rm", cmdArgs)
|
||||
buildFrameworkContainers()
|
||||
return true
|
||||
}
|
||||
|
||||
func IsPublicDashboard() bool {
|
||||
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
|
||||
envFilePath := utils.GetPath(utils.ApiPath) + myEdgeAppServiceEnvFilename
|
||||
_, err := godotenv.Read(envFilePath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func buildFrameworkContainers() {
|
||||
|
||||
cmdArgs := []string{utils.GetPath("wsPath") + "ws", "--build"}
|
||||
utils.ExecAndStream(utils.GetPath("wsPath"), "sh", cmdArgs)
|
||||
wsPath := utils.GetPath(utils.WsPath)
|
||||
cmdArgs := []string{wsPath + "ws", "--build"}
|
||||
utils.ExecAndStream(wsPath, "sh", cmdArgs)
|
||||
|
||||
time.Sleep(defaultContainerOperationSleepTime)
|
||||
|
||||
|
||||
@@ -224,9 +224,9 @@ func getDevicesSpaceUsage(devices []Device) []Device {
|
||||
bucketsUsageSplit := (uint64)(0)
|
||||
othersUsageSplit := (uint64)(0)
|
||||
|
||||
edgeappsDirSize, _ := getDirSize(utils.GetPath("edgeAppsPath"))
|
||||
edgeappsDirSize, _ := getDirSize(utils.GetPath(utils.EdgeAppsPath))
|
||||
// TODO for later: Figure out to get correct paths for each partition...
|
||||
wsAppDataDirSize, _ := getDirSize("/home/system/components/ws/appdata")
|
||||
wsAppDataDirSize, _ := getDirSize(utils.GetPath(utils.WsPath) + "/appdata")
|
||||
|
||||
if partition.Mountpoint == "/" {
|
||||
edgeappsUsageSplit = edgeappsDirSize + wsAppDataDirSize
|
||||
|
||||
+205
-5
@@ -4,14 +4,26 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"bufio"
|
||||
"path/filepath"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/edgebox-iot/edgeboxctl/internal/utils"
|
||||
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"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()
|
||||
@@ -64,7 +76,8 @@ func GetHostname() string {
|
||||
func SetupCloudOptions() {
|
||||
|
||||
var cloudEnv map[string]string
|
||||
cloudEnv, err := godotenv.Read(utils.GetPath("cloudEnvFileLocation"))
|
||||
cloudEnvFileLocationPath := utils.GetPath(utils.CloudEnvFileLocation)
|
||||
cloudEnv, err := godotenv.Read(cloudEnvFileLocationPath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error loading .env file for cloud version setup")
|
||||
@@ -83,6 +96,193 @@ 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{utils.GetPath("cloudEnvFileLocation")})
|
||||
|
||||
utils.Exec("/", "rm", []string{cloudEnvFileLocationPath})
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
||||
+160
-14
@@ -7,12 +7,17 @@ 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
|
||||
)
|
||||
@@ -29,10 +34,7 @@ type Task struct {
|
||||
}
|
||||
|
||||
type taskSetupTunnelArgs struct {
|
||||
BootnodeAddress string `json:"bootnode_address"`
|
||||
BootnodeToken string `json:"bootnode_token"`
|
||||
AssignedAddress string `json:"assigned_address"`
|
||||
NodeName string `json:"node_name"`
|
||||
DomainName string `json:"domain_name"`
|
||||
}
|
||||
|
||||
type taskStartEdgeAppArgs struct {
|
||||
@@ -64,6 +66,7 @@ type taskEnablePublicDashboardArgs struct {
|
||||
InternetURL string `json:"internet_url"`
|
||||
}
|
||||
|
||||
|
||||
const STATUS_CREATED int = 0
|
||||
const STATUS_EXECUTING int = 1
|
||||
const STATUS_FINISHED int = 2
|
||||
@@ -134,16 +137,36 @@ func ExecuteTask(task Task) Task {
|
||||
switch task.Task {
|
||||
case "setup_tunnel":
|
||||
|
||||
log.Println("Setting up bootnode connection...")
|
||||
log.Println("Setting up Cloudflare Tunnel...")
|
||||
var args taskSetupTunnelArgs
|
||||
err := json.Unmarshal([]byte(task.Args.String), &args)
|
||||
if err != nil {
|
||||
log.Printf("Error reading arguments of setup_bootnode task: %s", err)
|
||||
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)
|
||||
} 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...")
|
||||
@@ -328,18 +351,141 @@ func ExecuteSchedules(tick int) {
|
||||
|
||||
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(utils.GetPath("wsPath"), "tinc-boot", cmdargs)
|
||||
// Stop a the service if it is running
|
||||
system.StopService("cloudflared")
|
||||
|
||||
cmdargs = []string{"start", "tinc@dnet"}
|
||||
utils.Exec(utils.GetPath("wsPath"), "systemctl", cmdargs)
|
||||
// Uninstall the service if it is installed
|
||||
system.RemoveTunnelService()
|
||||
|
||||
cmdargs = []string{"enable", "tinc@dnet"}
|
||||
utils.Exec(utils.GetPath("wsPath"), "systemctl", cmdargs)
|
||||
fmt.Println("Creating cloudflared folder")
|
||||
cmdargs := []string{"/home/system/.cloudflared"}
|
||||
utils.Exec(wsPath, "mkdir", cmdargs)
|
||||
|
||||
output := "OK" // Better check / logging of command execution result.
|
||||
return output
|
||||
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\"}"
|
||||
}
|
||||
|
||||
func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string {
|
||||
|
||||
+57
-7
@@ -81,7 +81,7 @@ func DeleteEmptySlices(s []string) []string {
|
||||
func GetSQLiteDbConnectionDetails() string {
|
||||
|
||||
var apiEnv map[string]string
|
||||
apiEnv, err := godotenv.Read(GetPath("apiEnvFileLocation"))
|
||||
apiEnv, err := godotenv.Read(GetPath(ApiEnvFileLocation))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
@@ -100,6 +100,12 @@ func GetSQLiteFormattedDateTime(t time.Time) string {
|
||||
return formatedDatetime
|
||||
}
|
||||
|
||||
const CloudEnvFileLocation string = "cloudEnvFileLocation"
|
||||
const ApiEnvFileLocation string = "apiEnvFileLocation"
|
||||
const ApiPath string = "apiPath"
|
||||
const EdgeAppsPath string = "edgeAppsPath"
|
||||
const WsPath string = "wsPath"
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -113,7 +119,7 @@ func GetPath(pathKey string) string {
|
||||
}
|
||||
|
||||
switch pathKey {
|
||||
case "cloudEnvFileLocation":
|
||||
case CloudEnvFileLocation:
|
||||
|
||||
if env["CLOUD_ENV_FILE_LOCATION"] != "" {
|
||||
targetPath = env["CLOUD_ENV_FILE_LOCATION"]
|
||||
@@ -121,7 +127,7 @@ func GetPath(pathKey string) string {
|
||||
targetPath = "/home/system/components/edgeboxctl/cloud.env"
|
||||
}
|
||||
|
||||
case "apiEnvFileLocation":
|
||||
case ApiEnvFileLocation:
|
||||
|
||||
if env["API_ENV_FILE_LOCATION"] != "" {
|
||||
targetPath = env["API_ENV_FILE_LOCATION"]
|
||||
@@ -129,15 +135,15 @@ func GetPath(pathKey string) string {
|
||||
targetPath = "/home/system/components/api/edgebox.env"
|
||||
}
|
||||
|
||||
case "apiPath":
|
||||
case ApiPath:
|
||||
|
||||
if env["API_PATH"] != "" {
|
||||
targetPath = env["APT_PATH"]
|
||||
targetPath = env["API_PATH"]
|
||||
} else {
|
||||
targetPath = "/home/system/components/api/"
|
||||
}
|
||||
|
||||
case "edgeAppsPath":
|
||||
case EdgeAppsPath:
|
||||
|
||||
if env["EDGEAPPS_PATH"] != "" {
|
||||
targetPath = env["EDGEAPPS_PATH"]
|
||||
@@ -145,7 +151,7 @@ func GetPath(pathKey string) string {
|
||||
targetPath = "/home/system/components/apps/"
|
||||
}
|
||||
|
||||
case "wsPath":
|
||||
case WsPath:
|
||||
|
||||
if env["WS_PATH"] != "" {
|
||||
targetPath = env["WS_PATH"]
|
||||
@@ -186,3 +192,47 @@ 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.Fatal(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()
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ func TestGetPath(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
validPathKey := "wsPath"
|
||||
result = GetPath(validPathKey)
|
||||
result = GetPath(WsPath)
|
||||
if result != "/home/system/components/ws/" {
|
||||
t.Log("Expected /home/system/components/ws/ but got", result)
|
||||
t.Fail()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/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
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/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
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user