Files
proxmox/scripts/deploy-token-aggregation-for-publication.sh

271 lines
9.5 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Deploy token-aggregation service for publication (token lists, CoinGecko/CMC reports, bridge/routes).
# Run on explorer VM (VMID 5000) or host that serves explorer.d-bis.org.
#
# Prerequisites: Node 20+, PostgreSQL (for full indexing; API responds with defaults if DB empty)
# Usage: ./scripts/deploy-token-aggregation-for-publication.sh [INSTALL_DIR]
#
# After deploy: nginx must proxy /api/v1/ to this service BEFORE Blockscout (see TOKEN_AGGREGATION_REPORT_API_RUNBOOK).
# Explorer layouts vary: port 3000 or 3001 — match TOKEN_AGG_PORT in apply-nginx scripts.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
INSTALL_DIR="${1:-$REPO_ROOT/token-aggregation-build}"
BUNDLE_ROOT="$INSTALL_DIR"
SERVICE_INSTALL_DIR="$BUNDLE_ROOT/smom-dbis-138/services/token-aggregation"
SVC_DIR="$REPO_ROOT/smom-dbis-138/services/token-aggregation"
# shellcheck source=/dev/null
source "$REPO_ROOT/scripts/lib/load-project-env.sh" >/dev/null 2>&1 || true
upsert_env_var() {
local env_file="$1"
local key="$2"
local value="${3:-}"
[[ -n "$value" ]] || return 0
if grep -q "^${key}=" "$env_file"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$env_file"
else
printf '%s=%s\n' "$key" "$value" >> "$env_file"
fi
}
ensure_env_key() {
local env_file="$1"
local key="$2"
grep -q "^${key}=" "$env_file" && return 0
printf '%s=\n' "$key" >> "$env_file"
}
derive_cw_reserve_verifier() {
if [[ -n "${CW_RESERVE_VERIFIER_CHAIN138:-}" ]]; then
printf '%s' "$CW_RESERVE_VERIFIER_CHAIN138"
return 0
fi
local broadcast_json="$REPO_ROOT/smom-dbis-138/broadcast/DeployCWReserveVerifier.s.sol/138/run-latest.json"
[[ -f "$broadcast_json" ]] || return 0
node -e '
const fs = require("fs");
const file = process.argv[1];
try {
const data = JSON.parse(fs.readFileSync(file, "utf8"));
const tx = (data.transactions || []).find(
(entry) => entry.contractName === "CWReserveVerifier" &&
entry.transactionType === "CREATE" &&
entry.contractAddress
);
if (tx?.contractAddress) process.stdout.write(tx.contractAddress);
} catch (_) {
process.exit(0);
}
' "$broadcast_json"
}
derive_cw_asset_reserve_verifier() {
if [[ -n "${CW_ASSET_RESERVE_VERIFIER_DEPLOYED_CHAIN138:-}" ]]; then
printf '%s' "$CW_ASSET_RESERVE_VERIFIER_DEPLOYED_CHAIN138"
return 0
fi
node - <<'NODE' "$REPO_ROOT/config/smart-contracts-master.json"
const fs = require('fs');
const file = process.argv[2];
try {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
const address = data?.chains?.['138']?.contracts?.CWAssetReserveVerifier;
if (typeof address === 'string' && /^0x[a-fA-F0-9]{40}$/.test(address)) {
process.stdout.write(address);
}
} catch (_) {
process.exit(0);
}
NODE
}
derive_gru_transport_policy_amount() {
local env_key="$1"
node - <<'NODE' "$REPO_ROOT/config/gru-transport-active.json" "$env_key"
const fs = require('fs');
const file = process.argv[2];
const envKey = process.argv[3];
try {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
const familiesByKey = new Map();
for (const family of data.gasAssetFamilies || []) {
if (family && family.familyKey) familiesByKey.set(String(family.familyKey), family);
}
for (const pair of data.transportPairs || []) {
if (!pair || pair.active === false) continue;
if (pair?.maxOutstanding?.env === envKey) {
const family = familiesByKey.get(String(pair.familyKey || ''));
const cap = family?.perLaneCaps?.[String(pair.destinationChainId)];
if (typeof cap === 'string' && cap.trim()) {
process.stdout.write(cap.trim());
}
process.exit(0);
}
}
} catch (_) {
process.exit(0);
}
NODE
}
sync_gru_transport_env() {
local env_file="$1"
local chain138_l1_bridge="${CHAIN138_L1_BRIDGE:-${CW_L1_BRIDGE_CHAIN138:-}}"
local reserve_verifier=""
local asset_reserve_verifier=""
local key=""
local value=""
local derived_value=""
local refs=()
reserve_verifier="$(derive_cw_reserve_verifier || true)"
asset_reserve_verifier="$(derive_cw_asset_reserve_verifier || true)"
upsert_env_var "$env_file" "CHAIN138_L1_BRIDGE" "$chain138_l1_bridge"
upsert_env_var "$env_file" "CW_RESERVE_VERIFIER_CHAIN138" "$reserve_verifier"
upsert_env_var "$env_file" "CW_ASSET_RESERVE_VERIFIER_DEPLOYED_CHAIN138" "$asset_reserve_verifier"
refs=()
while IFS= read -r key; do
[[ -n "$key" ]] && refs+=("$key")
done < <(
node - <<'NODE' "$REPO_ROOT/config/gru-transport-active.json"
const fs = require('fs');
const file = process.argv[2];
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
const refs = new Set();
function walk(value) {
if (Array.isArray(value)) {
value.forEach(walk);
return;
}
if (!value || typeof value !== 'object') return;
if (typeof value.env === 'string' && value.env.trim()) refs.add(value.env.trim());
Object.values(value).forEach(walk);
}
walk(data);
for (const key of [...refs].sort()) console.log(key);
NODE
)
for key in "${refs[@]}"; do
case "$key" in
CHAIN138_L1_BRIDGE)
value="$chain138_l1_bridge"
;;
CW_RESERVE_VERIFIER_CHAIN138)
value="${CW_RESERVE_VERIFIER_CHAIN138:-$reserve_verifier}"
;;
CW_MAX_OUTSTANDING_*)
derived_value="$(derive_gru_transport_policy_amount "$key" || true)"
value="${!key:-$derived_value}"
;;
CW_GAS_OUTSTANDING_*|CW_GAS_ESCROWED_*|CW_GAS_TREASURY_BACKED_*|CW_GAS_TREASURY_CAP_*)
value="${!key:-0}"
;;
*)
value="${!key:-}"
;;
esac
if [[ -n "$value" ]]; then
upsert_env_var "$env_file" "$key" "$value"
else
ensure_env_key "$env_file" "$key"
fi
done
}
sync_chain138_public_rpc_env() {
local env_file="$1"
local public_chain138_rpc="${TOKEN_AGG_CHAIN138_RPC_URL:-http://192.168.11.221:8545}"
# Explorer-side read services must use the public Chain 138 RPC node, not the
# operator/deploy core RPC.
upsert_env_var "$env_file" "CHAIN_138_RPC_URL" "$public_chain138_rpc"
upsert_env_var "$env_file" "RPC_URL_138" "$public_chain138_rpc"
# Explicit alias for GET /api/v1/quote PMM on-chain path (see pmm-onchain-quote.ts).
upsert_env_var "$env_file" "TOKEN_AGGREGATION_CHAIN138_RPC_URL" "$public_chain138_rpc"
# Optional operator override: set in repo .env before deploy to use core RPC for PMM eth_calls only
# while keeping publication indexing on the public node above.
if [[ -n "${TOKEN_AGGREGATION_PMM_RPC_URL:-}" ]]; then
upsert_env_var "$env_file" "TOKEN_AGGREGATION_PMM_RPC_URL" "$TOKEN_AGGREGATION_PMM_RPC_URL"
fi
if [[ -n "${TOKEN_AGGREGATION_PMM_QUERY_TRADER:-}" ]]; then
upsert_env_var "$env_file" "TOKEN_AGGREGATION_PMM_QUERY_TRADER" "$TOKEN_AGGREGATION_PMM_QUERY_TRADER"
fi
}
if [ ! -d "$SVC_DIR" ]; then
echo "Token-aggregation not found at $SVC_DIR" >&2
exit 1
fi
echo "Deploying token-aggregation bundle to $BUNDLE_ROOT"
mkdir -p "$BUNDLE_ROOT/config"
mkdir -p "$BUNDLE_ROOT/cross-chain-pmm-lps"
# Fresh copy: stale node_modules types (file vs dir) break cp -a on re-runs.
rm -rf "$SERVICE_INSTALL_DIR"
mkdir -p "$SERVICE_INSTALL_DIR"
cp -a "$SVC_DIR"/. "$SERVICE_INSTALL_DIR/"
cp -a "$REPO_ROOT/config"/. "$BUNDLE_ROOT/config/"
cp -a "$REPO_ROOT/cross-chain-pmm-lps/config" "$BUNDLE_ROOT/cross-chain-pmm-lps/"
cd "$SERVICE_INSTALL_DIR"
if [ ! -f .env ]; then
if [ -f .env.example ]; then
cp .env.example .env
echo "Created .env from .env.example — set DATABASE_URL for persistent index; CUSDT/CUSDC already defaulted."
else
echo "Create .env with at least DATABASE_URL (and optional CHAIN_138_RPC_URL)." >&2
fi
fi
sync_gru_transport_env .env
sync_chain138_public_rpc_env .env
if command -v pnpm >/dev/null 2>&1 && [ -f "$REPO_ROOT/pnpm-lock.yaml" ]; then
(cd "$REPO_ROOT" && pnpm install --filter token-aggregation-service --no-frozen-lockfile 2>/dev/null) || true
fi
npm install
npm run build
npm prune --omit=dev >/dev/null 2>&1 || true
echo ""
echo "Token-aggregation built. Start with:"
echo " cd $SERVICE_INSTALL_DIR && node dist/index.js"
echo "Or add systemd unit. Default port from code: 3000 (match nginx TOKEN_AGG_PORT / fix-explorer-http-api-v1-proxy.sh uses 3001)."
echo ""
echo "If this is a standalone token_aggregation database (no explorer-monorepo schema), bootstrap the lightweight schema first:"
echo " cd $SERVICE_INSTALL_DIR && bash scripts/apply-lightweight-schema.sh"
echo ""
echo "Then apply nginx proxy (on same host), e.g.:"
echo " TOKEN_AGG_PORT=3001 CONFIG_FILE=/etc/nginx/sites-available/blockscout \\"
echo " bash $REPO_ROOT/scripts/fix-explorer-http-api-v1-proxy.sh"
echo " # or: explorer-monorepo/scripts/apply-nginx-token-aggregation-proxy.sh"
echo ""
echo "Verify:"
echo " pnpm run verify:token-aggregation-api"
echo "Push to explorer VM (LAN):"
echo " EXPLORER_SSH=root@192.168.11.140 bash scripts/deployment/push-token-aggregation-bundle-to-explorer.sh $BUNDLE_ROOT"
echo " SKIP_BRIDGE_ROUTES=0 bash scripts/verify/check-public-report-api.sh https://explorer.d-bis.org"
echo " ALLOW_BLOCKED=1 bash scripts/verify/check-gru-transport-preflight.sh https://explorer.d-bis.org"
echo " bash scripts/verify/check-gas-public-pool-status.sh"
echo ""
echo "Notes:"
echo " CW_ASSET_RESERVE_VERIFIER_DEPLOYED_CHAIN138 is synced as a neutral reference only."
echo " Leave CW_GAS_STRICT_ESCROW_VERIFIER_CHAIN138 and CW_GAS_HYBRID_CAP_VERIFIER_CHAIN138 unset until the live L1 bridge is explicitly wired to the generic gas verifier."
echo " GET /api/v1/quote uses on-chain PMM when RPC is set (RPC_URL_138 or TOKEN_AGGREGATION_*); optional TOKEN_AGGREGATION_PMM_RPC_URL in operator .env overrides PMM calls only."