#!/usr/bin/env bash # Publish the packaged artifacts to the env's S3 bucket and roll the box to them. # Run by build-artifacts.yml AFTER aws creds are configured (env: ENV, BUCKET, # DEPLOY_DOC). Each release is stored immutably under releases// and mirrored # to releases/latest/ (what the box's deploy.sh pulls). # # The deploy is fired by TAG (project=open-swe,env=), which is exactly what # the app deploy role's tag-scoped ssm:SendCommand allows — so this needs no # ec2:DescribeInstances and no instance id up front. set -euo pipefail : "${ENV:?}" "${BUCKET:?}" "${DEPLOY_DOC:?}" SHA="${GITHUB_SHA:?}" [ -f app.tar.gz ] && [ -f spa.tar.gz ] || { echo "ERROR: artifacts not built" >&2; exit 1; } echo "==> upload release ${SHA} to s3://${BUCKET}/releases/${SHA}/" for f in app.tar.gz spa.tar.gz; do aws s3 cp "${f}" "s3://${BUCKET}/releases/${SHA}/${f}" done echo "==> mirror to releases/latest/ (server-side copy)" for f in app.tar.gz spa.tar.gz; do aws s3 cp "s3://${BUCKET}/releases/${SHA}/${f}" "s3://${BUCKET}/releases/latest/${f}" done echo "==> fire ${DEPLOY_DOC} via SSM (tag-targeted: project=open-swe, env=${ENV})" CMD_ID="$(aws ssm send-command \ --document-name "${DEPLOY_DOC}" \ --targets "Key=tag:project,Values=open-swe" "Key=tag:env,Values=${ENV}" \ --comment "release ${SHA}" \ --query 'Command.CommandId' --output text)" echo "command: ${CMD_ID}" echo "==> wait for the deploy to finish" STATUS="Pending" IID="" for _ in $(seq 1 60); do sleep 10 # CommandInvocations is empty until SSM registers the target invocation. IID="$(aws ssm list-command-invocations --command-id "${CMD_ID}" \ --query 'CommandInvocations[0].InstanceId' --output text 2>/dev/null || echo None)" [ -z "${IID}" ] || [ "${IID}" = "None" ] && continue STATUS="$(aws ssm list-command-invocations --command-id "${CMD_ID}" \ --query 'CommandInvocations[0].Status' --output text 2>/dev/null || echo Pending)" case "${STATUS}" in Success | Failed | Cancelled | TimedOut) break ;; esac done if [ -z "${IID}" ] || [ "${IID}" = "None" ]; then echo "ERROR: no box picked up the deploy command (is a running open-swe ${ENV} box registered with SSM?)" >&2 exit 1 fi echo "==> deploy.sh output from ${IID}:" echo "----- stdout -----" aws ssm get-command-invocation --command-id "${CMD_ID}" --instance-id "${IID}" \ --query 'StandardOutputContent' --output text || true echo "----- stderr -----" aws ssm get-command-invocation --command-id "${CMD_ID}" --instance-id "${IID}" \ --query 'StandardErrorContent' --output text || true # Gate on the AGGREGATE command status (Success only if EVERY targeted invocation # succeeded), not CommandInvocations[0] — during a userDataCausesReplacement window # two instances can briefly share the project/env tags, and a partial failure on # the other instance must not be reported as success. TARGETS="$(aws ssm list-commands --command-id "${CMD_ID}" \ --query 'Commands[0].TargetCount' --output text 2>/dev/null || echo 1)" [ "${TARGETS}" = "1" ] || echo "WARNING: deploy fanned out to ${TARGETS} instances (expected 1)" AGG="$(aws ssm list-commands --command-id "${CMD_ID}" \ --query 'Commands[0].Status' --output text 2>/dev/null || echo Failed)" echo "==> aggregate deploy status: ${AGG} (across ${TARGETS} target(s))" [ "${AGG}" = "Success" ] || { echo "ERROR: deploy did not succeed (${AGG})" >&2; exit 1; } echo "==> ${ENV} rolled to release ${SHA}"