Skip to content

Commit

Permalink
fix: improve windows compatibility
Browse files Browse the repository at this point in the history
* Windows compatibility - Updated GetHomeDir() and notes for running with Admininistrator on Windows to create symlinks.

* fix: remove new image from git

* fix: remove new image from git

---------

Co-authored-by: Sam <[email protected]>
  • Loading branch information
southwolf and sammcj authored Nov 26, 2024
1 parent 925bf97 commit 6a49f90
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 26 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The application allows users to interactively select models, sort, filter, edit,
- [Key Bindings](#key-bindings)
- [Top](#top)
- [Inspect](#inspect)
- [Link](#link)
- [Command-line Options](#command-line-options)
- [Configuration](#configuration)
- [Installation and build from source](#installation-and-build-from-source)
Expand Down Expand Up @@ -128,6 +129,12 @@ Inspect (`i`)

![](screenshots/gollama-inspect.png)

#### Link

Link (`l`) and Link All (`L`)

Note: Requires Admin privileges if you're running Windows.

#### Command-line Options

- `-l`: List all available Ollama models and exit
Expand Down
22 changes: 7 additions & 15 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"time"

"github.com/fsnotify/fsnotify"
"github.com/sammcj/gollama/logging"
"github.com/sammcj/gollama/utils"
"github.com/spf13/viper"
)

Expand Down Expand Up @@ -72,24 +72,25 @@ func CreateDefaultConfig() error {
func LoadConfig() (Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), ".config", "gollama"))
// Dir of config file
viper.AddConfigPath(utils.GetConfigDir())

// Read the config file
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; create it
if err := CreateDefaultConfig(); err != nil {
// if the file already exists - return it, otherwise throw an error
if _, err := os.Stat(getConfigPath()); err == nil {
if _, err := os.Stat(utils.GetConfigPath()); err == nil {
return LoadConfig()
}
return Config{}, fmt.Errorf("failed to create default config: %w", err)
}
} else {
// if the config file is borked, recreate it and let the user know
if err := CreateDefaultConfig(); err != nil {
backupPath := getConfigPath() + ".borked." + time.Now().Format("2006-01-02")
if err := os.Rename(getConfigPath(), backupPath); err != nil {
backupPath := utils.GetConfigPath() + ".borked." + time.Now().Format("2006-01-02")
if err := os.Rename(utils.GetConfigPath(), backupPath); err != nil {
return Config{}, fmt.Errorf("failed to rename config file: %w", err)
}
if err := CreateDefaultConfig(); err != nil {
Expand Down Expand Up @@ -119,7 +120,7 @@ func SaveConfig(config Config) error {
viper.Set("sort_order", config.SortOrder)
}

configPath := getConfigPath()
configPath := utils.GetConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
Expand All @@ -141,12 +142,3 @@ func (c *Config) SaveIfModified() error {
func (c *Config) SetModified() {
c.modified = true
}

// getConfigPath returns the path to the configuration JSON file.
func getConfigPath() string {
homeDir, err := os.UserHomeDir()
if err != nil {
logging.ErrorLogger.Printf("Failed to get user home directory: %v\n", err)
}
return filepath.Join(homeDir, ".config", "gollama", "config.json")
}
8 changes: 4 additions & 4 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ func listModels(models []Model) {
os.Exit(1)
}

if len(models) == 0 {
fmt.Println("No models available to display.")
return
}
if len(models) == 0 {
fmt.Println("No models available to display.")
return
}

stripString := cfg.StripString
nameWidth, sizeWidth, quantWidth, modifiedWidth, idWidth, familyWidth := calculateColumnWidthsTerminal()
Expand Down
10 changes: 7 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/sammcj/gollama/config"
"github.com/sammcj/gollama/logging"
"github.com/sammcj/gollama/utils"
"github.com/sammcj/gollama/vramestimator"
)

Expand Down Expand Up @@ -309,10 +310,10 @@ func main() {
}

if *ollamaDirFlag == "" {
app.ollamaModelsDir = filepath.Join(os.Getenv("HOME"), ".ollama", "models")
app.ollamaModelsDir = filepath.Join(utils.GetHomeDir(), ".ollama", "models")
}
if *lmStudioDirFlag == "" {
app.lmStudioModelsDir = filepath.Join(os.Getenv("HOME"), ".cache", "lm-studio", "models")
app.lmStudioModelsDir = filepath.Join(utils.GetHomeDir(), ".cache", "lm-studio", "models")
}

if *listFlag {
Expand Down Expand Up @@ -345,13 +346,16 @@ func main() {
for _, model := range models {
// if cfg.LMStudioFilePaths is empty, use the default path in the user's home directory / .cache / lm-studio / models
if cfg.LMStudioFilePaths == "" {
cfg.LMStudioFilePaths = filepath.Join(os.Getenv("HOME"), ".cache", "lm-studio", "models")
cfg.LMStudioFilePaths = filepath.Join(utils.GetHomeDir(), ".cache", "lm-studio", "models")
}
message, err := linkModel(model.Name, cfg.LMStudioFilePaths, false, client)
logging.InfoLogger.Println(message)
fmt.Printf("Linking model %s to %s\n", model.Name, cfg.LMStudioFilePaths)
if err != nil {
logging.ErrorLogger.Printf("Error linking model %s: %v\n", model.Name, err)
fmt.Println("Error: Linking models failed. Please check if you are running without Administrator on Windows.")
fmt.Printf("Error detail: %v\n", err)
os.Exit(1)
} else {
logging.InfoLogger.Printf("Model %s linked\n", model.Name)
}
Expand Down
5 changes: 3 additions & 2 deletions operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ollama/ollama/api"
"github.com/sammcj/gollama/config"
"github.com/sammcj/gollama/logging"
"github.com/sammcj/gollama/utils"
)

func runModel(model string, cfg *config.Config) tea.Cmd {
Expand Down Expand Up @@ -584,7 +585,7 @@ func copyModelfile(modelName, newModelName string, client *api.Client) (string,

output := []byte(resp.Modelfile)

err = os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".config", "gollama", "modelfiles"), os.ModePerm)
err = os.MkdirAll(filepath.Join(utils.GetHomeDir(), ".config", "gollama", "modelfiles"), os.ModePerm)
if err != nil {
logging.ErrorLogger.Printf("Error creating modelfiles directory: %v\n", err)
return "", err
Expand All @@ -594,7 +595,7 @@ func copyModelfile(modelName, newModelName string, client *api.Client) (string,
newModelName = strings.ReplaceAll(newModelName, "/", "-")
newModelName = strings.ReplaceAll(newModelName, ":", "-")

newModelfilePath := filepath.Join(os.Getenv("HOME"), ".config", "gollama", "modelfiles", newModelName+".modelfile")
newModelfilePath := filepath.Join(utils.GetHomeDir(), ".config", "gollama", "modelfiles", newModelName+".modelfile")

err = os.WriteFile(newModelfilePath, output, 0644)
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package utils

import (
"os"
"path/filepath"

"github.com/sammcj/gollama/logging"
)

func GetHomeDir() string {
homeDir, err := os.UserHomeDir()
if err != nil {
logging.ErrorLogger.Printf("Failed to get user home directory: %v\n", err)

return ""
}
return homeDir
}

// getConfigDir returns the directory of the configuration JSON file.
func GetConfigDir() string {
return filepath.Join(GetHomeDir(), ".config", "gollama")
}

// getConfigPath returns the path to the configuration JSON file.
func GetConfigPath() string {
return filepath.Join(GetHomeDir(), ".config", "gollama", "config.json")
}
46 changes: 46 additions & 0 deletions utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package utils

import (
"os"
"path/filepath"
"runtime"
"testing"
)

func TestGetHomeDir(t *testing.T) {
expected := homeDir()
got := GetHomeDir()
if got != expected {
t.Errorf("GetHomeDir() = %v, want %v", got, expected)
}
}

func TestGetConfigDir(t *testing.T) {
expected := filepath.Join(homeDir(), ".config", "gollama")
got := GetConfigDir()
if got != expected {
t.Errorf("GetConfigDir() = %v, want %v", got, expected)
}
}

func TestGetConfigPath(t *testing.T) {
expected := filepath.Join(homeDir(), ".config", "gollama", "config.json")
got := GetConfigPath()
if got != expected {
t.Errorf("GetConfigPath() = %v, want %v", got, expected)
}
}

func homeDir() string {
// Get User Home directory (simplified). Refer to "os/file"
var env string
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else {
env = "HOME"
}
if v := os.Getenv(env); v != "" {
return v
}
return ""
}
5 changes: 3 additions & 2 deletions vramestimator/vramestimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/olekukonko/tablewriter"
"github.com/sammcj/gollama/logging"
"github.com/sammcj/gollama/utils"
"github.com/shirou/gopsutil/v3/mem"
)

Expand Down Expand Up @@ -375,7 +376,7 @@ func GetHuggingFaceToken() string {
accessToken = os.Getenv("HF_TOKEN")
}
if accessToken == "" {
tokenPath := filepath.Join(os.Getenv("HOME"), ".huggingface/token")
tokenPath := filepath.Join(utils.GetHomeDir(), ".huggingface/token")
if _, err := os.Stat(tokenPath); err == nil {
token, err := os.ReadFile(tokenPath)
if err == nil {
Expand All @@ -395,7 +396,7 @@ func GetModelConfig(modelID string) (ModelConfig, error) {
}
cacheMutex.RUnlock()

baseDir := filepath.Join(os.Getenv("HOME"), ".cache/huggingface/hub", modelID)
baseDir := filepath.Join(utils.GetHomeDir(), ".cache/huggingface/hub", modelID)
configPath := filepath.Join(baseDir, "config.json")
indexPath := filepath.Join(baseDir, "model.safetensors.index.json")

Expand Down

0 comments on commit 6a49f90

Please sign in to comment.