Compare commits

...
Author SHA1 Message Date
Emil b9f47e556c Add uninstallers for the coordinator and the worker (release assets)
coordinator / test (push) Waiting to run
python / test (push) Waiting to run
release / binaries (amd64, darwin) (push) Waiting to run
release / binaries (amd64, linux) (push) Waiting to run
release / binaries (amd64, windows) (push) Waiting to run
release / binaries (arm64, darwin) (push) Waiting to run
release / binaries (arm64, linux) (push) Waiting to run
release / binaries (arm64, windows) (push) Waiting to run
release / wheel (push) Waiting to run
release / release (push) Blocked by required conditions
release / image (push) Waiting to run
users / test (push) Waiting to run
2026-08-03 18:04:04 +03:00
Emil e6b489f117 Log version control and isolation work in the session goal 2026-08-03 16:45:50 +03:00
Emil 73dde99e3a Run the wizard checks and task runners in isolated python mode (-I)
coordinator / test (push) Waiting to run
python / test (push) Waiting to run
release / binaries (amd64, darwin) (push) Waiting to run
release / binaries (amd64, linux) (push) Waiting to run
release / binaries (amd64, windows) (push) Waiting to run
release / binaries (arm64, darwin) (push) Waiting to run
release / binaries (arm64, linux) (push) Waiting to run
release / binaries (arm64, windows) (push) Waiting to run
release / wheel (push) Waiting to run
release / release (push) Blocked by required conditions
release / image (push) Waiting to run
users / test (push) Waiting to run
2026-08-03 16:42:38 +03:00
13 changed files with 188 additions and 16 deletions
+2
View File
@@ -110,6 +110,8 @@ jobs:
artifacts/*
install.sh
install.ps1
uninstall.sh
uninstall.ps1
# Pre-release tags (e.g. v1.1.0-alpha.1) publish as pre-releases.
prerelease: ${{ contains(github.ref_name, '-alpha') }}
generate_release_notes: true
+10
View File
@@ -75,8 +75,18 @@
- ✅ На машине пользователя: визард alpha.15, правильный токен, venv из wheel, воркер emil-pc online, 15 пустых воркеров вычищены из БД.
- ✅ Гейт: race + lint + pytest 208.
## Progress (день 2: контроль версий + изоляция)
- ✅ Визард сверяет установленную версию scimesh с версией бинарника: preflight показывает «installed X, worker needs Y — press Install» и снова предлагает кнопку Install (ensureMatchingScimeshVersion, dev-сборки пропускают). Проверено на машине: a21 vs a22 → красный → Install → зелёный 1.1.0a22.
- ✅ Все проверки и task-раннеры запускаются с `-I` (isolated): каталог запуска больше не перекрывает venv (был артефакт cwd-затенения исходниками). Затронуты: check, дефолтные раннеры агента, визард (config/save/start), serve, agent_cmd.
- ✅ Релиз v1.1.0-alpha.22; полный гейт (race + lint 0 issues + pytest 213); бинарник юзера обновлён, рантайм переустановлен (a22), воркер с concurrency=4; большой джоб юзера (1934 шарда) завершён (очередь 0).
## Completion
COMPLETED — ночной план выполнен полностью (10 пунктов + 2 найденных бага, включая E2E quorum на релизном коде). Все гейты зелёные, релиз v1.1.0-alpha.16 опубликован.
## Progress (день 2: контроль версий + изоляция)
- ✅ Визард сверяет установленную версию scimesh с версией бинарника: preflight показывает «installed X, worker needs Y — press Install» и снова предлагает кнопку Install (ensureMatchingScimeshVersion, dev-сборки пропускают). Проверено на машине: a21 vs a22 → красный → Install → зелёный 1.1.0a22.
- ✅ Все проверки и task-раннеры запускаются с `-I` (isolated): каталог запуска больше не перекрывает venv (был артефакт cwd-затенения исходниками). Затронуты: check, дефолтные раннеры агента, визард (config/save/start), serve, agent_cmd.
- ✅ Релиз v1.1.0-alpha.22; полный гейт (race + lint 0 issues + pytest 213); бинарник юзера обновлён, рантайм переустановлен (a22), воркер с concurrency=4; большой джоб юзера (1934 шарда) завершён (очередь 0).
## Completion (предыдущая задача)
COMPLETED — пайплайн доведён до рабочего состояния и проверен на релизных артефактах v1.1.0-alpha.14.
+10 -1
View File
@@ -61,7 +61,16 @@ powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/emi
```
Set `SCIMESH_AUTO_START=0` to install without starting anything. The old demo
control room was removed: `/ui` is the admin console. A standalone
control room was removed: `/ui` is the admin console.
To remove a component, run the matching uninstaller (data is kept unless you
pass `--purge`):
```bash
curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.sh | bash -s coordinator
curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.sh | bash -s worker --purge
# Windows: irm .../uninstall.ps1 | iex (set $env:SCIMESH_COMPONENT, -Purge deletes data)
``` A standalone
worker is installed the same way (`bash -s worker`, or
`SCIMESH_COMPONENT=worker` on Windows); its installer opens the local setup
wizard (`worker-agent setup`) in the browser automatically.
+1 -1
View File
@@ -45,7 +45,7 @@ func runAgent(args []string) error {
return fmt.Errorf("--coordinator-url, --token, and --work-dir are required")
}
if *taskRunner == "" {
*taskRunner = "python -m scimesh.worker.task"
*taskRunner = "python -I -m scimesh.worker.task"
}
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
+2 -2
View File
@@ -236,9 +236,9 @@ func stopAgents(agents []*exec.Cmd) {
// system `python`.
func defaultTaskRunner(venvPython string) string {
if runtimeStatus(venvPython) {
return venvPython + " -m scimesh.worker.task"
return venvPython + " -I -m scimesh.worker.task"
}
return "python -m scimesh.worker.task"
return "python -I -m scimesh.worker.task"
}
// ensureRuntime creates the managed venv and installs scimesh into it, unless
+4 -2
View File
@@ -89,9 +89,11 @@ func CheckEnvironment(ctx context.Context) CheckReport {
func CheckEnvironmentWithPython(ctx context.Context, python string) CheckReport {
report := CheckReport{Agent: Version, Python: CheckItem{Name: "python", OK: true, Detail: python}}
// The version comes from importlib.metadata, so the wizard can compare the
// installed package with the binary version and offer an upgrade.
// installed package with the binary version and offer an upgrade. -I keeps
// the working directory out of sys.path, so a scimesh checkout in the
// wizard's cwd can never shadow the venv installation.
//nolint:gosec // G204: python is a resolved interpreter path, the argument list is constant
cmd := exec.CommandContext(ctx, python, "-c", "import importlib.metadata as m; print(m.version('scimesh'))")
cmd := exec.CommandContext(ctx, python, "-I", "-c", "import importlib.metadata as m; print(m.version('scimesh'))")
out, err := cmd.Output()
if err != nil {
// The worker executes workloads by spawning scimesh's task runner, so
+1 -1
View File
@@ -112,7 +112,7 @@ func LoadConfig() (*Config, error) {
return nil, err
}
if len(runner) == 0 {
runner = []string{"python", "-m", "scimesh.worker.task"}
runner = []string{"python", "-I", "-m", "scimesh.worker.task"}
}
maxTasks := 0
if raw := os.Getenv("MAX_TASKS"); raw != "" {
+1 -1
View File
@@ -112,7 +112,7 @@ func (f *ConfigFile) Config() (*Config, error) {
config.TaskRunner = f.TaskRunner
}
if len(config.TaskRunner) == 0 {
config.TaskRunner = []string{"python", "-m", "scimesh.worker.task"}
config.TaskRunner = []string{"python", "-I", "-m", "scimesh.worker.task"}
}
config.PollInterval = 2 * time.Second
config.RequestTimeout = 30 * time.Second
+2 -2
View File
@@ -323,7 +323,7 @@ func (s *Server) ensureVenvTaskRunner() {
return
}
if venv := s.venvPython(); venv != "" {
file.TaskRunner = []string{venv, "-m", "scimesh.worker.task"}
file.TaskRunner = []string{venv, "-I", "-m", "scimesh.worker.task"}
if payload, err := json.MarshalIndent(file, "", " "); err == nil {
_ = os.WriteFile(s.cfgPath, append(payload, '\n'), 0o600)
}
@@ -428,7 +428,7 @@ func (s *Server) handleSaveConfig(w http.ResponseWriter, r *http.Request) {
// workloads execute through scimesh's task runner, which lives in the venv.
if len(file.TaskRunner) == 0 {
if venv := s.venvPython(); venv != "" {
file.TaskRunner = []string{venv, "-m", "scimesh.worker.task"}
file.TaskRunner = []string{venv, "-I", "-m", "scimesh.worker.task"}
}
}
if err := agent.SaveConfigFile(s.cfgPath, file); err != nil {
@@ -429,8 +429,8 @@ func TestStartPinsTheVenvTaskRunner(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(config.TaskRunner) != 3 || config.TaskRunner[0] != venvPython || config.TaskRunner[1] != "-m" || config.TaskRunner[2] != "scimesh.worker.task" {
t.Errorf("task runner = %v, want the venv python runner", config.TaskRunner)
if len(config.TaskRunner) != 4 || config.TaskRunner[0] != venvPython || config.TaskRunner[1] != "-I" || config.TaskRunner[2] != "-m" || config.TaskRunner[3] != "scimesh.worker.task" {
t.Errorf("task runner = %v, want the venv python runner with -I", config.TaskRunner)
}
}
@@ -450,8 +450,8 @@ func TestSaveConfigPinsVenvRunnerWhenPresent(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(config.TaskRunner) != 3 || config.TaskRunner[0] != venvPython {
t.Errorf("task runner = %v, want the venv python", config.TaskRunner)
if len(config.TaskRunner) != 4 || config.TaskRunner[0] != venvPython || config.TaskRunner[1] != "-I" {
t.Errorf("task runner = %v, want the venv python with -I", config.TaskRunner)
}
}
@@ -462,7 +462,7 @@ func TestTestProbesTheVenvPythonAfterInstall(t *testing.T) {
// a fake scimesh version so the preflight goes green through the venv.
venvPython := filepath.Join(server.dir, "venv", "bin", "python")
_ = os.MkdirAll(filepath.Dir(venvPython), 0o755)
_ = os.WriteFile(venvPython, []byte("#!/bin/sh\nif [ \"$1\" = \"-c\" ]; then echo 9.9.9-test; exit 0; fi\nexit 0\n"), 0o755)
_ = os.WriteFile(venvPython, []byte("#!/bin/sh\nfor a in \"$@\"; do if [ \"$a\" = \"-c\" ]; then echo 9.9.9-test; exit 0; fi; done\nexit 0\n"), 0o755)
req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, base+"/api/test", strings.NewReader(`{"coordinator_url":"http://127.0.0.1:1"}`))
req.Header.Set("Content-Type", "application/json")
@@ -232,7 +232,7 @@ function draftConfig(){
cpu_count:state.cpu==='custom'?parseInt($('in-cpu').value||'1',10):0,
concurrency:parseInt($('in-conc').value||'1',10)
};
if(state.venvPython)cfg.task_runner=[state.venvPython,'-m','scimesh.worker.task'];
if(state.venvPython)cfg.task_runner=[state.venvPython,'-I','-m','scimesh.worker.task'];
return cfg;
}
+66
View File
@@ -0,0 +1,66 @@
# SciMesh uninstaller for Windows: stops the running component, removes its
# binary, and — when asked — deletes its data directory.
#
# powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.ps1 | iex"
#
# $env:SCIMESH_COMPONENT selects the component (coordinator | worker | all,
# default all). Data is kept unless $env:SCIMESH_PURGE -eq "1" or the
# -Purge switch is passed (in a pipe there is no interactive prompt).
param(
[switch]$Purge
)
$ErrorActionPreference = "Stop"
$Component = if ($env:SCIMESH_COMPONENT) { $env:SCIMESH_COMPONENT } else { "all" }
if ($Purge -or $env:SCIMESH_PURGE -eq "1") { $Purge = $true } else { $Purge = $false }
$InstallDir = if ($env:SCIMESH_INSTALL_DIR) {
$env:SCIMESH_INSTALL_DIR
} else {
Join-Path $env:LOCALAPPDATA "SciMesh"
}
function Remove-Component {
param([string]$Name, [string]$Binary)
Write-Host "Stopping $Name"
Get-Process -Name $Name -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$target = Join-Path $InstallDir $Binary
if (Test-Path $target) {
Write-Host "Removing $target"
Remove-Item -Force $target
}
}
function Remove-Data {
param([string]$Dir, [string]$Label)
if (-not (Test-Path $Dir)) { return }
if ($Purge) {
Remove-Item -Recurse -Force $Dir
Write-Host "Deleted $Dir"
return
}
# No interactive prompt in a pipe: keep the data by default.
Write-Host "Keeping $Dir ($Label). Pass -Purge to delete it."
}
switch ($Component) {
"coordinator" {
Remove-Component -Name "coordinator" -Binary "coordinator.exe"
Remove-Data -Dir (Join-Path $HOME ".scimesh") -Label "secrets, databases, artifacts, users"
}
"worker" {
Remove-Component -Name "worker-agent" -Binary "worker-agent.exe"
Remove-Data -Dir (Join-Path $HOME ".scimesh-worker") -Label "worker config, runtime venv, logs"
}
"all" {
Remove-Component -Name "coordinator" -Binary "coordinator.exe"
Remove-Component -Name "worker-agent" -Binary "worker-agent.exe"
Remove-Data -Dir (Join-Path $HOME ".scimesh") -Label "secrets, databases, artifacts, users"
Remove-Data -Dir (Join-Path $HOME ".scimesh-worker") -Label "worker config, runtime venv, logs"
}
default { throw "unknown component: $Component (use 'coordinator', 'worker' or 'all')" }
}
Write-Host ""
Write-Host "SciMesh $Component uninstalled."
Write-Host "Pass -Purge to also delete the data directories."
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# SciMesh uninstaller: stops the running component, removes its binary, and —
# when asked — deletes its data directory.
#
# curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.sh | bash -s coordinator
# curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.sh | bash -s worker
# curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/uninstall.sh # both
#
# Data (jobs, artifacts, users, secrets, the managed venv) is kept by default.
# Pass --purge (or set SCIMESH_PURGE=1) to delete it without asking; without
# it the script prompts interactively. Because `curl | bash` pipes have no
# interactive stdin, the default is always "keep data".
set -eu
COMPONENT="${1:-all}"
PURGE=0
for arg in "$@"; do
case "$arg" in
--purge) PURGE=1 ;;
esac
done
[ "${SCIMESH_PURGE:-0}" = "1" ] && PURGE=1
INSTALL_DIR="${SCIMESH_INSTALL_DIR:-$HOME/.local/bin}"
remove_component() {
case "$1" in
coordinator)
echo "Stopping the coordinator (serve)…"
pkill -x coordinator 2>/dev/null || true
echo "Removing $INSTALL_DIR/coordinator…"
rm -f "$INSTALL_DIR/coordinator"
;;
worker)
echo "Stopping the worker agent and its setup wizard…"
pkill -x worker-agent 2>/dev/null || true
echo "Removing $INSTALL_DIR/worker-agent…"
rm -f "$INSTALL_DIR/worker-agent"
;;
*) echo "unknown component: $1 (use 'coordinator', 'worker' or 'all')" >&2; exit 1 ;;
esac
}
remove_data() {
local dir="$1" label="$2"
if [ ! -d "$dir" ]; then
return 0
fi
if [ "$PURGE" = "1" ]; then
rm -rf "$dir"
echo "Deleted $dir"
return 0
fi
# In a pipe (curl | bash) stdin is exhausted, so the prompt defaults to keep.
printf "Delete %s (%s)? [y/N] " "$dir" "$label"
read -r answer || answer=""
case "$answer" in
y|Y|yes|YES) rm -rf "$dir"; echo "Deleted $dir" ;;
*) echo "Keeping $dir" ;;
esac
}
case "$COMPONENT" in
coordinator)
remove_component coordinator
remove_data "$HOME/.scimesh" "secrets, databases, artifacts, users, managed venv"
;;
worker)
remove_component worker
remove_data "$HOME/.scimesh-worker" "worker config, runtime venv, logs"
;;
all)
remove_component coordinator
remove_component worker
remove_data "$HOME/.scimesh" "secrets, databases, artifacts, users, managed venv"
remove_data "$HOME/.scimesh-worker" "worker config, runtime venv, logs"
;;
*) echo "unknown component: $COMPONENT (use 'coordinator', 'worker' or 'all')" >&2; exit 1 ;;
esac
echo
echo "SciMesh $COMPONENT uninstalled."
echo "Pass --purge to also delete the data directories without asking."