← 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