cmd/syncthing: Do auto-upgrade before startup (fixes #6384) (#6385)

This commit is contained in:
Simon Frei 2020-03-16 08:12:32 +01:00 committed by GitHub
parent 16698b12b1
commit c101a04179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 108 additions and 55 deletions

View File

@ -139,10 +139,12 @@ The following are valid values for the STTRACE variable:
%s`
)
// Environment options
var (
// Environment options
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
)
type RuntimeOptions struct {
@ -359,14 +361,24 @@ func main() {
}
if options.doUpgradeCheck {
checkUpgrade()
if _, err := checkUpgrade(); err != nil {
l.Warnln("Checking for upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
return
}
if options.doUpgrade {
release := checkUpgrade()
performUpgrade(release)
return
release, err := checkUpgrade()
if err == nil {
err = performUpgrade(release)
}
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
l.Infof("Upgraded to %q", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
if options.resetDatabase {
@ -462,45 +474,47 @@ func debugFacilities() string {
return b.String()
}
func checkUpgrade() upgrade.Release {
type errNoUpgrade struct {
current, latest string
}
func (e errNoUpgrade) Error() string {
return fmt.Sprintf("no upgrade available (current %q >= latest %q).", e.current, e.latest)
}
func checkUpgrade() (upgrade.Release, error) {
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
opts := cfg.Options()
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
return upgrade.Release{}, err
}
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
noUpgradeMessage := "No upgrade available (current %q >= latest %q)."
l.Infof(noUpgradeMessage, build.Version, release.Tag)
os.Exit(syncthing.ExitNoUpgradeAvailable.AsInt())
return upgrade.Release{}, errNoUpgrade{build.Version, release.Tag}
}
l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
return release
return release, nil
}
func performUpgrade(release upgrade.Release) {
func performUpgradeDirect(release upgrade.Release) error {
// Use leveldb database locks to protect against concurrent upgrades
_, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err == nil {
err = upgrade.To(release)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infof("Upgraded to %q", release.Tag)
} else {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Syncthing upgrading")
os.Exit(syncthing.ExitUpgrade.AsInt())
if _, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto); err != nil {
return errConcurrentUpgrade
}
return upgrade.To(release)
}
func performUpgrade(release upgrade.Release) error {
if err := performUpgradeDirect(release); err != nil {
if err != errConcurrentUpgrade {
return err
}
l.Infoln("Attempting upgrade through running Syncthing...")
return upgradeViaRest()
}
return nil
}
func upgradeViaRest() error {
@ -568,6 +582,45 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
os.Exit(syncthing.ExitError.AsInt())
}
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
opts.AutoUpgradeIntervalH = 12
// Set the option into the config as well, as the auto upgrade
// loop expects to read a valid interval from there.
cfg.SetOptions(opts)
cfg.Save()
}
// We don't tweak the user's choice of upgrading to pre-releases or
// not, as otherwise they cannot step off the candidate channel.
}
// Check if auto-upgrades should be done and if yes, do an initial
// upgrade immedately. The auto-upgrade routine can only be started
// later after App is initialised.
shouldAutoUpgrade := shouldUpgrade(cfg, runtimeOptions)
if shouldAutoUpgrade {
// Try to do upgrade directly
release, err := checkUpgrade()
if err == nil {
if err = performUpgradeDirect(release); err == nil {
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
}
// Log the error if relevant.
if err != nil {
if _, ok := err.(errNoUpgrade); !ok {
l.Infoln("Initial automatic upgrade:", err)
}
}
}
if runtimeOptions.unpaused {
setPauseState(cfg, false)
} else if runtimeOptions.paused {
@ -592,6 +645,10 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
if shouldAutoUpgrade {
go autoUpgrade(cfg, app, evLogger)
}
setupSignalHandling(app)
if len(os.Getenv("GOMAXPROCS")) == 0 {
@ -614,31 +671,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
go standbyMonitor(app)
}
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
opts.AutoUpgradeIntervalH = 12
// Set the option into the config as well, as the auto upgrade
// loop expects to read a valid interval from there.
cfg.SetOptions(opts)
cfg.Save()
}
// We don't tweak the user's choice of upgrading to pre-releases or
// not, as otherwise they cannot step off the candidate channel.
}
if opts := cfg.Options(); opts.AutoUpgradeIntervalH > 0 {
if runtimeOptions.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
} else {
go autoUpgrade(cfg, app, evLogger)
}
}
if err := app.Start(); err != nil {
os.Exit(syncthing.ExitError.AsInt())
}
@ -771,6 +803,20 @@ func standbyMonitor(app *syncthing.App) {
}
}
func shouldUpgrade(cfg config.Wrapper, runtimeOptions RuntimeOptions) bool {
if upgrade.DisabledByCompilation {
return false
}
if opts := cfg.Options(); opts.AutoUpgradeIntervalH < 0 {
return false
}
if runtimeOptions.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
return false
}
return true
}
func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
timer := time.NewTimer(0)
sub := evLogger.Subscribe(events.DeviceConnected)
@ -893,3 +939,10 @@ func setPauseState(cfg config.Wrapper, paused bool) {
os.Exit(syncthing.ExitError.AsInt())
}
}
func exitCodeForUpgrade(err error) int {
if _, ok := err.(errNoUpgrade); ok {
return syncthing.ExitNoUpgradeAvailable.AsInt()
}
return syncthing.ExitError.AsInt()
}