app_windows.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "log/slog"
  8. "os"
  9. "os/exec"
  10. "os/signal"
  11. "path/filepath"
  12. "strings"
  13. "syscall"
  14. "github.com/jmorganca/ollama/app/lifecycle"
  15. "github.com/jmorganca/ollama/app/store"
  16. "github.com/jmorganca/ollama/app/tray"
  17. "github.com/jmorganca/ollama/app/updater"
  18. )
  19. func init() {
  20. AppName += ".exe"
  21. CLIName += ".exe"
  22. // Logs, configs, downloads go to LOCALAPPDATA
  23. localAppData := os.Getenv("LOCALAPPDATA")
  24. AppDataDir = filepath.Join(localAppData, "Ollama")
  25. AppLogFile = filepath.Join(AppDataDir, "app.log")
  26. ServerLogFile = filepath.Join(AppDataDir, "server.log")
  27. // Executables are stored in APPDATA
  28. AppDir = filepath.Join(localAppData, "Programs", "Ollama")
  29. // Make sure we have PATH set correctly for any spawned children
  30. paths := strings.Split(os.Getenv("PATH"), ";")
  31. // Start with whatever we find in the PATH/LD_LIBRARY_PATH
  32. found := false
  33. for _, path := range paths {
  34. d, err := filepath.Abs(path)
  35. if err != nil {
  36. continue
  37. }
  38. if strings.EqualFold(AppDir, d) {
  39. found = true
  40. }
  41. }
  42. if !found {
  43. paths = append(paths, AppDir)
  44. pathVal := strings.Join(paths, ";")
  45. slog.Debug("setting PATH=" + pathVal)
  46. err := os.Setenv("PATH", pathVal)
  47. if err != nil {
  48. slog.Error(fmt.Sprintf("failed to update PATH: %s", err))
  49. }
  50. }
  51. // Make sure our logging dir exists
  52. _, err := os.Stat(AppDataDir)
  53. if errors.Is(err, os.ErrNotExist) {
  54. if err := os.MkdirAll(AppDataDir, 0o755); err != nil {
  55. slog.Error(fmt.Sprintf("create ollama dir %s: %v", AppDataDir, err))
  56. }
  57. }
  58. }
  59. func ShowLogs() {
  60. cmd_path := "c:\\Windows\\system32\\cmd.exe"
  61. slog.Debug(fmt.Sprintf("viewing logs with start %s", AppDataDir))
  62. cmd := exec.Command(cmd_path, "/c", "start", AppDataDir)
  63. cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false, CreationFlags: 0x08000000}
  64. err := cmd.Start()
  65. if err != nil {
  66. slog.Error(fmt.Sprintf("Failed to open log dir: %s", err))
  67. }
  68. }
  69. func Start() {
  70. cmd_path := "c:\\Windows\\system32\\cmd.exe"
  71. slog.Debug(fmt.Sprintf("viewing logs with start %s", AppDataDir))
  72. cmd := exec.Command(cmd_path, "/c", "start", AppDataDir)
  73. cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false, CreationFlags: 0x08000000}
  74. err := cmd.Start()
  75. if err != nil {
  76. slog.Error(fmt.Sprintf("Failed to open log dir: %s", err))
  77. }
  78. }
  79. func run() {
  80. initLogging()
  81. slog.Info("ollama windows app started")
  82. ctx, cancel := context.WithCancel(context.Background())
  83. var done chan int
  84. t, err := tray.NewTray()
  85. if err != nil {
  86. log.Fatalf("Failed to start: %s", err)
  87. }
  88. callbacks := t.GetCallbacks()
  89. signals := make(chan os.Signal, 1)
  90. signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
  91. go func() {
  92. slog.Debug("starting callback loop")
  93. for {
  94. select {
  95. case <-callbacks.Quit:
  96. slog.Debug("quit called")
  97. t.Quit()
  98. case <-signals:
  99. slog.Debug("shutting down due to signal")
  100. t.Quit()
  101. case <-callbacks.Update:
  102. err := updater.DoUpgrade(cancel, done)
  103. if err != nil {
  104. slog.Warn(fmt.Sprintf("upgrade attempt failed: %s", err))
  105. }
  106. case <-callbacks.ShowLogs:
  107. ShowLogs()
  108. case <-callbacks.DoFirstUse:
  109. err := lifecycle.GetStarted()
  110. if err != nil {
  111. slog.Warn(fmt.Sprintf("Failed to launch getting started shell: %s", err))
  112. }
  113. }
  114. }
  115. }()
  116. if !store.GetFirstTimeRun() {
  117. slog.Debug("First time run")
  118. err = t.DisplayFirstUseNotification()
  119. if err != nil {
  120. slog.Debug(fmt.Sprintf("XXX failed to display first use notification %v", err))
  121. }
  122. store.SetFirstTimeRun(true)
  123. } else {
  124. slog.Debug("Not first time, skipping first run notification")
  125. }
  126. if isServerRunning(ctx) {
  127. slog.Info("Detected another instance of ollama running, exiting")
  128. os.Exit(1)
  129. }
  130. done, err = SpawnServer(ctx, CLIName)
  131. if err != nil {
  132. // TODO - should we retry in a backoff loop?
  133. // TODO - should we pop up a warning and maybe add a menu item to view application logs?
  134. slog.Error(fmt.Sprintf("Failed to spawn ollama server %s", err))
  135. done = make(chan int, 1)
  136. done <- 1
  137. }
  138. updater.StartBackgroundUpdaterChecker(ctx, t.UpdateAvailable)
  139. t.Run()
  140. cancel()
  141. slog.Info("Waiting for ollama server to shutdown...")
  142. if done != nil {
  143. <-done
  144. }
  145. slog.Info("Ollama app exiting")
  146. }