push.sh
bash
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a
init: musehub initial commit
Human
171 days ago
| 1 | #!/usr/bin/env bash |
| 2 | # MuseHub deploy orchestrator — build, push to ECR, trigger blue-green via SSM. |
| 3 | # |
| 4 | # Usage: |
| 5 | # bash deploy/push.sh staging # deploy to staging only |
| 6 | # bash deploy/push.sh prod # deploy to prod only |
| 7 | # bash deploy/push.sh staging prod # staging first, then prod |
| 8 | # |
| 9 | # What it does: |
| 10 | # 1. Builds a linux/amd64 Docker image from the local repo. |
| 11 | # 2. Tags it with <commit-hash>-<timestamp> for traceability. |
| 12 | # 3. Pushes the image to ECR (musehub/musehub). |
| 13 | # 4. Sends an SSM command to each target instance to run deploy.sh, |
| 14 | # which pulls the image and performs a zero-downtime blue-green swap. |
| 15 | # 5. Polls SSM until the deploy completes or fails, streaming the output. |
| 16 | # |
| 17 | # Prerequisites (one-time, already done): |
| 18 | # - AWS CLI configured (musehub-infra as default profile) |
| 19 | # - Docker Desktop running |
| 20 | # - musehub-infra has ecr push permissions (musehub-ecr-push policy) |
| 21 | # - musehub-ec2-ssm role has ecr pull permissions (musehub-ecr-pull policy) |
| 22 | # - AWS CLI installed on instances (run deploy/bootstrap-instance.sh once) |
| 23 | # |
| 24 | # Rollback to a previous image: |
| 25 | # IMAGE_TAG=<previous-tag> bash deploy/push.sh staging |
| 26 | # (skips build+push, triggers SSM with the specified tag directly) |
| 27 | |
| 28 | set -euo pipefail |
| 29 | |
| 30 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 31 | REPO_DIR="$(dirname "$SCRIPT_DIR")" |
| 32 | |
| 33 | ECR_REGISTRY="992382692655.dkr.ecr.us-east-1.amazonaws.com" |
| 34 | ECR_REPO="musehub/musehub" |
| 35 | ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}" |
| 36 | REGION="us-east-1" |
| 37 | |
| 38 | STAGING_INSTANCE="i-07547cd20bee2dea5" |
| 39 | PROD_INSTANCE="i-0855d6efe7fa1a49d" |
| 40 | |
| 41 | # ── Parse targets ───────────────────────────────────────────────────────────── |
| 42 | |
| 43 | if [ $# -eq 0 ]; then |
| 44 | echo "Usage: bash deploy/push.sh [staging] [prod]" |
| 45 | echo " staging deploy to staging.musehub.ai" |
| 46 | echo " prod deploy to musehub.ai" |
| 47 | echo " staging prod staging first, then prod" |
| 48 | echo "" |
| 49 | echo "Rollback (skips build+push, redeploys a previous tag):" |
| 50 | echo " IMAGE_TAG=<tag> bash deploy/push.sh staging" |
| 51 | exit 1 |
| 52 | fi |
| 53 | |
| 54 | TARGETS=() |
| 55 | for arg in "$@"; do |
| 56 | case "$arg" in |
| 57 | staging|prod) TARGETS+=("$arg") ;; |
| 58 | *) echo "Unknown target: $arg (must be staging or prod)" >&2; exit 1 ;; |
| 59 | esac |
| 60 | done |
| 61 | |
| 62 | # ── Helpers ─────────────────────────────────────────────────────────────────── |
| 63 | |
| 64 | log() { echo "[push] $*"; } |
| 65 | die() { echo "[push] ERROR: $*" >&2; exit 1; } |
| 66 | |
| 67 | # ── Image tag ───────────────────────────────────────────────────────────────── |
| 68 | |
| 69 | # If IMAGE_TAG is already set (rollback mode), skip build+push. |
| 70 | if [ -n "${IMAGE_TAG:-}" ]; then |
| 71 | log "Rollback mode — using existing tag: $IMAGE_TAG" |
| 72 | SKIP_BUILD=true |
| 73 | else |
| 74 | COMMIT_HASH=$(muse -C "$REPO_DIR" rev-parse HEAD --json 2>/dev/null \ |
| 75 | | python3 -c "import sys,json; print(json.load(sys.stdin)['commit_id'][:8])" \ |
| 76 | 2>/dev/null || echo "local") |
| 77 | IMAGE_TAG="${COMMIT_HASH}-$(date +%Y%m%d%H%M%S)" |
| 78 | SKIP_BUILD=false |
| 79 | fi |
| 80 | |
| 81 | FULL_IMAGE="${ECR_IMAGE}:${IMAGE_TAG}" |
| 82 | log "Target image: $FULL_IMAGE" |
| 83 | |
| 84 | # ── Build ───────────────────────────────────────────────────────────────────── |
| 85 | |
| 86 | if [ "$SKIP_BUILD" = false ]; then |
| 87 | log "[1/3] Building image for linux/amd64..." |
| 88 | docker build \ |
| 89 | --platform linux/amd64 \ |
| 90 | --tag "$FULL_IMAGE" \ |
| 91 | --tag "${ECR_IMAGE}:latest" \ |
| 92 | "$REPO_DIR" |
| 93 | log "Build complete." |
| 94 | |
| 95 | # ── Push to ECR ─────────────────────────────────────────────────────────── |
| 96 | |
| 97 | log "[2/3] Logging in to ECR and pushing..." |
| 98 | aws ecr get-login-password --region "$REGION" | \ |
| 99 | docker login --username AWS --password-stdin "$ECR_REGISTRY" |
| 100 | docker push "$FULL_IMAGE" |
| 101 | docker push "${ECR_IMAGE}:latest" |
| 102 | log "Push complete. Tag: $IMAGE_TAG" |
| 103 | else |
| 104 | log "[1/3] Skipping build (rollback mode)." |
| 105 | log "[2/3] Skipping push (rollback mode)." |
| 106 | fi |
| 107 | |
| 108 | # ── Trigger deploy via SSM ──────────────────────────────────────────────────── |
| 109 | |
| 110 | log "[3/3] Triggering deploy on: ${TARGETS[*]}" |
| 111 | |
| 112 | deploy_to() { |
| 113 | local env="$1" |
| 114 | local instance_id="$2" |
| 115 | |
| 116 | log "" |
| 117 | log "→ Deploying to $env ($instance_id)..." |
| 118 | |
| 119 | local cmd_id |
| 120 | cmd_id=$(aws ssm send-command \ |
| 121 | --region "$REGION" \ |
| 122 | --instance-ids "$instance_id" \ |
| 123 | --document-name "AWS-RunShellScript" \ |
| 124 | --parameters "commands=[ |
| 125 | \"export ECR_IMAGE=${ECR_IMAGE}\", |
| 126 | \"export IMAGE_TAG=${IMAGE_TAG}\", |
| 127 | \"bash /opt/musehub/deploy/deploy.sh\" |
| 128 | ]" \ |
| 129 | --comment "musehub ${IMAGE_TAG} → ${env}" \ |
| 130 | --timeout-seconds 600 \ |
| 131 | --query "Command.CommandId" \ |
| 132 | --output text) |
| 133 | |
| 134 | log " SSM command ID: $cmd_id" |
| 135 | log " Polling for completion (up to 10 min)..." |
| 136 | |
| 137 | local status |
| 138 | for i in $(seq 1 120); do |
| 139 | sleep 5 |
| 140 | status=$(aws ssm get-command-invocation \ |
| 141 | --region "$REGION" \ |
| 142 | --command-id "$cmd_id" \ |
| 143 | --instance-id "$instance_id" \ |
| 144 | --query "Status" \ |
| 145 | --output text 2>/dev/null || echo "Pending") |
| 146 | |
| 147 | case "$status" in |
| 148 | Success) |
| 149 | log " ✅ $env deploy succeeded." |
| 150 | echo "" |
| 151 | aws ssm get-command-invocation \ |
| 152 | --region "$REGION" \ |
| 153 | --command-id "$cmd_id" \ |
| 154 | --instance-id "$instance_id" \ |
| 155 | --query "StandardOutputContent" \ |
| 156 | --output text 2>/dev/null || true |
| 157 | return 0 |
| 158 | ;; |
| 159 | Failed|Cancelled|TimedOut|Cancelling) |
| 160 | log " ❌ $env deploy FAILED (status: $status)" |
| 161 | echo "" |
| 162 | aws ssm get-command-invocation \ |
| 163 | --region "$REGION" \ |
| 164 | --command-id "$cmd_id" \ |
| 165 | --instance-id "$instance_id" \ |
| 166 | --query "[StandardOutputContent, StandardErrorContent]" \ |
| 167 | --output text 2>/dev/null || true |
| 168 | return 1 |
| 169 | ;; |
| 170 | InProgress|Pending|Delayed) |
| 171 | printf "." |
| 172 | ;; |
| 173 | esac |
| 174 | done |
| 175 | |
| 176 | die "$env deploy timed out after 10 min. Check SSM: $cmd_id" |
| 177 | } |
| 178 | |
| 179 | for target in "${TARGETS[@]}"; do |
| 180 | case "$target" in |
| 181 | staging) deploy_to "staging" "$STAGING_INSTANCE" ;; |
| 182 | prod) deploy_to "prod" "$PROD_INSTANCE" ;; |
| 183 | esac |
| 184 | done |
| 185 | |
| 186 | log "" |
| 187 | log "All done." |
| 188 | log " Tag: $IMAGE_TAG" |
| 189 | log " Image: $FULL_IMAGE" |
File History
1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a
init: musehub initial commit
Human
171 days ago