start_windows.go 2.99 KB
Newer Older
1
2
3
4
5
6
package cmd

import (
	"context"
	"errors"
	"fmt"
7
	"log/slog"
8
9
	"os"
	"os/exec"
10
	"path"
11
12
13
	"path/filepath"
	"strings"
	"syscall"
14
	"unsafe"
15

16
	"github.com/ollama/ollama/api"
17
18
19
20
21
	"golang.org/x/sys/windows"
)

const (
	Installer = "OllamaSetup.exe"
22
23
24
)

func startApp(ctx context.Context, client *api.Client) error {
25
26
27
	if len(isProcRunning(Installer)) > 0 {
		return fmt.Errorf("upgrade in progress...")
	}
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
	AppName := "ollama app.exe"
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	appExe := filepath.Join(filepath.Dir(exe), AppName)
	_, err = os.Stat(appExe)
	if errors.Is(err, os.ErrNotExist) {
		// Try the standard install location
		localAppData := os.Getenv("LOCALAPPDATA")
		appExe = filepath.Join(localAppData, "Ollama", AppName)
		_, err := os.Stat(appExe)
		if errors.Is(err, os.ErrNotExist) {
			// Finally look in the path
			appExe, err = exec.LookPath(AppName)
			if err != nil {
Michael Yang's avatar
lint  
Michael Yang committed
44
				return errors.New("could not locate ollama app")
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
			}
		}
	}
	// log.Printf("XXX attempting to start app %s", appExe)

	cmd_path := "c:\\Windows\\system32\\cmd.exe"
	cmd := exec.Command(cmd_path, "/c", appExe)
	// TODO - these hide flags aren't working - still pops up a command window for some reason
	cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000, HideWindow: true}

	// TODO this didn't help either...
	cmd.Stdin = strings.NewReader("")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("unable to start ollama app %w", err)
	}

	if cmd.Process != nil {
		defer cmd.Process.Release() //nolint:errcheck
	}
	return waitForServer(ctx, client)
}
69
70
71
72
73
74
75
76

func isProcRunning(procName string) []uint32 {
	pids := make([]uint32, 2048)
	var ret uint32
	if err := windows.EnumProcesses(pids, &ret); err != nil || ret == 0 {
		slog.Debug("failed to check for running installers", "error", err)
		return nil
	}
77
78
79
80
81
82
83
84
85
86
	if ret > uint32(len(pids)) {
		pids = make([]uint32, ret+10)
		if err := windows.EnumProcesses(pids, &ret); err != nil || ret == 0 {
			slog.Debug("failed to check for running installers", "error", err)
			return nil
		}
	}
	if ret < uint32(len(pids)) {
		pids = pids[:ret]
	}
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
	var matches []uint32
	for _, pid := range pids {
		if pid == 0 {
			continue
		}
		hProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, pid)
		if err != nil {
			continue
		}
		defer windows.CloseHandle(hProcess)
		var module windows.Handle
		var cbNeeded uint32
		cb := (uint32)(unsafe.Sizeof(module))
		if err := windows.EnumProcessModules(hProcess, &module, cb, &cbNeeded); err != nil {
			continue
		}
		var sz uint32 = 1024 * 8
		moduleName := make([]uint16, sz)
		cb = uint32(len(moduleName)) * (uint32)(unsafe.Sizeof(uint16(0)))
		if err := windows.GetModuleBaseName(hProcess, module, &moduleName[0], cb); err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
			continue
		}
		exeFile := path.Base(strings.ToLower(syscall.UTF16ToString(moduleName)))
		if strings.EqualFold(exeFile, procName) {
			matches = append(matches, pid)
		}
	}
	return matches
}