feat(nh3-extdev): install pi (earendil-works) + wire /opt/externs client agents to GLM 5.2
- user-level Node v22.23.0 LTS (static tarball, checksum-verified) + pi 0.79.7 installed -g into the user prefix (box is sudo-less: no root/apt/docker) - every /opt/externs/<client> wired to GLM 5.2 via the litellm gateway with an isolated PI_CODING_AGENT_DIR + scoped per-client key (models.json/settings.json + run-pi.sh launcher); replaces the scaffold's incorrect config.example guess - playbooks/install-pi-nh3-extdev.yaml: idempotent reproduce / add-client / upgrade (validated clean: 4 steps skipped, live gbcnc->GLM 5.2 round-trip OK) - README: settled role + per-client workspace layout; refresh system-details
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# Install the `pi` coding agent (earendil-works) on nh3-extdev and wire every
|
||||
# /opt/externs/<client> 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_<NAME>_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.<name>.{baseUrl, api, apiKey:"$ENV", models[]}
|
||||
# - $DIR/settings.json : defaultProvider + defaultModel (bare id)
|
||||
# The per-client GLM key lives in <workspace>/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" <<JSON
|
||||
{
|
||||
"providers": {
|
||||
"litellm-glm": {
|
||||
"baseUrl": "{{ gateway }}",
|
||||
"api": "openai-completions",
|
||||
"apiKey": "\$$KEYVAR",
|
||||
"models": [
|
||||
{ "id": "glm-5.2", "name": "GLM 5.2 (litellm/z.ai)" },
|
||||
{ "id": "glm-5.2-reasoning", "name": "GLM 5.2 reasoning (litellm/z.ai)" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
cat > "$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"
|
||||
Reference in New Issue
Block a user