|
|
|
@@ -2,6 +2,7 @@ package utils
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"epochsilicon/pkg/log"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
@@ -11,8 +12,6 @@ import (
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"epochsilicon/pkg/debug"
|
|
|
|
|
|
|
|
|
|
"fyne.io/fyne/v2"
|
|
|
|
|
"fyne.io/fyne/v2/dialog"
|
|
|
|
|
)
|
|
|
|
@@ -104,16 +103,16 @@ func CopyDir(src string, dst string) error {
|
|
|
|
|
|
|
|
|
|
// RunOsascript runs an AppleScript command using osascript.
|
|
|
|
|
func RunOsascript(scriptString string, myWindow fyne.Window) bool {
|
|
|
|
|
debug.Printf("Executing AppleScript: %s", scriptString)
|
|
|
|
|
log.Debugf("Executing AppleScript: %s", scriptString)
|
|
|
|
|
cmd := exec.Command("osascript", "-e", scriptString)
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
errMsg := fmt.Sprintf("AppleScript failed: %v\nOutput: %s", err, string(output))
|
|
|
|
|
dialog.ShowError(errors.New(errMsg), myWindow)
|
|
|
|
|
debug.Println(errMsg)
|
|
|
|
|
log.Debug(errMsg)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
debug.Printf("osascript output: %s", string(output))
|
|
|
|
|
log.Debugf("osascript output: %s", string(output))
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -264,14 +263,14 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mount the DMG and parse the mount point from plist output
|
|
|
|
|
debug.Printf("Mounting DMG: %s", dmgPath)
|
|
|
|
|
log.Debugf("Mounting DMG: %s", dmgPath)
|
|
|
|
|
mountCmd := exec.Command("hdiutil", "attach", dmgPath, "-nobrowse", "-plist")
|
|
|
|
|
mountOutput, err := mountCmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to mount DMG: %v, output: %s", err, string(mountOutput))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug.Printf("Mount output: %s", string(mountOutput))
|
|
|
|
|
log.Debugf("Mount output: %s", string(mountOutput))
|
|
|
|
|
|
|
|
|
|
mountPoint := ""
|
|
|
|
|
|
|
|
|
@@ -292,7 +291,7 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
end := strings.Index(nextLine, "</string>")
|
|
|
|
|
if start >= 8 && end > start {
|
|
|
|
|
mountPoint = nextLine[start:end]
|
|
|
|
|
debug.Printf("Found mount point in plist: %s", mountPoint)
|
|
|
|
|
log.Debugf("Found mount point in plist: %s", mountPoint)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -301,7 +300,7 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
|
|
|
|
|
// Fallback: try without -plist flag for simpler output
|
|
|
|
|
if mountPoint == "" {
|
|
|
|
|
debug.Printf("Plist parsing failed, trying simple mount")
|
|
|
|
|
log.Debugf("Plist parsing failed, trying simple mount")
|
|
|
|
|
// Unmount first if something was mounted
|
|
|
|
|
exec.Command("hdiutil", "detach", dmgPath, "-force").Run()
|
|
|
|
|
|
|
|
|
@@ -321,7 +320,7 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
for i := len(parts) - 1; i >= 0; i-- {
|
|
|
|
|
if strings.HasPrefix(parts[i], "/Volumes/") {
|
|
|
|
|
mountPoint = parts[i]
|
|
|
|
|
debug.Printf("Found mount point in simple output: %s", mountPoint)
|
|
|
|
|
log.Debugf("Found mount point in simple output: %s", mountPoint)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -336,11 +335,11 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
return fmt.Errorf("could not find mount point. Mount output: %s", string(mountOutput))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug.Printf("Using mount point: %s", mountPoint)
|
|
|
|
|
log.Debugf("Using mount point: %s", mountPoint)
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
// Unmount the DMG
|
|
|
|
|
debug.Printf("Unmounting DMG from: %s", mountPoint)
|
|
|
|
|
log.Debugf("Unmounting DMG from: %s", mountPoint)
|
|
|
|
|
unmountCmd := exec.Command("hdiutil", "detach", mountPoint, "-force")
|
|
|
|
|
unmountCmd.Run()
|
|
|
|
|
}()
|
|
|
|
@@ -354,7 +353,7 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
newAppPath = exactPath
|
|
|
|
|
} else {
|
|
|
|
|
// Search for any .app bundle in the mount point
|
|
|
|
|
debug.Printf("EpochSilicon.app not found at exact path, searching for .app bundles")
|
|
|
|
|
log.Debugf("EpochSilicon.app not found at exact path, searching for .app bundles")
|
|
|
|
|
entries, err := os.ReadDir(mountPoint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to read DMG contents: %v", err)
|
|
|
|
@@ -363,7 +362,7 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
if entry.IsDir() && strings.HasSuffix(entry.Name(), ".app") {
|
|
|
|
|
candidatePath := filepath.Join(mountPoint, entry.Name())
|
|
|
|
|
debug.Printf("Found .app bundle: %s", candidatePath)
|
|
|
|
|
log.Debugf("Found .app bundle: %s", candidatePath)
|
|
|
|
|
newAppPath = candidatePath
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
@@ -374,11 +373,11 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
return fmt.Errorf("no .app bundle found in DMG at %s", mountPoint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug.Printf("Found app to install: %s", newAppPath)
|
|
|
|
|
log.Debugf("Found app to install: %s", newAppPath)
|
|
|
|
|
|
|
|
|
|
// Create backup of current app
|
|
|
|
|
backupPath := currentAppPath + ".backup"
|
|
|
|
|
debug.Printf("Creating backup: %s -> %s", currentAppPath, backupPath)
|
|
|
|
|
log.Debugf("Creating backup: %s -> %s", currentAppPath, backupPath)
|
|
|
|
|
|
|
|
|
|
// Remove old backup if it exists
|
|
|
|
|
if PathExists(backupPath) {
|
|
|
|
@@ -390,16 +389,16 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove current app
|
|
|
|
|
debug.Printf("Removing current app: %s", currentAppPath)
|
|
|
|
|
log.Debugf("Removing current app: %s", currentAppPath)
|
|
|
|
|
if err := os.RemoveAll(currentAppPath); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to remove current app: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy new app
|
|
|
|
|
debug.Printf("Installing new app: %s -> %s", newAppPath, currentAppPath)
|
|
|
|
|
log.Debugf("Installing new app: %s -> %s", newAppPath, currentAppPath)
|
|
|
|
|
if err := CopyDir(newAppPath, currentAppPath); err != nil {
|
|
|
|
|
// Try to restore backup on failure
|
|
|
|
|
debug.Printf("Installation failed, restoring backup")
|
|
|
|
|
log.Debugf("Installation failed, restoring backup")
|
|
|
|
|
os.RemoveAll(currentAppPath)
|
|
|
|
|
CopyDir(backupPath, currentAppPath)
|
|
|
|
|
return fmt.Errorf("failed to install new app: %v", err)
|
|
|
|
@@ -408,25 +407,25 @@ func InstallUpdate(dmgPath string) error {
|
|
|
|
|
// Fix executable permissions for the main binary
|
|
|
|
|
executablePath := filepath.Join(currentAppPath, "Contents", "MacOS", "epochsilicon")
|
|
|
|
|
if PathExists(executablePath) {
|
|
|
|
|
debug.Printf("Setting executable permissions for: %s", executablePath)
|
|
|
|
|
log.Debugf("Setting executable permissions for: %s", executablePath)
|
|
|
|
|
if err := os.Chmod(executablePath, 0755); err != nil {
|
|
|
|
|
debug.Printf("Warning: failed to set executable permissions: %v", err)
|
|
|
|
|
log.Debugf("Warning: failed to set executable permissions: %v", err)
|
|
|
|
|
// Don't fail the entire update for this, but log it
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debug.Printf("Warning: executable not found at expected path: %s", executablePath)
|
|
|
|
|
log.Debugf("Warning: executable not found at expected path: %s", executablePath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove backup on success
|
|
|
|
|
os.RemoveAll(backupPath)
|
|
|
|
|
|
|
|
|
|
debug.Printf("Update installed successfully")
|
|
|
|
|
log.Debugf("Update installed successfully")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestDMGMount tests DMG mounting and returns mount point and app path for debugging
|
|
|
|
|
func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
debug.Printf("Testing DMG mount: %s", dmgPath)
|
|
|
|
|
log.Debugf("Testing DMG mount: %s", dmgPath)
|
|
|
|
|
|
|
|
|
|
// Mount the DMG with verbose output to better parse mount point
|
|
|
|
|
mountCmd := exec.Command("hdiutil", "attach", dmgPath, "-nobrowse", "-plist")
|
|
|
|
@@ -435,7 +434,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
return "", "", fmt.Errorf("failed to mount DMG: %v, output: %s", err, string(mountOutput))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug.Printf("Mount output: %s", string(mountOutput))
|
|
|
|
|
log.Debugf("Mount output: %s", string(mountOutput))
|
|
|
|
|
|
|
|
|
|
// Parse mount output to get mount point
|
|
|
|
|
mountPoint := ""
|
|
|
|
@@ -460,7 +459,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
|
|
|
|
|
// Second try: use hdiutil info to get mount points if first method failed
|
|
|
|
|
if mountPoint == "" {
|
|
|
|
|
debug.Printf("First mount point detection failed, trying hdiutil info")
|
|
|
|
|
log.Debugf("First mount point detection failed, trying hdiutil info")
|
|
|
|
|
infoCmd := exec.Command("hdiutil", "info", "-plist")
|
|
|
|
|
infoOutput, infoErr := infoCmd.CombinedOutput()
|
|
|
|
|
if infoErr == nil {
|
|
|
|
@@ -481,7 +480,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
return "", "", fmt.Errorf("could not find mount point. Mount output: %s", string(mountOutput))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug.Printf("Using mount point: %s", mountPoint)
|
|
|
|
|
log.Debugf("Using mount point: %s", mountPoint)
|
|
|
|
|
|
|
|
|
|
// Find the app in the mounted DMG
|
|
|
|
|
var newAppPath string
|
|
|
|
@@ -492,7 +491,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
newAppPath = exactPath
|
|
|
|
|
} else {
|
|
|
|
|
// Search for any .app bundle in the mount point
|
|
|
|
|
debug.Printf("EpochSilicon.app not found at exact path, searching for .app bundles")
|
|
|
|
|
log.Debugf("EpochSilicon.app not found at exact path, searching for .app bundles")
|
|
|
|
|
entries, err := os.ReadDir(mountPoint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Unmount before returning error
|
|
|
|
@@ -503,7 +502,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
if entry.IsDir() && strings.HasSuffix(entry.Name(), ".app") {
|
|
|
|
|
candidatePath := filepath.Join(mountPoint, entry.Name())
|
|
|
|
|
debug.Printf("Found .app bundle: %s", candidatePath)
|
|
|
|
|
log.Debugf("Found .app bundle: %s", candidatePath)
|
|
|
|
|
newAppPath = candidatePath
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
@@ -511,7 +510,7 @@ func TestDMGMount(dmgPath string) (string, string, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unmount after testing
|
|
|
|
|
debug.Printf("Unmounting test DMG from: %s", mountPoint)
|
|
|
|
|
log.Debugf("Unmounting test DMG from: %s", mountPoint)
|
|
|
|
|
exec.Command("hdiutil", "detach", mountPoint, "-force").Run()
|
|
|
|
|
|
|
|
|
|
if newAppPath == "" {
|
|
|
|
@@ -531,14 +530,14 @@ func CompareFileWithBundledResource(filePath, resourceName string) bool {
|
|
|
|
|
// Get file info for the existing file
|
|
|
|
|
fileInfo, err := os.Stat(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
debug.Printf("Failed to stat file %s: %v", filePath, err)
|
|
|
|
|
log.Debugf("Failed to stat file %s: %v", filePath, err)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the bundled resource
|
|
|
|
|
resource, err := fyne.LoadResourceFromPath(resourceName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
debug.Printf("Failed to load bundled resource %s: %v", resourceName, err)
|
|
|
|
|
log.Debugf("Failed to load bundled resource %s: %v", resourceName, err)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -546,7 +545,7 @@ func CompareFileWithBundledResource(filePath, resourceName string) bool {
|
|
|
|
|
fileSize := fileInfo.Size()
|
|
|
|
|
resourceSize := int64(len(resource.Content()))
|
|
|
|
|
|
|
|
|
|
debug.Printf("Comparing file sizes - %s: %d bytes vs bundled %s: %d bytes",
|
|
|
|
|
log.Debugf("Comparing file sizes - %s: %d bytes vs bundled %s: %d bytes",
|
|
|
|
|
filePath, fileSize, resourceName, resourceSize)
|
|
|
|
|
|
|
|
|
|
return fileSize == resourceSize
|
|
|
|
|