Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions hossted/service/compose/reconcile_compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"reflect"
"strconv"
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
Expand Down Expand Up @@ -618,29 +617,28 @@ func convertInt64ToString(value int64) string {
return strconv.FormatInt(value, 10) // Base 10 conversion
}

// GetCPUUsage returns the average CPU usage percentage as a formatted string
// getCPUUsage returns the average CPU usage percentage and the number of CPU cores.
func getCPUUsage() (string, error) {
percentages, err := cpu.Percent(time.Second, false)

// Get the number of CPU cores
numCores, err := cpu.Counts(true)
if err != nil {
return "", err
}

if len(percentages) > 0 {
return fmt.Sprintf("%.2f%%", percentages[0]), nil
}

return "", fmt.Errorf("no CPU usage data available")
// Convert the number of cores to a string
return fmt.Sprintf("%d", numCores), nil
Comment on lines +620 to +630
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Correct doc comment to match returning the number of CPU cores.
The doc states it returns average CPU usage, but it strictly returns the number of CPU cores. Update the doc or rename the function for clarity.

-// getCPUUsage returns the average CPU usage percentage and the number of CPU cores.
+// getCPUUsage returns the number of CPU cores as a string.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// getCPUUsage returns the average CPU usage percentage and the number of CPU cores.
func getCPUUsage() (string, error) {
percentages, err := cpu.Percent(time.Second, false)
// Get the number of CPU cores
numCores, err := cpu.Counts(true)
if err != nil {
return "", err
}
if len(percentages) > 0 {
return fmt.Sprintf("%.2f%%", percentages[0]), nil
}
return "", fmt.Errorf("no CPU usage data available")
// Convert the number of cores to a string
return fmt.Sprintf("%d", numCores), nil
// getCPUUsage returns the number of CPU cores as a string.
func getCPUUsage() (string, error) {
// Get the number of CPU cores
numCores, err := cpu.Counts(true)
if err != nil {
return "", err
}
// Convert the number of cores to a string
return fmt.Sprintf("%d", numCores), nil

}

// GetMemoryUsage returns the memory usage statistics as a formatted string
// getMemoryUsage returns a string summarizing the total memory of the server.
func getMemoryUsage() (string, error) {
vmStat, err := mem.VirtualMemory()
if err != nil {
return "", err
}

memUsage := fmt.Sprintf("%.2f%%", vmStat.UsedPercent)
return memUsage, nil
// Summarize total memory in a single string (in GB)
return fmt.Sprintf("%.2f GB", float64(vmStat.Total)/(1024*1024*1024)), nil
}

func runMonitoringCompose(monitoringEnable, osUUID, appUUID string) error {
Expand Down
181 changes: 139 additions & 42 deletions hossted/setDomain.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package hossted

import (
"bufio"
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/hossted/cli/hossted/service/compose"
Expand Down Expand Up @@ -102,11 +100,6 @@ func SetDomain(env, app, domain string) error {
return fmt.Errorf("\n\n%w", err)
}

err = ChangeMOTD(domain)
if err != nil {
return err
}

check := verifyInputFormat(domain, "domain")
if !check {
return fmt.Errorf("invalid domain input. Expecting domain name (e.g. example.com). input - %s", domain)
Expand All @@ -118,14 +111,25 @@ func SetDomain(env, app, domain string) error {
return err
}

// Use sed to change the domain
// TODO: check if the line really exists in the file first
fmt.Println("Changing settings...")
text := fmt.Sprintf("s/(PROJECT_BASE_URL=)(.*)/\\1%s/", domain)
cmd := exec.Command("sudo", "sed", "-i", "-E", text, envPath)
_, err = cmd.Output()
// Read the existing PROJECT_BASE_URL
existingDomain, err := GetProjectBaseURL(envPath)
if err != nil {
return err
return fmt.Errorf("error reading PROJECT_BASE_URL: %v", err)
}

fmt.Println("Current PROJECT_BASE_URL:", existingDomain)

// Update the PROJECT_BASE_URL with a new domain
//newDomain := "new-domain.com"
err = UpdateProjectBaseURL(envPath, domain)
if err != nil {
return fmt.Errorf("error updating PROJECT_BASE_URL: %v", err)
}

// Update the MOTD file with the new domain
err = UpdateMOTD(existingDomain, domain)
if err != nil {
return fmt.Errorf("error updating MOTD: %v", err)
}

// Try command
Expand Down Expand Up @@ -196,33 +200,6 @@ func submitPatchRequest(osInfo compose.OsInfo, accessInfo compose.AccessInfo) er
return compose.SendRequest(http.MethodPatch, composeUrl, osInfo.Token, newReq)
}

func ChangeMOTD(domain string) error {
filepath := "/etc/motd"

// Read the file
b, err := readProtected(filepath)
if err != nil {
return fmt.Errorf("unable to read the /etc/motd file. Please check %s and contact administrator: %w", filepath, err)
}
content := string(b)

// Match and update any URL that starts with https:// followed by a domain
re := regexp.MustCompile(`https://[\w\.\-]+\.\w+`)
updatedContent := re.ReplaceAllString(content, fmt.Sprintf("https://%s", domain))

if updatedContent == content {
return errors.New("no matching pattern found in /etc/motd. Please ensure the content is formatted correctly")
}

// Write the updated content back to the file
err = writeProtected(filepath, []byte(updatedContent))
if err != nil {
return fmt.Errorf("failed to write to the /etc/motd file: %w", err)
}

return nil
}

// CheckHosstedAuthFiles checks if the files ~/.hossted/auth.json and ~/.hossted/authresp.json exist.
func CheckHosstedAuthFiles() error {
// Get the home directory
Expand Down Expand Up @@ -292,3 +269,123 @@ func getProjectName() (string, error) {
}
return "", nil
}

// GetProjectBaseURL reads the PROJECT_BASE_URL value from the .env file.
func GetProjectBaseURL(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("failed to open .env file: %w", err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "PROJECT_BASE_URL=") {
// Extract and return the value after "PROJECT_BASE_URL="
return strings.TrimPrefix(line, "PROJECT_BASE_URL="), nil
}
}

if err := scanner.Err(); err != nil {
return "", fmt.Errorf("failed to read .env file: %w", err)
}

return "", fmt.Errorf("PROJECT_BASE_URL not found in .env file")
}

// UpdateProjectBaseURL updates the PROJECT_BASE_URL value in the .env file while preserving any existing path.
func UpdateProjectBaseURL(filePath, newDomain string) error {
// Open the .env file for reading
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open .env file: %w", err)
}
defer file.Close()

var updatedLines []string
scanner := bufio.NewScanner(file)
updated := false

for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "PROJECT_BASE_URL=") {
// Extract the existing value of PROJECT_BASE_URL
existingDomain := strings.TrimPrefix(line, "PROJECT_BASE_URL=")

// Use preservePath to update the domain while retaining the path
updatedDomain := preservePath(existingDomain, newDomain)
line = fmt.Sprintf("PROJECT_BASE_URL=%s", updatedDomain)
updated = true
}
updatedLines = append(updatedLines, line)
}

if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to read .env file: %w", err)
}

if !updated {
return fmt.Errorf("PROJECT_BASE_URL not found in .env file")
}

// Write the updated content back to the file
err = os.WriteFile(filePath, []byte(strings.Join(updatedLines, "\n")+"\n"), 0644)
if err != nil {
return fmt.Errorf("failed to write updated .env file: %w", err)
}

fmt.Println("Successfully updated PROJECT_BASE_URL in .env file.")
return nil
}

// UpdateMOTD searches for the existing domain in the MOTD file and replaces it with the new domain, preserving paths.
func UpdateMOTD(existingDomain, newDomain string) error {
const motdFilePath = "/etc/motd"

// Open the MOTD file for reading
file, err := os.Open(motdFilePath)
if err != nil {
return fmt.Errorf("failed to open MOTD file: %w", err)
}
defer file.Close()

var updatedLines []string
scanner := bufio.NewScanner(file)

for scanner.Scan() {
line := scanner.Text()

// Check if the line contains the existing domain
if strings.Contains(line, existingDomain) {
// Replace only the domain part, preserving any path
line = strings.ReplaceAll(line, existingDomain, preservePath(existingDomain, newDomain))
}

updatedLines = append(updatedLines, line)
}

if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to read MOTD file: %w", err)
}

// Write the updated content back to the MOTD file
err = os.WriteFile(motdFilePath, []byte(strings.Join(updatedLines, "\n")+"\n"), 0644)
if err != nil {
return fmt.Errorf("failed to write updated MOTD file: %w", err)
}

fmt.Println("Successfully updated the MOTD file.")
return nil
}

// preservePath replaces the domain in a URL while preserving the path.
func preservePath(existingDomain, newDomain string) string {
// Check if the existing domain contains a path
if idx := strings.Index(existingDomain, "/"); idx != -1 {
// Extract the path and append it to the new domain
return newDomain + existingDomain[idx:]
}
// If no path exists, just return the new domain
return newDomain
}
Comment on lines +382 to +391
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Schema or protocol considerations.
When the existingDomain includes "http://" or "https://", the slash detection might remove the protocol. Decide whether to preserve the scheme in addition to the path.

Loading