Repair Assistent SQLite natives after extension build completes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-23 20:31:12 +03:00
co-authored by Cursor
parent 208315e329
commit 84ed0bb47a
+106 -27
View File
@@ -229,15 +229,27 @@ print(__import__("json").dumps(found))
_ASSISTENT_SQLITE_BIN_PY = r""" _ASSISTENT_SQLITE_BIN_PY = r"""
from pathlib import Path from pathlib import Path
import json import json
skip = {"obj", "node_modules"}
def assistent_dlls(root: Path):
out = []
if not root.is_dir():
return out
for dll in root.rglob("*.dll"):
if any(p in skip for p in dll.parts):
continue
name = dll.name.lower()
if name == "swarmassistentextension.dll" or (
name.startswith("swarmextensionswarm-assistent") and name.endswith(".dll")
):
out.append(dll)
return out
roots = [Path("/mnt/swarm_data"), Path("/opt/swarmui")] roots = [Path("/mnt/swarm_data"), Path("/opt/swarmui")]
seen = set() seen = set()
found = [] found = []
for root in roots: for root in roots:
if not root.is_dir(): for dll in assistent_dlls(root):
continue
for dll in root.rglob("SwarmAssistentExtension.dll"):
if any(p in {"obj", "node_modules"} for p in dll.parts):
continue
key = str(dll) key = str(dll)
if key in seen: if key in seen:
continue continue
@@ -247,6 +259,7 @@ for root in roots:
pcl = list(dll_dir.glob("SQLitePCLRaw*.dll")) pcl = list(dll_dir.glob("SQLitePCLRaw*.dll"))
bundle = dll_dir / "SQLitePCLRaw.bundle_e_sqlite3.dll" bundle = dll_dir / "SQLitePCLRaw.bundle_e_sqlite3.dll"
native = list(dll_dir.glob("runtimes/**/e_sqlite3.*")) + list(dll_dir.glob("runtimes/**/libe_sqlite3.*")) native = list(dll_dir.glob("runtimes/**/e_sqlite3.*")) + list(dll_dir.glob("runtimes/**/libe_sqlite3.*"))
native_flat = list(dll_dir.glob("e_sqlite3.*")) + list(dll_dir.glob("libe_sqlite3.*"))
found.append({ found.append({
"path": str(dll.parent.parent) if dll.parent.name.startswith("net") else str(dll_dir), "path": str(dll.parent.parent) if dll.parent.name.startswith("net") else str(dll_dir),
"name": "swarm-assistent", "name": "swarm-assistent",
@@ -256,51 +269,84 @@ for root in roots:
"sqlitepcl": bool(pcl), "sqlitepcl": bool(pcl),
"sqlite_bundle": bundle.is_file(), "sqlite_bundle": bundle.is_file(),
"sqlite_native": bool(native), "sqlite_native": bool(native),
"sqlite_native_flat": bool(native_flat),
}) })
print(json.dumps(found)) print(json.dumps(found))
""" """
_ASSISTENT_SQLITE_REPAIR_PY = r""" _ASSISTENT_SQLITE_REPAIR_PY = r"""
from pathlib import Path from pathlib import Path
import json, shutil import json, os, shutil
skip = {"obj", "node_modules"} skip = {"obj", "node_modules"}
dlls = []
for root in (Path("/mnt/swarm_data"), Path("/opt/swarmui")): def assistent_dlls(root: Path):
out = []
if not root.is_dir(): if not root.is_dir():
continue return out
for dll in root.rglob("SwarmAssistentExtension.dll"): for dll in root.rglob("*.dll"):
if any(p in skip for p in dll.parts): if any(p in skip for p in dll.parts):
continue continue
dlls.append(dll) name = dll.name.lower()
if name == "swarmassistentextension.dll" or (
name.startswith("swarmextensionswarm-assistent") and name.endswith(".dll")
):
out.append(dll)
return out
def find_native(d: Path):
for rel in (
Path("runtimes/linux-x64/native/libe_sqlite3.so"),
Path("runtimes/linux-x64/native/e_sqlite3.so"),
):
p = d / rel
if p.is_file():
return p
for p in d.glob("runtimes/**/libe_sqlite3.so"):
return p
for p in d.glob("runtimes/**/e_sqlite3.so"):
return p
return None
def link_native(dest_dir: Path, src: Path):
for name in ("libe_sqlite3.so", "e_sqlite3.so"):
out = dest_dir / name
if out.exists() or out.is_symlink():
continue
try:
os.symlink(src, out)
except OSError:
shutil.copy2(src, out)
dlls = []
for root in (Path("/mnt/swarm_data"), Path("/opt/swarmui")):
dlls.extend(assistent_dlls(root))
def has_sqlite(d): def has_sqlite(d):
if not (d / "Microsoft.Data.Sqlite.dll").is_file(): if not (d / "Microsoft.Data.Sqlite.dll").is_file():
return False return False
if not list(d.glob("SQLitePCLRaw*.dll")): if not list(d.glob("SQLitePCLRaw*.dll")):
return False return False
return (d / "SQLitePCLRaw.bundle_e_sqlite3.dll").is_file() or list( if (d / "libe_sqlite3.so").is_file() or (d / "e_sqlite3.so").is_file():
d.glob("runtimes/**/e_sqlite3.*") return True
) or list(d.glob("runtimes/**/libe_sqlite3.*")) return bool(list(d.glob("runtimes/**/libe_sqlite3.*")) or list(d.glob("runtimes/**/e_sqlite3.*")))
donors = [d.parent for d in dlls if has_sqlite(d.parent)] donors = [d.parent for d in dlls if has_sqlite(d.parent)]
if not donors: if not donors:
nuget = Path.home() / ".nuget" / "packages" nuget = Path.home() / ".nuget" / "packages"
extra = []
if nuget.is_dir(): if nuget.is_dir():
extra.extend(nuget.rglob("Microsoft.Data.Sqlite.dll")) for f in nuget.rglob("Microsoft.Data.Sqlite.dll"):
extra.extend(nuget.rglob("SQLitePCLRaw.core.dll")) if f.parent not in donors:
# last resort: any copy on the disk under swarm donors.append(f.parent)
for root in (Path("/opt/swarmui"), Path("/root/.nuget")):
if root.is_dir():
extra.extend(root.rglob("Microsoft.Data.Sqlite.dll"))
for f in extra:
if f.parent not in donors:
donors.append(f.parent)
copied = [] copied = []
linked = []
missing = [] missing = []
for dll in dlls: for dll in dlls:
dest = dll.parent dest = dll.parent
native = find_native(dest)
if native is not None:
link_native(dest, native)
linked.append({"dll_dir": str(dest), "native": str(native)})
if has_sqlite(dest): if has_sqlite(dest):
continue continue
src = next((d for d in donors if d != dest and has_sqlite(d)), None) src = next((d for d in donors if d != dest and has_sqlite(d)), None)
@@ -311,7 +357,6 @@ for dll in dlls:
continue continue
names = ["Microsoft.Data.Sqlite.dll"] names = ["Microsoft.Data.Sqlite.dll"]
names += [p.name for p in src.glob("SQLitePCLRaw*.dll")] names += [p.name for p in src.glob("SQLitePCLRaw*.dll")]
names += [p.name for p in src.glob("e_sqlite3.*")]
for name in names: for name in names:
fp = src / name fp = src / name
if fp.is_file(): if fp.is_file():
@@ -326,7 +371,11 @@ for dll in dlls:
out.parent.mkdir(parents=True, exist_ok=True) out.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(fp, out) shutil.copy2(fp, out)
copied.append({"dll_dir": str(dest), "from": str(src)}) copied.append({"dll_dir": str(dest), "from": str(src)})
print(json.dumps({"copied": copied, "missing": missing, "dlls": [str(d) for d in dlls]})) n2 = find_native(dest)
if n2 is not None:
link_native(dest, n2)
linked.append({"dll_dir": str(dest), "native": str(n2)})
print(json.dumps({"copied": copied, "linked": linked, "missing": missing, "dlls": [str(d) for d in dlls]}))
""" """
@@ -394,10 +443,14 @@ def repair_assistent_sqlite_bins(cfg: Config, host: str, log: Log) -> bool:
return False return False
for row in copied: for row in copied:
log(f"Assistent Sqlite: скопировал рядом с {row.get('dll_dir')}") log(f"Assistent Sqlite: скопировал рядом с {row.get('dll_dir')}")
for row in data.get("linked") or []:
log(f"Assistent Sqlite: symlink native → {row.get('dll_dir')}")
for path in missing: for path in missing:
log(f"⚠ Assistent Sqlite: нет donor DLL для {path}") log(f"⚠ Assistent Sqlite: нет donor DLL для {path}")
if copied: if copied:
return True return True
if data.get("linked"):
return True
if not missing: if not missing:
log("Assistent Sqlite: private deps уже рядом с DLL") log("Assistent Sqlite: private deps уже рядом с DLL")
return True return True
@@ -435,7 +488,7 @@ def verify_assistent_sqlite_bins(cfg: Config, host: str, log: Log) -> bool:
) )
continue continue
if row.get("sqlite_dll") and row.get("sqlitepcl") and ( if row.get("sqlite_dll") and row.get("sqlitepcl") and (
row.get("sqlite_bundle") or row.get("sqlite_native") row.get("sqlite_bundle") or row.get("sqlite_native") or row.get("sqlite_native_flat")
): ):
log(f"Assistent {name}: Microsoft.Data.Sqlite + SQLitePCLRaw (+ native) рядом с DLL") log(f"Assistent {name}: Microsoft.Data.Sqlite + SQLitePCLRaw (+ native) рядом с DLL")
ok_any = True ok_any = True
@@ -1387,13 +1440,39 @@ def ensure_swarmui_running(cfg: Config, host: str, log: Log, restart: bool) -> N
if restart and active == "active": if restart and active == "active":
log("systemctl restart swarmui (новые extensions/autocomplete)") log("systemctl restart swarmui (новые extensions/autocomplete)")
run_ssh(cfg, host, "sudo -n systemctl restart swarmui", timeout=120) run_ssh(cfg, host, "sudo -n systemctl restart swarmui", timeout=120)
_wait_swarmui_extension_build(cfg, host, log)
return return
if active != "active": if active != "active":
log("systemctl start swarmui") log("systemctl start swarmui")
run_ssh(cfg, host, "sudo -n systemctl start swarmui", timeout=120) run_ssh(cfg, host, "sudo -n systemctl start swarmui", timeout=120)
_wait_swarmui_extension_build(cfg, host, log)
run_ssh(cfg, host, "sudo -n systemctl enable swarmui", check=False) run_ssh(cfg, host, "sudo -n systemctl enable swarmui", check=False)
def _wait_swarmui_extension_build(cfg: Config, host: str, log: Log, timeout_sec: int = 120) -> None:
"""Wait until SwarmUI compiles extensions (Assistent DLL appears in bin/extensions)."""
import time
deadline = time.monotonic() + timeout_sec
while time.monotonic() < deadline:
try:
raw = run_ssh(
cfg,
host,
"python3 - <<'PY'\n" + _ASSISTENT_SQLITE_BIN_PY + "\nPY",
check=False,
timeout=25,
).strip()
rows = json.loads(raw.splitlines()[-1]) if raw else []
if isinstance(rows, list) and rows and any(r.get("dll") for r in rows):
log("SwarmUI: extension DLL готов")
return
except Exception:
pass
time.sleep(5)
log("⚠ SwarmUI: extension DLL не появился за 2 мин — repair Sqlite может не сработать")
_OLLAMA_TAGS_PY = r""" _OLLAMA_TAGS_PY = r"""
import json, urllib.request import json, urllib.request
try: try: