Track optimized deploy_update.py (was gitignored).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
eric 2026-08-29 01:37:15 -05:00
parent 871ea78bf8
commit a1359d7b23

179
scripts/deploy_update.py Normal file
View File

@ -0,0 +1,179 @@
"""
Fast incremental deploy for nomadro.
- Pulls latest origin/dev1
- Rebuilds only services touched since last deploy
- Uses BuildKit cache mounts (npm/pip)
- Polls health instead of fixed long sleep
"""
from __future__ import annotations
import sys
import time
import paramiko
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
HOST = "107.173.30.245"
USER = "root"
PASS = "Xiao4669805"
GIT = "https://eric:Xiao4669805@gitea.dsx2020.com/eric/nomadweb.git"
DOMAIN = "nomadweb.nomadro.com"
REMOTE = "/opt/nomadweb"
MARKER = f"{REMOTE}/.last_deploy_sha"
COMPOSE = "docker-compose.prod.yml"
def run(client: paramiko.SSHClient, cmd: str, timeout: int = 900) -> tuple[int, str, str]:
print("\n>>", cmd[:180])
_, stdout, stderr = client.exec_command(cmd, timeout=timeout)
out = stdout.read().decode("utf-8", errors="replace")
err = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
if out.strip():
print(out[-5000:])
if err.strip():
# build logs often go to stderr
print("LOG:", err[-2500:])
print("exit:", code)
return code, out, err
def main() -> int:
t0 = time.time()
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASS, timeout=30)
env = (
f"DOMAIN={DOMAIN} "
f"NEXT_PUBLIC_API_URL=https://{DOMAIN}/api/v1 "
f"NEXT_PUBLIC_SITE_URL=https://{DOMAIN} "
f"CORS_ORIGINS=https://{DOMAIN},http://{DOMAIN} "
"DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 "
"BUILDKIT_PROGRESS=plain"
)
code, _, _ = run(
client,
f"cd {REMOTE} && git remote set-url origin {GIT} && "
f"git fetch --depth=50 origin && "
f"git checkout -B dev1 origin/dev1 && "
f"git reset --hard origin/dev1",
)
if code != 0:
client.close()
return code
# Decide what changed vs last successful deploy
detect = f"""
set -e
cd {REMOTE}
NEW=$(git rev-parse HEAD)
OLD=$(cat {MARKER} 2>/dev/null || true)
if [ -z "$OLD" ] || ! git cat-file -e "$OLD^{{commit}}" 2>/dev/null; then
echo "MODE=full"
echo "NEW=$NEW"
exit 0
fi
CHANGED=$(git diff --name-only "$OLD" "$NEW" || true)
echo "OLD=$OLD"
echo "NEW=$NEW"
echo "CHANGED_BEGIN"
echo "$CHANGED"
echo "CHANGED_END"
FE=0; BE=0
echo "$CHANGED" | grep -qE '^frontend/' && FE=1 || true
echo "$CHANGED" | grep -qE '^backend/' && BE=1 || true
echo "$CHANGED" | grep -qE '^docker-compose\\.prod\\.yml$' && FE=1 && BE=1 || true
if [ -z "$CHANGED" ]; then
echo "MODE=noop"
elif [ "$FE" = 1 ] && [ "$BE" = 0 ]; then
echo "MODE=frontend"
elif [ "$BE" = 1 ] && [ "$FE" = 0 ]; then
echo "MODE=backend"
elif [ "$FE" = 1 ] || [ "$BE" = 1 ]; then
echo "MODE=full"
else
echo "MODE=noop"
fi
"""
code, out, _ = run(client, detect, timeout=60)
if code != 0:
client.close()
return code
mode = "full"
new_sha = ""
for line in out.splitlines():
if line.startswith("MODE="):
mode = line.split("=", 1)[1].strip()
if line.startswith("NEW="):
new_sha = line.split("=", 1)[1].strip()
print(f"\nDeploy mode: {mode}")
if mode == "noop":
print("No changes since last deploy — skip build")
elif mode == "frontend":
code, _, _ = run(
client,
f"cd {REMOTE} && export {env} && "
f"docker compose -f {COMPOSE} build frontend && "
f"docker compose -f {COMPOSE} up -d --no-deps frontend",
timeout=900,
)
elif mode == "backend":
code, _, _ = run(
client,
f"cd {REMOTE} && export {env} && "
f"docker compose -f {COMPOSE} build backend && "
f"docker compose -f {COMPOSE} up -d --no-deps backend",
timeout=600,
)
else:
code, _, _ = run(
client,
f"cd {REMOTE} && export {env} && "
f"docker compose -f {COMPOSE} build && "
f"docker compose -f {COMPOSE} up -d",
timeout=900,
)
if code != 0:
client.close()
return code
# Fast health poll (max ~20s) instead of fixed sleep 12
health = f"""
ok=0
for i in $(seq 1 20); do
web=$(curl -s -o /dev/null -w '%{{http_code}}' http://127.0.0.1:3055/ || true)
api=$(curl -s http://127.0.0.1:8055/api/v1/health || true)
echo "try=$i web=$web api=$api"
echo "$api" | grep -q '"status":"ok"' && [ "$web" = "200" ] && ok=1 && break
sleep 1
done
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}' | grep nomadflow || true
curl -skI https://{DOMAIN} | head -6
curl -sk https://{DOMAIN}/api/v1/health
[ "$ok" = 1 ]
"""
code, _, _ = run(client, health, timeout=60)
if code != 0:
client.close()
return code
if new_sha:
run(client, f"echo {new_sha} > {MARKER}", timeout=20)
client.close()
elapsed = int(time.time() - t0)
print(f"\nDeploy OK in {elapsed}s (mode={mode})")
return 0
if __name__ == "__main__":
raise SystemExit(main())