"""Instance identity — a stable, human-friendly name for this Navi install.
Every deployed Navi carries ``instance.json`` in its working directory
(gitignored): ``{"name": "quiet-otter", "instance_id": "<uuid>", "created_at": "..."}``.
The name is generated once (adjective + animal — readable, speakable, collision-
free at household-network scale) and is meant to be renamed by hand if the owner
wants. The instance_id is the technical identity for audit and peer pinning.
Rename = edit the file, restart the server. Nothing else reads the name as
long-lived state, so this is safe.
"""
from __future__ import annotations
import json
import uuid as _uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
_ADJECTIVES = [
"quiet", "amber", "hollow", "silver", "brisk", "cobalt", "dawn", "ember",
"frost", "gentle", "harbor", "ivory", "jasper", "keen", "lucid", "mellow",
"nimble", "opal", "pale", "quartz", "rapid", "soft", "tidal", "umber",
"velvet", "wan", "crimson", "golden", "misty", "noble", "olive", "plum",
"royal", "slate", "topaz", "vivid", "wild", "yellow", "zesty", "bold",
"calm", "deep", "eager", "flint", "gray", "hasty", "iron", "jolly",
"kind", "lunar",
]
_ANIMALS = [
"otter", "falcon", "heron", "moth", "badger", "cormorant", "dingo", "elk",
"fox", "grouse", "hare", "ibex", "jackal", "koi", "lynx", "marten",
"newt", "osprey", "puffin", "quail", "raven", "stoat", "tapir", "urchin",
"vole", "wren", "yak", "zephyr", "beagle", "caribou", "dolphin", "ermine",
"ferret", "gecko", "haddock", "ibis", "jaguar", "kestrel", "lemur", "mink",
"narwhal", "ocelot", "penguin", "quokka", "robin", "seal", "thrush",
"umbra", "vicuna", "wallaby",
]
IDENTITY_FILE = "instance.json"
@dataclass(frozen=True)
class InstanceIdentity:
name: str
instance_id: str
created_at: str
def generate_name() -> str:
"""Random adjective-animal pair, e.g. ``quiet-otter``."""
import secrets
return f"{secrets.choice(_ADJECTIVES)}-{secrets.choice(_ANIMALS)}"
def load_identity(path: str | Path = IDENTITY_FILE) -> InstanceIdentity:
"""Read instance.json. Raises if the file is missing or malformed."""
data = json.loads(Path(path).read_text(encoding="utf-8"))
return InstanceIdentity(
name=str(data["name"]),
instance_id=str(data["instance_id"]),
created_at=str(data.get("created_at", "")),
)
def ensure_identity(path: str | Path = IDENTITY_FILE) -> InstanceIdentity:
"""Load the identity, creating the file on first run (install / boot)."""
p = Path(path)
if p.exists():
return load_identity(p)
identity = InstanceIdentity(
name=generate_name(),
instance_id=str(_uuid.uuid4()),
created_at=datetime.now(timezone.utc).isoformat(),
)
p.write_text(
json.dumps(
{
"name": identity.name,
"instance_id": identity.instance_id,
"created_at": identity.created_at,
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
try:
p.chmod(0o644)
except OSError:
pass
return identity