The control plane now advertises the same name the web UI answers on. That is safe because the client authenticates by SPKI pin and explicitly does not verify the hostname — "pin is the trust, not the name" — so no certificate covers or needs to cover either name. The per-host name still means something, though, and the rule it encodes has to survive: pinning binds a client to one server's key, so fmr may be a CNAME to exactly one host and never a multi-address service record. A second server gets enrolled as fmr-2 explicitly, because a client that reaches a different key does not fail over, it fails. Minting a link was broken and had been since the authenticated admin UI replaced the old admin API: enroll-link.sh still posted to 127.0.0.1:8444/admin/enroll-tokens, an endpoint that no longer exists on a listener that no longer binds loopback. Rather than add a second unauthenticated door — which is how the old one ended up briefly reachable from the network — the binary mints its own link. Whoever can run it against the state directory already holds every privilege the server has, so authenticating them to themselves would be theatre. EnrollmentURI is shared with the running server's EnrollmentLink rather than reimplemented. Two copies of that encoding would eventually disagree, and the failure mode is a pin that looks right and surfaces as an inscrutable TLS error rather than as a bad pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.1 KiB
Bash
53 lines
2.1 KiB
Bash
#!/usr/bin/env bash
|
|
# SPDX-FileCopyrightText: 2026 Echolot contributors
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# Mints an enrollment link on the probe server and prints it — as text, as a QR code if
|
|
# `qrencode` is around, and as an adb command if a device is attached.
|
|
#
|
|
# The link is minted by the server binary on the host rather than over HTTP. The admin API this
|
|
# used to call is gone: the admin UI that replaced it is authenticated, as it should be, and
|
|
# adding a second unauthenticated door on loopback is what briefly exposed the old one to the
|
|
# network. A root shell on the host needs no authentication anyway — whoever has one already has
|
|
# every privilege the server has.
|
|
#
|
|
# The link carries a single-use bearer token: treat it like a password until it is redeemed.
|
|
#
|
|
# Usage: echolot-app/scripts/enroll-link.sh [note]
|
|
set -euo pipefail
|
|
|
|
SSH_HOST="${ECHOLOT_SSH:-claude-echolot}"
|
|
NOTE="${1:-manual}"
|
|
|
|
# The env file is sourced rather than assumed: the state directory and the public URL live there,
|
|
# and minting against the wrong state directory would produce a token the running server has
|
|
# never heard of.
|
|
REMOTE='set -a; . /etc/echolot/server.env; set +a;
|
|
exec /usr/local/bin/echolot-server --mint-enroll-token'
|
|
RAW=$(ssh -o BatchMode=yes "$SSH_HOST" "sudo sh -c \"$REMOTE '$NOTE'\"" 2>/dev/null || true)
|
|
URI=$(printf '%s' "$RAW" | tr -d '\r' | grep -m1 '^echolot://enroll' || true)
|
|
|
|
if [ -z "$URI" ]; then
|
|
echo "could not mint a link — needs a server with --mint-enroll-token (v0.9.7+)." >&2
|
|
echo "raw response:" >&2
|
|
printf '%s\n' "$RAW" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "$URI"
|
|
echo
|
|
|
|
# A QR is the point of the format: scanning beats pasting a 200-character string onto a phone.
|
|
if command -v qrencode >/dev/null 2>&1; then
|
|
qrencode -t ANSIUTF8 "$URI"
|
|
else
|
|
echo "(install qrencode to get a scannable QR here)"
|
|
fi
|
|
|
|
# With a device attached, the deep link can be delivered straight to the app — no typing at all.
|
|
if command -v adb >/dev/null 2>&1 && [ -n "$(adb devices | sed -n '2p')" ]; then
|
|
echo
|
|
echo "attached device — deliver it directly with:"
|
|
echo " adb shell am start -a android.intent.action.VIEW -d '$URI'"
|
|
fi
|