← back to Charge And Explore

scripts/cutover-when-ready.sh

113 lines

#!/bin/bash
# Poll Tesla partner registration for chargeandexplore.com until Steve's
# dashboard Allowed-Origins add lands, then run the FULL approved cutover:
#   1. partner_accounts POST for chargeandexplore.com (+ public_key verify)
#   2. Kamatera: TESLA_REDIRECT_URI + GOOGLE_REDIRECT_URI -> new domain, pm2 restart
#   3. Kamatera: old vhost 301 -> new domain (keeps /.well-known/ proxied)
# Approved by Steve email "Dust2026 do autonomously approved" (2026-08-02, TK-10111).
set -u
cd "$(dirname "$0")/.." || exit 1
set -a; source .env; set +a

NEW=chargeandexplore.com
OLD=chargeandexplore.agentabrams.com
KAM=root@45.61.58.125
APP=/root/Projects/charge-and-explore
VHOST=/etc/nginx/sites-enabled/$OLD
LOG="data/cutover-when-ready.log"
INTERVAL=300
MAX_TRIES=144   # 12h
mkdir -p data

say() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
tklog() { TK_AGENT=claude-mail "$HOME/Projects/ticket-system/tk" log TK-10111 "$1" >/dev/null 2>&1; }

get_token() {
  # Never send an `audience` param (dual-region app; see register-when-ready.sh).
  curl -s -X POST https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode grant_type=client_credentials \
    --data-urlencode "client_id=$TESLA_CLIENT_ID" \
    --data-urlencode "client_secret=$TESLA_CLIENT_SECRET" \
    --data-urlencode "scope=openid vehicle_device_data" \
  | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))' 2>/dev/null
}

for i in $(seq 1 "$MAX_TRIES"); do
  TOKEN=$(get_token)
  if [ -z "$TOKEN" ]; then
    [ $((i % 6)) -eq 1 ] && say "attempt $i: token fetch failed"
    sleep "$INTERVAL"; continue
  fi
  REG=$(curl -s -w '\n%{http_code}' -X POST "$TESLA_AUDIENCE/api/1/partner_accounts" \
    -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
    -d "{\"domain\":\"$NEW\"}")
  CODE=$(echo "$REG" | tail -1); BODY=$(echo "$REG" | sed '$d')
  if [ "$CODE" != "200" ] && [ "$CODE" != "201" ]; then
    [ $((i % 6)) -eq 1 ] && say "attempt $i/$MAX_TRIES: registration HTTP $CODE (origin not added yet)"
    sleep "$INTERVAL"; continue
  fi

  say "REGISTRATION OK HTTP $CODE for $NEW — verifying public_key"
  VER=$(curl -s -o /dev/null -w '%{http_code}' \
    "$TESLA_AUDIENCE/api/1/partner_accounts/public_key?domain=$NEW" \
    -H "Authorization: Bearer $TOKEN")
  say "public_key verify HTTP $VER"
  tklog "cutover-when-ready: chargeandexplore.com REGISTERED with Tesla (HTTP $CODE, public_key $VER) — Steve's origin-add landed; running approved cutover"

  say "flipping redirect URIs on Kamatera + restart"
  ssh "$KAM" "cd $APP && cp .env .env.bak-cutover && \
    grep -v -E '^(TESLA_REDIRECT_URI|GOOGLE_REDIRECT_URI)=' .env > .env.tmp && \
    echo 'TESLA_REDIRECT_URI=https://$NEW/auth/tesla/callback' >> .env.tmp && \
    echo 'GOOGLE_REDIRECT_URI=https://$NEW/auth/google/callback' >> .env.tmp && \
    mv .env.tmp .env && chmod 600 .env && pm2 restart charge-and-explore" >>"$LOG" 2>&1
  sleep 3
  HZ=$(curl -s -o /dev/null -w '%{http_code}' "https://$NEW/healthz")
  say "post-flip healthz on $NEW: HTTP $HZ"
  if [ "$HZ" != "200" ]; then
    say "ABORT before 301: app unhealthy after flip — restoring .env.bak-cutover"
    ssh "$KAM" "cd $APP && cp .env.bak-cutover .env && pm2 restart charge-and-explore" >>"$LOG" 2>&1
    tklog "cutover-when-ready: FLIP ROLLED BACK — healthz $HZ after redirect flip; 301 not applied; needs live session"
    exit 2
  fi

  say "installing 301 on $OLD vhost (keeping /.well-known/ proxied)"
  ssh "$KAM" "mkdir -p /root/nginx-backups && cp $VHOST /root/nginx-backups/$OLD.pre-301.\$(date +%s) && \
    python3 - <<'PYEOF'
import re
p = '/etc/nginx/sites-enabled/chargeandexplore.agentabrams.com'
s = open(p).read()
if 'return 301 https://chargeandexplore.com' in s:
    print('301 already present'); raise SystemExit
# In the HTTPS server block, replace the proxy location / with well-known proxy + 301.
new_loc = '''    # Tesla pubkey + ACME stay served; everything else 301s to the new apex
    location /.well-known/ {
        proxy_pass http://localhost:9822;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
    }

    location / {
        return 301 https://chargeandexplore.com\$request_uri;
    }
'''
# Target only the proxy_pass location block (HTTP block's location / is a plain https redirect).
pat = re.compile(r'    location / \{\n        proxy_pass http://localhost:9822;.*?\n    \}\n', re.S)
s2, n = pat.subn(new_loc, s, count=1)
assert n == 1, 'proxy location block not found'
open(p,'w').write(s2)
print('vhost rewritten')
PYEOF
    nginx -t && systemctl reload nginx && sleep 2 && systemctl reload nginx" >>"$LOG" 2>&1

  R301=$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "https://$OLD/")
  WK=$(curl -s -o /dev/null -w '%{http_code}' "https://$OLD/.well-known/appspecific/com.tesla.3p.public-key.pem")
  say "verify: old / -> $R301 ; old well-known pubkey HTTP $WK"
  tklog "cutover-when-ready: CUTOVER COMPLETE — redirect URIs flipped (healthz 200), old->new 301 live ($R301), old well-known pubkey $WK. Full log data/cutover-when-ready.log"
  say "CUTOVER COMPLETE"
  exit 0
done
say "TIMEOUT — origin never authorized within 12h"
tklog "cutover-when-ready: TIMEOUT after 12h — Steve's Tesla dashboard origin-add for chargeandexplore.com never landed; cutover not run"
exit 3