"""Instance identity — a stable, human-friendly name for this Navi install.
Every deployed Navi carries ``instance.json`` in its working directory
(gitignored): ``{"name": "yuki", "instance_id": "<uuid>", "created_at": "..."}``.
The name is generated once — a single female name from the Japanese,
American or Spanish pool (~150 in total; collisions across a swarm are
possible, rename by hand when they happen). 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
_JAPANESE = [
"yuki", "sakura", "hana", "aki", "miku", "rin", "mei", "yui", "sana",
"nao", "kaori", "mika", "rika", "emi", "haruka", "asuka", "rei", "airi",
"kana", "honoka", "mitsuki", "natsumi", "sayuri", "suzume", "tamaki",
"tomoe", "ayame", "chiyo", "fumiko", "himari", "kaede", "kanna", "mai",
"mariko", "midori", "momoka", "natsuki", "noriko", "riko", "saori",
"shiori", "suzuka", "tsukiko", "umeko", "yoshiko", "yuzuki", "kirie",
"jun", "motoko", "satchiko",
]
_AMERICAN = [
"emma", "olivia", "ava", "mia", "chloe", "grace", "lily", "hannah",
"nora", "ruby", "alice", "clara", "daisy", "hazel", "iris", "jade",
"june", "leah", "lucy", "pearl", "rose", "sage", "stella", "violet",
"willa", "zoe", "amber", "brooke", "faith", "harmony", "josephine",
"katherine", "lydia", "madeline", "natalie", "paige", "riley", "scarlett",
"tessa", "trinity", "vivian", "abigail", "bethany", "delilah", "everly",
"jenna", "leona", "melody", "serenity", "tiffany",
]
_SPANISH = [
"sofia", "lucia", "valentina", "camila", "daniela", "carmen", "elena",
"gabriela", "ines", "lola", "marisol", "paulina", "raquel", "rosalia",
"serena", "teresa", "ximena", "alba", "bonita", "carlota", "esperanza",
"juanita", "mirta", "noelia", "paloma", "rosita", "adela", "belen",
"candela", "dolores", "estrella", "flor", "guadalupe", "jimena", "laura",
"miriam", "pilar", "rocio", "veronica", "yolanda", "catalina", "dalia",
"emilia", "fernanda", "gisela", "nayeli", "renata", "salma", "ursula",
"vanesa",
]
_NAME_POOLS = [_JAPANESE, _AMERICAN, _SPANISH] # union = the name pool
IDENTITY_FILE = "instance.json"
@dataclass(frozen=True)
class InstanceIdentity:
name: str
instance_id: str
created_at: str
def generate_name() -> str:
"""A single female name from any culture pool, e.g. ``yuki`` or ``sofia``.
Collisions across a swarm are possible (~150 names) — the owner renames
by hand (edit instance.json) when two machines draw the same name.
"""
import secrets
return secrets.choice(_JAPANESE + _AMERICAN + _SPANISH)
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