diff --git a/internal/app/base/base.go b/internal/app/base/base.go
index 1a11ffa8..372f6c88 100644
--- a/internal/app/base/base.go
+++ b/internal/app/base/base.go
@@ -57,7 +57,6 @@ import (
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
- "github.com/allan-simon/go-singleinstance"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
@@ -153,9 +152,9 @@ func New( //nolint:funlen
}
settingsObj := settings.New(settingsPath)
- lock, err := singleinstance.CreateLockFile(locations.GetLockFile())
+ lock, err := checkSingleInstance(locations.GetLockFile(), settingsObj)
if err != nil {
- logrus.Warnf("%v is already running", appName)
+ logrus.WithError(err).Warnf("%v is already running", appName)
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
}
diff --git a/internal/app/base/restart_test.go b/internal/app/base/restart_test.go
index 419e19d0..9e704f02 100644
--- a/internal/app/base/restart_test.go
+++ b/internal/app/base/restart_test.go
@@ -21,7 +21,9 @@ import (
"strings"
"testing"
+ "github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestIncrementRestartFlag(t *testing.T) {
@@ -47,3 +49,15 @@ func TestIncrementRestartFlag(t *testing.T) {
})
}
}
+
+func TestVersionLessThan(t *testing.T) {
+ r := require.New(t)
+
+ old := semver.MustParse("1.1.0")
+ current := semver.MustParse("1.1.1")
+ newer := semver.MustParse("1.1.2")
+
+ r.True(old.LessThan(current))
+ r.False(current.LessThan(current))
+ r.False(newer.LessThan(current))
+}
diff --git a/internal/app/base/singleinstance_unix.go b/internal/app/base/singleinstance_unix.go
new file mode 100644
index 00000000..535db8eb
--- /dev/null
+++ b/internal/app/base/singleinstance_unix.go
@@ -0,0 +1,101 @@
+// Copyright (c) 2022 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+//go:build !windows
+// +build !windows
+
+package base
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/Masterminds/semver/v3"
+ "github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
+ "github.com/ProtonMail/proton-bridge/v2/internal/constants"
+ "github.com/allan-simon/go-singleinstance"
+ "golang.org/x/sys/unix"
+)
+
+// checkSingleInstance returns error if a bridge instance is already running
+// This instance should be stop and window of running window should be brought
+// to focus.
+//
+// For macOS and Linux when already running version is older than this instance
+// it will kill old and continue with this new bridge (i.e. no error returned).
+func checkSingleInstance(lockFilePath string, settingsObj *settings.Settings) (*os.File, error) {
+ if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil {
+ // Bridge is not runnig, continue normally
+ return lock, nil
+ }
+
+ if err := runningVersionIsOlder(settingsObj); err != nil {
+ return nil, err
+ }
+
+ pid, err := getPID(lockFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := unix.Kill(pid, unix.SIGTERM); err != nil {
+ return nil, err
+ }
+
+ // Need to wait some time to release file lock
+ time.Sleep(time.Second)
+
+ return singleinstance.CreateLockFile(lockFilePath)
+}
+
+func getPID(lockFilePath string) (int, error) {
+ file, err := os.Open(filepath.Clean(lockFilePath))
+ if err != nil {
+ return 0, err
+ }
+ defer func() { _ = file.Close() }()
+
+ rawPID := make([]byte, 10) // PID is probably up to 7 digits long, 10 should be enough
+ n, err := file.Read(rawPID)
+ if err != nil {
+ return 0, err
+ }
+
+ return strconv.Atoi(strings.TrimSpace(string(rawPID[:n])))
+}
+
+func runningVersionIsOlder(settingsObj *settings.Settings) error {
+ currentVer, err := semver.StrictNewVersion(constants.Version)
+ if err != nil {
+ return err
+ }
+
+ runningVer, err := semver.StrictNewVersion(settingsObj.Get(settings.LastVersionKey))
+ if err != nil {
+ return err
+ }
+
+ if !runningVer.LessThan(currentVer) {
+ return errors.New("running version is not older")
+ }
+
+ return nil
+}
diff --git a/internal/app/base/singleinstance_windows.go b/internal/app/base/singleinstance_windows.go
new file mode 100644
index 00000000..d0c34752
--- /dev/null
+++ b/internal/app/base/singleinstance_windows.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2022 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+//go:build windows
+// +build windows
+
+package base
+
+import (
+ "os"
+
+ "github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
+ "github.com/allan-simon/go-singleinstance"
+)
+
+func checkSingleInstance(lockFilePath string, _ *settings.Settings) (*os.File, error) {
+ return singleinstance.CreateLockFile(lockFilePath)
+}