Restoring the PowerShell helper put it in update.go, which is shared — so a Linux build would have tried to run `powershell` to relaunch itself. It compiled and vetted cleanly for linux/amd64, which is exactly why it needed catching before somebody built it: the fault only shows on a real update, on a machine that has no PowerShell. scheduleRelaunch now lives in the platform files. Windows keeps the helper. Linux starts the new binary directly, which is right there and not a compromise: nothing holds an executable open while it runs, so the swap has already succeeded, and there is no mutex to race — the single-instance guard is an flock the dying process releases as it exits, and the new one waits for our pid first. The two guards were looking at the old location and had to follow: the Wait-Process/Start-Process check moves into relaunch_windows_test.go where it belongs, and TestEveryRelaunchPassesItsPid now scans updateswap_linux.go too — the direct spawn moved there, and without it the test would have gone quiet again. Checked from Windows, as BUILDING-LINUX.md says is done at every release: GOOS=linux go build ./... and go vet ./... both clean.
64 lines
2.7 KiB
Go
64 lines
2.7 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"hamlog/internal/applog"
|
|
)
|
|
|
|
// clearDownloadMark has nothing to clear on Linux: there is no
|
|
// mark-of-the-web, and no SmartScreen to refuse a programmatic launch.
|
|
func clearDownloadMark(path string) {}
|
|
|
|
// makeExecutable restores the executable bit. A binary downloaded over HTTP
|
|
// arrives 0644 — on Windows the extension decides and this is a no-op, but here
|
|
// a freshly installed OpsLog that nothing can exec is a dead station.
|
|
func makeExecutable(path string) error { return os.Chmod(path, 0o755) }
|
|
|
|
// scheduleDeferredSwap is the fallback for when the running binary could not be
|
|
// renamed out of the way — the path Windows needs a detached PowerShell helper
|
|
// for, because nothing there can move a file over a running image.
|
|
//
|
|
// On Linux it should never be reached. A rename only touches the directory
|
|
// entry, and the running process holds the inode, so replacing the binary of a
|
|
// live process is ordinary and the staging rename in DownloadAndApplyUpdate
|
|
// succeeds. If it did fail, the cause was the filesystem (read-only mount, no
|
|
// write permission on the directory, a full disk) and no helper would get past
|
|
// it either — so do the honest thing: try the move once more now, and say
|
|
// plainly what is wrong if it still refuses.
|
|
func (a *App) scheduleDeferredSwap(exe, pending string) error {
|
|
if err := os.Rename(pending, exe); err != nil {
|
|
return fmt.Errorf("install the new build: %w (the folder %s must be writable)", err, filepath.Dir(exe))
|
|
}
|
|
if err := makeExecutable(exe); err != nil {
|
|
return fmt.Errorf("make the new build executable: %w", err)
|
|
}
|
|
applog.Printf("update: installed %s over %s after the staging rename failed", filepath.Base(pending), filepath.Base(exe))
|
|
return nil
|
|
}
|
|
|
|
// scheduleRelaunch starts the new build. On Linux that is simply the new binary.
|
|
//
|
|
// None of the Windows machinery applies: nothing holds an executable open while
|
|
// it runs, so the swap above already succeeded, and there is no mutex to race —
|
|
// the single-instance guard is an flock the dying process releases as it exits,
|
|
// and the new one waits for our pid before trying it. No helper, no script, and
|
|
// nothing that needs to outlive us.
|
|
func (a *App) scheduleRelaunch(exe, dir string) error {
|
|
cmd := relaunchCmd(exe, "--post-update", "--wait-pid", strconv.Itoa(os.Getpid()))
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("schedule relaunch: %w", err)
|
|
}
|
|
applog.Printf("update: relaunch started as pid %d — it waits for this process (pid %d) to exit",
|
|
cmd.Process.Pid, os.Getpid())
|
|
// Released rather than waited on: this process is about to exit, and a child
|
|
// that outlives its parent must not be left as a zombie.
|
|
_ = cmd.Process.Release()
|
|
return nil
|
|
}
|