diff --git a/sandbox/auto.sh b/sandbox/auto.sh new file mode 100644 index 0000000..928ed29 --- /dev/null +++ b/sandbox/auto.sh @@ -0,0 +1,713 @@ +#!/usr/bin/env bash +# +# +#Bash script for safe COIN sandbox deployment that prepares new release directories, extracts the release from a Docker image, runs interactive pre-checks (Docker, SSH, DB migrations), +#supports dry-run and node-specific deploys, and deploys to node-3 and node-4 with logging and safety guards +# Features: +# - Full/partial release deploy to node-3 / node-4 +# - Manual EXPECTED_MIGRATION_ID check in DB +# - Colorful output +# - Logging to file +# - Interactive SELF-TEST +# - Manual stop for editing project.env +# - CLI flags: +# --dry-run +# --self-test-only +# --node3-only +# --node4-only +# --skip-db-check +# --skip-self-test +# --auto-yes +# --deploy-only node3|node4|node3,node4 +# --help +# +# +#For testing use ./deploy.sh --dry-run --self-test-only +# +# +#NAME DESCRIPTION DOCKER ENDPOINT ERROR +#default Current DOCKER_HOST based configuration unix:///var/run/docker.sock +#wlt-sbx-coinssm-ams tcp://10.95.81.151:2376 +#wlt-sbx-dkapp3-ams tcp://10.95.81.131:2376 +#wlt-sbx-dkapp4-ams * tcp://10.95.81.132:2376 +# + +set -euo pipefail + +############################################ +# -------- COLORS (for console only) ------- +############################################ + +RED="\033[1;31m" +GREEN="\033[1;32m" +YELLOW="\033[1;33m" +BLUE="\033[1;34m" +RESET="\033[0m" + +############################################ +# -------- ARGUMENT PARSING FLAGS ---------- +############################################ + +CMD_DRY_RUN=false +CMD_SELF_TEST_ONLY=false +CMD_NODE3_ONLY=false +CMD_NODE4_ONLY=false +CMD_SKIP_DB_CHECK=false +CMD_SKIP_SELF_TEST=false +CMD_AUTO_YES=false +CMD_ROLLBACK=false + +# NEW: deploy-only +CMD_DEPLOY_ONLY=false +DEPLOY_ONLY_NODES="" + +############################################ +# -------- STATUS FLAGS (summary) ---------- +############################################ +PREPARED_NODE3=false +PREPARED_NODE4=false +SELECTED_NODE3=false +SELECTED_NODE4=false +DEPLOY_ATTEMPT_NODE3=false +DEPLOY_ATTEMPT_NODE4=false + +show_help() { + echo "" + echo "COIN Sandbox Deployment Script — usage:" + echo "" + echo " --dry-run Run commands in simulation mode (no real changes)" + echo " --self-test-only Run self-test checks and exit (no deploy)" + echo " --node3-only Prepare BOTH nodes, but deploy only node-3 (no questions)" + echo " --node4-only Prepare BOTH nodes, but deploy only node-4 (no questions)" + echo " --skip-db-check Do not check migration ID in DB after node-3 deploy" + echo " --skip-self-test Skip self-test before deployment" + echo " --auto-yes Automatically answer YES to all confirmations" + echo " --rollback Revert to previous release (stops stacks, redeploys previous version)" + echo " --deploy-only node3|node4|node3,node4 Deploy only selected node(s), skip prepare & questions" + echo " --help, -h Show this help message" + echo "" + exit 0 +} + +# NEW: robust arg parsing to support --deploy-only value +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + CMD_DRY_RUN=true + shift + ;; + --self-test-only) + CMD_SELF_TEST_ONLY=true + shift + ;; + --node3-only) + CMD_NODE3_ONLY=true + shift + ;; + --node4-only) + CMD_NODE4_ONLY=true + shift + ;; + --skip-db-check) + CMD_SKIP_DB_CHECK=true + shift + ;; + --skip-self-test) + CMD_SKIP_SELF_TEST=true + shift + ;; + --auto-yes) + CMD_AUTO_YES=true + shift + ;; + --rollback) + CMD_ROLLBACK=true + shift + ;; + --deploy-only) + CMD_DEPLOY_ONLY=true + DEPLOY_ONLY_NODES="${2:-}" + shift 2 + ;; + --help|-h) + show_help + ;; + *) + echo "Unknown argument: $1" + echo "Use --help for usage." + exit 1 + ;; + esac +done + +# NEW: validate deploy-only input +if [ "$CMD_DEPLOY_ONLY" = true ]; then + if [[ ! "$DEPLOY_ONLY_NODES" =~ ^(node3|node4|node3,node4|node4,node3)$ ]]; then + echo "ERROR: --deploy-only requires: node3 | node4 | node3,node4" + echo "Example: ./deploy.sh --deploy-only node3" + exit 1 + fi +fi + +############################################ +# -------- INTERACTIVE INPUT --------------- +############################################ +prompt_var() { + local var_name="$1" + local default_value="$2" + local current_value="${!var_name:-}" + + if [ -n "$current_value" ]; then + printf -v "$var_name" "%s" "$current_value" + return + fi + + read -r -p "${var_name} [${default_value}]: " input + if [ -z "$input" ]; then + printf -v "$var_name" "%s" "$default_value" + else + printf -v "$var_name" "%s" "$input" + fi +} + + +############################################ +# -------- RELEASE INPUT ------------------- +############################################ + +prompt_var "TASK_ID" "41361" +prompt_var "RELEASE_VERSION" "25.22" +prompt_var "RELEASE_TAG" "2025-12-15-11eeef9e99" + +prompt_var "PREVIOUS_RELEASE_VERSION" "25.21" +prompt_var "PREVIOUS_RELEASE_TAG" "2025-12-05-ecacdc6c25" + +prompt_var "EXPECTED_MIGRATION_ID" "565" + + +############################################ +# -------- CONFIG (change per release) ----- +############################################ + +# Base directory for sandbox releases +BASE_DIR="/home/dev-wltsbx/encrypted/sandbox" + +# Docker registry +REGISTRY="wlt-sbx-hb-int.wltsbxinner.walletto.eu/coin/release" + +# Docker contexts +NODE3_CONTEXT="wlt-sbx-dkapp3-ams" +NODE4_CONTEXT="wlt-sbx-dkapp4-ams" + +# Docker stacks +NODE3_STACK="sbxapp3" +NODE4_STACK="sbxapp4" + +# DRY-RUN: default false, can be overridden by env or --dry-run +DRY_RUN="${DRY_RUN:-false}" +if [ "$CMD_DRY_RUN" = true ]; then + DRY_RUN=true +fi + + + +SSH_JUMP_HOST="${SSH_JUMP_HOST:-YOUR_JUMP_HOST}" +DB_HOST="${DB_HOST:-YOUR_DB_HOST}" +DB_PORT="${DB_PORT:-5432}" +DB_NAME="${DB_NAME:-coin}" +DB_USER="${DB_USER:-coin}" +DB_PASSWORD="${DB_PASSWORD:-YOUR_DB_PASSWORD}" + +############################################ +# -------- DERIVED PATHS ------------------- +############################################ + +NEW_SUFFIX="_sbx_${RELEASE_TAG}" +PREV_SUFFIX="_sbx_${PREVIOUS_RELEASE_TAG}" + +NODE4_NEW="${BASE_DIR}/${RELEASE_VERSION}${NEW_SUFFIX}-node-4" +NODE3_NEW="${BASE_DIR}/${RELEASE_VERSION}${NEW_SUFFIX}-node-3" + +NODE4_PREV="${BASE_DIR}/${PREVIOUS_RELEASE_VERSION}${PREV_SUFFIX}-node-4" +NODE3_PREV="${BASE_DIR}/${PREVIOUS_RELEASE_VERSION}${PREV_SUFFIX}-node-3" + +OLD_COIN="coin-${PREVIOUS_RELEASE_TAG}" +NEW_COIN="coin-${RELEASE_TAG}" + +TARBALL="${RELEASE_TAG}.tar.gz" + +############################################ +# -------- LOGGING SETUP ------------------- +############################################ + +LOG_DIR="${BASE_DIR}/logs" +mkdir -p "$LOG_DIR" + +TIMESTAMP="$(date '+%Y-%m-%d__%H-%M-%S')" +LOGFILE="${LOG_DIR}/deploy_${RELEASE_TAG}__${TIMESTAMP}_task-${TASK_ID}.log" +touch "$LOGFILE" + +############################################ +# -------- UTILS (log, run, confirm) ------- +############################################ + +log_msg() { + # Write to logfile without ANSI codes + printf "%s\n" "$(echo -e "$1" | sed 's/\x1B\[[0-9;]*[JKmsu]//g')" >> "$LOGFILE" + # Print to console with colors + echo -e "$1" +} + +run() { + log_msg "${BLUE}+ $*${RESET}" + if [ "$DRY_RUN" != "true" ]; then + "$@" + fi +} + +ensure_dir() { + if [ ! -d "$1" ]; then + log_msg "${RED}ERROR: directory not found: $1${RESET}" + exit 1 + fi +} + +confirm() { + local question="$1" + + if [ "$CMD_AUTO_YES" = true ]; then + log_msg "${YELLOW}[AUTO-YES] ${question}: YES${RESET}" + return 0 + fi + + read -r -p "${question} (yes/no): " answer + case "$answer" in + yes|y|Y) return 0 ;; + *) log_msg "${RED}Operation cancelled by user.${RESET}"; exit 1 ;; + esac +} + +# NEW: soft confirm (no => skip, not exit) +confirm_optional() { + local question="$1" + + if [ "$CMD_AUTO_YES" = true ]; then + log_msg "${YELLOW}[AUTO-YES] ${question}: YES${RESET}" + return 0 + fi + + while true; do + read -r -p "${question} (yes/no): " answer + case "$answer" in + yes|y|Y) return 0 ;; + no|n|N) return 1 ;; + *) echo "Please answer yes or no." ;; + esac + done +} + +print_summary() { + log_msg "" + log_msg "${BLUE}======== DEPLOY SUMMARY ========${RESET}" + + if [ "$CMD_DEPLOY_ONLY" = true ]; then + log_msg "Prepared:" + log_msg " - node-4 : skipped (deploy-only)" + log_msg " - node-3 : skipped (deploy-only)" + else + log_msg "Prepared:" + log_msg " - node-4 : ${PREPARED_NODE4}" + log_msg " - node-3 : ${PREPARED_NODE3}" + fi + + log_msg "" + log_msg "Selected:" + log_msg " - node-3 : ${SELECTED_NODE3}" + log_msg " - node-4 : ${SELECTED_NODE4}" + + log_msg "" + log_msg "Deploy attempted:" + log_msg " - node-3 : ${DEPLOY_ATTEMPT_NODE3}" + log_msg " - node-4 : ${DEPLOY_ATTEMPT_NODE4}" + + log_msg "" + log_msg "Mode:" + if [ "$CMD_DEPLOY_ONLY" = true ]; then + log_msg " - deploy-only : true (${DEPLOY_ONLY_NODES})" + else + log_msg " - deploy-only : false" + fi + log_msg "${BLUE}================================${RESET}" +} + +############################################ +# -------- SELF-TEST (interactive) --------- +############################################ + +self_test() { + log_msg "${BLUE}========== SELF-TEST ==========${RESET}" + + local issues=() + + # 1. Directories + [ -d "$BASE_DIR" ] || issues+=("BASE_DIR does not exist: $BASE_DIR") + [ -d "$NODE4_PREV" ] || issues+=("Previous node-4 release dir missing: $NODE4_PREV") + [ -d "$NODE3_PREV" ] || issues+=("Previous node-3 release dir missing: $NODE3_PREV") + + # 2. Docker contexts + if ! docker context ls >/dev/null 2>&1; then + issues+=("docker context ls failed (docker not available?)") + else + docker context ls --format '{{.Name}}' | grep -qx "$NODE3_CONTEXT" || \ + issues+=("docker context for node-3 not found: $NODE3_CONTEXT") + docker context ls --format '{{.Name}}' | grep -qx "$NODE4_CONTEXT" || \ + issues+=("docker context for node-4 not found: $NODE4_CONTEXT") + fi + + + # Config summary + log_msg "${YELLOW}Release configuration:${RESET}" + log_msg " Release version : ${RELEASE_VERSION}" + log_msg " Release tag : ${RELEASE_TAG}" + log_msg " Previous version: ${PREVIOUS_RELEASE_VERSION} (${PREVIOUS_RELEASE_TAG})" + log_msg " Task ID : ${TASK_ID}" + log_msg " BASE_DIR : ${BASE_DIR}" + log_msg " NODE3 context : ${NODE3_CONTEXT}" + log_msg " NODE4 context : ${NODE4_CONTEXT}" + log_msg " EXPECTED MIG ID : ${EXPECTED_MIGRATION_ID}" + log_msg " Log file : ${LOGFILE}" + + if [ "${#issues[@]}" -gt 0 ]; then + log_msg "${RED}Potential issues detected:${RESET}" + for item in "${issues[@]}"; do + log_msg " - ${item}" + done + confirm "⚠ Continue deployment despite these issues?" + else + log_msg "${GREEN}SELF-TEST: no critical issues detected.${RESET}" + confirm "Proceed with deployment?" + fi + + log_msg "${BLUE}========== SELF-TEST DONE ======${RESET}" +} + + +############################################ +# -------- ROLLBACK TO PREVIOUS RELEASE ---- +############################################ + +rollback() { + log_msg "${BLUE}=== ROLLBACK TO PREVIOUS RELEASE ===${RESET}" + log_msg "${YELLOW}Rolling back from ${RELEASE_VERSION} (${RELEASE_TAG})${RESET}" + log_msg "${YELLOW} to ${PREVIOUS_RELEASE_VERSION} (${PREVIOUS_RELEASE_TAG})${RESET}" + + confirm "⚠ This will stop current stacks and revert to previous release. Continue?" + + # Stop node-3 stack + log_msg "${YELLOW}Stopping node-3 stack (${NODE3_STACK})…${RESET}" + run docker context use "$NODE3_CONTEXT" + run docker stack rm "$NODE3_STACK" || log_msg "${YELLOW}Stack not found or already removed.${RESET}" + sleep 3 + + # Stop node-4 stack + log_msg "${YELLOW}Stopping node-4 stack (${NODE4_STACK})…${RESET}" + run docker context use "$NODE4_CONTEXT" + run docker stack rm "$NODE4_STACK" || log_msg "${YELLOW}Stack not found or already removed.${RESET}" + sleep 3 + + log_msg "${GREEN}Stacks stopped.${RESET}" + + # Deploy previous node-3 release + log_msg "${YELLOW}Deploying previous node-3 release…${RESET}" + ensure_dir "$NODE3_PREV" + cd "$NODE3_PREV" + run docker context use "$NODE3_CONTEXT" + run ./deploy.sh deploy \ + -n "$NODE3_CONTEXT" \ + -w "$NODE3_STACK" \ + -N node.env \ + -P project.env \ + -P project_node3.env \ + -f docker-compose.yml \ + -f custom.secrets.yml \ + -f docker-compose-testshop.yaml \ + -s secrets.override.env \ + -u + + # Deploy previous node-4 release + log_msg "${YELLOW}Deploying previous node-4 release…${RESET}" + ensure_dir "$NODE4_PREV" + cd "$NODE4_PREV" + run docker context use "$NODE4_CONTEXT" + run ./deploy.sh deploy \ + -n "$NODE4_CONTEXT" \ + -w "$NODE4_STACK" \ + -N node.env \ + -P project.env \ + -P project_node4.env \ + -f docker-compose.yml \ + -f custom.secrets.yml \ + -f docker-compose-testshop.yaml \ + -s secrets.override.env \ + -u + + log_msg "${GREEN}==================================================${RESET}" + log_msg "${GREEN} ROLLBACK COMPLETED${RESET}" + log_msg "${GREEN} Now running: ${PREVIOUS_RELEASE_VERSION} (${PREVIOUS_RELEASE_TAG})${RESET}" + log_msg "${GREEN}==================================================${RESET}" +} + +############################################ +# -------- NODE-4 PREPARATION -------------- +############################################ + +prepare_node4() { + log_msg "${BLUE}=== PREPARE NODE-4 ===${RESET}" + + ensure_dir "$NODE4_PREV" + ensure_dir "$BASE_DIR" + + run cp -r "$NODE4_PREV" "$NODE4_NEW" + cd "$NODE4_NEW" + + # Remove old coin dir + [ -d "$OLD_COIN" ] && run rm -rf "$OLD_COIN" + + log_msg "${YELLOW}Extracting release archive from Docker…${RESET}" + if [ "$DRY_RUN" != "true" ]; then + docker run -i --rm "${REGISTRY}:${RELEASE_TAG}" release | base64 -d > "$TARBALL" + else + log_msg "${BLUE}+ docker run … > $TARBALL (DRY RUN)${RESET}" + fi + + run tar -xzf "$TARBALL" + run rm -f "$TARBALL" + + ensure_dir "$NEW_COIN" + + # Copy deploy.sh and docker-compose.yml + run cp "${NEW_COIN}/deploy.sh" ./ + run cp "${NEW_COIN}/docker-compose.yml" ./ + + # Update TAG in node.env (TAG = release tag, not dir name) + if grep -q '^TAG=' node.env; then + run sed -i "s/^TAG=.*/TAG=${RELEASE_TAG}/" node.env + else + run bash -c "echo TAG=${RELEASE_TAG} >> node.env" + fi + + # Comment out all export TAG_* patch lines + if grep -q '^export TAG_' node.env; then + run sed -i 's/^export TAG_/#export TAG_/' node.env + fi + + # Manual edit of project.env + if [ -f project.env ]; then + log_msg "${YELLOW}Manual step required: review and edit project.env NOW.${RESET}" + log_msg "${YELLOW}For example: vi ${NODE4_NEW}/project.env${RESET}" + confirm "Continue after you have manually updated project.env?" + else + log_msg "${RED}project.env not found in ${NODE4_NEW}!${RESET}" + exit 1 + fi + + PREPARED_NODE4=true + log_msg "${GREEN}Node-4 prepared: ${NODE4_NEW}${RESET}" +} + +############################################ +# -------- NODE-3 PREPARATION -------------- +############################################ + +prepare_node3() { + log_msg "${BLUE}=== PREPARE NODE-3 ===${RESET}" + + ensure_dir "$NODE3_PREV" + ensure_dir "$NODE4_NEW" + + run cp -r "$NODE3_PREV" "$NODE3_NEW" + cd "$NODE3_NEW" + + [ -d "$OLD_COIN" ] && run rm -rf "$OLD_COIN" + + run cp -r "$NODE4_NEW/${NEW_COIN}" ./ + + run cp "${NEW_COIN}/deploy.sh" ./ + run cp "${NEW_COIN}/docker-compose.yml" ./ + + # Reuse already updated node.env and project.env from node-4 + run cp "$NODE4_NEW/node.env" ./ + run cp "$NODE4_NEW/project.env" ./ + + PREPARED_NODE3=true + log_msg "${GREEN}Node-3 prepared: ${NODE3_NEW}${RESET}" +} + +############################################ +# -------- NODE-3 DEPLOY ------------------- +############################################ + +deploy_node3() { + log_msg "${BLUE}=== DEPLOY NODE-3 ===${RESET}" + + cd "$NODE3_NEW" + + run docker context use "$NODE3_CONTEXT" + + DEPLOY_ATTEMPT_NODE3=true + run ./deploy.sh deploy \ + -n "$NODE3_CONTEXT" \ + -w "$NODE3_STACK" \ + -N node.env \ + -P project.env \ + -P project_node3.env \ + -f docker-compose.yml \ + -f custom.secrets.yml \ + -f docker-compose-testshop.yaml \ + -s secrets.override.env \ + -u + + run docker ps +} + +############################################ +# -------- NODE-4 DEPLOY ------------------- +############################################ + +deploy_node4() { + log_msg "${BLUE}=== DEPLOY NODE-4 ===${RESET}" + + cd "$NODE4_NEW" + + run docker context use "$NODE4_CONTEXT" + + DEPLOY_ATTEMPT_NODE4=true + run ./deploy.sh deploy \ + -n "$NODE4_CONTEXT" \ + -w "$NODE4_STACK" \ + -N node.env \ + -P project.env \ + -P project_node4.env \ + -f docker-compose.yml \ + -f custom.secrets.yml \ + -f docker-compose-testshop.yaml \ + -s secrets.override.env \ + -u + + run docker ps + + log_msg "${YELLOW}Reminder: run regression tests manually.${RESET}" +} + +############################################ +# -------- MAIN ---------------------------- +############################################ + +main() { + log_msg "${BLUE}==================================================${RESET}" + log_msg "${BLUE} COIN Sandbox Deployment Script${RESET}" + log_msg "${BLUE} Release: ${RELEASE_VERSION} (${RELEASE_TAG})${RESET}" + log_msg "${BLUE} Task ID: ${TASK_ID}${RESET}" + log_msg "${BLUE} Logfile: ${LOGFILE}${RESET}" + log_msg "${BLUE} DRY_RUN: ${DRY_RUN}${RESET}" + log_msg "${BLUE}==================================================${RESET}" + + # Rollback mode takes priority + if [ "$CMD_ROLLBACK" = true ]; then + rollback + print_summary + exit 0 + fi + + # Prevent conflicting flags + if [ "$CMD_NODE3_ONLY" = true ] && [ "$CMD_NODE4_ONLY" = true ]; then + log_msg "${RED}Cannot use --node3-only and --node4-only together.${RESET}" + exit 1 + fi + + # SELF-TEST + if [ "$CMD_SKIP_SELF_TEST" = false ]; then + self_test + else + log_msg "${YELLOW}SELF-TEST skipped (--skip-self-test).${RESET}" + fi + + # Only self-test and exit + if [ "$CMD_SELF_TEST_ONLY" = true ]; then + log_msg "${YELLOW}Self-test only mode: no deployment will be performed.${RESET}" + print_summary + exit 0 + fi + + ######################################################### + # DEPLOY-ONLY MODE: skip prepare and skip questions + ######################################################### + if [ "$CMD_DEPLOY_ONLY" = true ]; then + log_msg "${YELLOW}DEPLOY-ONLY mode enabled: ${DEPLOY_ONLY_NODES}${RESET}" + log_msg "${YELLOW}Skipping prepare phase and all confirmations.${RESET}" + + if [[ "$DEPLOY_ONLY_NODES" == *"node3"* ]]; then + SELECTED_NODE3=true + ensure_dir "$NODE3_NEW" + deploy_node3 + fi + + if [[ "$DEPLOY_ONLY_NODES" == *"node4"* ]]; then + SELECTED_NODE4=true + ensure_dir "$NODE4_NEW" + deploy_node4 + fi + + print_summary + exit 0 + fi + + ######################################################### + # NORMAL MODE: ALWAYS PREPARE BOTH NODES + ######################################################### + prepare_node4 + prepare_node3 + + ######################################################### + # DEPLOY SELECTION + ######################################################### + + if [ "$CMD_NODE3_ONLY" = true ]; then + log_msg "${YELLOW}Mode: node-3 only deployment.${RESET}" + SELECTED_NODE3=true + deploy_node3 + print_summary + exit 0 + fi + + if [ "$CMD_NODE4_ONLY" = true ]; then + log_msg "${YELLOW}Mode: node-4 only deployment.${RESET}" + SELECTED_NODE4=true + deploy_node4 + print_summary + exit 0 + fi + + # Interactive deploy questions + if confirm_optional "Запускать деплой node-3?"; then + SELECTED_NODE3=true + deploy_node3 + else + log_msg "${YELLOW}Node-3 deployment skipped by user choice.${RESET}" + fi + + if confirm_optional "Запускать деплой node-4?"; then + SELECTED_NODE4=true + deploy_node4 + else + log_msg "${YELLOW}Node-4 deployment skipped by user choice.${RESET}" + fi + + log_msg "${GREEN}==================================================${RESET}" + log_msg "${GREEN} DEPLOYMENT FINISHED.${RESET}" + log_msg "${GREEN} Expected DB last_id = ${EXPECTED_MIGRATION_ID}${RESET}" + log_msg "${GREEN}==================================================${RESET}" + + print_summary +} +main "$@"