Files
app/backend/fluksio/core/bootstrap.py
T
stroblmeandClaude Opus 5 dffdfce9e4 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>
2026-08-21 22:51:13 +02:00

65 lines
2.3 KiB
Python

"""First run: an account to sign in with.
An installation started from the command line is given no environment, so the
superuser a deployment sets in `.env` has to come from somewhere. The session
key is the CLI's own business — it has to be set before this module can be
imported at all.
"""
from __future__ import annotations
import secrets
from sqlmodel import Session, select
from fluksio import crud
from fluksio.models import User, UserCreate
#: A generated admin's address. `example.com` is reserved for exactly this
#: (RFC 2606) — `@localhost` and `.local` are too, and `EmailStr` refuses those.
#: It is a name to sign in with, not somewhere mail is sent.
DEFAULT_ADMIN = "admin@example.com"
def ensure_superuser(
session: Session, *, email: str | None = None, password: str | None = None
) -> tuple[User, str | None]:
"""The account to sign in with, made on first run.
Returns the user and, when it was just created, the password in clear —
the caller prints it once. An installation that already has a superuser is
left alone: this is a first run, not a password reset.
"""
existing = session.exec(
select(User).where(User.is_superuser == True) # noqa: E712
).first()
if existing is not None:
return existing, None
generated = password or secrets.token_urlsafe(12)
user = crud.create_user(
session=session,
user_create=UserCreate(
email=email or DEFAULT_ADMIN, password=generated, is_superuser=True
),
)
return user, generated
def pick_superuser(session: Session, email: str | None = None) -> User:
"""The account an enrolment acts as; a portal session arrives as this one."""
if email:
user = session.exec(select(User).where(User.email == email)).first()
if user is None:
raise LookupError(f"No account here with the address {email}")
return user
users = session.exec(
select(User).where(User.is_superuser == True) # noqa: E712
).all()
if not users:
raise LookupError("This installation has no superuser to enrol as")
if len(users) > 1:
addresses = ", ".join(sorted(u.email for u in users))
raise LookupError(f"Several superusers here — name one with --as: {addresses}")
return users[0]