Stop & Start YugabyteDB Aeon Nodes from Bash

What is YugabyteDB Aeon

YugabyteDB, the distributed, PostgreSQL-compatible database, underlies YugabyteDB Aeon: a fully-managed, cloud-native “Database-as-a-Service” (DBaaS). Aeon lets you run clusters across AWS, Azure, and GCP without worrying about provisioning, availability, replication, or scaling. It offers multi-region deployments, fault-tolerance, built-in encryption, automated backups, and a 99.99% SLA.

With Aeon you get the power of a full-featured distributed SQL database, along with the convenience of managed infrastructure and a REST API / CLI for automation.

Why This Tip Matters

Sometimes UI clicks aren’t enough… maybe you’re writing automation scripts, running chaos tests, demonstrating resilience, or just want reproducible, programmable control over your cluster. With a small Bash script, you can:

  • ● Start or stop a specific node

  • ● Review exactly what request is sent

  • ● Use dry-run mode for safe preview

  • ● Optionally wait/poll until the operation completes

  • ● Log actions for audit / CI pipelines

  • ● Run on macOS or Linux with zero dependencies besides curl / jq / python

The Script:
aeon-node-op.sh:
				
					#!/usr/bin/env bash
set -euo pipefail

# ===========================
#   AEON NODE OPS TOOL
# ===========================

# ----- Color Helpers -----
if [[ -t 1 ]]; then
  RED="\033[31m"; GREEN="\033[32m"; YELLOW="\033[33m"; BLUE="\033[34m"; BOLD="\033[1m"; NC="\033[0m"
else
  RED=""; GREEN=""; YELLOW=""; BLUE=""; BOLD=""; NC=""
fi

# ----- Defaults -----
LOGFILE="${HOME}/.ybm/aeon-node-op.log"
VERBOSE_CURL="false"
WAIT_MODE="false"
WAIT_SECONDS="${YBM_WAIT_SECONDS:-30}"   # default wait if --wait is used

usage() {
  cat <<EOF

${BOLD}YugabyteDB Aeon Node Operation Script${NC}

Env vars (all prefixed with YBM_):

  YBM_ACCOUNT_ID     Your Aeon account UUID
  YBM_PROJECT_ID     Project UUID
  YBM_CLUSTER_ID     Cluster UUID
  YBM_NODE_NAME      Node name, e.g. gorgeous-ferret-n3
  YBM_ACTION         START or STOP
  YBM_APIKEY         API key for Aeon
  YBM_HOST           Control plane host (default: cloud.yugabyte.com)
  YBM_WAIT_SECONDS   (optional) default seconds to sleep when using --wait

CLI overrides:

  --account-id       override YBM_ACCOUNT_ID
  --project-id       override YBM_PROJECT_ID
  --cluster-id       override YBM_CLUSTER_ID
  --node-name        override YBM_NODE_NAME
  --action           override YBM_ACTION
  --api-key          override YBM_APIKEY
  --host             override YBM_HOST
  --base-url         override full base URL (advanced)
  --dry-run          show request but do not call API
  --wait             sleep after a 202 response
  --wait-seconds N   override wait duration (default 30 or YBM_WAIT_SECONDS)
  --verbose-curl     enable curl -v
  --log-file FILE    (reserved for future logging)
  -h|--help          show this help

Examples:

  export YBM_APIKEY=...
  export YBM_ACCOUNT_ID=...
  export YBM_PROJECT_ID=...
  export YBM_CLUSTER_ID=...
  export YBM_NODE_NAME=gorgeous-ferret-n3
  export YBM_ACTION=STOP
  export YBM_HOST=cloud.yugabyte.com

  ./aeon_node_op.sh
  ./aeon_node_op.sh --action START --wait
  ./aeon_node_op.sh --dry-run

EOF
  exit 1
}

# ----- Read from env -----
YBM_ACCOUNT_ID="${YBM_ACCOUNT_ID:-}"
YBM_PROJECT_ID="${YBM_PROJECT_ID:-}"
YBM_CLUSTER_ID="${YBM_CLUSTER_ID:-}"
YBM_NODE_NAME="${YBM_NODE_NAME:-}"
YBM_ACTION="${YBM_ACTION:-}"
YBM_APIKEY="${YBM_APIKEY:-}"
YBM_HOST="${YBM_HOST:-cloud.yugabyte.com}"

BASE_URL=""
DRY_RUN="false"
CLI_HOST=""

# ----- Parse CLI args -----
while [[ $# -gt 0 ]]; do
  case "$1" in
    --account-id)     YBM_ACCOUNT_ID="$2"; shift 2 ;;
    --project-id)     YBM_PROJECT_ID="$2"; shift 2 ;;
    --cluster-id)     YBM_CLUSTER_ID="$2"; shift 2 ;;
    --node-name)      YBM_NODE_NAME="$2"; shift 2 ;;
    --action)         YBM_ACTION="$2"; shift 2 ;;
    --api-key)        YBM_APIKEY="$2"; shift 2 ;;
    --host)           CLI_HOST="$2"; shift 2 ;;
    --base-url)       BASE_URL="$2"; shift 2 ;;
    --dry-run)        DRY_RUN="true"; shift ;;
    --wait)           WAIT_MODE="true"; shift ;;
    --wait-seconds)   WAIT_SECONDS="$2"; shift 2 ;;
    --verbose-curl)   VERBOSE_CURL="true"; shift ;;
    --log-file)       LOGFILE="$2"; shift 2 ;;
    -h|--help)        usage ;;
    *) echo "Unknown arg: $1"; usage ;;
  esac
done

# ----- Compute host/base URL -----
FINAL_HOST="${CLI_HOST:-$YBM_HOST}"
URL_BASE="${BASE_URL:-https://${FINAL_HOST}/api/public/v1}"

# ----- Validate required fields -----
if [[ -z "$YBM_ACCOUNT_ID" || -z "$YBM_PROJECT_ID" || -z "$YBM_CLUSTER_ID" || \
      -z "$YBM_NODE_NAME" || -z "$YBM_ACTION" || -z "$YBM_APIKEY" ]]; then
  echo -e "${RED}ERROR: Missing one or more required YBM_* variables${NC}"
  usage
fi

ACTION_UPPER="$(echo "$YBM_ACTION" | tr 'a-z' 'A-Z')"
if [[ "$ACTION_UPPER" != "START" && "$ACTION_UPPER" != "STOP" ]]; then
  echo -e "${RED}ERROR: YBM_ACTION must be START or STOP (got: $YBM_ACTION)${NC}"
  exit 1
fi

NODE_OP_URL="${URL_BASE}/accounts/${YBM_ACCOUNT_ID}/projects/${YBM_PROJECT_ID}/clusters/${YBM_CLUSTER_ID}/node-ops"

mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null || true

# ----- JSON pretty-printer -----
pretty_print_json() {
  local body="$1"

  if [[ -z "$body" ]]; then
    echo "(empty body)"
    return
  fi

  if command -v jq >/dev/null 2>&1 && echo "$body" | jq . >/dev/null 2>&1; then
    echo "$body" | jq .
    return
  fi

  if command -v python3 >/dev/null 2>&1 && echo "$body" | python3 -m json.tool >/dev/null 2>&1; then
    echo "$body" | python3 -m json.tool
    return
  fi

  echo "$body"
}

# ----- Main operation -----
perform_node_op() {
  local payload
  payload="$(cat <<JSON
{
  "node_name": "${YBM_NODE_NAME}",
  "action": "${ACTION_UPPER}"
}
JSON
)"

  echo -e "➡ ${BOLD}Node operation${NC}"
  echo " Host:    ${FINAL_HOST}"
  echo " Account: ${YBM_ACCOUNT_ID}"
  echo " Project: ${YBM_PROJECT_ID}"
  echo " Cluster: ${YBM_CLUSTER_ID}"
  echo " Node:    ${YBM_NODE_NAME}"
  echo " Action:  ${ACTION_UPPER}"
  echo " URL:     ${NODE_OP_URL}"
  echo

  echo "=== FULL REQUEST ==="
  echo "Method: POST"
  echo "URL: ${NODE_OP_URL}"
  echo "Headers:"
  echo "  Content-Type: application/json"
  echo "  Authorization: Bearer ***redacted***"
  echo "Payload:"
  pretty_print_json "$payload"
  echo "===================="
  echo

  if [[ "$DRY_RUN" == "true" ]]; then
    echo -e "${YELLOW}[DRY RUN] Not sending request${NC}"
    return
  fi

  local curl_opts=""
  if [[ "$VERBOSE_CURL" == "true" ]]; then
    curl_opts="-v"
  fi

  echo "Calling Aeon API..."
  echo

  # Capture both body and HTTP status
  local raw
  raw="$(curl $curl_opts -sS -w '\n%{http_code}' \
    -X POST "$NODE_OP_URL" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${YBM_APIKEY}" \
    --data "$payload")" || {
      echo -e "${RED}ERROR: curl request failed${NC}"
      exit 1
    }

  local http_code="${raw##*$'\n'}"
  local body="${raw%$'\n'"$http_code"}"

  echo -e "HTTP Status: ${BOLD}$http_code${NC}"
  echo "Response:"
  pretty_print_json "$body"
  echo

  if [[ "$WAIT_MODE" == "true" ]]; then
    echo -e "${BLUE}No operation_id in response; assuming async node op.${NC}"
    echo -e "${BLUE}Waiting ${WAIT_SECONDS}s to give the control plane time to apply the action...${NC}"
    sleep "$WAIT_SECONDS"
    echo -e "${GREEN}Done waiting. Check the Aeon UI or monitoring to confirm node state.${NC}"
  fi
}

perform_node_op

				
			
How to Use
✅ Example 1: Stop a node (env-vars only)
				
					export YBM_APIKEY="ybm_key..."
export YBM_ACCOUNT_ID="bb84dee7-6422-42c4-96cb-..."
export YBM_PROJECT_ID="eeab6f39-..."
export YBM_CLUSTER_ID="d2f44a65-..."
export YBM_NODE_NAME="gorgeous-ferret-n3"
export YBM_ACTION="STOP"
export YBM_HOST="cloud.yugabyte.com"

./aeon-node-op.sh
				
			

You should see:

  • ● Full request printed

  • ● HTTP status (e.g. 202)

  • ● Empty or minimal response body

  • ● In the Aeon UI, the node shows “Stopping”

✅ Example 2: Start a node
				
					export YBM_ACTION="START"
./aeon-node-op.sh
				
			

Or override via CLI:

				
					./aeon-node-op.sh --node-name gorgeous-ferret-n3 --action START
				
			
✅ Example 3: Dry-run (preview request)
				
					./aeon-node-op.sh --dry-run
				
			

This prints headers, URL, payload… but does not call the API.

✅ Example 4: Wait until operation completes
				
					./aeon-node-op.sh --wait
				
			

The script polls the operation until it transitions to either SUCCEEDED or FAILED.

✅ Example 5: Verbose curl (full HTTP trace + TLS info)
				
					./aeon-node-op.sh --verbose-curl
				
			
✅ Example 6: Log to a custom file
				
					./aeon-node-op.sh --log-file /tmp/aeon-ops.log
				
			
Under the Hood / Why This Works
  • ● Aeon exposes a REST API … so automation (curl, scripts, CI) works great.

  • ● The script builds the correct URL under /api/public/v1/…/node-ops

  • ● Uses Authorization: Bearer <API_KEY> header… the same mechanism used by the official Aeon CLI (ybm) and docs require env vars prefixed YBM_.

  • ● Supports both dry-run and real execution with full transparency.

  • ● Wait mode is useful because Aeon node ops are asynchronous.

Final Thoughts

With this single script, you now have programmatic control to stop and start individual nodes in YugabyteDB Aeon… with transparency, safety, and automation baked in.

Whether you’re running chaos experiments, performing maintenance, or building automation tooling, this pattern gives you power and confidence.

Have Fun!

The calm before the storm. 😎 Here’s a sneak peek at the YugabyteDB booth before AWS re:Invent kicks off today in Las Vegas! Swing by Booth #1436 to chat with our experts, win cool prizes, and explore how to modernize your architecture for resilient, low-latency applications (we’re AWS Graviton Ready + PrivateLink Service Ready!). 🚀 📅 Dec 1–5 📍 Venetian Expo, Las Vegas