# Install the `pi` coding agent (earendil-works) on nh3-extdev and wire every # /opt/externs/ workspace to GLM 5.2 via the litellm gateway. # # Context: nh3-extdev is SUDO-LESS (no root, no apt, no docker). So Node is # installed user-level from the official static tarball (checksum-verified), # pi is installed `-g` into that user-space prefix, and each client gets an # ISOLATED pi config dir via PI_CODING_AGENT_DIR (set by its run-pi.sh launcher). # # Idempotent: a second run shows mostly skip/ok. Rerunnable to add a client — # append its name to `clients` (its workspace dir + secrets.env with an # EXTERNS__GLM_KEY must already exist; workspace scaffolding is separate). # # scripts/elway nh3-extdev --playbook playbooks/install-pi-nh3-extdev.yaml # # pi config layout (authoritative, from the installed package): # - PI_CODING_AGENT_DIR overrides the agent dir (default ~/.pi/agent) # - $DIR/models.json : providers..{baseUrl, api, apiKey:"$ENV", models[]} # - $DIR/settings.json : defaultProvider + defaultModel (bare id) # The per-client GLM key lives in /secrets.env (600, gitignored), # referenced indirectly so the key never lands in models.json. vars: node_ver: v22.23.0 # latest v22 LTS "Jod"; matches pi engine floor >=22.19.0 node_arch: linux-x64 node_root: /home/infra-ops/.local # absolute (not $HOME — elway doesn't shell-expand creates:); identity is always infra-ops gateway: http://10.250.50.70:4000/v1 externs: /opt/externs clients: gbcnc surefire svsconstruction steps: - name: Download + verify + extract user-level Node shell: | set -euo pipefail DEST="{{ node_root }}"; DIR="$DEST/node-{{ node_ver }}-{{ node_arch }}" mkdir -p "$DEST"; cd /tmp curl -fsSLO "https://nodejs.org/dist/{{ node_ver }}/node-{{ node_ver }}-{{ node_arch }}.tar.xz" curl -fsSL "https://nodejs.org/dist/{{ node_ver }}/SHASUMS256.txt" -o SHASUMS256.txt grep " node-{{ node_ver }}-{{ node_arch }}.tar.xz$" SHASUMS256.txt | sha256sum -c - tar -xJf "node-{{ node_ver }}-{{ node_arch }}.tar.xz" -C "$DEST" rm -f "node-{{ node_ver }}-{{ node_arch }}.tar.xz" SHASUMS256.txt # Tier-1 idempotency: skip the whole download if the node binary is already there. creates: "{{ node_root }}/node-{{ node_ver }}-{{ node_arch }}/bin/node" - name: Wire node/pi onto PATH for login + interactive shells shell: | set -euo pipefail LINE='export PATH="$HOME/.local/node-{{ node_ver }}-{{ node_arch }}/bin:$PATH"' for RC in "$HOME/.profile" "$HOME/.bashrc"; do grep -qF "$LINE" "$RC" 2>/dev/null || { printf '\n# >>> pi/node user-level PATH >>>\n%s\n# <<< pi/node user-level PATH <<<\n' "$LINE" >> "$RC" } done when: "! grep -qF 'pi/node user-level PATH' $HOME/.bashrc 2>/dev/null" - name: Install the latest pi coding agent into the user prefix shell: | set -euo pipefail export PATH="{{ node_root }}/node-{{ node_ver }}-{{ node_arch }}/bin:$PATH" npm install -g @earendil-works/pi-coding-agent creates: "{{ node_root }}/node-{{ node_ver }}-{{ node_arch }}/bin/pi" - name: Wire each client workspace to GLM 5.2 (isolated config + scoped key) shell: | set -euo pipefail for c in {{ clients }}; do W="{{ externs }}/$c"; PI="$W/.pi" [ -d "$PI" ] || { echo "!! $c: missing $PI (scaffold first)"; exit 1; } KEYVAR=$(grep -oE '^EXTERNS_[A-Z0-9_]+_GLM_KEY' "$W/secrets.env" | head -1) [ -n "$KEYVAR" ] || { echo "!! $c: no EXTERNS_*_GLM_KEY in secrets.env"; exit 1; } cat > "$PI/models.json" < "$PI/settings.json" <<'JSON' { "defaultProvider": "litellm-glm", "defaultModel": "glm-5.2" } JSON cat > "$W/run-pi.sh" <<'SH' #!/usr/bin/env bash # Launch pi for this client: isolated config dir + scoped GLM key + repo cwd. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -a; . "$HERE/secrets.env"; set +a export PI_CODING_AGENT_DIR="$HERE/.pi" cd "$HERE/repo" exec pi "$@" SH chmod 600 "$PI/models.json" "$PI/settings.json" chmod 700 "$W/run-pi.sh" rm -f "$PI/config.example" done # Re-write is deterministic; skip when gbcnc is already wired to the gateway # AND its launcher exists (proxy for "all three wired"). when: "! ( grep -qF '{{ gateway }}' {{ externs }}/gbcnc/.pi/models.json 2>/dev/null && test -x {{ externs }}/gbcnc/run-pi.sh )" verify: - name: pi binary reports a version shell: | export PATH="{{ node_root }}/node-{{ node_ver }}-{{ node_arch }}/bin:$PATH" pi --version changed_when: "false" - name: each client has models.json + settings.json + run-pi.sh shell: | for c in {{ clients }}; do W="{{ externs }}/$c" test -s "$W/.pi/models.json" && test -s "$W/.pi/settings.json" && test -x "$W/run-pi.sh" \ || { echo "$c incomplete"; exit 1; } done changed_when: "false" - name: gbcnc resolves glm-5.2 through the gateway (live round-trip) shell: | export PATH="{{ node_root }}/node-{{ node_ver }}-{{ node_arch }}/bin:$PATH" {{ externs }}/gbcnc/run-pi.sh --no-tools --no-session --approve -p "Reply with exactly: PI_GLM_OK" \ | grep -qF PI_GLM_OK changed_when: "false"