"""Direct SFTP sync + native rebuild (when Gitea push fails).""" from __future__ import annotations import sys import time from pathlib import Path import paramiko sys.stdout.reconfigure(encoding="utf-8", errors="replace") HOST = "107.173.30.245" USER = "root" PASS = "Xiao4669805" REMOTE = "/opt/nomadweb" DOMAIN = "nomadweb.nomadro.com" # Override via env or edit before run FILES = [ "README.md", ".gitignore", "frontend/src/app/changelog/page.tsx", "frontend/src/app/globals.css", "frontend/src/app/profile/page.tsx", "frontend/src/app/plan/page.tsx", "frontend/src/app/compare/page.tsx", "frontend/src/components/DestinationDetailClient.tsx", "frontend/src/components/DestinationMatcher.tsx", "frontend/src/components/CompareModal.tsx", "frontend/src/components/CompareClient.tsx", "frontend/src/components/GlobalSearch.tsx", "frontend/src/components/HomeClient.tsx", "frontend/src/components/KeyboardShortcuts.tsx", "frontend/src/components/Navbar.tsx", "frontend/src/components/TripPlanner.tsx", "frontend/src/components/WorldMap.tsx", "frontend/src/components/MovePlanClient.tsx", "frontend/src/lib/types.ts", "frontend/src/lib/planMeta.ts", "frontend/src/lib/compareScore.ts", "frontend/src/lib/tripActions.ts", "frontend/src/lib/matcherPrefs.ts", "frontend/src/lib/tripStorage.ts", "frontend/src/lib/planSync.ts", "frontend/src/lib/auth.tsx", "frontend/src/lib/api.ts", "backend/app/services/auth.py", "backend/app/routers/auth.py", "backend/app/schemas.py", ] BUILD = f""" set -euo pipefail exec 9>/var/lock/nomadro-deploy.lock if ! flock -n 9; then echo LOCK_BUSY exit 75 fi REMOTE={REMOTE} DOMAIN={DOMAIN} export NEXT_TELEMETRY_DISABLED=1 export NEXT_PUBLIC_API_URL=https://${{DOMAIN}}/api/v1 export NEXT_PUBLIC_SITE_URL=https://${{DOMAIN}} export NODE_ENV=production echo RESTART_API systemctl restart nomadro-api echo BUILD_START cd "$REMOTE/frontend" npm run build mkdir -p .next/standalone/.next rm -rf .next/standalone/public .next/standalone/.next/static cp -a public .next/standalone/public cp -a .next/static .next/standalone/.next/static systemctl restart nomadro-web ok=0 for i in $(seq 1 40); 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" if echo "$api" | grep -q '"status":"ok"' && [ "$web" = "200" ]; then ok=1 break fi sleep 1 done curl -skI "https://${{DOMAIN}}/plan" | head -6 curl -sk "https://${{DOMAIN}}/api/v1/health" echo DIRECT_SYNC=1 [ "$ok" = 1 ] """ def ensure_dir(sftp: paramiko.SFTPClient, path: str) -> None: parts = path.strip("/").split("/") cur = "" for p in parts: cur += "/" + p try: sftp.stat(cur) except FileNotFoundError: try: sftp.mkdir(cur) except OSError: pass def main() -> int: root = Path(__file__).resolve().parents[1] t0 = time.time() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) print("Connecting...") client.connect(HOST, username=USER, password=PASS, timeout=30) sftp = client.open_sftp() for rel in FILES: local = root / rel if not local.exists(): print(f"SKIP missing {rel}") continue remote = f"{REMOTE}/{rel}" print(f"UPLOAD {rel}") ensure_dir(sftp, str(Path(remote).parent).replace("\\", "/")) sftp.put(str(local), remote) sftp.close() print("Uploaded. Building...") _, stdout, stderr = client.exec_command( f"cat > /tmp/nomadro-direct.sh <<'EOF'\n{BUILD}\nEOF\nbash /tmp/nomadro-direct.sh", timeout=900, ) while True: line = stdout.readline() if not line: break print(line, end="") err = stderr.read().decode("utf-8", errors="replace") code = stdout.channel.recv_exit_status() if err.strip(): print("LOG:", err[-4000:]) print("exit:", code) client.close() print(f"Done in {int(time.time() - t0)}s") return code if __name__ == "__main__": raise SystemExit(main())