26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
145 lines
3.8 KiB
Go
145 lines
3.8 KiB
Go
package update
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/daemon"
|
|
"github.com/kunchenguid/no-mistakes/internal/paths"
|
|
)
|
|
|
|
type daemonResetError struct {
|
|
err error
|
|
daemonOffline bool
|
|
}
|
|
|
|
func (e *daemonResetError) Error() string {
|
|
return e.err.Error()
|
|
}
|
|
|
|
func (e *daemonResetError) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
func (u *updater) ensureDaemonUsesCurrentExecutable() error {
|
|
if u == nil || u.paths == nil || u.executablePath == "" {
|
|
return nil
|
|
}
|
|
alive, err := daemonIsRunning(u.paths)
|
|
if err != nil || !alive {
|
|
return nil
|
|
}
|
|
runningPath, err := daemonExecutablePath(u.paths)
|
|
if err != nil || runningPath == "" {
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine daemon executable path: %w", err)
|
|
}
|
|
return errors.New("cannot determine daemon executable path")
|
|
}
|
|
currentPath := resolveExecutablePath(u.executablePath)
|
|
runningPath = resolveExecutablePath(runningPath)
|
|
if executablePathsMatch(currentPath, runningPath) {
|
|
return nil
|
|
}
|
|
if u.confirmDaemonTakeover(runningPath, currentPath) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("daemon is running from %s, but update is running from %s; run update using the same binary that started the daemon, or restart the daemon from this binary first", runningPath, currentPath)
|
|
}
|
|
|
|
func (u *updater) confirmDaemonTakeover(runningPath, currentPath string) bool {
|
|
if u.assumeYes {
|
|
fmt.Fprintf(u.stderrWriter(), "daemon is running from %s, but update is running from %s; replacing the running daemon because -y was provided\n", runningPath, currentPath)
|
|
return true
|
|
}
|
|
|
|
fmt.Fprintf(u.stderrWriter(), "daemon is running from %s, but update is running from %s\n", runningPath, currentPath)
|
|
fmt.Fprint(u.stderrWriter(), "Replace the running daemon with this binary? [y/N] ")
|
|
return readYes(u.stdin)
|
|
}
|
|
|
|
func executablePathsMatch(a, b string) bool {
|
|
if currentGOOS != "windows" {
|
|
return a == b
|
|
}
|
|
a = filepath.Clean(strings.ReplaceAll(a, `\`, "/"))
|
|
b = filepath.Clean(strings.ReplaceAll(b, `\`, "/"))
|
|
return strings.EqualFold(a, b)
|
|
}
|
|
|
|
func defaultResetDaemon(p *paths.Paths) error {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
alive, err := daemonIsRunning(p)
|
|
if err == nil && !alive && !daemonArtifactsExist(p) {
|
|
return nil
|
|
}
|
|
if err := daemonStop(p); err != nil {
|
|
return fmt.Errorf("stop daemon: %w", err)
|
|
}
|
|
if err := daemonStart(p); err != nil {
|
|
running, checkErr := daemonIsRunning(p)
|
|
offline := checkErr == nil && !running
|
|
return &daemonResetError{err: fmt.Errorf("start daemon: %w", err), daemonOffline: offline}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func daemonArtifactsExist(p *paths.Paths) bool {
|
|
for _, path := range []string{p.Socket(), p.PIDFile()} {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func runningDaemonExecutablePath(p *paths.Paths) (string, error) {
|
|
if p == nil {
|
|
return "", fmt.Errorf("resolve daemon executable: nil paths")
|
|
}
|
|
pid, err := daemon.ReadPID(p)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve daemon executable: %w", err)
|
|
}
|
|
path, err := executablePathForPID(pid)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve daemon executable: %w", err)
|
|
}
|
|
if path == "" {
|
|
return "", fmt.Errorf("resolve daemon executable: empty process command")
|
|
}
|
|
return resolveExecutablePath(path), nil
|
|
}
|
|
|
|
func executablePathForPID(pid int) (string, error) {
|
|
if currentGOOS == "linux" {
|
|
return os.Readlink(filepath.Join("/proc", strconv.Itoa(pid), "exe"))
|
|
}
|
|
if currentGOOS == "windows" {
|
|
return windowsExecutablePathForPID(pid)
|
|
}
|
|
cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(out)), nil
|
|
}
|
|
|
|
func resolveExecutablePath(path string) string {
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
if resolved, err := filepath.EvalSymlinks(path); err == nil {
|
|
return resolved
|
|
}
|
|
return path
|
|
}
|