v0.20.0:0 - per-spark ssh-key copy + wireguard status badge

This commit is contained in:
Keysat
2026-06-15 09:53:40 -05:00
parent 5341fcc506
commit e87158c492
7 changed files with 133 additions and 10 deletions
+47
View File
@@ -401,6 +401,53 @@ async def wake_spark(name: str) -> dict:
return {"ok": True, "spark": name, "mac": mac, "delivered_via": delivered_via}
@app.post("/api/spark/{name}/ssh-key")
async def spark_ssh_key(name: str) -> dict:
"""Ensure the named Spark has an ed25519 keypair and return its PUBLIC key.
This is the Spark's *outbound* identity — the key it uses to log in to other
machines (e.g. the operator's Mac). It is the opposite direction from, and
distinct from, the package's own key shown by the StartOS "Show Public Key"
action (which grants this dashboard SSH access to the Sparks).
Non-destructive: generates the key only if absent, never overwrites an
existing one (which may already be an identity the Spark uses elsewhere).
Public keys are not secret, so returning it is safe. No request-supplied
value reaches the command — `name` is constrained to a fixed set and
host/user come from operator config — so there is nothing to shell-quote.
"""
if name not in ("spark1", "spark2"):
raise HTTPException(404, f"unknown spark: {name}")
host = settings.spark1_host if name == "spark1" else settings.spark2_host
user = settings.spark1_user if name == "spark1" else settings.spark2_user
if not host or not user:
raise HTTPException(400, f"{name} is not configured")
# Empty passphrase so the key is usable unattended; comment carries the
# remote hostname so it's identifiable in an authorized_keys file later.
cmd = (
"set -e; "
"mkdir -p ~/.ssh && chmod 700 ~/.ssh; "
"if [ ! -f ~/.ssh/id_ed25519 ]; then "
'ssh-keygen -t ed25519 -N "" -C "spark-control@$(hostname)" -f ~/.ssh/id_ed25519 >/dev/null 2>&1; '
"echo CREATED=1; else echo CREATED=0; fi; "
"[ -f ~/.ssh/id_ed25519.pub ] || ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub; "
"echo PUBKEY=$(cat ~/.ssh/id_ed25519.pub)"
)
rc, out, err = await ssh_run(host, user, cmd, settings, timeout=15)
if rc != 0:
raise HTTPException(502, f"couldn't read/create the SSH key on {name}: {err.strip() or out.strip() or f'rc={rc}'}")
created = False
pubkey = ""
for line in out.splitlines():
if line.startswith("CREATED="):
created = line.strip() == "CREATED=1"
elif line.startswith("PUBKEY="):
pubkey = line[len("PUBKEY="):].strip()
if not pubkey:
raise HTTPException(502, f"no public key returned from {name}")
return {"ok": True, "spark": name, "host": host, "user": user, "pubkey": pubkey, "created": created}
@app.get("/api/services")
async def get_services() -> dict:
"""Lifecycle state of always-on support services (Parakeet, Kokoro, …).