mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 10:23:14 +00:00
Some checks are pending
Build & publish app artifacts / Publish + deploy (dev) (push) Waiting to run
Build & publish app artifacts / Publish + deploy (prod) (push) Waiting to run
Infra CD / Infra CI (pre-deploy) (push) Waiting to run
Infra CD / Deploy open-swe-dev (push) Blocked by required conditions
Infra CD / Deploy open-swe-prod (push) Blocked by required conditions
Agent CI / Agent lint (push) Waiting to run
Agent CI / Agent format check (push) Waiting to run
Agent CI / Agent unit tests (push) Waiting to run
Agent CI / Playwright E2E (push) Waiting to run
T20 CD safety nets. Two gaps closed before the first real prod deploy: 1. Promotion gate. promote_dev_to_prod.yml previously fast-forwarded dev→main unconditionally. It now hard-gates on check-dev-green.sh: every check-run on the dev HEAD must be completed+passing AND the Agent CI suite (lint/format/unit/E2E) must be present+success, or the promotion blocks (fails safe on a missing/renamed check). The promote run excludes its OWN check-run by run-id (unforgeable), never by the mutable name "promote", so a colliding red check cannot hide. Fields are read with a 0x1F separator so an empty conclusion (every in_progress check) cannot shift columns. ci.yml now also runs on push:dev so dev HEAD actually carries that signal (a PR check alone can be admin-merged past). 2. Rollback + last-good. publish-and-deploy.sh advances releases/last-good/ only after a successful roll (deploy.sh gates on `systemctl is-active`), and makes releases/latest/ transactional — reverting to the prior release if the roll fails so a replaced box never self-deploys a broken release. New rollback.yml + rollback.sh re-point latest at last-good (or an explicit sha) and re-fire the deploy; prod is gated by the `prod` Environment approval, same as a deploy. The shared fire/wait/aggregate-gate logic is factored into roll-box.sh (used by both forward and backward rolls). Least-privilege: drop the unused s3:DeleteObject from the app deploy role — publish/rollback/deploy only Get+Put (S3-to-S3 copy), and the rollback fallback now depends on immutable release history staying intact. Lifecycle expiry (not CI) handles old-version cleanup. Gate logic unit-tested (7 cases + jq round-trip). IAM change + release-safety control cross-reviewed by GPT-4.1: APPROVE, no blocks. Claude-Session: https://claude.ai/code/session_01DMhLf4G5V8MStJQyAW95hi
66 lines
3 KiB
Bash
Executable file
66 lines
3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Roll an env BACK to a prior release: re-point releases/latest/ at a chosen release
|
|
# and re-fire the deploy. Run by rollback.yml after aws creds are configured.
|
|
#
|
|
# ENV dev | prod (required)
|
|
# BUCKET open-swe-<env>-assets (required)
|
|
# DEPLOY_DOC open-swe-<env>-deploy (required)
|
|
# TARGET_SHA release sha to restore; blank => releases/last-good/ (optional)
|
|
#
|
|
# releases/latest/ is moved TRANSACTIONALLY (same as the forward deploy): pointed at
|
|
# the target, the box rolled, and on a failed roll latest is reverted to whatever it
|
|
# was before the rollback attempt — so a failed rollback never leaves latest at a
|
|
# release the box could not bring up. releases/last-good/ is left untouched; advance
|
|
# it by running a forward deploy.
|
|
#
|
|
# Reuses the app deploy role's existing releases/* write + tag-scoped ssm:SendCommand
|
|
# — no new IAM. The fire/wait/gate is the SAME roll-box.sh the forward deploy uses.
|
|
set -euo pipefail
|
|
: "${ENV:?}" "${BUCKET:?}" "${DEPLOY_DOC:?}"
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Copy a release prefix into releases/latest/ AND record the resolved sha, so
|
|
# releases/latest/sha.txt is always a real commit a later revert can resolve.
|
|
point_latest() { # $1 = source prefix (releases/<sha>|releases/last-good); $2 = sha to record
|
|
local src="$1" sha="$2" f
|
|
for f in app.tar.gz spa.tar.gz; do
|
|
aws s3 cp "s3://${BUCKET}/${src}/${f}" "s3://${BUCKET}/releases/latest/${f}"
|
|
done
|
|
printf '%s\n' "${sha}" | aws s3 cp - "s3://${BUCKET}/releases/latest/sha.txt"
|
|
}
|
|
|
|
if [ -n "${TARGET_SHA:-}" ]; then
|
|
SRC="releases/${TARGET_SHA}"
|
|
LABEL="${TARGET_SHA}"
|
|
RESOLVED_SHA="${TARGET_SHA}"
|
|
else
|
|
SRC="releases/last-good"
|
|
LABEL="last-good"
|
|
# resolve last-good's real sha so latest/sha.txt records a commit, not a label.
|
|
RESOLVED_SHA="$(aws s3 cp "s3://${BUCKET}/releases/last-good/sha.txt" - 2>/dev/null | tr -d '[:space:]' || true)"
|
|
RESOLVED_SHA="${RESOLVED_SHA:-last-good}"
|
|
fi
|
|
echo "==> rollback ${ENV} to ${LABEL} (s3://${BUCKET}/${SRC}/, sha=${RESOLVED_SHA})"
|
|
|
|
# Refuse to roll back to a release that is not fully present.
|
|
for f in app.tar.gz spa.tar.gz; do
|
|
aws s3 ls "s3://${BUCKET}/${SRC}/${f}" >/dev/null 2>&1 \
|
|
|| { echo "ERROR: ${SRC}/${f} not found in s3://${BUCKET} — cannot roll back to ${LABEL}" >&2; exit 1; }
|
|
done
|
|
|
|
# Capture what latest points at now, so a failed rollback can be reverted.
|
|
PREV_SHA="$(aws s3 cp "s3://${BUCKET}/releases/latest/sha.txt" - 2>/dev/null | tr -d '[:space:]' || true)"
|
|
|
|
echo "==> point releases/latest/ -> ${SRC} (was ${PREV_SHA:-<none>})"
|
|
point_latest "${SRC}" "${RESOLVED_SHA}"
|
|
|
|
if ! ROLL_COMMENT="rollback ${ENV} to ${LABEL}" bash "${HERE}/roll-box.sh"; then
|
|
if [ -n "${PREV_SHA}" ]; then
|
|
echo "!! rollback deploy failed — reverting releases/latest/ -> ${PREV_SHA}" >&2
|
|
point_latest "releases/${PREV_SHA}" "${PREV_SHA}"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> ${ENV} rolled back to ${LABEL}"
|
|
echo "NOTE: releases/last-good/ is left unchanged; re-run a forward deploy to advance it."
|