Files
OpsLog/BUILDING-LINUX.md
T
rouggy 13fd2367bd docs(linux): name the two things that bit a first Debian 13 build
Both found on a real trixie box, which is the first time any of this has
run on Linux.

Go can be too NEW, and that is the harder failure to meet cold. The
Wails CLI parses this package with the golang.org/x/tools its own go.mod
pins, and that release cannot read the export data a newer compiler
writes. With go1.27 — which is simply what go.dev offers as current —
the build dies with

  internal error: package "math" without types was imported from
  "hamlog/internal/geo"

naming neither Go nor Wails nor anything an operator could act on. The
setup script now says so when it sees 1.27 or later, and the doc tells
people to take 1.26 rather than the latest. Every version in both is
1.26.3, which is what the Windows build uses.

And the sound-server check was asking the wrong question. It ran `pactl
info`, but pactl comes from pulseaudio-utils, a package that has nothing
to do with whether a server is running — so a Debian 13 desktop with
PipeWire working perfectly was told it had no sound server. OpsLog
speaks the PulseAudio protocol itself and connects to the native socket,
so the socket is what the check looks for now, with pactl used only to
print the server's name when it happens to be there. It also says to run
from the desktop session rather than over SSH, the socket belonging to
the logged-in session.
2026-09-11 09:46:01 +02:00

4.9 KiB

Building OpsLog on Linux

OpsLog is developed on Windows. The Linux build shares every line of the frontend and all but a handful of Go files; what differs is listed at the bottom of this page.

It cannot be cross-compiled from Windows. Wails links against the system WebKit on Linux, which needs cgo and the GTK/WebKit headers, so the binary has to be produced on a Linux machine (or a container). What can be checked from Windows — and is, at every release — is that the Go half still compiles:

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./...
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go vet ./...

The short way

./scripts/linux-setup.sh

It checks everything below, prints the one install command your distribution needs if something is missing, and builds when nothing is. The rest of this page is what it checks, for when you would rather do it by hand.

Dependencies

# Debian / Ubuntu
sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev nodejs npm

# Fedora
sudo dnf install gcc-c++ pkgconf-pkg-config gtk3-devel webkit2gtk4.1-devel nodejs npm

# Arch
sudo pacman -S base-devel pkgconf gtk3 webkit2gtk-4.1 nodejs npm

Go and node do not come from the package manager. No current distribution ships a Go new enough for go.mod (Debian 12 has 1.19, Debian 13 has 1.24, Ubuntu 24.04 / Mint 22 have 1.22), and Ubuntu 22.04 / Mint 21 ship node 12 where Vite needs 18. Both are the usual reason a first build fails with an error that points somewhere else entirely.

Take 1.26, not the latest. Go can also be too NEW. The Wails CLI parses this package with the golang.org/x/tools its own go.mod pins, and that release cannot read the export data a newer compiler writes — so a fresh go1.27 fails the build with something that names neither Go nor Wails:

internal error: package "math" without types was imported from "hamlog/internal/geo"

Met on Debian 13, where go.dev simply offers 1.27 as the current release. 1.25 and 1.26 are what this repository is built with. If you already installed a newer one, replace it and reinstall the CLI so it is rebuilt:

# Go, from go.dev
wget https://go.dev/dl/go1.26.3.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.26.3.linux-amd64.tar.gz
echo 'export PATH=/usr/local/go/bin:$HOME/go/bin:$PATH' >> ~/.profile   # log out and back in

# node 20, only if `node -v` is below 18
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install nodejs

Then the Wails CLI:

go install github.com/wailsapp/wails/v2/cmd/[email protected]
wails doctor          # says what is still missing

libwebkit2gtk-4.0 also works; pass -tags webkit2_40 to wails build if your distribution only has the older one.

Build

wails build           # → build/bin/OpsLog
./build/bin/OpsLog

wails dev works the same as on Windows.

Runtime requirements

  • PulseAudio or PipeWire for the voice keyer, the QSO recorder and the CW decoder. PipeWire is fine — OpsLog speaks the PulseAudio protocol, which pipewire-pulse answers. Without a sound server those three features report "cannot reach the sound server" and everything else works normally.

  • Serial port access for CAT, keyers, rotators and amplifiers. Ports appear as /dev/ttyUSB0, /dev/ttyACM0… and on most distributions belong to the dialout group:

    sudo usermod -aG dialout $USER      # log out and back in
    

    This is the single most common reason a rig that works in WSJT-X shows "permission denied" in OpsLog.

  • TrustedQSL (tqsl) for LoTW uploads, from your package manager. OpsLog finds it on PATH.

Where OpsLog keeps its data

Next to the binary, in data/ — the same portable layout as on Windows, so a folder in your home directory carries the logbook with it.

If the binary sits somewhere you cannot write (/usr/bin, /opt), OpsLog uses ~/.local/share/OpsLog/data instead and says so in startup.log. The startup log itself lives in ~/.cache/OpsLog/startup.log.

What is different from the Windows build

OmniRig Not available — it is Windows COM automation. Use a native backend instead: Icom CI-V (USB and network), Yaesu, Kenwood/Elecraft, FlexRadio, TCI, Xiegu.
Denkovi USB relay Not available — it needs FTDI's ftd2xx.dll. The other relay backends work.
Audio PulseAudio/PipeWire instead of WASAPI. Same devices, same fixed 16 kHz mono format.
Auto-update Works, and is simpler: Linux lets a running binary be replaced, so none of the Windows deferred-swap machinery is needed.
Window placement OpsLog cannot read the monitor layout, so a saved window position is always trusted rather than clamped onto a visible screen.
Single instance An flock on $XDG_RUNTIME_DIR/OpsLog/instance.lock instead of a named mutex. It cannot raise the existing window, only refuse to start a second one.