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
+13 -13
View File
@@ -5,7 +5,7 @@ shows it on the wall, and somebody with an account types that code into the
panels dialog to say which panel the device is. The device polls, collects the
credential the approval minted, and never asks again.
A screen hanging somewhere this installation is not reachable from does the
A screen hanging somewhere this instance is not reachable from does the
same thing through the portal, which forwards those two calls down the tunnel
without a session — a device with no credential is the whole point of them —
and mints the credential itself when the approval comes. Which side minted it
@@ -81,7 +81,7 @@ class _Pending(BaseModel):
#: Self-reported and worth what that is worth.
device: str = ""
#: Whether it came down the tunnel. A device that reached the portal
#: cannot reach this installation, so its credential has to be minted
#: cannot reach this instance, so its credential has to be minted
#: where it can collect it.
remote: bool = False
@@ -99,7 +99,7 @@ class _PendingStore:
A device's poll lands on whichever API worker the proxy picked, and a
screen pairing through the portal lands on whichever one holds the tunnel
— so a code minted by one worker has to be findable from the next. Redis
is where this installation already keeps what must outlive a process.
is where this instance already keeps what must outlive a process.
Without one there is one process by definition (the pip install, and the
tests), and a dictionary is the same thing for it.
@@ -224,8 +224,8 @@ def _describe(request: Request) -> str:
def _mint_at_hub(panel_id: str) -> str:
"""Ask the portal for this panel's credential.
A device that arrived through the portal cannot reach this installation, so
a token this installation signed would be one it could never present: the
A device that arrived through the portal cannot reach this instance, so
a token this instance signed would be one it could never present: the
portal verifies what crosses its tunnel, and it verifies against its own
key. It mints, we say which panel — and the scope check here decides the
rest, on this call and on every later one.
@@ -234,7 +234,7 @@ def _mint_at_hub(panel_id: str) -> str:
if config is None:
raise HTTPException(
status_code=409,
detail="That device came through a portal this installation is no "
detail="That device came through a portal this instance is no "
"longer enrolled with",
)
try:
@@ -264,14 +264,14 @@ class PanelsPublic(BaseModel):
reliable answer to it: an admin working through the portal is on the
portal's origin, and this one is for a screen on this network.
An installation enrolled with a portal has a second address, built by the
An instance enrolled with a portal has a second address, built by the
dialog from what ``/cloud/status`` reports rather than from here — a panel
is not the thing that knows whether remote access is on.
"""
panels: list[PanelDef] = Field(default_factory=list)
#: Whatever this installation was told it is reachable at. The same setting
#: the password-reset links are built from, so an installation that has it
#: Whatever this instance was told it is reachable at. The same setting
#: the password-reset links are built from, so an instance that has it
#: wrong has it wrong in both places.
frontend_host: str = ""
@@ -310,7 +310,7 @@ async def save_panels(body: PanelsConfig) -> Any:
def _save() -> None:
# Read and write under one lock. The nonce belongs to this
# installation, not to whoever is writing the panels back: a client
# instance, not to whoever is writing the panels back: a client
# holding an older copy must not be able to undo a revocation by
# saving an arrangement — and reading it in a separate step from
# writing it is exactly how an unpair in between was undone.
@@ -335,7 +335,7 @@ def start_pairing(request: Request) -> Any:
All this hands out is a code that means nothing until somebody with an
account approves it, so the worst an unwelcome caller achieves is an entry
that expires ten minutes later. Reachable from the internet when this
installation is enrolled with a portal, which is what the cap and the
instance is enrolled with a portal, which is what the cap and the
portal's own per-address limits are between.
"""
if _pending.count() >= MAX_PENDING:
@@ -412,8 +412,8 @@ def approve_pairing(panel_id: str, body: PairRequest, current_user: CurrentUser)
A credential minted here names the approver, so what the panel does stays
attributable to a person rather than to nobody. One minted by the portal —
for a device that reached this installation only through it — names the
account this installation was enrolled with instead, since that is the one
for a device that reached this instance only through it — names the
account this instance was enrolled with instead, since that is the one
every portal-borne request already acts as.
"""
panel = find(panel_id)