scripts/upgrade-irv-ml1-cuda: stage remote script via scp, exec via ssh -t

The previous version piped a heredoc to `ssh -t host bash -s` —
which can't work because `-t` requires a real TTY and heredoc puts
stdin in pipe mode. They're mutually exclusive (orientation.md
gotcha, tripped over once already). The remote sudo therefore aborted
with 'a terminal is required to read the password' before doing
anything.

Restructured: the remote half of each phase is built as a tempfile
locally, scp'd to /tmp on irv-ml1, made executable, and invoked via
`ssh -t host /tmp/upgrade-cuda-remote.sh phase1`. Now stdin is the
real TTY (no pipe), sudo prompts work, and the rest of the script
runs as before.

Same three phases, same rollback behavior, same idempotency story.
Just a transport fix for the local→remote half.
This commit is contained in:
vh
2026-04-25 16:40:00 -07:00
parent 5a5ab94939
commit e4a809cfc0
+120 -87
View File
@@ -18,87 +18,143 @@
# Verify new driver + CUDA version, restart containers, # Verify new driver + CUDA version, restart containers,
# and print the next step (flip Kokoro to GPU variant). # and print the next step (flip Kokoro to GPU variant).
# #
# Idempotent: rerunning phase1 after the repo is already added is a no-op # Implementation note: the remote work runs from a script scp'd to
# until the apt step. phase2 can be re-run safely; container restarts # /tmp on irv-ml1, then invoked via `ssh -t host bash /tmp/script`.
# via `docker compose up -d` are idempotent. # Piping a heredoc to `ssh -t host bash -s` doesn't work — `-t` needs
# a real TTY but heredoc puts stdin in pipe mode (orientation.md
# gotcha; tripped over once already).
#
# Idempotent: rerunning phase1 after the repo is already added is a
# no-op until the apt step. phase2 can be re-run safely; container
# restarts via `docker compose up -d` are idempotent.
set -euo pipefail set -euo pipefail
HOST=irv-ml1 HOST=irv-ml1
CONTAINERS=(comfyui cosyvoice qwen3-tts index-tts parakeet kokoro) REMOTE_SCRIPT=/tmp/upgrade-cuda-remote.sh
say() { printf '\033[1;36m▸ %s\033[0m\n' "$*"; } say() { printf '\033[1;36m▸ %s\033[0m\n' "$*"; }
ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; } warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; }
err() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; } err() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; }
phase1() { # Build the remote-side script as a local tempfile, scp it, exec it
say "Phase 1: pre-flight + install + reboot" # via `ssh -t`. The remote script takes its own phase as $1.
say "Connecting to $HOST (sudo will prompt once)" write_remote_script() {
local local_tmp
# -t requests a TTY so sudo can prompt. The whole heredoc runs as local_tmp=$(mktemp)
# a single remote shell — sudo's credential cache covers all cat > "$local_tmp" <<'REMOTE'
# subsequent invocations within it. #!/usr/bin/env bash
ssh -t "$HOST" "bash -s" <<EOF # Remote half of upgrade-irv-ml1-cuda.sh — runs on irv-ml1.
set -euo pipefail set -euo pipefail
echo "=== current driver + CUDA ===" CONTAINERS=(comfyui cosyvoice qwen3-tts index-tts parakeet kokoro)
nvidia-smi --query-gpu=driver_version --format=csv,noheader
nvidia-smi | grep "CUDA Version"
echo remote_phase1() {
echo "=== prime sudo (we only want one password prompt this whole script) ===" echo "=== current driver + CUDA ==="
sudo -v nvidia-smi --query-gpu=driver_version --format=csv,noheader
nvidia-smi | grep "CUDA Version"
echo echo
echo "=== adding NVIDIA CUDA APT repo for Debian 12 ===" echo "=== prime sudo (one password prompt for everything below) ==="
sudo install -d -m 0755 /etc/apt/keyrings sudo -v
if [ ! -f /etc/apt/keyrings/nvidia-cuda.gpg ]; then
sudo apt-get install -y curl gpg
curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/3bf863cc.pub \\
| sudo gpg --dearmor --yes -o /etc/apt/keyrings/nvidia-cuda.gpg
fi
echo "deb [signed-by=/etc/apt/keyrings/nvidia-cuda.gpg] https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/ /" \\
| sudo tee /etc/apt/sources.list.d/nvidia-cuda.list >/dev/null
sudo apt-get update -qq echo
echo "=== adding NVIDIA CUDA APT repo for Debian 12 ==="
echo sudo install -d -m 0755 /etc/apt/keyrings
echo "=== cuda-drivers candidate ===" if [ ! -f /etc/apt/keyrings/nvidia-cuda.gpg ]; then
apt-cache policy cuda-drivers | head -10 sudo apt-get install -y curl gpg
curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/3bf863cc.pub \
echo | sudo gpg --dearmor --yes -o /etc/apt/keyrings/nvidia-cuda.gpg
echo "=== snapshotting current nvidia packages (rollback reference) ==="
dpkg -l | grep -E "^ii\s+(nvidia-|cuda-)" | awk '{print \$2 "=" \$3}' \\
| sudo tee /tmp/nvidia-pre-upgrade.txt >/dev/null
echo "snapshot at /tmp/nvidia-pre-upgrade.txt"
echo
echo "=== stopping GPU containers ==="
for stack in ${CONTAINERS[*]}; do
if [ -d "/opt/docker/compose/\$stack" ]; then
echo " stopping \$stack"
(cd "/opt/docker/compose/\$stack" && docker compose stop) || echo " (no-op)"
fi fi
done echo "deb [signed-by=/etc/apt/keyrings/nvidia-cuda.gpg] https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/ /" \
| sudo tee /etc/apt/sources.list.d/nvidia-cuda.list >/dev/null
echo sudo apt-get update -qq
echo "=== installing cuda-drivers (DKMS rebuild ~3-5 min) ==="
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y cuda-drivers
echo echo
echo "=== rebooting in 5 s ===" echo "=== cuda-drivers candidate ==="
sleep 5 apt-cache policy cuda-drivers | head -10
sudo systemctl reboot
EOF
echo
echo "=== snapshotting current nvidia packages (rollback reference) ==="
dpkg -l | awk '/^ii\s+(nvidia-|cuda-)/ {print $2 "=" $3}' \
| sudo tee /tmp/nvidia-pre-upgrade.txt >/dev/null
echo "snapshot at /tmp/nvidia-pre-upgrade.txt"
echo
echo "=== stopping GPU containers ==="
for stack in "${CONTAINERS[@]}"; do
if [ -d "/opt/docker/compose/$stack" ]; then
echo " stopping $stack"
(cd "/opt/docker/compose/$stack" && docker compose stop) || echo " (no-op)"
fi
done
echo
echo "=== installing cuda-drivers (DKMS rebuild ~3-5 min) ==="
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y cuda-drivers
echo
echo "=== rebooting in 5 s ==="
sleep 5
sudo systemctl reboot
}
remote_phase2() {
echo "=== new driver + CUDA ==="
nvidia-smi --query-gpu=driver_version --format=csv,noheader
nvidia-smi | grep "CUDA Version"
echo
echo "=== restarting GPU containers ==="
for stack in comfyui cosyvoice qwen3-tts index-tts parakeet; do
if [ -d "/opt/docker/compose/$stack" ]; then
echo " starting $stack"
(cd "/opt/docker/compose/$stack" && docker compose up -d) || echo " (failed — check manually)"
fi
done
sleep 8
echo
echo "=== container status ==="
docker ps --format "table {{.Names}}\t{{.Status}}" | head -15
}
remote_rollback() {
echo "=== rollback: reinstalling pre-upgrade nvidia packages ==="
[ -f /tmp/nvidia-pre-upgrade.txt ] || { echo "no snapshot found"; exit 1; }
sudo apt-get install -y --allow-downgrades $(tr '\n' ' ' < /tmp/nvidia-pre-upgrade.txt)
echo "=== rebooting in 5 s ==="
sleep 5
sudo systemctl reboot
}
case "${1:-}" in
phase1) remote_phase1 ;;
phase2) remote_phase2 ;;
rollback) remote_rollback ;;
*) echo "remote: usage: $0 {phase1|phase2|rollback}" >&2; exit 2 ;;
esac
REMOTE
scp -q "$local_tmp" "$HOST:$REMOTE_SCRIPT"
rm -f "$local_tmp"
ssh "$HOST" "chmod +x $REMOTE_SCRIPT"
}
phase1() {
say "Phase 1: pre-flight + install + reboot"
say "Staging remote script on $HOST"
write_remote_script
say "Running phase1 (sudo will prompt once via ssh -t)"
# No piped stdin — ssh -t's TTY is real and sudo can prompt.
ssh -t "$HOST" "$REMOTE_SCRIPT phase1"
ok "Phase 1 dispatched. Host rebooting." ok "Phase 1 dispatched. Host rebooting."
say "Wait ~2 min, then run: $0 phase2" say "Wait ~2 min, then run: $0 phase2"
} }
phase2() { phase2() {
say "Phase 2: wait for host, verify, restart containers" say "Phase 2: wait for host, verify, restart containers"
say "waiting for $HOST to come back online…" say "waiting for $HOST to come back online…"
for i in $(seq 1 60); do for i in $(seq 1 60); do
if ssh -o ConnectTimeout=3 -o BatchMode=yes "$HOST" true 2>/dev/null; then if ssh -o ConnectTimeout=3 -o BatchMode=yes "$HOST" true 2>/dev/null; then
@@ -110,30 +166,12 @@ phase2() {
done done
echo echo
ssh "$HOST" 'bash -s' <<'EOF' # Re-stage in case the previous /tmp script was wiped on reboot.
set -euo pipefail write_remote_script
ssh "$HOST" "$REMOTE_SCRIPT phase2"
echo "=== new driver + CUDA ==="
nvidia-smi --query-gpu=driver_version --format=csv,noheader
nvidia-smi | grep "CUDA Version"
echo
echo "=== restarting GPU containers ==="
for stack in comfyui cosyvoice qwen3-tts index-tts parakeet; do
if [ -d "/opt/docker/compose/$stack" ]; then
echo " starting $stack"
(cd "/opt/docker/compose/$stack" && docker compose up -d) || echo " (failed — check manually)"
fi
done
sleep 8
echo
echo "=== container status ==="
docker ps --format "table {{.Names}}\t{{.Status}}" | head -15
EOF
ok "Phase 2 complete." ok "Phase 2 complete."
cat <<'EOF' cat <<'NEXT'
──────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────
NEXT — flip Kokoro from cpu → gpu variant now that CUDA 12.9 is live: NEXT — flip Kokoro from cpu → gpu variant now that CUDA 12.9 is live:
@@ -146,22 +184,17 @@ NEXT — flip Kokoro from cpu → gpu variant now that CUDA 12.9 is live:
' '
scripts/elway irv-ml1 --playbook playbooks/deploy-kokoro.yaml scripts/elway irv-ml1 --playbook playbooks/deploy-kokoro.yaml
After that, three more deploys (re-runnable now that the driver is current): After that, the two outstanding deploys:
scripts/elway irv-ml1 --playbook playbooks/deploy-vibevoice.yaml scripts/elway irv-ml1 --playbook playbooks/deploy-vibevoice.yaml
scripts/elway irv-ml1 --playbook playbooks/deploy-chatterbox.yaml scripts/elway irv-ml1 --playbook playbooks/deploy-chatterbox.yaml
──────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────
EOF NEXT
} }
# rollback helper — restores the snapshot from /tmp/nvidia-pre-upgrade.txt
rollback() { rollback() {
say "Rollback: reinstalling pre-upgrade nvidia packages on $HOST" say "Rollback: reinstalling pre-upgrade nvidia packages on $HOST"
ssh -t "$HOST" 'bash -s' <<'EOF' write_remote_script
set -euo pipefail ssh -t "$HOST" "$REMOTE_SCRIPT rollback"
[ -f /tmp/nvidia-pre-upgrade.txt ] || { echo "no snapshot found"; exit 1; }
sudo apt-get install -y --allow-downgrades $(cat /tmp/nvidia-pre-upgrade.txt | tr '\n' ' ')
sudo systemctl reboot
EOF
ok "Rollback dispatched. Host rebooting." ok "Rollback dispatched. Host rebooting."
} }