Three fewer things to remember

**The portal link puts itself back up.** It already retried a connection that
raised, but a session that ended *cleanly* — a portal restarting, a proxy
closing an idle socket — returned normally and went straight back round the
loop with no wait at all, so an engine could spin against a portal that was
merely saying goodbye politely. Every ending now reconnects on a delay, and
the delay turns on whether the attempt got as far as attaching: one that stood
up and dropped is a network event and retries at once, one that never stood up
waits longer each time. Jittered, so a portal coming back is not met by every
installation it serves in the same instant. Ping timeouts are named rather
than defaulted, since they are what bounds how long a suspended laptop's dead
socket looks alive, and the keepalive task is awaited so the reason a link
went reaches the log instead of the garbage collector.

**`fluksio enroll <code>`** is the whole command now; hub.fluksio.com is the
default and `--portal` names another. The one command run before anything
works should not need two flags.

**`fluksio run` syncs first.** The reason a run exists is usually the edit
before it, so remembering to sync was remembering to do something the computer
could do — including the worker refresh, which is what makes an edit to your
own package take effect at all. `--no-sync` opts out for a tight loop.

That last one needed discovery fixed: it only ever looked at top-level `*.py`,
so a repository whose code is in a package — the ordinary shape — found
nothing from its own root. It now descends into the packages it holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ue1tkFWB1bcGy3aWhCKpU
This commit is contained in:
2026-08-24 18:17:16 +02:00
co-authored by Claude Fable 5
parent 99f6530698
commit 7e4f03369b
8 changed files with 294 additions and 29 deletions
+20 -7
View File
@@ -27,6 +27,11 @@ import fluksio
#: Where an installation keeps everything, unless it is told otherwise.
DEFAULT_HOME = Path("~/.fluksio")
#: The portal a claim code is redeemed at, unless another is named. Having a
#: default is the difference between one flag and two on the one command that
#: is run before anything works.
DEFAULT_PORTAL = "https://hub.fluksio.com"
#: WAL — what lets readers work while the engine writes — is not supported on
#: these. The database would be corrupt or locked, so it is worth saying.
NETWORK_FILESYSTEMS = ("nfs", "nfs4", "cifs", "smb", "smb3", "lustre", "fuse.sshfs")
@@ -233,13 +238,12 @@ def cmd_serve(args: argparse.Namespace) -> int:
_print_new_admin(admin_email, generated)
if args.enroll:
if not args.portal:
print("error: --enroll needs --portal", file=sys.stderr, flush=True)
return 1
if not cloud_config.exists():
# Before the engine starts, so the connector finds the config and
# dials out as part of coming up rather than needing a restart.
failed = _enroll(args.portal, args.enroll, args.admin_email)
failed = _enroll(
args.portal or DEFAULT_PORTAL, args.enroll, args.admin_email
)
if failed:
return failed
@@ -274,7 +278,7 @@ def cmd_serve(args: argparse.Namespace) -> int:
_say(" The dashboard is served by the portal; nothing is served here.")
else:
_say(" No portal. Pair this installation with:")
_say(" fluksio enroll <code> --portal https://hub.example.com")
_say(" fluksio enroll <code>")
_say(f" Signed in as {admin_email}")
_say(f" token in {token_path}")
_mention_other_installation(data_dir)
@@ -328,14 +332,23 @@ def _parser() -> argparse.ArgumentParser:
serve.add_argument("--admin-email", default=None)
serve.add_argument("--admin-password", default=None)
serve.add_argument("--enroll", metavar="CODE", help="claim code, if not yet paired")
serve.add_argument("--portal", metavar="URL", help="the portal --enroll redeems at")
serve.add_argument(
"--portal",
metavar="URL",
help=f"the portal --enroll redeems at (default {DEFAULT_PORTAL})",
)
serve.set_defaults(func=cmd_serve)
enroll = subparsers.add_parser(
"enroll", help="pair this installation with a portal"
)
enroll.add_argument("code", help="the claim code minted on the portal")
enroll.add_argument("--portal", required=True, metavar="URL")
enroll.add_argument(
"--portal",
default=DEFAULT_PORTAL,
metavar="URL",
help=f"a portal of your own, instead of {DEFAULT_PORTAL}",
)
enroll.add_argument(
"--as",
dest="as_email",