123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- package main
- import (
- "context"
- "errors"
- "fmt"
- "log"
- "log/slog"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "strings"
- "syscall"
- "github.com/jmorganca/ollama/app/lifecycle"
- "github.com/jmorganca/ollama/app/store"
- "github.com/jmorganca/ollama/app/tray"
- "github.com/jmorganca/ollama/app/updater"
- )
- func init() {
- AppName += ".exe"
- CLIName += ".exe"
- // Logs, configs, downloads go to LOCALAPPDATA
- localAppData := os.Getenv("LOCALAPPDATA")
- AppDataDir = filepath.Join(localAppData, "Ollama")
- AppLogFile = filepath.Join(AppDataDir, "app.log")
- ServerLogFile = filepath.Join(AppDataDir, "server.log")
- // Executables are stored in APPDATA
- AppDir = filepath.Join(localAppData, "Programs", "Ollama")
- // Make sure we have PATH set correctly for any spawned children
- paths := strings.Split(os.Getenv("PATH"), ";")
- // Start with whatever we find in the PATH/LD_LIBRARY_PATH
- found := false
- for _, path := range paths {
- d, err := filepath.Abs(path)
- if err != nil {
- continue
- }
- if strings.EqualFold(AppDir, d) {
- found = true
- }
- }
- if !found {
- paths = append(paths, AppDir)
- pathVal := strings.Join(paths, ";")
- slog.Debug("setting PATH=" + pathVal)
- err := os.Setenv("PATH", pathVal)
- if err != nil {
- slog.Error(fmt.Sprintf("failed to update PATH: %s", err))
- }
- }
- // Make sure our logging dir exists
- _, err := os.Stat(AppDataDir)
- if errors.Is(err, os.ErrNotExist) {
- if err := os.MkdirAll(AppDataDir, 0o755); err != nil {
- slog.Error(fmt.Sprintf("create ollama dir %s: %v", AppDataDir, err))
- }
- }
- }
- func ShowLogs() {
- cmd_path := "c:\\Windows\\system32\\cmd.exe"
- slog.Debug(fmt.Sprintf("viewing logs with start %s", AppDataDir))
- cmd := exec.Command(cmd_path, "/c", "start", AppDataDir)
- cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false, CreationFlags: 0x08000000}
- err := cmd.Start()
- if err != nil {
- slog.Error(fmt.Sprintf("Failed to open log dir: %s", err))
- }
- }
- func Start() {
- cmd_path := "c:\\Windows\\system32\\cmd.exe"
- slog.Debug(fmt.Sprintf("viewing logs with start %s", AppDataDir))
- cmd := exec.Command(cmd_path, "/c", "start", AppDataDir)
- cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false, CreationFlags: 0x08000000}
- err := cmd.Start()
- if err != nil {
- slog.Error(fmt.Sprintf("Failed to open log dir: %s", err))
- }
- }
- func run() {
- initLogging()
- slog.Info("ollama windows app started")
- ctx, cancel := context.WithCancel(context.Background())
- var done chan int
- t, err := tray.NewTray()
- if err != nil {
- log.Fatalf("Failed to start: %s", err)
- }
- callbacks := t.GetCallbacks()
- signals := make(chan os.Signal, 1)
- signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
- go func() {
- slog.Debug("starting callback loop")
- for {
- select {
- case <-callbacks.Quit:
- slog.Debug("quit called")
- t.Quit()
- case <-signals:
- slog.Debug("shutting down due to signal")
- t.Quit()
- case <-callbacks.Update:
- err := updater.DoUpgrade(cancel, done)
- if err != nil {
- slog.Warn(fmt.Sprintf("upgrade attempt failed: %s", err))
- }
- case <-callbacks.ShowLogs:
- ShowLogs()
- case <-callbacks.DoFirstUse:
- err := lifecycle.GetStarted()
- if err != nil {
- slog.Warn(fmt.Sprintf("Failed to launch getting started shell: %s", err))
- }
- }
- }
- }()
- if !store.GetFirstTimeRun() {
- slog.Debug("First time run")
- err = t.DisplayFirstUseNotification()
- if err != nil {
- slog.Debug(fmt.Sprintf("XXX failed to display first use notification %v", err))
- }
- store.SetFirstTimeRun(true)
- } else {
- slog.Debug("Not first time, skipping first run notification")
- }
- if isServerRunning(ctx) {
- slog.Info("Detected another instance of ollama running, exiting")
- os.Exit(1)
- }
- done, err = SpawnServer(ctx, CLIName)
- if err != nil {
- // TODO - should we retry in a backoff loop?
- // TODO - should we pop up a warning and maybe add a menu item to view application logs?
- slog.Error(fmt.Sprintf("Failed to spawn ollama server %s", err))
- done = make(chan int, 1)
- done <- 1
- }
- updater.StartBackgroundUpdaterChecker(ctx, t.UpdateAvailable)
- t.Run()
- cancel()
- slog.Info("Waiting for ollama server to shutdown...")
- if done != nil {
- <-done
- }
- slog.Info("Ollama app exiting")
- }
|