将发布与上线授权边界调整为一次明确的生产发布指令可连续执行 publish 和 deploy,同时保留仅构建镜像时的停止边界。 为现有发布脚本增加 compose/kubernetes helper 选择和显式 SSH 私钥支持,保持完整 SHA、digest 与生产基线校验不变。 验证:bash -n、ShellCheck、manual-release-test。
120 lines
4.0 KiB
Bash
Executable File
120 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
production_host=${AI_GATEWAY_PRODUCTION_HOST:-root@110.42.51.33}
|
|
deploy_mode=${AI_GATEWAY_DEPLOY_MODE:-compose}
|
|
case $deploy_mode in
|
|
compose) default_remote_helper=/usr/local/sbin/easyai-ai-gateway-release ;;
|
|
kubernetes) default_remote_helper=/usr/local/sbin/easyai-ai-gateway-cluster-release ;;
|
|
*) echo 'AI_GATEWAY_DEPLOY_MODE must be compose or kubernetes' >&2; exit 1 ;;
|
|
esac
|
|
remote_helper=${AI_GATEWAY_REMOTE_RELEASE_HELPER:-$default_remote_helper}
|
|
production_ssh_key=${AI_GATEWAY_PRODUCTION_SSH_KEY:-}
|
|
action=deploy
|
|
value=
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
scripts/deploy-production-release.sh dist/releases/<git-sha>.json
|
|
scripts/deploy-production-release.sh --rollback <historical-git-sha>
|
|
scripts/deploy-production-release.sh --status
|
|
|
|
This command changes production. Run it only after a successful publish
|
|
command and an explicit user request to release or deploy to production.
|
|
EOF
|
|
}
|
|
|
|
case ${1:-} in
|
|
--status)
|
|
action=status
|
|
[[ $# -eq 1 ]] || { usage >&2; exit 64; }
|
|
;;
|
|
--rollback)
|
|
action=rollback
|
|
[[ $# -eq 2 ]] || { usage >&2; exit 64; }
|
|
value=$2
|
|
;;
|
|
-h|--help|'')
|
|
usage
|
|
[[ ${1:-} == -h || ${1:-} == --help ]] && exit 0
|
|
exit 64
|
|
;;
|
|
*)
|
|
[[ $# -eq 1 ]] || { usage >&2; exit 64; }
|
|
value=$1
|
|
;;
|
|
esac
|
|
|
|
[[ $production_host =~ ^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+$ ]] || {
|
|
echo 'AI_GATEWAY_PRODUCTION_HOST must use user@host syntax' >&2
|
|
exit 1
|
|
}
|
|
[[ $remote_helper =~ ^/[A-Za-z0-9._/-]+$ ]] || {
|
|
echo 'AI_GATEWAY_REMOTE_RELEASE_HELPER must be an absolute safe path' >&2
|
|
exit 1
|
|
}
|
|
ssh_options=(-o BatchMode=yes)
|
|
scp_options=(-q -o BatchMode=yes)
|
|
if [[ -n $production_ssh_key ]]; then
|
|
[[ $production_ssh_key == /* && -f $production_ssh_key && ! -L $production_ssh_key ]] || {
|
|
echo 'AI_GATEWAY_PRODUCTION_SSH_KEY must be an absolute regular file' >&2
|
|
exit 1
|
|
}
|
|
ssh_options+=(-i "$production_ssh_key")
|
|
scp_options+=(-i "$production_ssh_key")
|
|
fi
|
|
|
|
if [[ $action == status ]]; then
|
|
exec ssh "${ssh_options[@]}" "$production_host" "$remote_helper status"
|
|
fi
|
|
|
|
if [[ $action == rollback ]]; then
|
|
[[ $value =~ ^[0-9a-f]{40}$ ]] || {
|
|
echo 'rollback release must be a full lowercase Git SHA' >&2
|
|
exit 1
|
|
}
|
|
# shellcheck disable=SC2029 # Both arguments are validated safe tokens above.
|
|
ssh "${ssh_options[@]}" "$production_host" "$remote_helper" rollback "$value"
|
|
echo "production_rollback=PASS release=$value"
|
|
exit 0
|
|
fi
|
|
|
|
manifest=$(cd "$(dirname "$value")" 2>/dev/null && pwd)/$(basename "$value")
|
|
[[ -f $manifest && ! -L $manifest ]] || {
|
|
echo 'release manifest must be a regular file' >&2
|
|
exit 1
|
|
}
|
|
node "$root/scripts/release-manifest.mjs" validate "$manifest"
|
|
source_sha=$(node "$root/scripts/release-manifest.mjs" get "$manifest" sourceSha)
|
|
base_sha=$(node "$root/scripts/release-manifest.mjs" get "$manifest" baseReleaseSha)
|
|
remote_manifest=/tmp/easyai-ai-gateway-release-$source_sha.json
|
|
status_file=$(mktemp)
|
|
|
|
cleanup() {
|
|
rm -f "$status_file"
|
|
ssh "${ssh_options[@]}" "$production_host" rm -f -- "$remote_manifest" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT HUP INT TERM
|
|
|
|
if ssh "${ssh_options[@]}" "$production_host" "$remote_helper" status >"$status_file" 2>/dev/null; then
|
|
node "$root/scripts/release-manifest.mjs" validate "$status_file" >/dev/null
|
|
current_sha=$(node "$root/scripts/release-manifest.mjs" get "$status_file" sourceSha)
|
|
[[ $current_sha == "$base_sha" ]] || {
|
|
printf 'production base changed after publish: current=%s manifest_base=%s\n' \
|
|
"$current_sha" "$base_sha" >&2
|
|
exit 1
|
|
}
|
|
else
|
|
[[ -z $base_sha ]] || {
|
|
echo 'production release status is unavailable for a non-bootstrap manifest' >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
scp "${scp_options[@]}" "$manifest" "$production_host:$remote_manifest"
|
|
# shellcheck disable=SC2029 # remote_manifest is derived from a validated full SHA.
|
|
ssh "${ssh_options[@]}" "$production_host" "$remote_helper" deploy "$remote_manifest"
|
|
echo "production_deploy=PASS release=$source_sha"
|