宁波 NGINX 仅加载 conf.d/*.conf,原切换脚本写入 sites-enabled 后不会生效。统一将维护页、集群入口和回滚配置写入实际加载的 conf.d 入口,并保留切换前配置备份。\n\n验证:bash -n、ShellCheck、git diff --check、双节点 nginx -t 与 prepare。
414 lines
17 KiB
Bash
Executable File
414 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
# shellcheck source=scripts/cluster/common.sh
|
|
source "$script_dir/common.sh"
|
|
|
|
action=${1:-preflight}
|
|
release_manifest=${2:-}
|
|
[[ $action == preflight || $action == cutover ]] || {
|
|
echo 'usage: migrate-production.sh {preflight|cutover} dist/releases/<sha>.json' >&2
|
|
exit 64
|
|
}
|
|
[[ -f $release_manifest && ! -L $release_manifest ]] || {
|
|
echo 'release manifest must be a regular file' >&2
|
|
exit 1
|
|
}
|
|
|
|
load_cluster_env
|
|
node "$cluster_root/scripts/release-manifest.mjs" validate "$release_manifest" >/dev/null
|
|
source_sha=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" sourceSha)
|
|
api_image=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" images.api)
|
|
|
|
if [[ $action == cutover && ${AI_GATEWAY_CUTOVER_WINDOW_OVERRIDE:-} != I_ACCEPT_PRODUCTION_RISK ]]; then
|
|
shanghai_hour=$(TZ=Asia/Shanghai date '+%H')
|
|
if (( 10#$shanghai_hour < 2 || 10#$shanghai_hour >= 4 )); then
|
|
echo 'cutover is restricted to 02:00-04:00 Asia/Shanghai' >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo '[cutover] checking Docker, K3s, CNPG, disk and backup prerequisites'
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" 'bash -s' <<'REMOTE'
|
|
set -euo pipefail
|
|
docker inspect easyai-ai-gateway-api-1 easyai-ai-gateway-postgres-1 >/dev/null
|
|
docker exec easyai-ai-gateway-postgres-1 pg_isready >/dev/null
|
|
k3s kubectl get --raw=/readyz >/dev/null
|
|
[[ $(k3s kubectl get nodes --no-headers | awk '$2 == "Ready" { count++ } END { print count + 0 }') -eq 3 ]]
|
|
[[ $(k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.readyInstances}') -eq 2 ]]
|
|
primary=$(k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.currentPrimary}')
|
|
[[ $primary =~ ^easyai-postgres-[0-9]+$ ]]
|
|
for pod in $(k3s kubectl get pods -n easyai -l cnpg.io/cluster=easyai-postgres \
|
|
-o jsonpath='{.items[*].metadata.name}'); do
|
|
k3s kubectl exec -n easyai "$pod" -c postgres -- \
|
|
test -r /projected/barman-aws/config
|
|
done
|
|
archived_before=$(k3s kubectl exec -n easyai "$primary" -c postgres -- \
|
|
psql -X -U postgres -d postgres -At -c \
|
|
'SELECT archived_count FROM pg_stat_archiver;')
|
|
archived_after=$(k3s kubectl exec -i -n easyai "$primary" -c postgres -- \
|
|
bash -s -- "$archived_before" <<'POD'
|
|
set -euo pipefail
|
|
archived_before=$1
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d postgres -At \
|
|
-c "SELECT pg_logical_emit_message(true, 'easyai-ha-preflight', clock_timestamp()::text);" \
|
|
-c 'SELECT pg_switch_wal();' >/dev/null
|
|
for _ in $(seq 1 45); do
|
|
archived_after=$(psql -X -U postgres -d postgres -At -c \
|
|
'SELECT archived_count FROM pg_stat_archiver;')
|
|
if (( archived_after > archived_before )); then
|
|
printf '%s\n' "$archived_after"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
exit 1
|
|
POD
|
|
)
|
|
(( archived_after > archived_before ))
|
|
[[ $(k3s kubectl get cluster easyai-postgres -n easyai \
|
|
-o jsonpath='{.status.conditions[?(@.type=="ContinuousArchiving")].status}') == True ]]
|
|
latest_completed_backup=$(k3s kubectl get backups -n easyai -o json |
|
|
jq -r '[.items[] | select(
|
|
.spec.cluster.name == "easyai-postgres" and .status.phase == "completed"
|
|
) | .status.stoppedAt] | sort | last // empty')
|
|
[[ -n $latest_completed_backup ]]
|
|
latest_completed_epoch=$(date -d "$latest_completed_backup" '+%s')
|
|
(( $(date '+%s') - latest_completed_epoch < 172800 ))
|
|
[[ $(df --output=avail -B1 /var/lib/docker | tail -1) -gt 2147483648 ]]
|
|
[[ -f /etc/nginx/sites-available/ai.51easyai.com-maintenance ]]
|
|
[[ -f /etc/nginx/sites-available/ai.51easyai.com-cluster ]]
|
|
nginx -t
|
|
REMOTE
|
|
|
|
if [[ $action == preflight ]]; then
|
|
echo "production_migration_preflight=PASS release=$source_sha"
|
|
exit 0
|
|
fi
|
|
|
|
working_directory=$(mktemp -d "$cluster_root/.local-secrets/cluster/cutover.XXXXXX")
|
|
trap '[[ $working_directory == "$cluster_root"/.local-secrets/cluster/cutover.* ]] && rm -rf -- "$working_directory"' EXIT HUP INT TERM
|
|
cluster_scp "$script_dir/oss-object.mjs" "$CLUSTER_NINGBO_HOST:/root/easyai-oss-object.mjs"
|
|
|
|
remote_cutover_script="$working_directory/remote-cutover.sh"
|
|
umask 077
|
|
cp /dev/null "$remote_cutover_script"
|
|
chmod 0700 "$remote_cutover_script"
|
|
cat >"$remote_cutover_script" <<'REMOTE'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
source_sha=$1
|
|
api_image=$2
|
|
active_site=${AI_GATEWAY_NGINX_ACTIVE_SITE:-/etc/nginx/conf.d/easyai-ai-gateway-ai.51easyai.com.conf}
|
|
timestamp=$(date -u '+%Y%m%dT%H%M%SZ')
|
|
backup_directory=/var/backups/easyai-ai-gateway/pre-cutover-"$timestamp"
|
|
old_site_backup=$backup_directory/nginx-site.conf
|
|
cutover_started=$(date +%s)
|
|
traffic_committed=false
|
|
|
|
rollback_before_commit() {
|
|
status=$?
|
|
trap - ERR
|
|
if [[ $traffic_committed == false ]]; then
|
|
echo '[cutover] rolling back to the frozen Docker database' >&2
|
|
k3s kubectl scale deployment -n easyai \
|
|
easyai-api-ningbo easyai-api-hongkong easyai-web-ningbo easyai-web-hongkong \
|
|
--replicas=0 >/dev/null 2>&1 || true
|
|
docker start easyai-ai-gateway-api-1 easyai-ai-gateway-web-1 >/dev/null 2>&1 || true
|
|
if [[ -f $old_site_backup ]]; then
|
|
install -m 0644 "$old_site_backup" "$active_site"
|
|
nginx -t && systemctl reload nginx
|
|
fi
|
|
else
|
|
echo '[cutover] traffic is already committed; automatic database rollback is unsafe' >&2
|
|
fi
|
|
exit "$status"
|
|
}
|
|
trap rollback_before_commit ERR
|
|
|
|
install -d -m 0700 "$backup_directory"
|
|
[[ -e $active_site || -L $active_site ]] || {
|
|
echo "active NGINX site is unavailable: $active_site" >&2
|
|
exit 1
|
|
}
|
|
cp -L "$active_site" "$old_site_backup"
|
|
chmod 0600 "$old_site_backup"
|
|
install -m 0644 /etc/nginx/sites-available/ai.51easyai.com-maintenance "$active_site"
|
|
nginx -t
|
|
systemctl reload nginx
|
|
curl -ksS --resolve ai.51easyai.com:443:127.0.0.1 \
|
|
-o /dev/null -w '%{http_code}' https://ai.51easyai.com/api/v1/healthz |
|
|
grep -qx 503
|
|
|
|
docker stop --time 60 easyai-ai-gateway-api-1 >/dev/null
|
|
postgres_user=$(docker inspect easyai-ai-gateway-postgres-1 \
|
|
--format '{{range .Config.Env}}{{println .}}{{end}}' |
|
|
awk -F= '$1 == "POSTGRES_USER" { print substr($0, index($0, "=") + 1) }')
|
|
postgres_database=$(docker inspect easyai-ai-gateway-postgres-1 \
|
|
--format '{{range .Config.Env}}{{println .}}{{end}}' |
|
|
awk -F= '$1 == "POSTGRES_DB" { print substr($0, index($0, "=") + 1) }')
|
|
[[ $postgres_user =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
|
|
[[ $postgres_database =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
|
|
|
|
snapshot_sql=$backup_directory/acceptance-snapshot.sql
|
|
cat >"$snapshot_sql" <<'SQL'
|
|
\pset tuples_only on
|
|
\pset format unaligned
|
|
SELECT 'server_version=' || current_setting('server_version');
|
|
SELECT 'extensions=' || COALESCE(string_agg(extname || ':' || extversion, ',' ORDER BY extname), '')
|
|
FROM pg_extension WHERE extname <> 'plpgsql';
|
|
SELECT 'tables=' || count(*) FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind = 'r' AND n.nspname = 'public';
|
|
SELECT 'sequences=' || COALESCE(string_agg(schemaname || '.' || sequencename || ':' ||
|
|
COALESCE(last_value::text, 'null'), ',' ORDER BY schemaname, sequencename), '')
|
|
FROM pg_sequences WHERE schemaname = 'public';
|
|
SELECT 'catalog=' || md5(COALESCE(string_agg(definition, E'\n' ORDER BY definition), ''))
|
|
FROM (
|
|
SELECT 'column:' || table_name || ':' || column_name || ':' || data_type || ':' ||
|
|
is_nullable || ':' || COALESCE(column_default, '') AS definition
|
|
FROM information_schema.columns WHERE table_schema = 'public'
|
|
UNION ALL
|
|
SELECT 'constraint:' || conrelid::regclass::text || ':' || conname || ':' ||
|
|
pg_get_constraintdef(oid) FROM pg_constraint WHERE connamespace = 'public'::regnamespace
|
|
UNION ALL
|
|
SELECT 'index:' || indexrelid::regclass::text || ':' || pg_get_indexdef(indexrelid)
|
|
FROM pg_index WHERE indrelid IN (
|
|
SELECT oid FROM pg_class WHERE relnamespace = 'public'::regnamespace
|
|
)
|
|
) definitions;
|
|
SELECT 'gateway_tasks=' || count(*) FROM gateway_tasks;
|
|
SELECT 'gateway_task_attempts=' || count(*) FROM gateway_task_attempts;
|
|
SELECT 'gateway_wallet_transactions=' || count(*) FROM gateway_wallet_transactions;
|
|
SELECT 'gateway_upload_assets=' || count(*) FROM gateway_upload_assets;
|
|
SELECT 'gateway_users=' || count(*) FROM gateway_users;
|
|
SELECT 'gateway_api_keys=' || count(*) FROM gateway_api_keys;
|
|
SELECT 'gateway_providers=' || count(*) FROM gateway_providers;
|
|
SELECT 'task_sample=' || md5(COALESCE(string_agg(id::text, ',' ORDER BY id), ''))
|
|
FROM (SELECT id FROM gateway_tasks ORDER BY id LIMIT 100) sample;
|
|
SQL
|
|
|
|
docker exec -i easyai-ai-gateway-postgres-1 \
|
|
psql -X -v ON_ERROR_STOP=1 -U "$postgres_user" -d "$postgres_database" \
|
|
<"$snapshot_sql" >"$backup_directory/source.snapshot"
|
|
docker exec easyai-ai-gateway-postgres-1 \
|
|
pg_dumpall -U "$postgres_user" --globals-only \
|
|
>"$backup_directory/globals.sql"
|
|
docker exec easyai-ai-gateway-postgres-1 \
|
|
pg_dump -U "$postgres_user" -d "$postgres_database" \
|
|
--format=custom --compress=6 \
|
|
>"$backup_directory/database.dump"
|
|
[[ -s $backup_directory/database.dump && -s $backup_directory/globals.sql ]]
|
|
docker exec -i easyai-ai-gateway-postgres-1 pg_restore -l \
|
|
<"$backup_directory/database.dump" >/dev/null
|
|
sha256sum "$backup_directory/database.dump" "$backup_directory/globals.sql" \
|
|
"$backup_directory/source.snapshot" >"$backup_directory/SHA256SUMS"
|
|
sha256sum -c "$backup_directory/SHA256SUMS"
|
|
|
|
export ALI_KEY
|
|
export ALI_SECRET
|
|
ALI_KEY=$(k3s kubectl get secret easyai-oss-backup -n easyai \
|
|
-o jsonpath='{.data.ACCESS_KEY_ID}' | base64 -d)
|
|
ALI_SECRET=$(k3s kubectl get secret easyai-oss-backup -n easyai \
|
|
-o jsonpath='{.data.ACCESS_SECRET_KEY}' | base64 -d)
|
|
export ALI_REGION=oss-cn-shanghai
|
|
export ALI_BUCKET=wangbo0808
|
|
object_prefix=easyai-ai-gateway/production/pre-cutover/"$timestamp"
|
|
for file in database.dump globals.sql source.snapshot SHA256SUMS; do
|
|
node /root/easyai-oss-object.mjs put "$object_prefix/$file" "$backup_directory/$file"
|
|
done
|
|
node /root/easyai-oss-object.mjs head "$object_prefix/database.dump"
|
|
|
|
primary=$(k3s kubectl get cluster easyai-postgres -n easyai \
|
|
-o jsonpath='{.status.currentPrimary}')
|
|
[[ $primary =~ ^easyai-postgres-[0-9]+$ ]]
|
|
k3s kubectl exec -n easyai "$primary" -c postgres -- \
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d postgres \
|
|
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'easyai_ai_gateway' AND pid <> pg_backend_pid();" \
|
|
-c 'DROP DATABASE IF EXISTS easyai_ai_gateway;' \
|
|
-c 'CREATE DATABASE easyai_ai_gateway OWNER easyai;'
|
|
k3s kubectl exec -i -n easyai "$primary" -c postgres -- \
|
|
pg_restore --exit-on-error --no-owner --role=easyai \
|
|
-U postgres -d easyai_ai_gateway <"$backup_directory/database.dump"
|
|
k3s kubectl exec -i -n easyai "$primary" -c postgres -- \
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway \
|
|
<"$snapshot_sql" >"$backup_directory/restored.snapshot"
|
|
diff -u "$backup_directory/source.snapshot" "$backup_directory/restored.snapshot"
|
|
|
|
cat >"$backup_directory/migrator-job.yaml" <<EOF
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: easyai-migrator-${source_sha:0:12}
|
|
namespace: easyai
|
|
labels:
|
|
app.kubernetes.io/part-of: easyai-ai-gateway
|
|
easyai.io/release: ${source_sha}
|
|
spec:
|
|
backoffLimit: 0
|
|
ttlSecondsAfterFinished: 86400
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: easyai-migrator
|
|
spec:
|
|
restartPolicy: Never
|
|
imagePullSecrets:
|
|
- name: easyai-registry
|
|
nodeSelector:
|
|
easyai.io/site: ningbo
|
|
containers:
|
|
- name: migrator
|
|
image: ${api_image}
|
|
command: ["/app/easyai-ai-gateway-migrate"]
|
|
envFrom:
|
|
- secretRef:
|
|
name: easyai-ai-gateway-runtime
|
|
EOF
|
|
k3s kubectl delete job "easyai-migrator-${source_sha:0:12}" -n easyai \
|
|
--ignore-not-found=true --wait=true >/dev/null
|
|
k3s kubectl apply -f "$backup_directory/migrator-job.yaml" >/dev/null
|
|
k3s kubectl wait --for=condition=complete \
|
|
"job/easyai-migrator-${source_sha:0:12}" -n easyai --timeout=300s
|
|
k3s kubectl exec -n easyai "$primary" -c postgres -- \
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -At -c \
|
|
"SELECT count(*) FROM schema_migrations WHERE version='0089_file_storage_request_asset_scene';" |
|
|
grep -qx 1
|
|
k3s kubectl exec -n easyai "$primary" -c postgres -- \
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -At -c \
|
|
"SELECT count(*) FROM file_storage_channels WHERE deleted_at IS NULL AND provider='server_main_openapi' AND config->'scenes' ? 'request_asset';" |
|
|
grep -Eq '^[1-9][0-9]*$'
|
|
|
|
elapsed=$(( $(date +%s) - cutover_started ))
|
|
if (( elapsed > 600 )); then
|
|
echo "cutover exceeded ten-minute rollback boundary: ${elapsed}s" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$backup_directory" >/root/.easyai-pre-cutover-backup
|
|
printf 'database_restore=PASS seconds=%s backup=%s\n' "$elapsed" "$backup_directory"
|
|
REMOTE
|
|
|
|
cluster_scp "$remote_cutover_script" "$CLUSTER_NINGBO_HOST:/root/easyai-remote-cutover.sh"
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
"chmod 0700 /root/easyai-remote-cutover.sh && /root/easyai-remote-cutover.sh '$source_sha' '$api_image'"
|
|
|
|
traffic_committed=false
|
|
rollback_local_before_commit() {
|
|
status=$?
|
|
trap - ERR
|
|
if [[ $traffic_committed == false ]]; then
|
|
echo '[cutover] local activation failed; restoring Docker before public traffic commit' >&2
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" 'bash -s' <<'REMOTE' || true
|
|
set -euo pipefail
|
|
backup_directory=$(cat /root/.easyai-pre-cutover-backup)
|
|
[[ $backup_directory == /var/backups/easyai-ai-gateway/pre-cutover-* ]]
|
|
k3s kubectl scale deployment -n easyai \
|
|
easyai-api-ningbo easyai-api-hongkong easyai-web-ningbo easyai-web-hongkong \
|
|
--replicas=0 >/dev/null 2>&1 || true
|
|
docker start easyai-ai-gateway-api-1 easyai-ai-gateway-web-1 >/dev/null
|
|
install -m 0644 "$backup_directory/nginx-site.conf" \
|
|
/etc/nginx/conf.d/easyai-ai-gateway-ai.51easyai.com.conf
|
|
nginx -t
|
|
systemctl reload nginx
|
|
REMOTE
|
|
fi
|
|
exit "$status"
|
|
}
|
|
trap rollback_local_before_commit ERR
|
|
|
|
echo '[cutover] starting both application sites with River workers frozen'
|
|
AI_GATEWAY_ACTIVATE_WORKERS=false \
|
|
"$script_dir/bootstrap-platform.sh" activate "$release_manifest"
|
|
|
|
for site_ip in 10.77.0.1 10.77.0.2; do
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
"curl -fsS --max-time 10 http://$site_ip:30088/api/v1/healthz >/dev/null"
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
"curl -fsS --max-time 10 http://$site_ip:30088/api/v1/readyz | grep -q '\"ok\":true'"
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
"curl -fsS --max-time 10 http://$site_ip:30178/ | grep -q 'EasyAI AI Gateway'"
|
|
done
|
|
|
|
"$script_dir/install-nginx-edges.sh" activate
|
|
for edge_host in "${CLUSTER_NINGBO_HOST#*@}" "${CLUSTER_HONGKONG_HOST#*@}"; do
|
|
curl -fsS --resolve "ai.51easyai.com:443:$edge_host" \
|
|
https://ai.51easyai.com/api/v1/healthz >/dev/null
|
|
curl -fsS --resolve "ai.51easyai.com:443:$edge_host" \
|
|
https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true'
|
|
done
|
|
|
|
traffic_committed=true
|
|
echo '[cutover] traffic committed; enabling Hong Kong worker, then Ningbo worker'
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" 'bash -s' <<'REMOTE'
|
|
set -euo pipefail
|
|
kubectl='k3s kubectl'
|
|
$kubectl set env deployment/easyai-api-hongkong -n easyai \
|
|
AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED=true
|
|
$kubectl rollout status deployment/easyai-api-hongkong -n easyai --timeout=300s
|
|
$kubectl set env deployment/easyai-api-ningbo -n easyai \
|
|
AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED=true
|
|
$kubectl rollout status deployment/easyai-api-ningbo -n easyai --timeout=300s
|
|
backup_resource=$(cat <<'EOF' | $kubectl create -f - -o name
|
|
apiVersion: postgresql.cnpg.io/v1
|
|
kind: Backup
|
|
metadata:
|
|
generateName: easyai-post-cutover-
|
|
namespace: easyai
|
|
spec:
|
|
cluster:
|
|
name: easyai-postgres
|
|
method: plugin
|
|
pluginConfiguration:
|
|
name: barman-cloud.cloudnative-pg.io
|
|
EOF
|
|
)
|
|
backup_name=${backup_resource#*/}
|
|
backup_phase=
|
|
for _ in $(seq 1 240); do
|
|
backup_phase=$($kubectl get backup "$backup_name" -n easyai \
|
|
-o jsonpath='{.status.phase}')
|
|
[[ $backup_phase == completed ]] && break
|
|
if [[ $backup_phase == failed ]]; then
|
|
echo "post-cutover CNPG backup failed: $backup_name" >&2
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
[[ $backup_phase == completed ]] || {
|
|
echo "post-cutover CNPG backup did not complete in time: $backup_name" >&2
|
|
exit 1
|
|
}
|
|
docker update --restart=no easyai-ai-gateway-api-1 \
|
|
easyai-ai-gateway-web-1 easyai-ai-gateway-postgres-1 >/dev/null
|
|
docker stop --time 30 easyai-ai-gateway-web-1 easyai-ai-gateway-postgres-1 >/dev/null
|
|
rm -f -- /root/easyai-remote-cutover.sh /root/easyai-oss-object.mjs
|
|
echo "post_cutover_backup=PASS name=$backup_name"
|
|
REMOTE
|
|
|
|
"$script_dir/install-release-helper.sh"
|
|
remote_manifest=/tmp/easyai-cutover-release-"$source_sha".json
|
|
cluster_scp "$release_manifest" "$CLUSTER_NINGBO_HOST:$remote_manifest"
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
"/usr/local/sbin/easyai-ai-gateway-cluster-release adopt '$remote_manifest' && rm -f '$remote_manifest'"
|
|
|
|
set_local_environment() {
|
|
local key=$1
|
|
local value=$2
|
|
local temporary
|
|
temporary=$(mktemp "$cluster_env_file.next.XXXXXX")
|
|
chmod 0600 "$temporary"
|
|
awk -v key="$key" -v value="$value" -F= '
|
|
$1 == key { if (!written) print key "=" value; written = 1; next }
|
|
{ print }
|
|
END { if (!written) print key "=" value }
|
|
' "$cluster_env_file" >"$temporary"
|
|
mv "$temporary" "$cluster_env_file"
|
|
}
|
|
set_local_environment AI_GATEWAY_DEPLOY_MODE kubernetes
|
|
set_local_environment AI_GATEWAY_REMOTE_RELEASE_HELPER /usr/local/sbin/easyai-ai-gateway-cluster-release
|
|
set_local_environment AI_GATEWAY_PRODUCTION_SSH_KEY "$cluster_ssh_key"
|
|
|
|
trap - ERR
|
|
echo "production_cutover=PASS release=$source_sha old_postgres_retained=true"
|