2021-05-31 13:21:14 +02:00
package system
import (
"fmt"
"strconv"
2022-02-06 15:37:50 +01:00
"strings"
2023-03-20 00:54:42 +01:00
"log"
"os"
2023-06-10 17:29:54 +02:00
"io"
2024-12-07 01:26:45 +01:00
"errors"
2023-03-20 00:54:42 +01:00
"os/exec"
"bufio"
"path/filepath"
"io/ioutil"
"encoding/json"
2022-02-06 15:37:50 +01:00
"github.com/edgebox-iot/edgeboxctl/internal/utils"
2021-05-31 13:21:14 +02:00
2022-02-06 15:37:50 +01:00
"github.com/joho/godotenv"
2022-10-08 18:41:37 +02:00
"github.com/shirou/gopsutil/host"
2024-12-07 01:26:45 +01:00
"github.com/go-yaml/yaml"
2021-05-31 13:21:14 +02:00
)
2023-03-20 00:54:42 +01:00
type cloudflaredTunnelJson struct {
AccountTag string ` json:"AccountTag" `
TunnelSecret string ` json:"TunnelSecret" `
TunnelID string ` json:"TunnelID" `
}
2022-02-06 15:37:50 +01:00
// GetUptimeInSeconds: Returns a value (as string) of the total system uptime
2021-05-31 13:21:14 +02:00
func GetUptimeInSeconds ( ) string {
uptime , _ := host . Uptime ( )
return strconv . FormatUint ( uptime , 10 )
}
2022-02-06 15:37:50 +01:00
// GetUptimeFormatted: Returns a humanized version that can be useful for logging
2021-05-31 13:21:14 +02:00
func GetUptimeFormatted ( ) string {
uptime , _ := host . Uptime ( )
days := uptime / ( 60 * 60 * 24 )
hours := ( uptime - ( days * 60 * 60 * 24 ) ) / ( 60 * 60 )
minutes := ( ( uptime - ( days * 60 * 60 * 24 ) ) - ( hours * 60 * 60 ) ) / 60
return fmt . Sprintf ( "%d days, %d hours, %d minutes" , days , hours , minutes )
}
2022-02-06 15:37:50 +01:00
2022-10-08 18:41:37 +02:00
// GetIP: Returns the ip address of the instance
2022-02-06 15:37:50 +01:00
func GetIP ( ) string {
ip := ""
// Trying to find a valid IP (For direct connection, not tunneled)
ethResult := utils . ExecAndGetLines ( "/" , "ip" , [ ] string { "-o" , "-4" , "addr" , "list" , "eth0" } )
for ethResult . Scan ( ) {
adapterRawInfo := strings . Fields ( ethResult . Text ( ) )
if ip == "" {
ip = strings . Split ( adapterRawInfo [ 3 ] , "/" ) [ 0 ]
}
}
// If no IP was found yet, try wlan0
if ip == "" {
wlanResult := utils . ExecAndGetLines ( "/" , "ip" , [ ] string { "-o" , "-4" , "addr" , "list" , "wlan0" } )
for wlanResult . Scan ( ) {
adapterRawInfo := strings . Fields ( wlanResult . Text ( ) )
if ip == "" {
ip = strings . Split ( adapterRawInfo [ 3 ] , "/" ) [ 0 ]
}
}
}
return ip
}
func GetHostname ( ) string {
return utils . Exec ( "/" , "hostname" , [ ] string { } )
}
// SetupCloudOptions: Reads the designated env file looking for options to write into the options table. Meant to be used on initial setup. Deletes source env file after operation.
func SetupCloudOptions ( ) {
var cloudEnv map [ string ] string
2022-10-08 18:41:37 +02:00
cloudEnvFileLocationPath := utils . GetPath ( utils . CloudEnvFileLocation )
cloudEnv , err := godotenv . Read ( cloudEnvFileLocationPath )
2022-02-06 15:37:50 +01:00
if err != nil {
fmt . Println ( "Error loading .env file for cloud version setup" )
}
if cloudEnv [ "NAME" ] != "" {
utils . WriteOption ( "NAME" , cloudEnv [ "NAME" ] )
}
if cloudEnv [ "EMAIL" ] != "" {
utils . WriteOption ( "EMAIL" , cloudEnv [ "EMAIL" ] )
}
2023-11-05 02:01:16 +01:00
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" ] )
}
2023-11-05 02:03:46 +01:00
if cloudEnv [ "CLUSTER_SSH_PORT" ] != "" {
utils . WriteOption ( "CLUSTER_SSH_PORT" , cloudEnv [ "CLUSTER_SSH_PORT" ] )
}
2022-02-06 15:37:50 +01:00
if cloudEnv [ "EDGEBOXIO_API_TOKEN" ] != "" {
utils . WriteOption ( "EDGEBOXIO_API_TOKEN" , cloudEnv [ "EDGEBOXIO_API_TOKEN" ] )
}
// In the end of this operation takes place, remove the env file as to not overwrite any options once they are set.
2022-10-08 18:41:37 +02:00
utils . Exec ( "/" , "rm" , [ ] string { cloudEnvFileLocationPath } )
2022-02-06 15:37:50 +01:00
}
2023-03-20 00:54:42 +01:00
2023-10-29 20:48:14 +01:00
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" } )
}
2023-10-28 18:43:18 +02:00
// 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 )
}
2023-03-20 00:54:42 +01:00
// 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 )
}
2023-05-29 21:52:27 +02:00
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 )
}
}
2023-03-20 00:54:42 +01:00
// 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 )
}
2023-06-10 17:29:54 +02:00
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
}
2024-07-04 16:22:32 +02:00
func CheckUpdates ( ) {
fmt . Println ( "Checking for Edgebox System Updates." )
// Configure the service and start it
cmd := exec . Command ( "sh" , "/home/system/components/updater/run.sh" , "--check" )
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 ( )
fmt . Println ( "Error running updates check." )
utils . WriteOption ( "SYSTEM_UPDATES" , "[]" )
return
}
// Read targets.env file into JSON list structure
targets := [ ] string { }
targetsFile , err := os . Open ( "/home/system/components/updater/targets.env" )
if err != nil {
fmt . Println ( "No targets.env file found. Skipping." )
utils . WriteOption ( "SYSTEM_UPDATES" , "[]" )
return
}
defer targetsFile . Close ( )
scanner = bufio . NewScanner ( targetsFile )
for scanner . Scan ( ) {
text := scanner . Text ( )
// text line should look like: {"target": "<target>", "version": "<version>"}
target := strings . Split ( text , "=" )
newText := "{\"target\": \"" + strings . Replace ( target [ 0 ] , "_VERSION" , "" , - 1 ) + "\", \"version\": \"" + target [ 1 ] + "\"}"
targets = append ( targets , newText )
}
if scanner . Err ( ) != nil {
fmt . Println ( "Error reading update targets file." )
utils . WriteOption ( "SYSTEM_UPDATES" , "[]" )
return
}
// convert targets to string
targetsString := strings . Join ( targets , "," )
targetsString = "[" + targetsString + "]"
fmt . Println ( targetsString )
// Write option with targets
utils . WriteOption ( "SYSTEM_UPDATES" , targetsString )
}
func ApplyUpdates ( ) {
fmt . Println ( "Applying Edgebox System Updates." )
utils . WriteOption ( "UPDATING_SYSTEM" , "true" )
// Configure the service and start it
cmd := exec . Command ( "sh" , "/home/system/components/updater/run.sh" , "--update" )
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 ( ) )
}
// If the system did not yet restart, set updating system to false
utils . WriteOption ( "UPDATING_SYSTEM" , "false" )
}
2024-12-07 01:26:45 +01:00
func FetchBrowserDevPasswordFromFile ( ) ( string , error ) {
fmt . Println ( "Executing FetchBrowserDevPasswordFromFile" )
// Read the "password" entry on the yaml file
// Read the yaml file in system.GetPath(BrowserDevPasswordFileLocation)
yamlFile , err := ioutil . ReadFile ( utils . GetPath ( utils . BrowserDevPasswordFileLocation ) )
if err != nil {
return "" , err
}
// Parse the yaml file and get the "password" entry
var yamlFileMap yaml . MapSlice
err = yaml . Unmarshal ( yamlFile , & yamlFileMap )
if err != nil {
return "" , err
}
for _ , item := range yamlFileMap {
key , value := item . Key , item . Value
if key == "password" {
if pwString , ok := value . ( string ) ; ok {
return pwString , nil
} else {
return "" , errors . New ( "password value is not a string" )
}
}
}
return "" , errors . New ( "password key not found" )
}
func SetBrowserDevPasswordFile ( password string ) error {
// Get current password from file
currentPassword , err := FetchBrowserDevPasswordFromFile ( )
if err != nil {
fmt . Println ( "Error fetching current password from file." )
return err
}
// Write the new password on the file using ReplaceTextInFile
err = ReplaceTextInFile ( utils . GetPath ( utils . BrowserDevPasswordFileLocation ) , currentPassword , password )
if err != nil {
fmt . Println ( "Error writing new password to file." )
return err
}
return nil
}
func ReplaceTextInFile ( filePath string , oldText string , newText string ) error {
// Open the file for reading
file , err := os . OpenFile ( filePath , os . O_RDWR , 0644 )
if err != nil {
return err
}
// Read the file contents
data , err := ioutil . ReadAll ( file )
if err != nil {
return err
}
// Close the file
err = file . Close ( )
if err != nil {
return err
}
// Replace the text in the file
newData := strings . Replace ( string ( data ) , oldText , newText , - 1 )
// Write the new data back to the file
err = ioutil . WriteFile ( filePath , [ ] byte ( newData ) , 0644 )
if err != nil {
return err
}
return nil
}