From 1d6268645392bb67a7a8ea1eb00de4c3da905485 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:08:03 +0000 Subject: [PATCH 1/4] Fix CLI update for running binaries Co-Authored-By: jhaynie@agentuity.com --- internal/util/upgrade.go | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/util/upgrade.go b/internal/util/upgrade.go index 48d53609..48becf29 100644 --- a/internal/util/upgrade.go +++ b/internal/util/upgrade.go @@ -308,6 +308,18 @@ func replaceBinary(ctx context.Context, logger logger.Logger, assetPath, version } if err := checkWritePermission(currentExe); err != nil { + if strings.Contains(err.Error(), "binary is currently running") { + var updateErr error + updateAction := func() { + updateErr = updateRunningBinary(currentExe, binaryPath, fileMode) + } + tui.ShowSpinner("Setting up background update...", updateAction) + if updateErr != nil { + return fmt.Errorf("failed to set up background update: %w", updateErr) + } + tui.ShowSuccess("Successfully scheduled update to %s. The update will complete when this process exits.", version) + return nil + } return fmt.Errorf("insufficient permissions to update binary: %w", err) } @@ -369,6 +381,9 @@ func checkWritePermission(filePath string) error { file, err := os.OpenFile(filePath, os.O_WRONLY, info.Mode()) if err != nil { + if strings.Contains(err.Error(), "text file busy") { + return fmt.Errorf("binary is currently running: %w", err) + } return err } file.Close() @@ -376,6 +391,37 @@ func checkWritePermission(filePath string) error { return nil } +func updateRunningBinary(currentExe, newBinary string, fileMode os.FileMode) error { + dir := filepath.Dir(currentExe) + tmpBinary := filepath.Join(dir, ".agentuity.new") + oldBinary := filepath.Join(dir, ".agentuity.old") + + if err := copyFile(newBinary, tmpBinary); err != nil { + return fmt.Errorf("failed to copy new binary to temp location: %w", err) + } + + if err := os.Chmod(tmpBinary, fileMode); err != nil { + return fmt.Errorf("failed to set permissions on new binary: %w", err) + } + + script := fmt.Sprintf(`#!/bin/sh +# Wait for the process to exit +sleep 1 +# Perform the update +mv "%s" "%s" +mv "%s" "%s" +rm "%s" +`, currentExe, oldBinary, tmpBinary, currentExe, oldBinary) + + updateScript := filepath.Join(dir, ".agentuity_updater.sh") + if err := os.WriteFile(updateScript, []byte(script), 0755); err != nil { + return fmt.Errorf("failed to create update script: %w", err) + } + + cmd := exec.Command("sh", "-c", fmt.Sprintf("nohup %s > /dev/null 2>&1 &", updateScript)) + return cmd.Start() +} + func extractBinary(ctx context.Context, logger logger.Logger, assetPath, extractDir string) string { var extractErr error var binaryPath string From 6b661c7943ab9ae7b1541d68b14e9671685985d2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:10:49 +0000 Subject: [PATCH 2/4] Fix undefined fileMode variable Co-Authored-By: jhaynie@agentuity.com --- internal/util/upgrade.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/util/upgrade.go b/internal/util/upgrade.go index 48becf29..2a4660bd 100644 --- a/internal/util/upgrade.go +++ b/internal/util/upgrade.go @@ -307,6 +307,12 @@ func replaceBinary(ctx context.Context, logger logger.Logger, assetPath, version return fmt.Errorf("failed to extract binary") } + info, err := os.Stat(currentExe) + if err != nil { + return fmt.Errorf("failed to get file info: %w", err) + } + fileMode := info.Mode() + if err := checkWritePermission(currentExe); err != nil { if strings.Contains(err.Error(), "binary is currently running") { var updateErr error @@ -323,12 +329,6 @@ func replaceBinary(ctx context.Context, logger logger.Logger, assetPath, version return fmt.Errorf("insufficient permissions to update binary: %w", err) } - info, err := os.Stat(currentExe) - if err != nil { - return fmt.Errorf("failed to get file info: %w", err) - } - fileMode := info.Mode() - var replaceErr error replaceAction := func() { if err := copyFile(binaryPath, currentExe); err != nil { From ca9060ddbbe4908b752f1de8a3faf124b3bbb5bc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:15:47 +0000 Subject: [PATCH 3/4] Add cleanupUpdateFiles function for Windows compatibility and script cleanup Co-Authored-By: jhaynie@agentuity.com --- internal/util/upgrade.go | 77 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/internal/util/upgrade.go b/internal/util/upgrade.go index 2a4660bd..6aaeb300 100644 --- a/internal/util/upgrade.go +++ b/internal/util/upgrade.go @@ -391,10 +391,26 @@ func checkWritePermission(filePath string) error { return nil } +// cleanupUpdateFiles removes any leftover temporary files from previous update attempts +func cleanupUpdateFiles(dir string) { + tmpFiles := []string{ + filepath.Join(dir, ".agentuity.new"), + filepath.Join(dir, ".agentuity.old"), + filepath.Join(dir, ".agentuity_updater.sh"), + filepath.Join(dir, ".agentuity_updater.ps1"), + } + + for _, file := range tmpFiles { + _ = os.Remove(file) + } +} + func updateRunningBinary(currentExe, newBinary string, fileMode os.FileMode) error { dir := filepath.Dir(currentExe) tmpBinary := filepath.Join(dir, ".agentuity.new") oldBinary := filepath.Join(dir, ".agentuity.old") + + cleanupUpdateFiles(dir) if err := copyFile(newBinary, tmpBinary); err != nil { return fmt.Errorf("failed to copy new binary to temp location: %w", err) @@ -404,22 +420,69 @@ func updateRunningBinary(currentExe, newBinary string, fileMode os.FileMode) err return fmt.Errorf("failed to set permissions on new binary: %w", err) } - script := fmt.Sprintf(`#!/bin/sh + if runtime.GOOS == "windows" { + script := fmt.Sprintf(` +$currentExe = "%s" +$oldBinary = "%s" +$tmpBinary = "%s" +$updateScript = $MyInvocation.MyCommand.Path + +# Wait for the process to exit (give it a moment) +Start-Sleep -Seconds 1 + +# Perform the update +try { + # Move current binary to old + if (Test-Path $currentExe) { + Move-Item -Path $currentExe -Destination $oldBinary -Force + } + + # Move new binary to current location + Move-Item -Path $tmpBinary -Destination $currentExe -Force + + # Clean up old binary + if (Test-Path $oldBinary) { + Remove-Item -Path $oldBinary -Force + } + + # Clean up this script + Remove-Item -Path $updateScript -Force +} catch { + # If something goes wrong, try to restore the old binary + if (Test-Path $oldBinary) { + Move-Item -Path $oldBinary -Destination $currentExe -Force + } +} +`, currentExe, oldBinary, tmpBinary) + + updateScript := filepath.Join(dir, ".agentuity_updater.ps1") + if err := os.WriteFile(updateScript, []byte(script), 0644); err != nil { + return fmt.Errorf("failed to create update script: %w", err) + } + + cmd := exec.Command("powershell", "-WindowStyle", "Hidden", "-Command", + fmt.Sprintf("Start-Process powershell -ArgumentList '-WindowStyle Hidden -ExecutionPolicy Bypass -File \"%s\"' -WindowStyle Hidden", updateScript)) + return cmd.Start() + } else { + script := fmt.Sprintf(`#!/bin/sh # Wait for the process to exit sleep 1 # Perform the update mv "%s" "%s" mv "%s" "%s" rm "%s" +# Clean up this script +rm -- "$0" `, currentExe, oldBinary, tmpBinary, currentExe, oldBinary) - updateScript := filepath.Join(dir, ".agentuity_updater.sh") - if err := os.WriteFile(updateScript, []byte(script), 0755); err != nil { - return fmt.Errorf("failed to create update script: %w", err) - } + updateScript := filepath.Join(dir, ".agentuity_updater.sh") + if err := os.WriteFile(updateScript, []byte(script), 0755); err != nil { + return fmt.Errorf("failed to create update script: %w", err) + } - cmd := exec.Command("sh", "-c", fmt.Sprintf("nohup %s > /dev/null 2>&1 &", updateScript)) - return cmd.Start() + cmd := exec.Command("sh", "-c", fmt.Sprintf("nohup %s > /dev/null 2>&1 &", updateScript)) + return cmd.Start() + } } func extractBinary(ctx context.Context, logger logger.Logger, assetPath, extractDir string) string { From 7c43d1751c76523192565747c42831cd05a45d74 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:16:16 +0000 Subject: [PATCH 4/4] Fix formatting with go fmt Co-Authored-By: jhaynie@agentuity.com --- internal/util/upgrade.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/util/upgrade.go b/internal/util/upgrade.go index 6aaeb300..15d11c5e 100644 --- a/internal/util/upgrade.go +++ b/internal/util/upgrade.go @@ -409,7 +409,7 @@ func updateRunningBinary(currentExe, newBinary string, fileMode os.FileMode) err dir := filepath.Dir(currentExe) tmpBinary := filepath.Join(dir, ".agentuity.new") oldBinary := filepath.Join(dir, ".agentuity.old") - + cleanupUpdateFiles(dir) if err := copyFile(newBinary, tmpBinary); err != nil { @@ -460,7 +460,7 @@ try { return fmt.Errorf("failed to create update script: %w", err) } - cmd := exec.Command("powershell", "-WindowStyle", "Hidden", "-Command", + cmd := exec.Command("powershell", "-WindowStyle", "Hidden", "-Command", fmt.Sprintf("Start-Process powershell -ArgumentList '-WindowStyle Hidden -ExecutionPolicy Bypass -File \"%s\"' -WindowStyle Hidden", updateScript)) return cmd.Start() } else {