v0.20.0:0 - per-spark ssh-key copy + wireguard status badge
This commit is contained in:
@@ -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, …).
|
||||
|
||||
Reference in New Issue
Block a user