#!/usr/bin/env bash # Sync this repo to the talkingbooklet server and reload PM2. # Existing .env files on the server are kept. A chat-engine token is # created once if the API does not already have one. set -euo pipefail ROOT="$(cd "$(dirname "$0")" && pwd)" KEY="${DEPLOY_KEY:-$HOME/Documents/Keys/VirtualEngage.pem}" HOST="${DEPLOY_HOST:-ubuntu@15.207.15.52}" DEST="${DEPLOY_PATH:-/var/www/talkingbooklet}" SITE="${DEPLOY_SITE:-https://talkingbooklet.virtualengage.in}" if [[ ! -f "$KEY" ]]; then echo "SSH key not found: $KEY" >&2 exit 1 fi rsync -rlz --delete --omit-dir-times \ --no-perms --no-owner --no-group \ -e "ssh -i ${KEY} -o StrictHostKeyChecking=accept-new" \ --exclude node_modules \ --exclude venv \ --exclude .env \ --exclude frontend/dist \ --exclude .git \ --exclude .DS_Store \ --exclude __pycache__ \ --exclude .pytest_cache \ --exclude '*.pem' \ "${ROOT}/" "${HOST}:${DEST}/" ENV_KEYS="$(mktemp)" python3 - "$ROOT" <<'PY' > "$ENV_KEYS" import pathlib, sys root = pathlib.Path(sys.argv[1]) wanted = [ "BEDROCK_MODEL_CHEAP", "BEDROCK_MODEL_COUNT", "BEDROCK_MODEL_EMBED", "EMBED_DIMENSIONS", "OPENSEARCH_INDEX_ALIAS", "OPENSEARCH_INDEX_VERSION", "INGEST_VERSION", "MODEL_FULL_CONTEXT", "MODEL_HYBRID", "MODEL_SUMMARIZE_HISTORY", "MODEL_JUDGE", "AUTO_FULL_CONTEXT_MAX_TOKENS", "AUTO_PREMIUM_PLANS", "AGENT_MAX_ITERATIONS", "AGENT_MAX_TOOL_CALLS", "AGENT_TURN_TIMEOUT_MS", "MAX_ANSWER_TOKENS_VOICE", "MAX_ANSWER_TOKENS_CHAT", "CACHE_TTL", "CACHE_KEEPALIVE", "CACHE_KEEPALIVE_MAX", "CACHE_MIN_TOKENS", "PERSONA", "HISTORY_VERBATIM_TURNS", "HISTORY_TOKEN_BUDGET", "TOOL_RESULT_MAX_CHARS", "CHAPTER_RETURN_TOKEN_CAP", "RERANK_MODEL", "PRICE_TABLE_JSON", ] found = {} for name in ("ingestion/.env", "chat-engine/.env"): path = root / name if not path.exists(): continue for line in path.read_text().splitlines(): if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) if key in wanted and value != "": found[key] = value sys.stdout.write("".join(f"{key}={value}\n" for key, value in found.items())) PY ssh -i "$KEY" -o StrictHostKeyChecking=accept-new "$HOST" "python3 ${DEST}/deploy/merge_env.py ${DEST}/chat-engine/.env" < "$ENV_KEYS" ssh -i "$KEY" -o StrictHostKeyChecking=accept-new "$HOST" "python3 ${DEST}/deploy/merge_env.py ${DEST}/ingestion/.env" < "$ENV_KEYS" rm -f "$ENV_KEYS" ssh -i "$KEY" -o StrictHostKeyChecking=accept-new "$HOST" \ "DEST=$(printf '%q' "$DEST") SITE=$(printf '%q' "$SITE") bash -s" <<'REMOTE' set -euo pipefail cd "$DEST" npm install (cd backend && npm install) (cd frontend && npm install && npm run build) ./ai_agent/venv/bin/pip install -r ai_agent/requirements.txt python3 - <<'PY' import os, pathlib, re, secrets root = pathlib.Path(os.environ["DEST"]) backend = (root / "backend" / ".env").read_text() match = re.search(r"^CHAT_ENGINE_TOKEN=(.*)$", backend, re.M) token = match.group(1).strip() if match and match.group(1).strip() else secrets.token_hex(32) def upsert(text, key, value): line = f"{key}={value}" pattern = re.compile("^" + re.escape(key) + "=.*$", re.M) if pattern.search(text): return pattern.sub(line, text, count=1) return text.rstrip() + "\n" + line + "\n" site = os.environ["SITE"] backend = upsert(backend, "CHAT_ENGINE_URL", "http://127.0.0.1:3001") backend = upsert(backend, "CHAT_ENGINE_TOKEN", token) backend = upsert(backend, "FRONTEND_ORIGIN", site) backend = upsert(backend, "HOST", "127.0.0.1") (root / "backend" / ".env").write_text(backend) engine_path = root / "chat-engine" / ".env" engine = engine_path.read_text() if engine_path.exists() else "" engine = upsert(engine, "CHAT_ENGINE_HOST", "127.0.0.1") engine = upsert(engine, "CHAT_ENGINE_PORT", "3001") engine = upsert(engine, "CHAT_ENGINE_TOKEN", token) engine_path.write_text(engine) agent_path = root / "ai_agent" / ".env" agent = agent_path.read_text() if agent_path.exists() else "" agent = upsert(agent, "CHAT_ENGINE_URL", "http://127.0.0.1:3001") agent = upsert(agent, "CHAT_ENGINE_TOKEN", token) agent_path.write_text(agent) print("env linked") PY sudo python3 - <<'PY' from pathlib import Path path = Path("/etc/nginx/sites-enabled/virtualpr") text = path.read_text() needle = "proxy_pass http://127.0.0.1:3000;" extra = """proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_buffering off; proxy_cache off; proxy_read_timeout 300s; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;""" idx = text.find(needle) nearby = text[idx:idx + 400] if idx >= 0 else "" if idx >= 0 and "proxy_buffering off" not in nearby: path.write_text(text.replace(needle, extra, 1)) print("nginx updated") else: print("nginx already streams /api") PY sudo nginx -t sudo systemctl reload nginx pm2 startOrReload "$DEST/ecosystem.config.cjs" --only talkingbooklet-engine pm2 startOrReload "$DEST/ecosystem.config.cjs" --only talkingbooklet-api pm2 startOrReload "$DEST/ecosystem.config.cjs" --only talkingbooklet-agent pm2 save sleep 2 curl -fsS http://127.0.0.1:3001/health echo curl -fsS http://127.0.0.1:3000/ echo REMOTE