From 28d3c7ce3981d0229a01f137390afbea4230da0d Mon Sep 17 00:00:00 2001 From: shpark Date: Sun, 31 May 2026 07:36:03 +0000 Subject: [PATCH 1/3] feat(bootstrap): implement audit installation helpers --- internal/bootstrap/audit.go | 124 ++++++++++++++++++ internal/bootstrap/audit_test.go | 212 +++++++++++++++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 internal/bootstrap/audit.go create mode 100644 internal/bootstrap/audit_test.go diff --git a/internal/bootstrap/audit.go b/internal/bootstrap/audit.go new file mode 100644 index 0000000..a608e90 --- /dev/null +++ b/internal/bootstrap/audit.go @@ -0,0 +1,124 @@ +package bootstrap + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" +) + +const ( + AuditArtifactLlamaServer = "llama_server" + AuditArtifactModel = "model" +) + +type InstalledManifest struct { + ManifestURL string `json:"manifest_url"` + ManifestVersion int `json:"manifest_version"` + Platform string `json:"platform"` + InstalledAt string `json:"installed_at"` // UTC RFC3339 timestamp + ModelVariant string `json:"model_variant,omitempty"` + Artifacts map[string]InstalledArtifact `json:"artifacts"` +} + +type InstalledArtifact struct { + // Tarbar info + URL string `json:"url"` + SHA256 string `json:"sha256"` + + // Extracted binary (e.g., llama-server) + Path string `json:"path"` // SHA of this file may not matched with SHA of URL + Size int64 `json:"size,omitempty"` +} + +func InstalledManifestPath(p *Paths) string { + return filepath.Join(p.Home, "installed.json") +} + +func WriteInstalledManifest(p *Paths, im *InstalledManifest) error { + data, err := json.MarshalIndent(im, "", " ") + if err != nil { + return fmt.Errorf("installed.json: marshal: %w", err) + } + + target := InstalledManifestPath(p) + if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil { + return fmt.Errorf("installed.json: mkdir parent: %w", err) + } + + // Atomic write by writing to tmp then renaming + tmp, err := os.CreateTemp(filepath.Dir(target), ".installed.json.*") + if err != nil { + return fmt.Errorf("installed.json: tmp: %w", err) + } + + tmpName := tmp.Name() + defer os.Remove(tmpName) + + if _, err := tmp.Write(data); err != nil { + _ = tmp.Close() + return fmt.Errorf("installed.json: write: %w", err) + } + if err := tmp.Close(); err != nil { + return fmt.Errorf("installed.json: close: %w", err) + } + if err := os.Chmod(tmpName, 0o600); err != nil { + return fmt.Errorf("installed.json: chmod: %w", err) + } + if err := os.Rename(tmpName, target); err != nil { + return fmt.Errorf("installed.json: rename: %w", err) + } + + return nil +} + +func ReadInstalledManifest(p *Paths) (*InstalledManifest, error) { + data, err := os.ReadFile(InstalledManifestPath(p)) + if err != nil { + return nil, err + } + + var record InstalledManifest + if err := json.Unmarshal(data, &record); err != nil { + return nil, fmt.Errorf("installed.json: parse: %w", err) + } + + return &record, nil +} + +func recordInstall(p *Paths, manifestURL string, manifestVersion int, modelVariant string, artifacts map[string]InstalledArtifact) error { + cur, err := ReadInstalledManifest(p) + if err != nil && !os.IsNotExist(err) { + cur = nil // use fresh data on failure + } + if cur == nil { + cur = &InstalledManifest{} + } + + if cur.Artifacts == nil { + cur.Artifacts = map[string]InstalledArtifact{} + } + + cur.ManifestURL = manifestURL + cur.ManifestVersion = manifestVersion + cur.Platform = PlatformTuple() + cur.InstalledAt = time.Now().UTC().Format(time.RFC3339) + if modelVariant != "" { + cur.ModelVariant = modelVariant + } + for k, v := range artifacts { + cur.Artifacts[k] = v + } + + return WriteInstalledManifest(p, cur) +} + +func statSize(path string, fallback int64) int64 { + info, err := os.Stat(path) + if err != nil { + return fallback + } + + return info.Size() +} diff --git a/internal/bootstrap/audit_test.go b/internal/bootstrap/audit_test.go new file mode 100644 index 0000000..52b1169 --- /dev/null +++ b/internal/bootstrap/audit_test.go @@ -0,0 +1,212 @@ +package bootstrap + +import ( + "encoding/json" + "os" + "testing" +) + +func newAuditTestPaths(t *testing.T) *Paths { + t.Helper() + t.Setenv(EnvHome, t.TempDir()) + + paths, err := Resolve() + if err != nil { + t.Fatalf("Resolve: %v", err) + } + if err := paths.EnsureDirs(); err != nil { + t.Fatalf("EnsureDirs: %v", err) + } + + return paths +} + +func TestWriteAndReadInstalledManifest_RoundTrip(t *testing.T) { + paths := newAuditTestPaths(t) + + manifest := &InstalledManifest{ + ManifestURL: "https://example/manifest.json", + ManifestVersion: 1, + Platform: PlatformTuple(), + InstalledAt: "2026-05-31T00:00:00Z", + ModelVariant: "qwen3-embedding-0.6b.q6_K", + Artifacts: map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: "https://example/llama.tar.gz", SHA256: "aaa", Path: "/x/bin/llama-server", Size: 1234}, + AuditArtifactModel: {URL: "https://example/qwen3.gguf", SHA256: "bbb", Path: "/x/models/v.gguf", Size: 5678}, + }, + } + if err := WriteInstalledManifest(paths, manifest); err != nil { + t.Fatalf("WriteInstalledManifest: %v", err) + } + + info, err := os.Stat(InstalledManifestPath(paths)) + if err != nil { + t.Fatalf("stat: %v", err) + } + if perm := info.Mode().Perm(); perm != 0o600 { + t.Errorf("mode = %v, want 0o600", perm) + } + + got, err := ReadInstalledManifest(paths) + if err != nil { + t.Fatalf("ReadInstalledManifest: %v", err) + } + if got.ManifestURL != manifest.ManifestURL || + got.ManifestVersion != manifest.ManifestVersion || + got.ModelVariant != manifest.ModelVariant { + t.Errorf("top-level mismatch: got %+v want %+v", got, manifest) + } + if got.Artifacts[AuditArtifactLlamaServer].SHA256 != "aaa" { + t.Errorf("llama_server sha = %q", got.Artifacts[AuditArtifactLlamaServer].SHA256) + } + if got.Artifacts[AuditArtifactModel].URL != "https://example/qwen3.gguf" { + t.Errorf("model url = %q", got.Artifacts[AuditArtifactModel].URL) + } +} + +func TestReadInstalledManifest_NotInstalled(t *testing.T) { + paths := newAuditTestPaths(t) + + _, err := ReadInstalledManifest(paths) + if !os.IsNotExist(err) { + t.Errorf("err = %v, want os.IsNotExist (no install has run)", err) + } +} + +func TestRecordInstall_OverwritesAtomically(t *testing.T) { + paths := newAuditTestPaths(t) + + // Initial installation + if err := recordInstall(paths, "https://m", 1, "", map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: "first", SHA256: "1"}, + }); err != nil { + t.Fatalf("recordInstall #1: %v", err) + } + + // Update (or re-install) + if err := recordInstall(paths, "https://m", 1, "", map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: "second", SHA256: "2"}, + }); err != nil { + t.Fatalf("recordInstall #2: %v", err) + } + + got, err := ReadInstalledManifest(paths) + if err != nil { + t.Fatalf("ReadInstalledManifest: %v", err) + } + if got.Artifacts[AuditArtifactLlamaServer].URL != "second" { + t.Errorf("URL = %q, want second", got.Artifacts[AuditArtifactLlamaServer].URL) + } +} + +// recordInstall must preserve a previously-written artifact when a partial +// install only knows about one slot. EnsureLlamaServer followed by +// EnsureModel must not wipe the llama_server entry. +func TestRecordInstall_PreservesExistingArtifacts(t *testing.T) { + paths := newAuditTestPaths(t) + + // First install: llama_server only + if err := recordInstall(paths, "https://m1", 1, "", map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: "u-llama", SHA256: "s-llama", Path: "/p/llama"}, + }); err != nil { + t.Fatalf("recordInstall #1: %v", err) + } + + // Second install: model only + if err := recordInstall(paths, "https://m2", 1, "qwen", map[string]InstalledArtifact{ + AuditArtifactModel: {URL: "u-model", SHA256: "s-model", Path: "/p/model"}, + }); err != nil { + t.Fatalf("recordInstall #2: %v", err) + } + + got, err := ReadInstalledManifest(paths) + if err != nil { + t.Fatalf("ReadInstalledManifest: %v", err) + } + + if got.ManifestURL != "https://m2" { + t.Errorf("manifest_url = %q, want last write", got.ManifestURL) + } + if got.ModelVariant != "qwen" { + t.Errorf("model_variant = %q", got.ModelVariant) + } + + // recordInstall must preserve previously written artifact + if a, ok := got.Artifacts[AuditArtifactLlamaServer]; !ok || a.URL != "u-llama" { + t.Errorf("llama_server entry lost or wrong: %+v", a) + } + if a, ok := got.Artifacts[AuditArtifactModel]; !ok || a.URL != "u-model" { + t.Errorf("model entry missing: %+v", a) + } +} + +func TestRecordInstall_RecoversFromCorruptFile(t *testing.T) { + paths := newAuditTestPaths(t) + + if err := os.WriteFile(InstalledManifestPath(paths), []byte("{not json"), 0o600); err != nil { + t.Fatalf("seed corrupt file: %v", err) + } + + if err := recordInstall(paths, "https://m", 1, "v", map[string]InstalledArtifact{ + AuditArtifactModel: {URL: "u", SHA256: "s", Path: "/p"}, + }); err != nil { + t.Fatalf("recordInstall: %v", err) + } + + got, err := ReadInstalledManifest(paths) + if err != nil { + t.Fatalf("ReadInstalledManifest: %v", err) + } + if got.Artifacts[AuditArtifactModel].URL != "u" { + t.Errorf("post-recovery URL = %q", got.Artifacts[AuditArtifactModel].URL) + } +} + +func TestInstalledManifest_JSONShape(t *testing.T) { + paths := newAuditTestPaths(t) + + manifest := &InstalledManifest{ + ManifestURL: "https://m", + ManifestVersion: 1, + Platform: "linux-amd64", + InstalledAt: "2026-01-01T00:00:00Z", + ModelVariant: "v", + Artifacts: map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: "u", SHA256: "s", Path: "/p", Size: 9}, + }, + } + if err := WriteInstalledManifest(paths, manifest); err != nil { + t.Fatalf("Write: %v", err) + } + + data, err := os.ReadFile(InstalledManifestPath(paths)) + if err != nil { + t.Fatal(err) + } + + var raw map[string]any + if err := json.Unmarshal(data, &raw); err != nil { + t.Fatal(err) + } + + for _, k := range []string{"manifest_url", "manifest_version", "platform", "installed_at", "model_variant", "artifacts"} { + if _, ok := raw[k]; !ok { + t.Errorf("missing top-level key %q", k) + } + } + + arts, ok := raw["artifacts"].(map[string]any) + if !ok { + t.Fatal("artifacts not an object") + } + llama, ok := arts["llama_server"].(map[string]any) + if !ok { + t.Fatal("artifacts.llama_server missing") + } + + for _, k := range []string{"url", "sha256", "path", "size"} { + if _, ok := llama[k]; !ok { + t.Errorf("artifacts.llama_server.%s missing", k) + } + } +} From 5075ede03078aecd9a6343ad49e23f6d7de9b421 Mon Sep 17 00:00:00 2001 From: shpark Date: Sun, 31 May 2026 07:39:24 +0000 Subject: [PATCH 2/3] feat(bootstrap): add audit installation --- internal/bootstrap/install.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/internal/bootstrap/install.go b/internal/bootstrap/install.go index c1f7e8f..a5b4446 100644 --- a/internal/bootstrap/install.go +++ b/internal/bootstrap/install.go @@ -93,6 +93,18 @@ func EnsureAll(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, if err != nil { return "", "", "", err } + + // Audit installation + llamaSpec, _ := m.LlamaServerForCurrentPlatform() + modelSpec, _ := m.ModelSpec(variant) + auditArtifacts := map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBin, Size: statSize(llamaBin, llamaSpec.Size)}, + AuditArtifactModel: {URL: modelSpec.URL, SHA256: modelSpec.SHA256, Path: modelPath, Size: statSize(modelPath, modelSpec.Size)}, + } + if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, variant, auditArtifacts); auditErr != nil { + logger.Warn("audit: installed.json write failed", "err", auditErr) + } + return llamaBin, modelPath, variant, nil } @@ -114,7 +126,20 @@ func EnsureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog. return "", fmt.Errorf("install lock: %w", err) } defer lock.Release() - return ensureLlamaServer(ctx, p, m, logger, reporter) + + llamaBin, err := ensureLlamaServer(ctx, p, m, logger, reporter) + if err != nil { + return "", err + } + + llamaSpec, _ := m.LlamaServerForCurrentPlatform() + if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, "", map[string]InstalledArtifact{ + AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBin, Size: statSize(llamaBin, llamaSpec.Size)}, + }); auditErr != nil { + logger.Warn("audit: installed.json write failed", "err", auditErr) + } + + return llamaBin, nil } // EnsureModel ensures the model GGUF is installed. Skips @@ -149,6 +174,14 @@ func EnsureModel(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger if err != nil { return "", "", err } + + modelSpec, _ := m.ModelSpec(variant) + if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, variant, map[string]InstalledArtifact{ + AuditArtifactModel: {URL: modelSpec.URL, SHA256: modelSpec.SHA256, Path: modelPath, Size: statSize(modelPath, modelSpec.Size)}, + }); auditErr != nil { + logger.Warn("audit: installed.json write failed (non-fatal)", "err", auditErr) + } + return modelPath, variant, nil } From 1224b2a551687ac2b7649e8af7db0f58b4287480 Mon Sep 17 00:00:00 2001 From: shpark Date: Mon, 1 Jun 2026 02:27:15 +0000 Subject: [PATCH 3/3] refactor(bootstrap): change 'ensure*' functions to return installed spec --- internal/bootstrap/audit.go | 2 +- internal/bootstrap/install.go | 67 ++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/internal/bootstrap/audit.go b/internal/bootstrap/audit.go index a608e90..172115a 100644 --- a/internal/bootstrap/audit.go +++ b/internal/bootstrap/audit.go @@ -23,7 +23,7 @@ type InstalledManifest struct { } type InstalledArtifact struct { - // Tarbar info + // Tarball info URL string `json:"url"` SHA256 string `json:"sha256"` diff --git a/internal/bootstrap/install.go b/internal/bootstrap/install.go index a5b4446..dcf0ce1 100644 --- a/internal/bootstrap/install.go +++ b/internal/bootstrap/install.go @@ -56,7 +56,7 @@ type StatusReporter func(stage string, bytesDone, bytesTotal int64) // side isn't redownloaded only to be discarded. // // logger may be nil (slog.Default used). reporter may be nil. -func EnsureAll(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, reporter StatusReporter) (llamaBin, modelPath, variant string, err error) { +func EnsureAll(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, reporter StatusReporter) (llamaBinPath, modelPath, variant string, err error) { if logger == nil { logger = slog.Default() } @@ -82,30 +82,28 @@ func EnsureAll(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, } defer lock.Release() - llamaBin, err = ensureLlamaServer(ctx, p, m, logger, reporter) + llamaBinPath, llamaSpec, err := ensureLlamaServer(ctx, p, m, logger, reporter) if err != nil { return "", "", "", err } if reporter != nil { reporter("model", 0, 0) } - modelPath, err = ensureModel(ctx, p, m, variant, logger, reporter) + modelPath, modelSpec, err := ensureModel(ctx, p, m, variant, logger, reporter) if err != nil { return "", "", "", err } // Audit installation - llamaSpec, _ := m.LlamaServerForCurrentPlatform() - modelSpec, _ := m.ModelSpec(variant) auditArtifacts := map[string]InstalledArtifact{ - AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBin, Size: statSize(llamaBin, llamaSpec.Size)}, + AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBinPath, Size: statSize(llamaBinPath, llamaSpec.Size)}, AuditArtifactModel: {URL: modelSpec.URL, SHA256: modelSpec.SHA256, Path: modelPath, Size: statSize(modelPath, modelSpec.Size)}, } if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, variant, auditArtifacts); auditErr != nil { logger.Warn("audit: installed.json write failed", "err", auditErr) } - return llamaBin, modelPath, variant, nil + return llamaBinPath, modelPath, variant, nil } // EnsureLlamaServer ensures the llama-server binary is installed. The @@ -127,19 +125,18 @@ func EnsureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog. } defer lock.Release() - llamaBin, err := ensureLlamaServer(ctx, p, m, logger, reporter) + llamaBinPath, llamaSpec, err := ensureLlamaServer(ctx, p, m, logger, reporter) if err != nil { return "", err } - llamaSpec, _ := m.LlamaServerForCurrentPlatform() if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, "", map[string]InstalledArtifact{ - AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBin, Size: statSize(llamaBin, llamaSpec.Size)}, + AuditArtifactLlamaServer: {URL: llamaSpec.URL, SHA256: llamaSpec.SHA256, Path: llamaBinPath, Size: statSize(llamaBinPath, llamaSpec.Size)}, }); auditErr != nil { logger.Warn("audit: installed.json write failed", "err", auditErr) } - return llamaBin, nil + return llamaBinPath, nil } // EnsureModel ensures the model GGUF is installed. Skips @@ -170,12 +167,11 @@ func EnsureModel(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger } defer lock.Release() - modelPath, err = ensureModel(ctx, p, m, variant, logger, reporter) + modelPath, modelSpec, err := ensureModel(ctx, p, m, variant, logger, reporter) if err != nil { return "", "", err } - modelSpec, _ := m.ModelSpec(variant) if auditErr := recordInstall(p, ResolveManifestURL(), m.Version, variant, map[string]InstalledArtifact{ AuditArtifactModel: {URL: modelSpec.URL, SHA256: modelSpec.SHA256, Path: modelPath, Size: statSize(modelPath, modelSpec.Size)}, }); auditErr != nil { @@ -270,15 +266,15 @@ func ResolveModelVariant(p *Paths, m *Manifest) (string, error) { return "", errors.New("model variant not specified: set RUNED_MODEL_VARIANT, config.model_variant, or manifest.default_model") } -// ensureLlamaServer returns the path to the llama-server executable, +// ensureLlamaServer returns the path to the llama-server executable and manifest spec, // extracting or downloading the artifact as needed. A sidecar marker // file (.llama_server.sha256) tracks the last-installed tarball hash so // repeat boots don't re-extract. -func ensureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, reporter StatusReporter) (string, error) { +func ensureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog.Logger, reporter StatusReporter) (string, *LlamaServerSpec, error) { // Caller emits the stage tick before AcquireLock. spec, err := m.LlamaServerForCurrentPlatform() if err != nil { - return "", err + return "", nil, err } target := llamaServerTarget(p, spec) logger.Info("ensure llama_server: target", @@ -294,7 +290,7 @@ func ensureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog. if _, serr := os.Stat(target); serr == nil { logger.Info("ensure llama_server: cache hit, skipping download", "marker", marker) - return target, nil + return target, spec, nil } logger.Info("ensure llama_server: marker matches but target missing, redoing install", "target", target) @@ -304,14 +300,16 @@ func ensureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog. switch spec.Extract { case "": if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil { - return "", fmt.Errorf("ensure llama_server: mkdir: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: mkdir: %w", err) } + logger.Info("ensure llama_server: downloading raw binary", "url", spec.URL) + if err := downloadWithRetry(ctx, spec.URL, spec.SHA256, spec.Size, target, progress, logger, "llama_server"); err != nil { - return "", fmt.Errorf("ensure llama_server: download: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: download: %w", err) } if err := os.Chmod(target, 0o755); err != nil { - return "", fmt.Errorf("ensure llama_server: chmod: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: chmod: %w", err) } case "tar.gz": tarPath := filepath.Join(p.Cache, "llama-server.tar.gz") @@ -319,29 +317,29 @@ func ensureLlamaServer(ctx context.Context, p *Paths, m *Manifest, logger *slog. "url", spec.URL, "cache", tarPath) if err := downloadWithRetry(ctx, spec.URL, spec.SHA256, spec.Size, tarPath, progress, logger, "llama_server"); err != nil { - return "", fmt.Errorf("ensure llama_server: download: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: download: %w", err) } defer os.Remove(tarPath) logger.Info("ensure llama_server: extracting tarball", "dest", p.LlamaDir) extracted, err := ExtractTarGz(tarPath, p.LlamaDir) if err != nil { - return "", fmt.Errorf("ensure llama_server: extract: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: extract: %w", err) } logger.Info("ensure llama_server: extracted", "files", len(extracted)) default: - return "", fmt.Errorf("manifest: unsupported extract type %q", spec.Extract) + return "", nil, fmt.Errorf("manifest: unsupported extract type %q", spec.Extract) } if _, err := os.Stat(target); err != nil { - return "", fmt.Errorf("ensure llama_server: exec missing after install: %s: %w", target, err) + return "", nil, fmt.Errorf("ensure llama_server: exec missing after install: %s: %w", target, err) } if err := os.Chmod(target, 0o755); err != nil { - return "", fmt.Errorf("ensure llama_server: chmod target: %w", err) + return "", nil, fmt.Errorf("ensure llama_server: chmod target: %w", err) } // Marker write is an optimization; tolerate failure (we'll just re-extract next boot). _ = os.WriteFile(marker, []byte(spec.SHA256), 0o600) logger.Info("ensure llama_server: install complete", "target", target) - return target, nil + return target, spec, nil } // llamaServerTarget computes the on-disk path of the executable after @@ -355,11 +353,12 @@ func llamaServerTarget(p *Paths, spec *LlamaServerSpec) string { return filepath.Join(p.LlamaDir, filepath.FromSlash(exec)) } -func ensureModel(ctx context.Context, p *Paths, m *Manifest, variant string, logger *slog.Logger, reporter StatusReporter) (string, error) { +// Return model path and manifest spec, downloading artifact if not exist +func ensureModel(ctx context.Context, p *Paths, m *Manifest, variant string, logger *slog.Logger, reporter StatusReporter) (string, ArtifactSpec, error) { // Caller emits the stage tick before invoking us. spec, err := m.ModelSpec(variant) if err != nil { - return "", err + return "", ArtifactSpec{}, err } target := p.ModelPath(variant) logger.Info("ensure model: target", @@ -370,20 +369,22 @@ func ensureModel(ctx context.Context, p *Paths, m *Manifest, variant string, log ok, err := FileMatchesSHA256(target, spec.SHA256) if err != nil { - return "", fmt.Errorf("ensure model: hash existing: %w", err) + return "", ArtifactSpec{}, fmt.Errorf("ensure model: hash existing: %w", err) } if ok { logger.Info("ensure model: cache hit, skipping download") - return target, nil + return target, spec, nil } if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil { - return "", err + return "", ArtifactSpec{}, err } + logger.Info("ensure model: downloading GGUF", "url", spec.URL) progress := makeProgress(logger, reporter, "model", spec.Size) if err := downloadWithRetry(ctx, spec.URL, spec.SHA256, spec.Size, target, progress, logger, "model"); err != nil { - return "", fmt.Errorf("ensure model: download: %w", err) + return "", ArtifactSpec{}, fmt.Errorf("ensure model: download: %w", err) } logger.Info("ensure model: install complete", "target", target) - return target, nil + + return target, spec, nil }