#!/usr/bin/env bash # # Give a machine an identity in an llmgw cluster. # # Run on the host that holds the certificate authority, because that is the one # thing that cannot be delegated: this script issues the credential by which the # control plane will believe the node is who it says it is. # # What it does, in the order it matters: # 1. issue a client certificate whose common name is the node id # 2. copy the CA root, the certificate and its key to the machine # 3. trust the root system-wide, so other tooling there stops complaining # 4. install the binary, the configuration and the unit # 5. start the agent # # The node id, the VM name, the hostname and the certificate common name are all # the same string. They do not have to be, and every time they are not, someone # spends an afternoon working out which of the four a message is talking about. set -euo pipefail usage() { cat >&2 <&2; exit 1; } [ -f "$PKI/ca-key.pem" ] || { echo "no authority in $PKI; run 'llmgw ctl cert init-ca' first" >&2; exit 1; } SSH="ssh -o BatchMode=yes -o ConnectTimeout=10 $SSH_USER@$ADDR" say() { printf ' %s\n' "$*"; } echo "onboarding $NODE at $ADDR" $SSH true || { echo "cannot reach $SSH_USER@$ADDR over ssh" >&2; exit 1; } # 1. The certificate. Issued fresh every time: re-onboarding a machine should # not depend on finding an old key, and the old one keeps working until it # is revoked, which is a separate decision. say "issuing a certificate" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT "$BINARY" ctl cert issue-client "$NODE" --dir "$PKI" --out "$TMP" >/dev/null # 2 and 3. Identity, and trusting the root so future tooling on that machine # does not treat our certificates as strangers. say "installing the certificate and trusting the root" scp -q "$PKI/ca.pem" "$TMP/$NODE.pem" "$TMP/$NODE-key.pem" "$SSH_USER@$ADDR:/tmp/" $SSH "set -e sudo install -d -m 0755 /etc/llmgw sudo install -m 0644 /tmp/ca.pem /etc/llmgw/ca.pem sudo install -m 0644 /tmp/$NODE.pem /etc/llmgw/agent.pem sudo install -m 0600 /tmp/$NODE-key.pem /etc/llmgw/agent-key.pem sudo chown root:root /etc/llmgw/* sudo install -m 0644 /tmp/ca.pem /usr/local/share/ca-certificates/llmgw-ca.crt sudo update-ca-certificates >/dev/null 2>&1 || true rm -f /tmp/ca.pem /tmp/$NODE.pem /tmp/$NODE-key.pem" # 4. The binary, the configuration and the unit. say "installing the binary" scp -q "$BINARY" "$SSH_USER@$ADDR:/tmp/llmgw" $SSH 'sudo install -m 0755 /tmp/llmgw /usr/local/bin/llmgw && rm -f /tmp/llmgw' say "writing the configuration" FABRICS="fabrics: []" if [ -n "$FABRIC_NAME" ]; then FABRICS="fabrics: - name: $FABRIC_NAME hcas: [$(echo "$FABRIC_HCAS" | sed 's/,/, /g')]" fi cat > "$TMP/agent.yaml" < "$TMP/llmgw-agent.service" <<'UNIT' [Unit] Description=llmgw node agent After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/local/bin/llmgw agent --config /etc/llmgw/agent.yaml # Root, because the agent launches engines and needs device access. Engines # outlive a restart of this unit and are adopted again on the next start, so # KillMode must not take the whole process group with it. KillMode=process Restart=on-failure RestartSec=2s StateDirectory=llmgw-agent [Install] WantedBy=multi-user.target UNIT scp -q "$TMP/llmgw-agent.service" "$SSH_USER@$ADDR:/tmp/" say "starting the agent" $SSH 'set -e sudo install -m 0644 -o root -g root /tmp/agent.yaml /etc/llmgw/agent.yaml sudo install -m 0644 /tmp/llmgw-agent.service /etc/systemd/system/llmgw-agent.service rm -f /tmp/agent.yaml /tmp/llmgw-agent.service sudo systemctl daemon-reload sudo systemctl enable --now llmgw-agent >/dev/null sudo systemctl restart llmgw-agent' echo echo "$NODE onboarded. It should appear in 'llmgw ctl nodes' within a few seconds." echo "If it does not, the control plane's log says why — a certificate whose" echo "common name does not match the claimed node id is refused by design."