Start without git, and say what that costs

A `pip install` on a locked-down host — the case the CLI exists for —
may have no git, and the store shelled out to it while building the flow
repository, so the engine refused to start at all. The store is files;
git is their history. Missing it is now one warning and no commits
rather than a stack trace, which is the difference between a machine
that runs your experiments and one that does not.

Found by installing the wheels into a bare python:3.12-slim and pairing
it with the portal: `fluksio enroll` took the code, `fluksio serve`
dialled out, and the hub was proxying requests through the tunnel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 23:00:09 +02:00
co-authored by Claude Opus 5
parent 10b0ba9e49
commit 0ad8d576ad
4 changed files with 68 additions and 23 deletions
+10 -15
View File
@@ -124,14 +124,17 @@ def _enroll(portal: str, code: str, as_email: str | None) -> int:
from fluksio.core.db import engine
with Session(engine) as session:
_, generated = ensure_superuser(session, email=as_email)
admin, generated = ensure_superuser(session, email=as_email)
if generated:
_print_new_admin(session, generated)
_print_new_admin(admin.email, generated)
try:
user = pick_superuser(session, as_email)
except LookupError as exc:
print(f"error: {exc}", file=sys.stderr, flush=True)
return 1
# Read before the session closes: the instance is detached after it,
# and touching an attribute then goes back to a database that is gone.
email = user.email
try:
config = enroll_mod.enroll(session, user, portal, code)
except enroll_mod.AlreadyEnrolled as exc:
@@ -146,22 +149,14 @@ def _enroll(portal: str, code: str, as_email: str | None) -> int:
return 1
_say(
f"Connected to {config.portal_url} as {user.email} "
f"Connected to {config.portal_url} as {email} "
f"(installation {config.installation_id})."
)
return 0
def _print_new_admin(session: object, password: str) -> None:
from sqlmodel import Session, select
from fluksio.models import User
assert isinstance(session, Session)
user = session.exec(
select(User).where(User.is_superuser == True) # noqa: E712
).first()
_say(f"Created the admin account {user.email if user else ''}")
def _print_new_admin(email: str, password: str) -> None:
_say(f"Created the admin account {email}")
_say(f" password: {password}")
_say(" Shown once. Change it from the dashboard.")
@@ -177,11 +172,11 @@ def cmd_serve(args: argparse.Namespace) -> int:
from fluksio.core.db import engine
with Session(engine) as session:
_, generated = ensure_superuser(
admin, generated = ensure_superuser(
session, email=args.admin_email, password=args.admin_password
)
if generated:
_print_new_admin(session, generated)
_print_new_admin(admin.email, generated)
if args.enroll:
if not args.portal:
+30 -8
View File
@@ -94,6 +94,9 @@ def _same_content(left: FlowDef, right: FlowDef) -> bool:
class FlowStore:
"""Reads and writes flows, committing every change."""
#: One warning per process when there is no git to commit with.
_warned_no_git = False
def __init__(self, root: Path) -> None:
self.root = root
self.root.mkdir(parents=True, exist_ok=True)
@@ -109,12 +112,27 @@ class FlowStore:
# -------------------------------------------------------------------------
def _git(self, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", "-C", str(self.root), *args],
capture_output=True,
text=True,
check=False,
)
try:
return subprocess.run(
["git", "-C", str(self.root), *args],
capture_output=True,
text=True,
check=False,
)
except FileNotFoundError:
# No git on this machine — a `pip install` on a locked-down host is
# where this happens. The flows are files either way, which is what
# the store is; what is lost is their history, so say it once and
# carry on rather than refusing to start.
if not FlowStore._warned_no_git:
FlowStore._warned_no_git = True
logger.warning(
"git is not installed, so flow changes are not versioned. "
"Install it to get a commit per save."
)
return subprocess.CompletedProcess(
args=list(args), returncode=1, stdout="", stderr="git is not installed"
)
def _commit(self, message: str, allow_empty: bool = False) -> None:
self._git("add", "-A")
@@ -129,8 +147,12 @@ class FlowStore:
"-m",
message,
)
if result.returncode != 0 and "nothing to commit" not in result.stdout:
logger.warning("Could not commit flow change: %s", result.stdout.strip())
if result.returncode == 0 or "nothing to commit" in result.stdout:
return
if result.stderr == "git is not installed":
# Already said once, in `_git`. Repeating it per save is noise.
return
logger.warning("Could not commit flow change: %s", result.stdout.strip())
# -------------------------------------------------------------------------
# Paths