Several operators hit "stage current exe: rename …\OpsLog.exe …\OpsLog.exe.old:
Accès refusé" and could not update again. Two separate causes, both ours to
handle.
The staging name was fixed. os.Rename replaces its target, so a single leftover
".old" that could not be deleted — a scanner holding it open is the usual
reason, and the pre-existing os.Remove was best-effort and ignored — made every
later update fail with that error, permanently, recoverable only by deleting the
file by hand. Staging now uses a unique ".old-<nanos>", which no leftover can
block, and the startup cleanup sweeps the pattern instead of one name.
Renaming a running image is legal on Windows, but some endpoint protection
(Bitdefender's ransomware remediation among them) blocks it outright, and no
retry gets past that. So the swap is deferred: the new build is parked beside
the old one and a detached helper moves it into place after this process exits,
when the file is no longer a running image. It keeps trying for ten seconds,
since a scanner tends to let go a beat after the process dies.
A short retry stays in front of both, for the ordinary case of a scanner holding
the file it has just watched being written.
If even the deferred move fails, OpsLog restarts on the CURRENT version rather
than leaving the operator with nothing — someone mid-QSO losing their logger is
worse than an update that waits — and only a successful swap passes
--post-update, so the download survives for the next attempt instead of being
swept by the cleanup.
The new build downloaded and OpsLog quit, but never came back. Two Windows
causes: the freshly written exe carried the internet Zone.Identifier mark, so
SmartScreen wanted to prompt "are you sure you want to open this?" — invisibly,
since we launch it programmatically — and silently blocked the launch; and
starting the new exe while the old one was still exiting raced the single-
instance mutex.
Now the swapped exe's Zone.Identifier stream is removed, and the relaunch is
done by a detached, hidden PowerShell that Wait-Process's on our PID (so we're
fully gone and the mutex is free) before Start-Process'ing the new exe.
CheckForUpdate now also resolves the release's downloadable asset (portable
.exe, or a .zip to unpack). DownloadAndApplyUpdate streams it with progress
events (update:progress 0-100), swaps it in for the running exe (rename the
current one to .old — allowed for a running image on Windows — move the new one
into place, roll back on failure), then relaunches with --post-update and quits.
main: a --post-update relaunch retries the single-instance mutex for ~20 s so
the fresh process waits for the old one to exit and free it, then clears the
.old exe left behind. Falls back to opening the release page when a release has
no auto-download asset.