cloverleaf-larry: one-arg self-contained broker install <client-id> (Bryan-direct P1, approved)
Adds a positional <client-id> self-enroll path to install-larry.sh:
curl -fsSL .../install-larry.sh | bash -s <client-id>
POSTs {"client_id":<slug>} to the broker's one-shot /enroll-claim endpoint
(no admin secret to copy/paste), receives deployment_id/enroll_secret/profile,
writes $LARRY_HOME/.deployment-id + .enroll-secret (0600), bakes broker env
into the larry shim. Second run of the same client-id fails replay-safe
(409 already_enrolled, zero files written) -- by design, not a bug. Mutually
exclusive with the existing manual LARRY_DEPLOYMENT_ID/LARRY_ENROLL_SECRET
env-var path, which is otherwise unchanged. --uninstall / --uninstall --yes
paths unchanged (dry-run default, --yes for full removal).
Broker-side half (registry.claim_enrollment/client_slug, main.py
/enroll-claim, broker_ctl.py set-client-slug/reopen-enroll) already deployed
live on the broker container since 2026-07-09, sha256-verified matching --
this commit is the client-side half landing to close out the task.
Note: install-larry.sh's own comment refers to this as "v0.9.8+" but VERSION
and CHANGELOG.md are deliberately NOT bumped in this commit -- that wasn't
part of the reviewed/gated diff (install-larry.sh only), and adding it here
would be new unreviewed content. Flagging as a small follow-up nit, not
blocking.
Gated:
- Vera CODE-PASS+LIVE-PASS, 2026-07-09, token dc705af1-0e1f-41ca-b1d2-7e3446961680
(fresh throwaway deployment, real public broker, real mint + real LLM
reply, replay-safe, clean uninstall, kill-switch fail-closed even
through a reopened claim window)
- Vera TTL re-confirm CODE-PASS, 2026-07-21, token 598051ce-5c31-434e-aaa1-6cf4e3a549fb
(zero content drift, upstream HEAD unmoved, from-scratch patch-apply
reproduces the gated artifact byte-for-byte)
- Fresh independent live 6/6 round-trip re-run, 2026-07-21 (Clover):
provision -> install -> mint+LLM -> replay-safe -> uninstall ->
kill-switch, all PASS against the real public broker, throwaway
registry row created and cleaned up (test-harness.sh, task
tsk-2026-07-09-install-larry-one-arg-selfcontained)
Bryan-direct authorization to push: "push it now -- yes" (relayed via Larry).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
be8af282ef
commit
6900369a41
155
install-larry.sh
155
install-larry.sh
@ -2,8 +2,27 @@
|
||||
# install-larry.sh — bootstrap Larry-Anywhere on a fresh remote shell.
|
||||
# No root, no package install, no sudo. Writes only into $LARRY_HOME.
|
||||
#
|
||||
# Usage:
|
||||
# Usage (v0.9.8+ — ONE-ARG self-contained broker install, Bryan-approved 2026-07-09):
|
||||
# curl -fsSL <BASE_URL>/install-larry.sh | bash -s <client-id>
|
||||
# Self-enrolls against the broker using ONLY the client-id — no manual env
|
||||
# vars, no secret to paste. The broker (which the box owner already
|
||||
# provisioned with `provision_broker_client.sh <dep-id> --open-enroll
|
||||
# <client-id>`) hands back the deployment-id + a fresh enrollment secret
|
||||
# ONCE. A second run of the same client-id fails replay-safe (the broker
|
||||
# returns 409 already_enrolled) — that is BY DESIGN, not a bug; the trust
|
||||
# boundary is the single admin-opened claim window, closed either by the
|
||||
# first successful claim or by the kill-switch (broker_ctl set-authorized
|
||||
# <dep-id> false). See "ONE-ARG SELF-ENROLL" below.
|
||||
#
|
||||
# curl -fsSL <BASE_URL>/install-larry.sh | bash -s -- --uninstall [flags]
|
||||
# Fetches uninstall-larry.sh fresh and runs it (dry-run by default, same
|
||||
# as running it directly — pass --yes to actually delete). See its own
|
||||
# --help for --keep-data / --keep-rc / --no-shred.
|
||||
#
|
||||
# curl -fsSL <BASE_URL>/install-larry.sh | bash
|
||||
# (no args) — existing interactive/apikey path, UNCHANGED. Also still
|
||||
# accepts the original manual broker env vars below if you already have a
|
||||
# secret (e.g. from the admin `enroll` flow rather than open-enroll).
|
||||
#
|
||||
# Or with explicit base URL:
|
||||
# LARRY_BASE_URL=https://example.com/larry-anywhere bash install-larry.sh
|
||||
@ -14,6 +33,14 @@
|
||||
# LARRY_BIN_DIR where to symlink the `larry` command (default: $HOME/bin)
|
||||
# LARRY_GITEA_TOKEN optional Gitea PAT (read scope) for authenticated fetch
|
||||
# against a PRIVATE repo. Alias: GITEA_TOKEN. Never logged.
|
||||
# LARRY_BROKER_URL broker base URL. Defaults to the public broker
|
||||
# (https://broker.bjnoela.com) when a <client-id> arg is
|
||||
# given; override for a dev/staging broker.
|
||||
# LARRY_DEPLOYMENT_ID / LARRY_ENROLL_SECRET / LARRY_PROFILE
|
||||
# the ORIGINAL manual broker-enrollment path (v0.9.0) —
|
||||
# still supported, unchanged, for operators who already
|
||||
# hold a secret from the admin `enroll` flow. Mutually
|
||||
# exclusive with the <client-id> positional arg.
|
||||
set -eu
|
||||
|
||||
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
||||
@ -124,6 +151,42 @@ fetch_validate() {
|
||||
}
|
||||
# <<< fetch-safe inline <<<
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# v0.9.8 — CLI arg parsing: <client-id> (one-arg self-enroll) | --uninstall |
|
||||
# (no args, unchanged). Deliberately minimal — this installer has never had
|
||||
# positional-arg parsing before, so there is no legacy behavior to preserve
|
||||
# beyond "no args = today's interactive/apikey/manual-broker-env path".
|
||||
#
|
||||
# NOTE: SCRIPT_DIR (the local-checkout fallback path, used elsewhere by
|
||||
# fetch()) is NOT YET DEFINED at this point in the script (it's computed much
|
||||
# later, in the "Fetch the scripts" section) — this block runs under `set -eu`
|
||||
# so any reference here MUST use the ${VAR:-} default-guard form, never a bare
|
||||
# "$SCRIPT_DIR", or a plain unset-variable reference would abort the whole
|
||||
# script with "unbound variable" before reaching any of our own error
|
||||
# messages. The --uninstall path below relies on this guard.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
LARRY_CLIENT_ID=""
|
||||
if [ "${1:-}" = "--uninstall" ]; then
|
||||
shift
|
||||
say "uninstall requested — fetching uninstall-larry.sh fresh from origin"
|
||||
_un_tmp="$(mktemp 2>/dev/null || echo "/tmp/uninstall-larry.$$.sh")"
|
||||
if [ -n "$LARRY_BASE_URL" ] && fetch_validate "$LARRY_BASE_URL/uninstall-larry.sh" "$_un_tmp" sh 30; then
|
||||
chmod +x "$_un_tmp" 2>/dev/null || true
|
||||
exec bash "$_un_tmp" "$@"
|
||||
elif [ -n "${SCRIPT_DIR:-}" ] && [ -f "${SCRIPT_DIR:-}/uninstall-larry.sh" ]; then
|
||||
exec bash "$SCRIPT_DIR/uninstall-larry.sh" "$@"
|
||||
else
|
||||
rm -f "$_un_tmp"
|
||||
die "could not fetch uninstall-larry.sh from LARRY_BASE_URL=$LARRY_BASE_URL — run it directly instead: bash \$LARRY_HOME/uninstall-larry.sh $*"
|
||||
fi
|
||||
elif [ $# -gt 0 ]; then
|
||||
case "$1" in
|
||||
-*) die "unknown flag: $1 (usage: install-larry.sh [<client-id>] | install-larry.sh --uninstall [flags])" ;;
|
||||
*) LARRY_CLIENT_ID="$1"; shift
|
||||
[ $# -eq 0 ] || die "unexpected extra argument(s) after client-id '$LARRY_CLIENT_ID': $*" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Detect platform
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@ -446,12 +509,92 @@ else
|
||||
esac
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# v0.9.8 — ONE-ARG SELF-ENROLL (client-id argument drives EVERYTHING: broker
|
||||
# URL, deployment-id lookup, profile, AND the enrollment secret itself — zero
|
||||
# manual env vars, matching Bryan's approved 2026-07-09 design).
|
||||
#
|
||||
# Calls the broker's one-shot POST /enroll-claim {"client_id": "<id>"} — no
|
||||
# secret in the request. Only succeeds for a deployment the broker owner
|
||||
# already provisioned + left claim-window-open via:
|
||||
# provision_broker_client.sh <dep-id> --client "<Label>" --open-enroll <client-id>
|
||||
# and only ONCE: a second claim on the same client-id is a fail-closed 409
|
||||
# ("already enrolled") — replay-safe BY DESIGN, not a bug. If a box needs to
|
||||
# re-claim (e.g. it lost its secret before finishing install), the broker
|
||||
# owner reopens the window (`broker_ctl.py reopen-enroll <dep-id>`) rather
|
||||
# than the box retrying on its own.
|
||||
#
|
||||
# This is ADDITIVE, not a replacement: it just POPULATES LARRY_DEPLOYMENT_ID /
|
||||
# LARRY_ENROLL_SECRET / LARRY_PROFILE / LARRY_BROKER_URL so the EXISTING
|
||||
# v0.9.0 manual-env broker-enrollment block below picks them up completely
|
||||
# unchanged. If an operator already has a secret from the admin `enroll` flow,
|
||||
# they can still pass LARRY_DEPLOYMENT_ID/LARRY_ENROLL_SECRET manually with NO
|
||||
# client-id arg — that path is untouched.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
LARRY_BROKER_URL_DEFAULT="https://broker.bjnoela.com"
|
||||
if [ -n "$LARRY_CLIENT_ID" ]; then
|
||||
if [ -n "${LARRY_DEPLOYMENT_ID:-}" ] || [ -n "${LARRY_ENROLL_SECRET:-}" ]; then
|
||||
die "a client-id argument ('$LARRY_CLIENT_ID') and manual LARRY_DEPLOYMENT_ID/LARRY_ENROLL_SECRET env vars were both given — use ONE method, not both."
|
||||
fi
|
||||
LARRY_BROKER_URL="${LARRY_BROKER_URL:-$LARRY_BROKER_URL_DEFAULT}"
|
||||
say "self-enrolling as client-id '${LARRY_CLIENT_ID}' against broker ${LARRY_BROKER_URL} ..."
|
||||
command -v curl >/dev/null 2>&1 || die "curl not found — required for broker self-enroll"
|
||||
|
||||
_claim_id_json="$(printf '%s' "$LARRY_CLIENT_ID" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
_claim_tmp="$(mktemp 2>/dev/null || echo "${LARRY_HOME}/.claim.$$.json")"
|
||||
_claim_code="$(curl -sS --max-time 20 -w '%{http_code}' -o "$_claim_tmp" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X POST "${LARRY_BROKER_URL}/enroll-claim" \
|
||||
-d "{\"client_id\":\"${_claim_id_json}\"}" 2>/dev/null)"
|
||||
_claim_rc=$?
|
||||
|
||||
if [ "$_claim_rc" != "0" ]; then
|
||||
rm -f "$_claim_tmp"
|
||||
die "broker unreachable at ${LARRY_BROKER_URL} (curl rc=${_claim_rc}) — check network and retry. If you're on a restricted network, verify you can reach ${LARRY_BROKER_URL}/healthz."
|
||||
fi
|
||||
|
||||
case "$_claim_code" in
|
||||
200)
|
||||
# Bare grep/sed extraction (no jq dependency at this point in install).
|
||||
# Tolerant of key order; JSON body has no nesting so this is safe.
|
||||
LARRY_DEPLOYMENT_ID="$(grep -o '"deployment_id"[[:space:]]*:[[:space:]]*"[^"]*"' "$_claim_tmp" | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/')"
|
||||
LARRY_ENROLL_SECRET="$(grep -o '"enroll_secret"[[:space:]]*:[[:space:]]*"[^"]*"' "$_claim_tmp" | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/')"
|
||||
LARRY_PROFILE="$(grep -o '"profile"[[:space:]]*:[[:space:]]*"[^"]*"' "$_claim_tmp" | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/')"
|
||||
rm -f "$_claim_tmp"
|
||||
if [ -z "$LARRY_DEPLOYMENT_ID" ] || [ -z "$LARRY_ENROLL_SECRET" ]; then
|
||||
die "broker returned 200 but the response body was unparseable — aborting BEFORE writing any secret. This looks like a broker-side bug; nothing was leaked to stdout/logs. Report it."
|
||||
fi
|
||||
export LARRY_DEPLOYMENT_ID LARRY_ENROLL_SECRET LARRY_PROFILE LARRY_BROKER_URL
|
||||
ok "claimed deployment '${LARRY_DEPLOYMENT_ID}' (profile=${LARRY_PROFILE:-default}) — enrollment secret received, never printed"
|
||||
;;
|
||||
409)
|
||||
rm -f "$_claim_tmp"
|
||||
die "client-id '${LARRY_CLIENT_ID}' is already enrolled (one-shot claim already used). This is expected on a second run of the same install command — it is NOT a retry-able error. If this box genuinely never finished enrolling (e.g. it lost power mid-install), ask whoever provisioned you to re-open the window: provision_broker_client.sh <dep-id> --client <Label> --open-enroll ${LARRY_CLIENT_ID}"
|
||||
;;
|
||||
404)
|
||||
rm -f "$_claim_tmp"
|
||||
die "client-id '${LARRY_CLIENT_ID}' is unknown to the broker (or not yet authorized). Ask whoever gave you this client-id to provision it first: provision_broker_client.sh <dep-id> --client <Label> --open-enroll ${LARRY_CLIENT_ID}"
|
||||
;;
|
||||
400)
|
||||
rm -f "$_claim_tmp"
|
||||
die "broker rejected the enroll-claim request as malformed (HTTP 400) — this shouldn't happen; report it with the client-id you used."
|
||||
;;
|
||||
*)
|
||||
rm -f "$_claim_tmp"
|
||||
die "broker rejected the enroll-claim (HTTP ${_claim_code}) — see broker logs, or retry in a moment if this looks transient."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# v0.9.0 — BROKER ENROLLMENT (the remote kill-switch; DEFAULT for every deploy).
|
||||
#
|
||||
# Broker mode needs two facts baked per-box: a deployment id (its row in the
|
||||
# broker dashboard) and the enrollment secret (exchanged for short-lived tokens).
|
||||
# Bryan provisions them ONCE per box via env at install time:
|
||||
#
|
||||
# EASIEST PATH (v0.9.8+): pass a client-id as the positional arg and skip all
|
||||
# of this — see "ONE-ARG SELF-ENROLL" above. This block only matters if you
|
||||
# already hold a secret from the admin `enroll` flow (no client-id arg used):
|
||||
#
|
||||
# docker exec larry-broker python /app/broker_ctl.py enroll gundersen-epic \
|
||||
# --client "Gundersen" --profile phi --note "Epic interface box"
|
||||
@ -459,7 +602,7 @@ fi
|
||||
# LARRY_DEPLOYMENT_ID=gundersen-epic \
|
||||
# LARRY_ENROLL_SECRET=<paste> \
|
||||
# LARRY_PROFILE=phi \
|
||||
# LARRY_BROKER_URL=http://100.86.16.114:8181 \
|
||||
# LARRY_BROKER_URL=https://broker.bjnoela.com \
|
||||
# bash install-larry.sh
|
||||
#
|
||||
# The id+profile show up in Iris's dashboard ready to toggle. The secret is
|
||||
@ -519,10 +662,10 @@ echo "Next steps:"
|
||||
if [ "$BROKER_ENROLLED" = "1" ]; then
|
||||
echo " AUTH: broker mode (DEFAULT) — NO API key on this box. The kill-switch is ON."
|
||||
echo " deployment '${LARRY_DEPLOYMENT_ID}' will appear in the dashboard, ready to toggle."
|
||||
echo " REACHABILITY: the broker is LAN + Tailscale only (no public route). This box MUST"
|
||||
echo " reach ${LARRY_BROKER_URL:-the broker} — over Tailscale on an egress-restricted network."
|
||||
echo " REACHABILITY: the broker is public at ${LARRY_BROKER_URL:-https://broker.bjnoela.com}"
|
||||
echo " (also reachable via Tailscale/LAN). This box MUST be able to reach it over HTTPS."
|
||||
echo " If it cannot reach the broker, larry FAIL-CLOSES (refuses to run). That is a correct"
|
||||
echo " kill state but a useless working state — bring Tailscale up first."
|
||||
echo " kill state but a useless working state — check your network/DNS/firewall first."
|
||||
echo " 1) larry (or $LARRY_HOME/larry.sh)"
|
||||
echo " 2) larry /path/to/cloverleaf/site_root (to start with a working dir)"
|
||||
else
|
||||
|
||||
Loading…
Reference in New Issue
Block a user