diff --git a/coordinator/internal/agent/check.go b/coordinator/internal/agent/check.go index e81741b..f26568e 100644 --- a/coordinator/internal/agent/check.go +++ b/coordinator/internal/agent/check.go @@ -85,8 +85,10 @@ func CheckEnvironment(ctx context.Context) CheckReport { out, err := cmd.Output() if err != nil { // The worker executes workloads by spawning scimesh's task runner, so - // the package is a hard requirement, not an optimisation. - report.Scimesh = CheckItem{Name: "scimesh", OK: false, Detail: "the worker runs workloads through scimesh — install with: pip install scimesh"} + // the package is a hard requirement, not an optimisation. The PyPI + // name belongs to a different project, so the wizard installs from + // SCIMESH_PIP_PACKAGE instead of suggesting a bare pip install. + report.Scimesh = CheckItem{Name: "scimesh", OK: false, Detail: "the worker runs workloads through scimesh — install it from your wheel or index (SCIMESH_PIP_PACKAGE)"} return report } report.Scimesh = CheckItem{Name: "scimesh", OK: true, Detail: strings.TrimSpace(string(out))} diff --git a/coordinator/internal/agent/setupui/server.go b/coordinator/internal/agent/setupui/server.go index c3584a7..45161f7 100644 --- a/coordinator/internal/agent/setupui/server.go +++ b/coordinator/internal/agent/setupui/server.go @@ -450,7 +450,13 @@ func (s *Server) handleInstallRuntime(w http.ResponseWriter, r *http.Request) { pkg = os.Getenv("SCIMESH_PIP_PACKAGE") } if pkg == "" { - pkg = "scimesh" + // No PyPI default on purpose: the PyPI name "scimesh" belongs to an + // unrelated project, so a silent `pip install scimesh` would install + // the wrong software. + writeJSON(w, http.StatusConflict, map[string]string{ + "error": "no scimesh source configured: set SCIMESH_PIP_PACKAGE to your wheel, checkout or index, then retry", + }) + return } python3, err := exec.LookPath("python3") diff --git a/coordinator/internal/agent/setupui/server_test.go b/coordinator/internal/agent/setupui/server_test.go index a122718..ff63217 100644 --- a/coordinator/internal/agent/setupui/server_test.go +++ b/coordinator/internal/agent/setupui/server_test.go @@ -311,18 +311,35 @@ func TestInstallRuntimeCreatesVenvAndReportsPython(t *testing.T) { return nil }) - rec, data := postJSON(t, base, "/api/runtime/install", map[string]any{}) + rec, data := postJSON(t, base, "/api/runtime/install", map[string]any{"scimesh_package": "/wheels/scimesh.whl"}) if rec.Code != http.StatusOK || data["ok"] != true { t.Fatalf("install: got %d %v, want 200 ok", rec.Code, data) } - if installedPkg != "scimesh" { - t.Errorf("package = %q, want the default scimesh", installedPkg) + if installedPkg != "/wheels/scimesh.whl" { + t.Errorf("package = %q, want the requested wheel", installedPkg) } if !strings.HasSuffix(data["python"].(string), "venv/bin/python") { t.Errorf("python = %v, want the venv python", data["python"]) } } +func TestInstallRuntimeRequiresASource(t *testing.T) { + sup := &fakeSup{} + _, base := newTestServerWithInstall(t, sup, func(ctx context.Context, venvPython, pkg string) error { + t.Fatal("install must not run without a package source") + return nil + }) + // No source anywhere (SCIMESH_PIP_PACKAGE unset, request empty): 409 with + // guidance. The PyPI name is another project, so no silent fallback. + rec, data := postJSON(t, base, "/api/runtime/install", map[string]any{}) + if rec.Code != http.StatusConflict { + t.Fatalf("install without source: got %d, want 409", rec.Code) + } + if !strings.Contains(data["error"].(string), "SCIMESH_PIP_PACKAGE") { + t.Errorf("error = %v, want a hint about SCIMESH_PIP_PACKAGE", data["error"]) + } +} + func TestInstallRuntimeFailureIsExplained(t *testing.T) { sup := &fakeSup{} _, base := newTestServerWithInstall(t, sup, func(ctx context.Context, venvPython, pkg string) error { diff --git a/install.ps1 b/install.ps1 index 12d2d65..0740280 100644 --- a/install.ps1 +++ b/install.ps1 @@ -97,6 +97,7 @@ if ($Component -eq "coordinator") { Write-Host "For a coordinator started with 'coordinator serve', the worker token is" Write-Host "in ~\.scimesh\worker.token on that machine. Set SCIMESH_PIP_PACKAGE to" Write-Host "install scimesh into a managed venv, or install it yourself:" - Write-Host " pip install scimesh" + Write-Host " set SCIMESH_PIP_PACKAGE=" + Write-Host " $Target setup" } } diff --git a/install.sh b/install.sh index 697b997..d0a3d54 100644 --- a/install.sh +++ b/install.sh @@ -112,5 +112,5 @@ else echo "For a coordinator started with 'coordinator serve', the worker token is" echo "in ~/.scimesh/worker.token on that machine. Set SCIMESH_PIP_PACKAGE to" echo "install scimesh into a managed venv, or install it yourself:" - echo " pip install scimesh" + echo " SCIMESH_PIP_PACKAGE= worker-agent setup" fi diff --git a/mkdocs/index.md b/mkdocs/index.md index cde3d57..ad92d1b 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -113,10 +113,12 @@ chmod +x coordinator ``` It spawns `python -m scimesh.worker.task`, so the machine needs Python 3 - with the `scimesh` package (`pip install scimesh`, or let the managed venv - do it via `SCIMESH_PIP_PACKAGE`). For a `coordinator serve` instance, the - worker token is in `~/.scimesh/worker.token`. On Windows set - `SCIMESH_COMPONENT=worker` for `install.ps1`. + with the `scimesh` package. The wizard installs it into its own venv; the + package must come from your wheel, checkout or index — point + `SCIMESH_PIP_PACKAGE` at it (the PyPI name `scimesh` belongs to an unrelated + project). For a `coordinator serve` instance, the worker token is in + `~/.scimesh/worker.token`. On Windows set `SCIMESH_COMPONENT=worker` for + `install.ps1`. - **coordinator** needs no external services at all in its default mode: `coordinator serve` embeds SQLite (both databases), the userservice, and local workers. The `SCIMESH_DB=postgres` engine remains for cluster