Rename Installation to Instance

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
This commit is contained in:
2026-08-31 10:12:01 +02:00
co-authored by Claude Opus 5
parent 6534855492
commit d01a8dad37
101 changed files with 374 additions and 375 deletions
+10 -10
View File
@@ -1,8 +1,8 @@
"""Connecting this installation to a Fluksio portal, and cutting it loose.
"""Connecting this instance to a Fluksio portal, and cutting it loose.
Entirely optional, and superuser-only to change: enrolling grants a remote
party the rights of the account that performed it, which is not a decision an
ordinary user of this installation gets to make on everyone else's behalf.
ordinary user of this instance gets to make on everyone else's behalf.
Admitting further portal accounts is the same decision made again, so it is
guarded the same way. A person let in this way gets an ordinary local account
@@ -47,7 +47,7 @@ class EnrollBody(BaseModel):
@router.get("/status", dependencies=[Depends(get_current_user)])
def read_status(request: Request) -> dict[str, Any]:
"""Whether this installation is enrolled, and whether the link is up.
"""Whether this instance is enrolled, and whether the link is up.
Readable by any signed-in user: everyone here has a right to know whether
the machine they are using can be reached from outside.
@@ -61,7 +61,7 @@ def read_status(request: Request) -> dict[str, Any]:
"portal_url": config.portal_url if config else None,
"issuer": config.issuer if config else None,
"portal_account": config.portal_account if config else None,
"installation_id": config.installation_id if config else None,
"instance_id": config.instance_id if config else None,
"last_error": None,
"connected_since": None,
}
@@ -80,7 +80,7 @@ async def enroll(
"""Redeem a claim code and start dialling the portal.
The account performing this is mapped to the portal account that owns the
installation, so the owner's portal sessions arrive here as them. Widening
instance, so the owner's portal sessions arrive here as them. Widening
that to anyone else is a local decision made one person at a time, below —
never something the portal can do from its side.
@@ -121,11 +121,11 @@ def add_remote_user(session: SessionDep, body: RemoteUserBody) -> Any:
if config is None:
raise HTTPException(
status_code=409,
detail="This installation is not connected to a portal",
detail="This instance is not connected to a portal",
)
try:
response = httpx.post(
f"{config.portal_url.rstrip('/')}/api/v1/installation-members/",
f"{config.portal_url.rstrip('/')}/api/v1/instance-members/",
headers={"Authorization": f"Bearer {config.token}"},
json={"code": body.code.strip()},
timeout=15.0,
@@ -140,7 +140,7 @@ def add_remote_user(session: SessionDep, body: RemoteUserBody) -> Any:
)
if response.status_code == 409:
raise HTTPException(
status_code=409, detail="That code belongs to this installation's owner"
status_code=409, detail="That code belongs to this instance's owner"
)
if response.status_code != 200:
raise HTTPException(
@@ -183,14 +183,14 @@ def forget_remote_user(portal_sub: str) -> None:
Best effort on purpose: the local account is what grants access, so it is
already over by the time this runs. A portal that cannot be reached keeps a
row that opens nothing — the installation refuses the session either way.
row that opens nothing — the instance refuses the session either way.
"""
config = cloud_config.load()
if config is None:
return
try:
response = httpx.delete(
f"{config.portal_url.rstrip('/')}/api/v1/installation-members/{portal_sub}",
f"{config.portal_url.rstrip('/')}/api/v1/instance-members/{portal_sub}",
headers={"Authorization": f"Bearer {config.token}"},
timeout=15.0,
)