diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 190f94d47..572762141 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -83,9 +83,11 @@ const ( func main() { var reset bool var showVersion bool + var doUpgrade bool flag.StringVar(&confDir, "home", getDefaultConfDir(), "Set configuration directory") flag.BoolVar(&reset, "reset", false, "Prepare to resync from cluster") flag.BoolVar(&showVersion, "version", false, "Show version") + flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade") flag.Usage = usageFor(flag.CommandLine, usage, extraUsage) flag.Parse() @@ -99,6 +101,14 @@ func main() { return } + if doUpgrade { + err := upgrade() + if err != nil { + fatalln(err) + } + return + } + if len(os.Getenv("GOGC")) == 0 { debug.SetGCPercent(25) } diff --git a/cmd/syncthing/upgrade_unix.go b/cmd/syncthing/upgrade_unix.go new file mode 100644 index 000000000..eeb0352ae --- /dev/null +++ b/cmd/syncthing/upgrade_unix.go @@ -0,0 +1,146 @@ +// +build !windows + +package main + +import ( + "archive/tar" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + "strings" + + "bitbucket.org/kardianos/osext" +) + +type githubRelease struct { + Tag string `json:"tag_name"` + Prelease bool `json:"prerelease"` + Assets []githubAsset `json:"assets"` +} + +type githubAsset struct { + URL string `json:"url"` + Name string `json:"name"` +} + +func upgrade() error { + path, err := osext.Executable() + if err != nil { + return err + } + + resp, err := http.Get("https://api.github.com/repos/calmh/syncthing/releases?per_page=1") + if err != nil { + return err + } + + var rels []githubRelease + json.NewDecoder(resp.Body).Decode(&rels) + resp.Body.Close() + + if len(rels) != 1 { + return fmt.Errorf("Unexpected number of releases: %d", len(rels)) + } + rel := rels[0] + + if rel.Tag > Version { + infof("Attempting upgrade to %s...", rel.Tag) + } else if rel.Tag == Version { + okf("Already running the latest version, %s. Not upgrading.", Version) + return nil + } else { + okf("Current version %s is newer than latest release %s. Not upgrading.", Version, rel.Tag) + return nil + } + + expectedRelease := fmt.Sprintf("syncthing-%s-%s-%s.", runtime.GOOS, runtime.GOARCH, rel.Tag) + for _, asset := range rel.Assets { + if strings.HasPrefix(asset.Name, expectedRelease) { + if strings.HasSuffix(asset.Name, ".tar.gz") { + infof("Downloading %s...", asset.Name) + fname, err := readTarGZ(asset.URL) + if err != nil { + return err + } + + old := path + "." + Version + err = os.Rename(path, old) + if err != nil { + return err + } + err = os.Rename(fname, path) + if err != nil { + return err + } + + okf("Upgraded %q to %s.", path, rel.Tag) + okf("Previous version saved in %q.", old) + + return nil + } + } + } + + return nil +} + +func readTarGZ(url string) (string, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + + req.Header.Add("Accept", "application/octet-stream") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return "", err + } + + tr := tar.NewReader(gr) + if err != nil { + return "", err + } + + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + // end of tar archive + break + } + if err != nil { + return "", err + } + + if path.Base(hdr.Name) == "syncthing" { + fname := filepath.Join(os.TempDir(), "syncthing.new") + of, err := os.Create(fname) + if err != nil { + return "", err + } + io.Copy(of, tr) + err = of.Close() + if err != nil { + os.Remove(fname) + return "", err + } + + os.Chmod(fname, os.FileMode(hdr.Mode)) + return fname, nil + } + } + + return "", fmt.Errorf("No upgrade found") +} diff --git a/cmd/syncthing/upgrade_windows.go b/cmd/syncthing/upgrade_windows.go new file mode 100644 index 000000000..3d0eaf5c4 --- /dev/null +++ b/cmd/syncthing/upgrade_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package main + +func upgrade() { + fatalln("Upgrade currently unsupported on Windows") +}