github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/agent/install.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime"
     7  
     8  	"github.com/google/uuid"
     9  
    10  	"github.com/mutagen-io/mutagen/pkg/filesystem"
    11  	"github.com/mutagen-io/mutagen/pkg/logging"
    12  	"github.com/mutagen-io/mutagen/pkg/prompting"
    13  )
    14  
    15  // Install installs the current binary to the appropriate location for an agent
    16  // binary with the current Mutagen version.
    17  func Install() error {
    18  	// Compute the destination.
    19  	destination, err := installPath()
    20  	if err != nil {
    21  		return fmt.Errorf("unable to compute agent destination: %w", err)
    22  	}
    23  
    24  	// Compute the path to the current executable.
    25  	executablePath, err := os.Executable()
    26  	if err != nil {
    27  		return fmt.Errorf("unable to determine executable path: %w", err)
    28  	}
    29  
    30  	// Relocate the current executable to the installation path.
    31  	if err = filesystem.Rename(nil, executablePath, nil, destination, true); err != nil {
    32  		return fmt.Errorf("unable to relocate agent executable: %w", err)
    33  	}
    34  
    35  	// Success.
    36  	return nil
    37  }
    38  
    39  // install attempts to probe an endpoint and install the appropriate agent
    40  // binary over the specified transport.
    41  func install(logger *logging.Logger, transport Transport, prompter string) error {
    42  	// Detect the target platform.
    43  	goos, goarch, posix, err := probe(transport, prompter)
    44  	if err != nil {
    45  		return fmt.Errorf("unable to probe remote platform: %w", err)
    46  	}
    47  
    48  	// Find the appropriate agent binary. Ensure that it's cleaned up when we're
    49  	// done with it.
    50  	if err := prompting.Message(prompter, "Extracting agent..."); err != nil {
    51  		return fmt.Errorf("unable to message prompter: %w", err)
    52  	}
    53  	agentExecutable, err := ExecutableForPlatform(goos, goarch, "")
    54  	if err != nil {
    55  		return fmt.Errorf("unable to get agent for platform: %w", err)
    56  	}
    57  	defer os.Remove(agentExecutable)
    58  
    59  	// Copy the agent to the remote. We use a unique identifier for the
    60  	// temporary destination. For Windows remotes, we add a ".exe" suffix, which
    61  	// will automatically make the file executable on the remote (POSIX systems
    62  	// are handled separately below). For POSIX systems, we add a dot prefix to
    63  	// hide the executable.
    64  	if err := prompting.Message(prompter, "Copying agent..."); err != nil {
    65  		return fmt.Errorf("unable to message prompter: %w", err)
    66  	}
    67  	randomUUID, err := uuid.NewRandom()
    68  	if err != nil {
    69  		return fmt.Errorf("unable to generate UUID for agent copying: %w", err)
    70  	}
    71  	destination := BaseName + randomUUID.String()
    72  	if goos == "windows" {
    73  		destination += ".exe"
    74  	}
    75  	if posix {
    76  		destination = "." + destination
    77  	}
    78  	if err = transport.Copy(agentExecutable, destination); err != nil {
    79  		return fmt.Errorf("unable to copy agent binary: %w", err)
    80  	}
    81  
    82  	// For cases where we're copying from a Windows system to a POSIX remote,
    83  	// invoke "chmod +x" to add executability back to the copied binary. This is
    84  	// necessary under the specified circumstances because as soon as the agent
    85  	// binary is extracted from the bundle, it will lose its executability bit
    86  	// since Windows can't preserve this. This will also be applied to Windows
    87  	// POSIX remotes, but a "chmod +x" there will just be a no-op.
    88  	if runtime.GOOS == "windows" && posix {
    89  		if err := prompting.Message(prompter, "Setting agent executability..."); err != nil {
    90  			return fmt.Errorf("unable to message prompter: %w", err)
    91  		}
    92  		executabilityCommand := fmt.Sprintf("chmod +x %s", destination)
    93  		if err := run(transport, executabilityCommand); err != nil {
    94  			return fmt.Errorf("unable to set agent executability: %w", err)
    95  		}
    96  	}
    97  
    98  	// Invoke the remote installation.
    99  	if err := prompting.Message(prompter, "Installing agent..."); err != nil {
   100  		return fmt.Errorf("unable to message prompter: %w", err)
   101  	}
   102  	var installCommand string
   103  	if posix {
   104  		installCommand = fmt.Sprintf("./%s %s", destination, CommandInstall)
   105  	} else {
   106  		installCommand = fmt.Sprintf("%s %s", destination, CommandInstall)
   107  	}
   108  	if err := run(transport, installCommand); err != nil {
   109  		return fmt.Errorf("unable to invoke agent installation: %w", err)
   110  	}
   111  
   112  	// Success.
   113  	return nil
   114  }