67 lines
2.8 KiB
Go
67 lines
2.8 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// relaunchAfterUpdate restarts this bastion after a self-update swapped its
|
|
// binary — because on Windows nothing else will.
|
|
//
|
|
// update.go ends a successful swap with os.Exit(0), and its comment says why
|
|
// that is enough: "systemd Restart=always re-execs the path, now the new
|
|
// binary". True on Linux. On Windows there IS no supervisor — deploy/install.ps1
|
|
// installs the exe, puts it on PATH, and registers nothing with the Service
|
|
// Control Manager. So every Windows self-update has been a clean, silent
|
|
// shutdown: the update applies, the process exits, and the machine stops
|
|
// heartbeating until someone notices and starts it by hand.
|
|
//
|
|
// Measured on a real desktop updating 0.23.0 -> 0.23.1: the heartbeat
|
|
// stopped at the moment of the swap and the box went dark. It had just
|
|
// been anchored, and because the daemon never came back the anchor never reached
|
|
// the host either — the machine looked simply unanchored, with nothing anywhere
|
|
// saying a process had exited. That is the worst part: the failure has no
|
|
// symptom except absence.
|
|
//
|
|
// So on Windows we start the new binary ourselves before exiting. DETACHED_PROCESS
|
|
// plus CREATE_NEW_PROCESS_GROUP so the child does not die with this console, and
|
|
// it is deliberately NOT inheriting our handles — a child holding the parent's
|
|
// stdio keeps the invoking terminal from ever returning.
|
|
//
|
|
// Best-effort, and it must stay that way: if the relaunch cannot start, the
|
|
// update itself already succeeded and the binary on disk is the new one. Report
|
|
// it and exit rather than pretending, so the operator knows to start it.
|
|
//
|
|
// NOT AS A SERVICE. A networked install runs as a Windows service
|
|
// (sysservice_windows.go), and there the Service Control Manager is the
|
|
// supervisor: the exit that follows ends the process without reporting a stop,
|
|
// which the manager counts as a failure and answers with a restart of the new
|
|
// binary. A detached copy started here would be a second bastion outside the
|
|
// manager, racing the one it is about to start.
|
|
func relaunchAfterUpdate() {
|
|
if runningAsService {
|
|
return
|
|
}
|
|
self, err := os.Executable()
|
|
if err != nil {
|
|
fmt.Printf("self-update: cannot find my own path to restart (%v) — start it again with `privatae-bastion run`\n", err)
|
|
return
|
|
}
|
|
cmd := exec.Command(self, os.Args[1:]...)
|
|
cmd.SysProcAttr = &windows.SysProcAttr{
|
|
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
fmt.Printf("self-update: could not restart automatically (%v) — the new binary is installed, start it with `privatae-bastion run`\n", err)
|
|
return
|
|
}
|
|
fmt.Printf("self-update: restarted as pid %d\n", cmd.Process.Pid)
|
|
// Do NOT Wait: the child is meant to outlive this process.
|
|
_ = cmd.Process.Release()
|
|
}
|