Repository: syncthing/syncthing (language: go)

Issue:
Trying to launch Syncthing when already running resets cert.pem to 0 bytes
### What happened?

1. Start Syncthing.
2. Try to start Syncthing again using the same `--config` (or `--home`) directory.
3. The new instance fails to start with the following error:

```
[start] 2025/04/12 01:58:55.751711 main.go:432: INFO: syncthing v2.0.0-beta.6 "Hafnium Hornet" (go1.24.2 windows-amd64) builder@github.syncthing.net 2025-04-07 09:43:08 UTC [stnoupgrade]
[start] 2025/04/12 01:58:55.751711 utils.go:64: INFO: Generating ECDSA key and certificate for syncthing...
[start] 2025/04/12 01:58:55.753718 main.go:440: WARNING: Failed to load/generate certificate: save cert: write R:\test\syncthing\syncthing1\cert.pem: The process cannot access the file because another process has locked a portion of the file.
[monitor] 2025/04/12 01:58:55.755224 monitor.go:199: INFO: Syncthing exited: exit status 1
```

4. Despite that, the `cert.pem` file has been wiped out and has a size of 0 bytes.

Tested under Windows 10 x64.

Ref 1: https://github.com/syncthing/syncthing/commit/7762e39fb3ad90a13034d43039112583cd0f514b
Ref 2: https://forum.syncthing.net/t/syncthing-device-id-keeps-switching/24147

### Syncthing version

v1.29.4 and beta v2

### Platform & operating system

Windows

### Browser version

_No response_

### Relevant log output

```shell

```


Discussion / hints:
Sweet this seems like rather the opposite of what locking is supposed to accomplish

Fix patch:
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 1faff628ef3..943d11dc87d 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -376,7 +376,7 @@ func (options serveOptions) Run() error {
 	if options.Upgrade {
 		release, err := checkUpgrade()
 		if err == nil {
-			lf := flock.New(locations.Get(locations.CertFile))
+			lf := flock.New(locations.Get(locations.LockFile))
 			locked, err := lf.TryLock()
 			if err != nil {
 				l.Warnln("Upgrade:", err)
@@ -386,6 +386,8 @@ func (options serveOptions) Run() error {
 			} else {
 				err = upgrade.To(release)
 			}
+			_ = lf.Unlock()
+			_ = os.Remove(locations.Get(locations.LockFile))
 		}
 		if err != nil {
 			l.Warnln("Upgrade:", err)
@@ -546,7 +548,7 @@ func syncthingMain(options serveOptions) {
 	}
 
 	// Ensure we are the only running instance
-	lf := flock.New(locations.Get(locations.CertFile))
+	lf := flock.New(locations.Get(locations.LockFile))
 	locked, err := lf.TryLock()
 	if err != nil {
 		l.Warnln("Failed to acquire lock:", err)
@@ -692,6 +694,10 @@ func syncthingMain(options serveOptions) {
 		pprof.StopCPUProfile()
 	}
 
+	// Best effort remove lockfile, doesn't matter if it succeeds
+	_ = lf.Unlock()
+	_ = os.Remove(locations.Get(locations.LockFile))
+
 	os.Exit(int(status))
 }
 
diff --git a/lib/locations/locations.go b/lib/locations/locations.go
index 197c686eb67..bbf11d91961 100644
--- a/lib/locations/locations.go
+++ b/lib/locations/locations.go
@@ -33,6 +33,7 @@ const (
 	AuditLog      LocationEnum = "auditLog"
 	GUIAssets     LocationEnum = "guiAssets"
 	DefFolder     LocationEnum = "defFolder"
+	LockFile      LocationEnum = "lockFile"
 )
 
 type BaseDirEnum string
@@ -124,6 +125,7 @@ var locationTemplates = map[LocationEnum]string{
 	AuditLog:      "${data}/audit-%{timestamp}.log",
 	GUIAssets:     "${config}/gui",
 	DefFolder:     "${userHome}/Sync",
+	LockFile:      "${data}/syncthing.lock",
 }
 
 var locations = make(map[LocationEnum]string)


Test patch: