167 lines
4.1 KiB
Go
167 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/ollama/ollama/app/lifecycle"
|
|
"github.com/ollama/ollama/app/store"
|
|
"github.com/ollama/ollama/app/tray"
|
|
"github.com/ollama/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")
|
|
}
|