8 Commits
Author SHA1 Message Date
rouggyandClaude Opus 5 8b1dff581b feat(linux): the Go half of OpsLog builds for Linux
Measured rather than guessed: the whole repository was cross-compiled for
linux/amd64 and the gaps closed one by one. There were fewer than expected.

Flex and TCI were never Windows-specific — they carried //go:build windows by
inheritance and import nothing but net and gorilla/websocket. Untagged, no code
change. The two backends a Linux operator is most likely to own were already
portable.

Audio was 560 lines, not 2287: only devices.go and engine.go touch WASAPI, while
manager.go, recorder.go, wav.go and mp3.go were pure Go wearing the tag by
association. The whole platform surface is seven functions, now implemented a
second time on PulseAudio through github.com/jfreymuth/pulse — pure Go over the
server socket, so the no-cgo rule survives, and PipeWire answers the same
protocol. The fixed 16 kHz mono format and the server-side resampling mirror
what AUTOCONVERTPCM does on Windows, for the same reason.

OmniRig is the only real loss, and its backend still EXISTS off Windows rather
than being compiled out of app.go: a settings database is portable, so an
operator moving a profile across keeps "omnirig" saved and must be told to pick
a native backend instead of meeting a nil one.

The parts where Linux is not Windows, and where a compile-only stub would have
been a silent bug:

  - data dir: still beside the binary, but ~/.local/share/OpsLog/data when that
    folder belongs to the system — decided by trying the write, because /opt and
    /usr/local are writable on some stations and not others.
  - single instance: an flock, not a pid file. The kernel drops it however the
    process dies, so a crash leaves nothing to delete by hand. This is the guard
    that stops two instances fighting over the rig frequency.
  - update: simpler here. Unix renames over a running binary, so the deferred
    swap the Windows path needs a detached helper for is unreachable.
  - tasklist/taskkill become /proc and SIGTERM; the boot log moves out of /tmp,
    which is wiped exactly when the evidence is wanted.
  - serial ports sorted naturally: /dev/ttyUSB10 was landing between USB1 and
    USB2, the same trap COM10 fell into.

release.ps1 now cross-builds and vets for linux before it builds the exe, and
refuses the release if that fails — a port rots one unguarded x/sys/windows call
at a time.

Nothing has been executed on Linux yet: Wails needs webkit2gtk and cgo there, so
the binary must be built on Linux. scripts/linux-setup.sh checks the machine and
does it; BUILDING-LINUX.md is the manual version.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-09 10:21:27 +02:00
rouggy e0b110392a fix(update): wait for the old process, not for a fixed window
The relaunch after an update stopped working, and the regression is mine:
removing the PowerShell helper — which is what Defender was reading as a
dropper — also removed the wait it was doing. Nothing took over the job.

The numbers made it certain rather than unlucky. The instance being
replaced is allowed THIRTY seconds to shut down (armExitWatchdog forces
it out at that point) because it closes a remote logbook, a CAT session
and sometimes a backup. The new instance was patient with the
single-instance mutex for TWENTY. On any station where shutting down ran
past that, the new process gave up and exited in silence: no window after
an update, and the previous OpsLog still in the task manager. Exactly the
report.

Both relaunch paths now pass --wait-pid, and the new process waits on
that process's handle — a plain kernel wait, which ends the instant the
old one ends, however long or short that is, and looks nothing like a
script starting another program. The mutex retry stays as a backstop and
goes to forty-five seconds, so it is longer than the wait it exists for
rather than shorter.

And when the old process really has not gone, the message says that
instead of "OpsLog is already running" — after an update the operator did
not start a second copy, and what they need to know is which one to
close.

A test keeps the two spawn sites honest: a relaunch added without
--wait-pid is this bug again.
2026-09-08 09:18:06 +02:00
rouggy 0430aab78e fix(update): relaunch without a hidden PowerShell
Windows Defender removed 0.27.14 from a station as
Trojan:Script/Wacatac.H!ml. That detection is machine-learning, not a
signature, and the behaviour it scored is ours: an unsigned binary
replaces itself on disk, clears the mark-of-the-web, and spawns a
windowless PowerShell that waits for its own process to die before
starting another executable. Byte for byte, that is a dropper; the model
reads the shape, not the intention, and "Script/" names the PowerShell.

The wait it was written for is not needed. --post-update already makes
the new instance patient with the single-instance mutex — twenty seconds
of it — so the new exe can be started directly while this one is still
shutting down and simply wait its turn.

The deferred-swap fallback keeps its helper: nothing else on a stock
Windows can wait for a pid and then move a file over an image that is
still running. It is reached only when the rename failed, never on the
ordinary path.
2026-09-07 09:36:02 +02:00
rouggy c0dc1bfcc4 feat(qsl): credit line at the foot of the default QSL e-mail
"Designed & sent by OpsLog — https://github.com/GregTroar/OpsLog/releases/latest"
now closes the default body, so a fresh install sends it without being asked.

Put in the TEMPLATE, not appended at send time. Two consequences, both of them
the point: an operator who does not want it deletes the line once and it is
gone, and one who has already written their own body never sees it appear —
a stored template is returned verbatim and the default is only the fallback.
Appending it at send would have made it unremovable, which is not a thing to do
to somebody's outgoing mail.

The link is the human release page. updateCheckURL sits next to it in update.go
and answers JSON, which is the easy mistake here — a correspondent who clicks
the credit gets a download page, and a test says so.
2026-08-16 11:07:06 +02:00
rouggy 17819ea673 fix(update): survive an exe that cannot be renamed
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.
2026-08-09 15:38:50 +02:00
rouggy d327db3f57 fix: auto-update relaunch — clear Mark-of-the-Web + wait for exit
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.
2026-07-19 19:53:19 +02:00
rouggy 5d0906f00e feat: in-app auto-updater — download, swap and relaunch
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.
2026-07-19 18:14:56 +02:00
rouggy 957182611d feat: check for available updates 2026-06-16 19:52:23 +02:00