#!/usr/bin/env bash set -euo pipefail ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) cd "$ROOT_DIR" LOG_DIR="${LOCAL_STAGING_LOG_DIR:-$ROOT_DIR/reports/ci/local-staging}" mkdir -p "$LOG_DIR" export GOCACHE="${LOCAL_STAGING_GOCACHE:-$ROOT_DIR/.gocache}" export GOMODCACHE="${LOCAL_STAGING_GOMODCACHE:-$ROOT_DIR/.gomodcache}" mkdir -p "$GOCACHE" "$GOMODCACHE" BRANCH="${LOCAL_STAGING_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" GO_IMAGE="${LOCAL_STAGING_GO_IMAGE:-docker.io/library/golang:1.24}" GOLANGCI_IMAGE="${LOCAL_STAGING_GOLANGCI_IMAGE:-docker.io/golangci/golangci-lint:v1.60.1}" HELM_IMAGE="${LOCAL_STAGING_HELM_IMAGE:-docker.io/alpine/helm:3.15.2}" KUBECTL_IMAGE="${LOCAL_STAGING_KUBECTL_IMAGE:-docker.io/bitnami/kubectl:1.30.1}" IMAGE_NAME="${LOCAL_STAGING_IMAGE_NAME:-mev-bot}" IMAGE_TAG="${LOCAL_STAGING_IMAGE_TAG:-staging-local}" IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" IMAGE_TAR="${LOCAL_STAGING_IMAGE_TAR:-$ROOT_DIR/${IMAGE_NAME}-${IMAGE_TAG}.tar}" HELM_RELEASE="${LOCAL_STAGING_HELM_RELEASE:-mev-bot}" HELM_NAMESPACE="${LOCAL_STAGING_HELM_NAMESPACE:-mev-bot-staging}" HELM_CHART="${LOCAL_STAGING_HELM_CHART:-charts/mev-bot}" HELM_DRY_RUN="${LOCAL_STAGING_HELM_DRY_RUN:-true}" KUBECONFIG_PATH="${LOCAL_STAGING_KUBECONFIG:-$HOME/.kube/config}" SKIP_DOCKER="${LOCAL_STAGING_SKIP_DOCKER:-false}" SKIP_DEPLOY="${LOCAL_STAGING_SKIP_DEPLOY:-false}" CONTAINER_RUNTIME="${LOCAL_STAGING_RUNTIME:-}" if [[ -z "$CONTAINER_RUNTIME" ]]; then if command -v podman >/dev/null 2>&1; then CONTAINER_RUNTIME=podman elif command -v docker >/dev/null 2>&1; then CONTAINER_RUNTIME=docker else echo "ERROR: Neither podman nor docker is available. Install one or set LOCAL_STAGING_RUNTIME." >&2 exit 1 fi fi if ! command -v "$CONTAINER_RUNTIME" >/dev/null 2>&1; then echo "ERROR: Container runtime '$CONTAINER_RUNTIME' not found in PATH" >&2 exit 1 fi CONTAINER_CMD=("$CONTAINER_RUNTIME") if [[ "$CONTAINER_RUNTIME" == "podman" ]]; then CONTAINER_CMD+=("--remote") fi CONTAINER_USER="$(id -u):$(id -g)" RUNTIME_ARGS=(-u "$CONTAINER_USER" -v "$ROOT_DIR":/work -w /work -v "$GOCACHE":/gocache -v "$GOMODCACHE":/gomodcache -e GOCACHE=/gocache -e GOMODCACHE=/gomodcache) if [[ "$CONTAINER_RUNTIME" == "podman" ]]; then RUNTIME_ARGS+=(--security-opt label=disable) fi run_step() { local name="$1" shift local logfile="$LOG_DIR/${name}.log" printf '[%s] Starting %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$name" if "$@" |& tee "$logfile"; then printf '[%s] Completed %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$name" else printf '[%s] Failed %s; see %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$name" "$logfile" exit 1 fi } log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" } require_cmd() { if ! command -v "$1" >/dev/null 2>&1; then echo "ERROR: Required command '$1' not found" >&2 exit 1 fi } run_container_step() { local step="$1" local image="$2" shift 2 local cmd="$*" run_step "$step" "${CONTAINER_CMD[@]}" run --rm "${RUNTIME_ARGS[@]}" "$image" bash -lc "$cmd" } run_container_step_env() { local step="$1" local image="$2" shift 2 local env_args=() while [[ $# -gt 0 && "$1" == --env=* ]]; do env_args+=("${1/--env=/}") shift done local cmd="$*" local run_args=("${RUNTIME_ARGS[@]}") for env in "${env_args[@]}"; do run_args+=(-e "$env") done run_step "$step" "${CONTAINER_CMD[@]}" run --rm "${run_args[@]}" "$image" bash -lc "$cmd" } require_cmd git log "Running local staging pipeline for branch ${BRANCH} using ${CONTAINER_CMD[*]}" log "Logs: $LOG_DIR" HOST_GOROOT="${LOCAL_STAGING_GOROOT:-}" if [[ -z "$HOST_GOROOT" ]] && command -v go >/dev/null 2>&1; then HOST_GOROOT=$(go env GOROOT 2>/dev/null || true) fi if [[ -n "$HOST_GOROOT" && -d "$HOST_GOROOT" ]]; then RUNTIME_ARGS+=(-v "$HOST_GOROOT":/goroot:ro -e GOROOT=/goroot) GO_BIN_PATH="/goroot/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" else GO_BIN_PATH="/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" fi run_container_step_env setup-dependencies "$GO_IMAGE" "export PATH=$GO_BIN_PATH; go mod download && go mod verify" run_container_step_env lint "$GOLANGCI_IMAGE" --env=GOLANGCI_LINT_CACHE=/tmp/golangci-lint --env=PATH="$GO_BIN_PATH" "golangci-lint run --timeout=10m" run_container_step_env unit-tests "$GO_IMAGE" "export PATH=$GO_BIN_PATH; go test -race -coverprofile=coverage.out ./..." if [[ -f coverage.out ]]; then mv coverage.out "$LOG_DIR/coverage.out" fi run_container_step_env math-audit "$GO_IMAGE" "export PATH=$GO_BIN_PATH; go run ./tools/math-audit --vectors default --report reports/math/latest" run_container_step_env profit-simulation "$GO_IMAGE" "export PATH=$GO_BIN_PATH; ./scripts/run_profit_simulation.sh" if [[ "$SKIP_DOCKER" != "true" ]]; then run_step docker-build "${CONTAINER_CMD[@]}" build -t "$IMAGE_REF" . run_step docker-save "${CONTAINER_CMD[@]}" save "$IMAGE_REF" -o "$IMAGE_TAR" else log "Skipping Docker/Podman build and save (LOCAL_STAGING_SKIP_DOCKER=true)" fi if [[ "$SKIP_DEPLOY" != "true" ]]; then HELM_RUN_ARGS=("${RUNTIME_ARGS[@]}") if [[ -f "$KUBECONFIG_PATH" ]]; then HELM_RUN_ARGS+=(-v "$KUBECONFIG_PATH":/kubeconfig:ro -e KUBECONFIG=/kubeconfig) elif [[ "$HELM_DRY_RUN" == "false" ]]; then echo "ERROR: kubeconfig not found at $KUBECONFIG_PATH" >&2 exit 1 fi HELM_CMD=(helm upgrade --install "$HELM_RELEASE" "$HELM_CHART" --set "image.tag=${IMAGE_TAG}" --namespace "$HELM_NAMESPACE") if [[ "$HELM_DRY_RUN" != "false" ]]; then HELM_CMD+=('--dry-run') fi run_step helm-upgrade "${CONTAINER_CMD[@]}" run --rm "${HELM_RUN_ARGS[@]}" "$HELM_IMAGE" bash -lc "${HELM_CMD[*]}" if command -v kubectl >/dev/null 2>&1 && [[ "$HELM_DRY_RUN" == "false" ]]; then run_step rollout-status kubectl rollout status "deploy/${HELM_RELEASE}" -n "$HELM_NAMESPACE" --timeout=120s run_step rollout-logs kubectl logs "deploy/${HELM_RELEASE}" -n "$HELM_NAMESPACE" --tail=100 elif [[ "$HELM_DRY_RUN" == "false" ]]; then KUBE_RUN_ARGS=("${RUNTIME_ARGS[@]}") if [[ -f "$KUBECONFIG_PATH" ]]; then KUBE_RUN_ARGS+=(-v "$KUBECONFIG_PATH":/kubeconfig:ro -e KUBECONFIG=/kubeconfig) fi run_step rollout-status "${CONTAINER_CMD[@]}" run --rm "${KUBE_RUN_ARGS[@]}" "$KUBECTL_IMAGE" bash -lc "kubectl rollout status deploy/${HELM_RELEASE} -n ${HELM_NAMESPACE} --timeout=120s" run_step rollout-logs "${CONTAINER_CMD[@]}" run --rm "${KUBE_RUN_ARGS[@]}" "$KUBECTL_IMAGE" bash -lc "kubectl logs deploy/${HELM_RELEASE} -n ${HELM_NAMESPACE} --tail=100" else log "Skipping rollout status/log tail (dry run or kube tooling unavailable)" fi else log "Skipping deploy stage (LOCAL_STAGING_SKIP_DEPLOY=true)" fi log "Local staging pipeline completed"