mirror of
https://github.com/abiosoft/colima.git
synced 2026-05-17 12:10:34 +00:00
ai: refactor model runners (#1516)
Signed-off-by: Abiola Ibrahim <git@abiosoft.com>
This commit is contained in:
+35
-10
@@ -90,19 +90,44 @@ var modelSetupCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
// Build header for alternate screen
|
||||
var header string
|
||||
separator := "────────────────────────────────────────"
|
||||
if runner.Name() == model.RunnerDocker {
|
||||
header = fmt.Sprintf("Colima - Docker Model Runner Setup\n%s", separator)
|
||||
} else {
|
||||
header = fmt.Sprintf("Colima - Ramalama Setup\n%s", separator)
|
||||
// Check if setup is needed (on primary screen)
|
||||
status, err := runner.CheckSetup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run in alternate screen with header
|
||||
return terminal.WithAltScreen(func() error {
|
||||
// Print version info on primary screen
|
||||
fmt.Println(runner.DisplayName())
|
||||
if status.CurrentVersion != "" {
|
||||
fmt.Printf("current: %s\n", status.CurrentVersion)
|
||||
}
|
||||
if status.LatestVersion != "" {
|
||||
fmt.Printf("latest: %s\n", status.LatestVersion)
|
||||
}
|
||||
|
||||
if !status.NeedsSetup {
|
||||
fmt.Println()
|
||||
fmt.Println("Already up to date")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build header for alternate screen
|
||||
separator := "────────────────────────────────────────"
|
||||
header := fmt.Sprintf("Colima - %s Setup\n%s", runner.DisplayName(), separator)
|
||||
|
||||
// Run setup in alternate screen
|
||||
if err := terminal.WithAltScreen(func() error {
|
||||
return runner.Setup()
|
||||
}, header)
|
||||
}, header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Print new version on primary screen after update
|
||||
if newVersion := runner.GetCurrentVersion(); newVersion != "" {
|
||||
fmt.Printf("updated: %s\n", newVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+12
-70
@@ -10,80 +10,15 @@ import (
|
||||
"github.com/abiosoft/colima/environment/host"
|
||||
"github.com/abiosoft/colima/environment/vm/lima"
|
||||
"github.com/abiosoft/colima/store"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const ramalamaReleasesURL = "https://api.github.com/repos/containers/ramalama/releases/latest"
|
||||
|
||||
// SetupOrUpdateRamalama handles both fresh installs and updates with version checking.
|
||||
// SetupOrUpdateRamalama installs or updates ramalama.
|
||||
// Call CheckSetup() first to determine if setup is needed and display version info.
|
||||
func SetupOrUpdateRamalama() error {
|
||||
s, _ := store.Load()
|
||||
|
||||
// Fresh install - no version check needed
|
||||
if !s.RamalamaProvisioned {
|
||||
if err := ProvisionRamalama(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Print installed version
|
||||
if version := GetRamalamaVersion(); version != "" {
|
||||
fmt.Println("AI model runner")
|
||||
fmt.Printf("version: %s", version)
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update - check versions first
|
||||
currentVersion := GetRamalamaVersion()
|
||||
if currentVersion == "" {
|
||||
// Can't determine current version, proceed with update
|
||||
log.Debug("could not determine current ramalama version, proceeding with update")
|
||||
return ProvisionRamalama()
|
||||
}
|
||||
|
||||
latestVersion, err := getLatestRamalamaVersion()
|
||||
if err != nil {
|
||||
log.Debugf("could not fetch latest ramalama version: %v", err)
|
||||
return fmt.Errorf("could not check for updates: %w", err)
|
||||
}
|
||||
|
||||
// Compare versions
|
||||
current, err := semver.NewVersion(currentVersion)
|
||||
if err != nil {
|
||||
log.Debugf("could not parse current version %q: %v", currentVersion, err)
|
||||
return ProvisionRamalama()
|
||||
}
|
||||
|
||||
latest, err := semver.NewVersion(latestVersion)
|
||||
if err != nil {
|
||||
log.Debugf("could not parse latest version %q: %v", latestVersion, err)
|
||||
return ProvisionRamalama()
|
||||
}
|
||||
|
||||
// Show version info
|
||||
fmt.Println("AI model runner")
|
||||
fmt.Printf("current: %s", currentVersion)
|
||||
fmt.Println()
|
||||
fmt.Printf("latest: %s", latestVersion)
|
||||
fmt.Println()
|
||||
|
||||
if current.Compare(*latest) >= 0 {
|
||||
fmt.Println()
|
||||
fmt.Println("Already up to date")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ProvisionRamalama(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Print new version
|
||||
if newVersion := GetRamalamaVersion(); newVersion != "" {
|
||||
fmt.Printf("updated: %s", newVersion)
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
return ProvisionRamalama()
|
||||
}
|
||||
|
||||
// GetRamalamaVersion returns the currently installed ramalama version in the VM.
|
||||
@@ -216,8 +151,15 @@ func ProvisionRamalama() error {
|
||||
script := `set -e
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# install ramalama
|
||||
curl -fsSL https://ramalama.ai/install.sh | bash
|
||||
# ensure pipx is available
|
||||
sudo apt-get update -y && sudo apt-get install -y pipx
|
||||
|
||||
# install ramalama via pipx; upgrade if ramalama is already installed
|
||||
if command -v ramalama >/dev/null 2>&1; then
|
||||
pipx upgrade ramalama
|
||||
else
|
||||
pipx install ramalama
|
||||
fi
|
||||
|
||||
# pull ramalama container images
|
||||
docker pull quay.io/ramalama/ramalama
|
||||
|
||||
+104
-2
@@ -15,6 +15,9 @@ import (
|
||||
"github.com/abiosoft/colima/environment/vm/lima/limaconfig"
|
||||
"github.com/abiosoft/colima/store"
|
||||
"github.com/abiosoft/colima/util"
|
||||
"github.com/abiosoft/colima/util/terminal"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RunnerType represents the type of AI model runner.
|
||||
@@ -25,10 +28,22 @@ const (
|
||||
RunnerRamalama RunnerType = "ramalama"
|
||||
)
|
||||
|
||||
// SetupStatus contains the result of checking if setup is needed.
|
||||
type SetupStatus struct {
|
||||
// NeedsSetup indicates whether setup/update is required.
|
||||
NeedsSetup bool
|
||||
// CurrentVersion is the currently installed version (empty if not installed).
|
||||
CurrentVersion string
|
||||
// LatestVersion is the latest available version (empty if not checked).
|
||||
LatestVersion string
|
||||
}
|
||||
|
||||
// Runner defines the interface for AI model runners.
|
||||
type Runner interface {
|
||||
// Name returns the runner type name.
|
||||
Name() RunnerType
|
||||
// DisplayName returns a human-readable name for the runner.
|
||||
DisplayName() string
|
||||
// ValidatePrerequisites checks runner-specific requirements.
|
||||
ValidatePrerequisites(a app.App) error
|
||||
// EnsureProvisioned ensures the runner is set up (no-op for docker).
|
||||
@@ -43,8 +58,14 @@ type Runner interface {
|
||||
// This is a blocking call that runs until interrupted.
|
||||
// The model should already be available (call EnsureModel first).
|
||||
Serve(model string, port int) error
|
||||
// CheckSetup checks if setup/update is needed and returns version info.
|
||||
// This should be called before Setup() to display version info on primary screen.
|
||||
CheckSetup() (SetupStatus, error)
|
||||
// Setup installs or updates the runner.
|
||||
// Call CheckSetup() first to determine if setup is needed.
|
||||
Setup() error
|
||||
// GetCurrentVersion returns the currently installed version.
|
||||
GetCurrentVersion() string
|
||||
}
|
||||
|
||||
// GetRunner returns the appropriate Runner based on type.
|
||||
@@ -101,6 +122,10 @@ func (d *dockerRunner) Name() RunnerType {
|
||||
return RunnerDocker
|
||||
}
|
||||
|
||||
func (d *dockerRunner) DisplayName() string {
|
||||
return "Docker Model Runner"
|
||||
}
|
||||
|
||||
func (d *dockerRunner) ValidatePrerequisites(a app.App) error {
|
||||
return validateCommonPrerequisites(a)
|
||||
}
|
||||
@@ -255,10 +280,22 @@ func normalizeModelName(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *dockerRunner) CheckSetup() (SetupStatus, error) {
|
||||
// Docker Model Runner always reinstalls; no version comparison
|
||||
return SetupStatus{
|
||||
NeedsSetup: true,
|
||||
CurrentVersion: GetDockerModelVersion(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dockerRunner) Setup() error {
|
||||
return SetupOrUpdateDocker()
|
||||
}
|
||||
|
||||
func (d *dockerRunner) GetCurrentVersion() string {
|
||||
return GetDockerModelVersion()
|
||||
}
|
||||
|
||||
// gpuSubcommands are ramalama subcommands that need GPU device passthrough.
|
||||
var gpuSubcommands = map[string]bool{
|
||||
"run": true,
|
||||
@@ -275,6 +312,10 @@ func (r *ramalamaRunner) Name() RunnerType {
|
||||
return RunnerRamalama
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) DisplayName() string {
|
||||
return "Ramalama"
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) ValidatePrerequisites(a app.App) error {
|
||||
return validateCommonPrerequisites(a)
|
||||
}
|
||||
@@ -285,11 +326,15 @@ func (r *ramalamaRunner) EnsureProvisioned() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !cli.Prompt("AI model support requires initial setup (this may take a few minutes depending on internet connection speed). Continue") {
|
||||
prompt := fmt.Sprintf("%s requires initial setup (this may take a few minutes depending on internet connection speed). Continue", r.DisplayName())
|
||||
if !cli.Prompt(prompt) {
|
||||
return fmt.Errorf("setup cancelled")
|
||||
}
|
||||
|
||||
return ProvisionRamalama()
|
||||
separator := "────────────────────────────────────────"
|
||||
header := fmt.Sprintf("Colima - %s Setup\n%s", r.DisplayName(), separator)
|
||||
|
||||
return terminal.WithAltScreen(ProvisionRamalama, header)
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) BuildArgs(args []string) ([]string, error) {
|
||||
@@ -333,6 +378,63 @@ func (r *ramalamaRunner) buildRamalamaArgs(args []string) []string {
|
||||
return ramalamaArgs
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) CheckSetup() (SetupStatus, error) {
|
||||
s, _ := store.Load()
|
||||
|
||||
// Fresh install - no version check needed
|
||||
if !s.RamalamaProvisioned {
|
||||
return SetupStatus{NeedsSetup: true}, nil
|
||||
}
|
||||
|
||||
// Get current version
|
||||
currentVersion := GetRamalamaVersion()
|
||||
if currentVersion == "" {
|
||||
// Can't determine current version, proceed with update
|
||||
log.Debug("could not determine current ramalama version, proceeding with update")
|
||||
return SetupStatus{NeedsSetup: true}, nil
|
||||
}
|
||||
|
||||
// Fetch latest version
|
||||
latestVersion, err := getLatestRamalamaVersion()
|
||||
if err != nil {
|
||||
log.Debugf("could not fetch latest ramalama version: %v", err)
|
||||
return SetupStatus{}, fmt.Errorf("could not check for updates: %w", err)
|
||||
}
|
||||
|
||||
// Compare versions
|
||||
current, err := semver.NewVersion(currentVersion)
|
||||
if err != nil {
|
||||
log.Debugf("could not parse current version %q: %v", currentVersion, err)
|
||||
return SetupStatus{
|
||||
NeedsSetup: true,
|
||||
CurrentVersion: currentVersion,
|
||||
LatestVersion: latestVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
latest, err := semver.NewVersion(latestVersion)
|
||||
if err != nil {
|
||||
log.Debugf("could not parse latest version %q: %v", latestVersion, err)
|
||||
return SetupStatus{
|
||||
NeedsSetup: true,
|
||||
CurrentVersion: currentVersion,
|
||||
LatestVersion: latestVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
needsSetup := current.Compare(*latest) < 0
|
||||
|
||||
return SetupStatus{
|
||||
NeedsSetup: needsSetup,
|
||||
CurrentVersion: currentVersion,
|
||||
LatestVersion: latestVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) Setup() error {
|
||||
return SetupOrUpdateRamalama()
|
||||
}
|
||||
|
||||
func (r *ramalamaRunner) GetCurrentVersion() string {
|
||||
return GetRamalamaVersion()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user