Add the fluksio CLI: serve, enroll, worker
`pip install fluksio && fluksio serve` on a machine with no Docker, no database and no configuration — which is the case this is for: a node on a cluster where ports cannot be opened. It makes its data directory, its key and an admin account, prints the password once, and serves. Pairing is `fluksio enroll <code> --portal …`, doing what the Settings screen does through the same function, before the engine starts and without one running — a machine nobody can route to has no browser pointed at it either. The portal serves the dashboard, so nothing is served here. Two things had to give way. `fastapi[standard]` pulls a cloud CLI that wants sentry-sdk 2.x while we pinned below it — no pip resolution existed, so the pin is lifted, which the comment beside it had been waiting for and which also lets the Python cap go. And `uv` is now a dependency rather than something to find on PATH: the Modules screen is how a data scientist installs torch, and it was quietly falling back to the engine's own interpreter. The CLI imports nothing from the engine before it has set DATA_DIR — the settings are built on the first import of core.config, and reaching it early put the database in the working directory. There is a test for that now, because the failure is silent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -37,11 +38,25 @@ VENV_DIR = settings.FLOWS_DIR.parent / "user-venv"
|
||||
SYNC_TIMEOUT = 300
|
||||
|
||||
|
||||
def uv_bin() -> str:
|
||||
"""Where ``uv`` is, without depending on what PATH happens to hold.
|
||||
|
||||
It is a dependency, so it is installed beside the interpreter running this
|
||||
— which is what a systemd unit naming an absolute ExecStart, or a container
|
||||
entrypoint, would otherwise miss.
|
||||
"""
|
||||
beside = Path(sys.executable).with_name("uv")
|
||||
if beside.exists():
|
||||
return str(beside)
|
||||
return shutil.which("uv") or "uv"
|
||||
|
||||
|
||||
def venv_python() -> str:
|
||||
"""The interpreter node code runs on.
|
||||
|
||||
Falls back to the engine's own when there is no venv — a deployment without
|
||||
``uv`` still runs python nodes, it just cannot add packages to them.
|
||||
Falls back to the engine's own when there is no venv — an installation that
|
||||
could not build one still runs python nodes, it just cannot add packages
|
||||
to them.
|
||||
"""
|
||||
path = VENV_DIR / "bin" / "python"
|
||||
return str(path) if path.exists() else sys.executable
|
||||
@@ -65,7 +80,7 @@ def ensure_venv() -> None:
|
||||
# inside another is how a user pin ends up resolving against app packages.
|
||||
subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
uv_bin(),
|
||||
"venv",
|
||||
"--python",
|
||||
str(Path(sys.base_prefix, "bin", "python3")),
|
||||
@@ -95,7 +110,7 @@ def sync(requirements: str) -> tuple[bool, str]:
|
||||
# Empty means empty: without the flag uv refuses to clear a venv,
|
||||
# so deleting the last line would leave the package installed.
|
||||
[
|
||||
"uv",
|
||||
uv_bin(),
|
||||
"pip",
|
||||
"sync",
|
||||
"--allow-empty-requirements",
|
||||
|
||||
Reference in New Issue
Block a user