Follows the portal: the noun is "instance" everywhere the app says it — UI strings, CLI output, error details, docs and comments. The wire keys (`instance_id`, `instance_token`) and the hub route this calls move with it. An existing cloud.json is adopted rather than refused: without the key alias the dataclass fails to parse, which the caller swallows and reads as "never enrolled" instead of "reconnect". `instance_key` on a node type becomes `target_key`. It means the outside thing a node points at, which is a different sense of the word, and keeping both would put two meanings of "instance" in one codebase. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""First run: an account to sign in with.
|
|
|
|
An instance 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 instance 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 instance 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]
|