minio/selfupdate

repository·master·Indexed 21 days ago

https://github.com/minio/selfupdate

A Go package for implementing secure self-updating capabilities in Go programs and single-file executables. It supports cross-platform binary updates, binary patching via the bsdiff algorithm to reduce download sizes, and security features including checksum verification and code signing verification using minisign.

Tokens
3.6K
Snippets
14
Records
20
Agent score
74%

What's inside selfupdate

  1. Overview of selfupdate features

    master

    The selfupdate package allows developers to build secure, self-updating Go programs or other single-file targets. It supports various update workflows, including auto-updates and manual user-initiated updates.

    Key capabilities include:

    • Cross-platform support: Works on multiple platforms, including Windows.
    • Binary patching: Reduces download sizes by applying patches instead of full binaries.
    • Security: Supports checksum verification and code signing verification to ensure binary integrity.
    • Flexibility: Can be used to update arbitrary files, not just the running executable.
  2. Use binarydist for binary diff and patch operations

    master
    The binarydist package provides functionality to perform binary diffing and patching. It implements the algorithm described in the bsdiff specification, making it compatible with standard bsdiff tools. You can use this package to generate differences between files and apply those differences to reconstruct a target file.
  3. Find the current executable and its folder using osext

    master

    The osext package provides extensions to the standard os package to reliably locate the currently running executable file. This is useful for performing self-upgrades or locating resources relative to the binary.

    It is preferred over using os.Args[0] or the current working directory, as both can be unreliable or manipulated. The package provides multi-platform support for:

    • Linux
    • OS X
    • Windows
    • Plan 9
    • BSDs.
  4. Update a Go program from a URL

    master

    You can implement self-updating functionality by fetching a new binary from a URL and applying it using selfupdate.Apply. This method replaces the current executable with the content provided in the reader. Ensure you handle the HTTP response body and provide appropriate error handling for the update process.

    import (
        "fmt"
        "net/http"
    
        "github.com/minio/selfupdate"
    )
    
    func doUpdate(url string) error {
        resp, err := http.Get(url)
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        err = selfupdate.Apply(resp.Body, selfupdate.Options{})
        if err != nil {
            // error handling
        }
        return err
    }
  5. Configure selfupdate.Options

    master

    The selfupdate.Options struct allows you to customize the update process. Key fields include:

    • Patcher: An implementation of the Patcher interface used to apply binary patches (e.g., selfupdate.NewBSDiffPatcher()). Use this when shipping patches instead of full binaries to save bandwidth.
    • Hash: The hash function used for checksum verification. It defaults to crypto.SHA256.
    • Checksum: A byte slice representing the expected checksum of the new binary. This is used to verify the integrity of the update.
  6. Configure update behavior with Options

    master

    The Options struct defines how the update is processed and where files are placed.

    FieldDescription
    TargetPathPath to the file to update. If empty, it defaults to the current running executable.
    TargetModeFile permissions for the new binary. Defaults to 0755 if set to 0.
    ChecksumA byte slice containing the expected checksum of the new binary.
    VerifierAn implementation of the Verifier interface for signature verification.
    HashThe crypto.Hash function to use for checksumming. Defaults to crypto.SHA256 if not set.
    PatcherAn implementation of the Patcher interface. If provided, the update reader is treated as a patch applied to the existing binary rather than a full replacement.
    OldSavePathIf provided, the old executable is moved here after a successful update. If empty, the old executable is removed (or hidden on Windows).
  7. Verify updates with checksums

    master

    To ensure the integrity of the downloaded binary, provide a checksum in selfupdate.Options. By default, selfupdate uses crypto.SHA256 to validate the file.

    import (
    	"crypto"
    	_ "crypto/sha256"
    	"encoding/hex"
    	"io"
    	"github.com/minio/selfupdate"
    )
    
    func updateWithChecksum(binary io.Reader, hexChecksum string) error {
    	checksum, err := hex.DecodeString(hexChecksum)
    	if err != nil {
    		return err
    	}
    
    	err = selfupdate.Apply(binary, selfupdate.Options{
    		Hash:     crypto.SHA256, // default
    		Checksum: checksum,
    	})
    	if err != nil {
    		// handle error
    	}
    	return err
    }
  8. Update using a binary patch

    master

    To reduce download sizes, you can ship binary patches instead of full binaries. You must provide a Patcher in the selfupdate.Options. The selfupdate package provides a NewBSDiffPatcher() for bsdiff format patches.

    import (
    	"io"
    	"github.com/minio/selfupdate"
    )
    
    func updateWithPatch(patch io.Reader) error {
    	err := selfupdate.Apply(patch, selfupdate.Options{
    		Patcher: selfupdate.NewBSDiffPatcher(),
    	})
    	if err != nil {
    		// handle error
    	}
    	return err
    }
  9. Handle failed rollbacks with RollbackError()

    master

    When Apply or CommitBinary fails, the library attempts to restore the original binary by renaming the .old file back to the target path. If this rollback attempt itself fails, the filesystem is left in an inconsistent state (the original binary is gone and the new one was not placed).

    Use RollbackError(err) to inspect the error. If it returns a non-nil error, the update process failed to clean up after itself, and manual intervention is required.

  10. Update a program using selfupdate.Apply

    master

    The primary way to update a program is by calling selfupdate.Apply. This function takes an io.Reader containing the new binary (or patch) and a selfupdate.Options struct. If the update fails, you can use selfupdate.RollbackError(err) to check if the error is related to a failed rollback attempt, allowing you to handle failed updates gracefully.

    import (
    	"fmt"
    	"net/http"
    	"github.com/minio/selfupdate"
    )
    
    func doUpdate(url string) error {
    	// request the new file
    	resp, err := http.Get(url)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    
    	// Apply the update from the response body
    	err = selfupdate.Apply(resp.Body, selfupdate.Options{})
    	if err != nil {
    		if rerr := selfupdate.RollbackError(err); rerr != nil {
    			fmt.Printf("Failed to rollback from bad update: %v\n", rerr)
    		}
    	}
    	return err
    }
  11. Use Verifier to verify binary signatures with minisign

    master

    The Verifier struct provides a high-level interface for verifying binary data using minisign signatures. You can initialize a verifier, load signature and public key data from various sources (URL, file, or raw bytes), and then call Verify against a byte slice representing the binary content.

    Note that the passphrase argument in the loading methods is used to unmarshal the minisign.PublicKey.

    import "github.com/minio/selfupdate"
    
    // Initialize a new verifier
    verifier := selfupdate.NewVerifier()
    
    // Load signature and public key from a file
    // The passphrase parameter is used to unmarshal the public key
    err := verifier.LoadFromFile("path/to/signature.minisign", "public-key-content")
    if err != nil {
        panic(err)
    }
    
    // Verify the binary content
    binContent := []byte("...")
    err = verifier.Verify(binContent)
    if err != nil {
        // Handle verification failure (e.g., "selfupdate: signature verification failed")
        panic(err)
    }
  12. Check update permissions with CheckPermissions()

    master

    Before attempting an update, call CheckPermissions() to verify if the current process has sufficient rights to write to the target directory and create the necessary temporary files. It attempts to create and then immediately remove a dummy file (.<filename>.check-perm) in the target directory to validate write access.

    opts := selfupdate.Options{TargetPath: "/usr/local/bin/myapp"}
    if err := opts.CheckPermissions(); err != nil {
        log.Fatalf("Insufficient permissions to perform update: %v", err)
    }