96 lines
3.9 KiB
Bash
96 lines
3.9 KiB
Bash
#!/usr/bin/env sh
|
|
set -eu
|
|
|
|
if [ "$#" -ne 2 ]; then
|
|
echo "Usage: $0 <40-character-commit-sha> <release-artifact-directory>" >&2
|
|
exit 64
|
|
fi
|
|
|
|
RELEASE_SHA="$1"
|
|
ARTIFACT_DIR="${2%/}"
|
|
case "${RELEASE_SHA}" in
|
|
*[!0-9a-f]*|'') echo "Release SHA must contain exactly 40 lowercase hexadecimal characters." >&2; exit 64 ;;
|
|
esac
|
|
if [ "${#RELEASE_SHA}" -ne 40 ]; then
|
|
echo "Release SHA must contain exactly 40 lowercase hexadecimal characters." >&2
|
|
exit 64
|
|
fi
|
|
|
|
API_ARCHIVE="${ARTIFACT_DIR}/projectbrain-api-${RELEASE_SHA}.tar"
|
|
WEB_ARCHIVE="${ARTIFACT_DIR}/projectbrain-web-${RELEASE_SHA}.tar"
|
|
DIGEST_FILE="${ARTIFACT_DIR}/archive-sha256.txt"
|
|
for path in "${API_ARCHIVE}" "${WEB_ARCHIVE}" "${DIGEST_FILE}"; do
|
|
if [ ! -f "${path}" ]; then echo "Missing release artifact: ${path}" >&2; exit 66; fi
|
|
done
|
|
for command in sha256sum skopeo docker curl; do
|
|
if ! command -v "${command}" >/dev/null 2>&1; then echo "Required command is unavailable: ${command}" >&2; exit 69; fi
|
|
done
|
|
|
|
verify_digest() {
|
|
archive="$1"
|
|
filename="$(basename "${archive}")"
|
|
expected="$(awk -v file="${filename}" '$2 == file || $2 ~ ("/" file "$") { print $1; exit }' "${DIGEST_FILE}")"
|
|
actual="$(sha256sum "${archive}" | awk '{ print $1 }')"
|
|
if [ -z "${expected}" ] || [ "${actual}" != "${expected}" ]; then
|
|
echo "Digest mismatch for ${filename}." >&2
|
|
exit 65
|
|
fi
|
|
printf '%s' "${actual}"
|
|
}
|
|
|
|
API_DIGEST="$(verify_digest "${API_ARCHIVE}")"
|
|
WEB_DIGEST="$(verify_digest "${WEB_ARCHIVE}")"
|
|
skopeo copy "oci-archive:${API_ARCHIVE}" "docker-daemon:projectbrain-api:${RELEASE_SHA}"
|
|
skopeo copy "oci-archive:${WEB_ARCHIVE}" "docker-daemon:projectbrain-web:${RELEASE_SHA}"
|
|
|
|
API_IMAGE_ID="$(docker image inspect --format '{{.Id}}' "projectbrain-api:${RELEASE_SHA}")"
|
|
WEB_IMAGE_ID="$(docker image inspect --format '{{.Id}}' "projectbrain-web:${RELEASE_SHA}")"
|
|
|
|
: "${POSTGRES_PASSWORD:?set a non-default POSTGRES_PASSWORD in the environment or .env}"
|
|
: "${PROJECTBRAIN_OWNER_PASSWORD:?set PROJECTBRAIN_OWNER_PASSWORD in the environment or .env}"
|
|
: "${PROJECTBRAIN_AUTH_SECRET:?set PROJECTBRAIN_AUTH_SECRET in the environment or .env}"
|
|
if [ "${POSTGRES_PASSWORD}" = "projectbrain" ]; then echo "Default PostgreSQL password is forbidden." >&2; exit 78; fi
|
|
if [ "${#PROJECTBRAIN_OWNER_PASSWORD}" -lt 12 ]; then echo "Owner password must contain at least 12 characters." >&2; exit 78; fi
|
|
if [ "${#PROJECTBRAIN_AUTH_SECRET}" -lt 32 ]; then echo "Auth secret must contain at least 32 characters." >&2; exit 78; fi
|
|
|
|
export PROJECTBRAIN_RELEASE_SHA="${RELEASE_SHA}"
|
|
export PROJECTBRAIN_DEPLOY_SLOT="${PROJECTBRAIN_DEPLOY_SLOT:-stage}"
|
|
export PROJECTBRAIN_WEB_PORT="${PROJECTBRAIN_STAGE_WEB_PORT:-1210}"
|
|
COMPOSE_PROJECT="projectbrain-${PROJECTBRAIN_DEPLOY_SLOT}-${RELEASE_SHA%????????????????????????????????}"
|
|
|
|
docker compose \
|
|
--project-name "${COMPOSE_PROJECT}" \
|
|
--file docker-compose.yml \
|
|
--file docker-compose.release.yml \
|
|
config --quiet
|
|
docker compose \
|
|
--project-name "${COMPOSE_PROJECT}" \
|
|
--file docker-compose.yml \
|
|
--file docker-compose.release.yml \
|
|
up --detach --no-build --pull never
|
|
|
|
READINESS_URL="http://127.0.0.1:${PROJECTBRAIN_WEB_PORT}/api/system/readiness"
|
|
attempt=0
|
|
until READINESS="$(curl --fail --silent --show-error "${READINESS_URL}")"; do
|
|
attempt=$((attempt + 1))
|
|
if [ "${attempt}" -ge 30 ]; then echo "Staged release did not become ready: ${READINESS_URL}" >&2; exit 70; fi
|
|
sleep 2
|
|
done
|
|
|
|
EVIDENCE_FILE="${ARTIFACT_DIR}/staged-deployment-${RELEASE_SHA}.txt"
|
|
{
|
|
echo "release_sha=${RELEASE_SHA}"
|
|
echo "compose_project=${COMPOSE_PROJECT}"
|
|
echo "web_port=${PROJECTBRAIN_WEB_PORT}"
|
|
echo "api_archive_sha256=${API_DIGEST}"
|
|
echo "web_archive_sha256=${WEB_DIGEST}"
|
|
echo "api_image_id=${API_IMAGE_ID}"
|
|
echo "web_image_id=${WEB_IMAGE_ID}"
|
|
echo "readiness_url=${READINESS_URL}"
|
|
echo "readiness=${READINESS}"
|
|
echo "verified_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
} > "${EVIDENCE_FILE}"
|
|
|
|
echo "Staged immutable release ${RELEASE_SHA} as ${COMPOSE_PROJECT}."
|
|
echo "Evidence: ${EVIDENCE_FILE}"
|