#!/usr/bin/env bash set -euo pipefail RELEASE_ID=${1:?release id is required} WEB_ARCHIVE=${2:?open portal web archive is required} API_BINARY=${3:?open platform API binary is required} ROOT=${OPEN_PLATFORM_ROOT:-/opt/lingniu-vehicle-open-platform} BASE_URL=${OPEN_PLATFORM_BASE_URL:-http://127.0.0.1:20310} SERVICE=${OPEN_PLATFORM_SERVICE:-lingniu-vehicle-open-platform} SYSTEMCTL_BIN=${SYSTEMCTL_BIN:-systemctl} CURL_BIN=${CURL_BIN:-curl} RELEASE_HISTORY_LIMIT=${RELEASE_HISTORY_LIMIT:-12} case "$RELEASE_ID" in ''|*[!A-Za-z0-9._-]*) printf 'invalid release id: %s\n' "$RELEASE_ID" >&2; exit 1 ;; esac test -f "$WEB_ARCHIVE" || { printf 'open portal archive is missing\n' >&2; exit 1; } test -f "$API_BINARY" || { printf 'open platform API binary is missing\n' >&2; exit 1; } old= if test -L "$ROOT/current"; then old=$(readlink -f "$ROOT/current") fi next="$ROOT/releases/$RELEASE_ID" env_file="$ROOT/env/open-platform.env" switched=false set_release_env() { python3 - "$env_file" "$1" <<'PY' import pathlib import sys path = pathlib.Path(sys.argv[1]) release = sys.argv[2] lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else [] updated = [] found = False for line in lines: if line.startswith("OPEN_PLATFORM_RELEASE="): updated.append("OPEN_PLATFORM_RELEASE=" + release) found = True else: updated.append(line) if not found: updated.append("OPEN_PLATFORM_RELEASE=" + release) path.write_text("\n".join(updated) + "\n", encoding="utf-8") PY chmod 0600 "$env_file" } rollback() { status=$? trap - ERR set +e if test "$switched" = true && test -n "$old"; then ln -s "$old" "$ROOT/current.rollback" python3 -c 'import os,sys; os.replace(sys.argv[1],sys.argv[2])' "$ROOT/current.rollback" "$ROOT/current" set_release_env "$(basename "$old")" "$SYSTEMCTL_BIN" restart "$SERVICE" elif test "$switched" = true; then "$SYSTEMCTL_BIN" stop "$SERVICE" rm -f "$ROOT/current" set_release_env "" fi printf 'open platform release failed; current restored to %s\n' "${old:-none}" >&2 exit "$status" } trap rollback ERR test ! -e "$next" || { printf 'release already exists: %s\n' "$next" >&2; exit 1; } mkdir -p "$next/web" "$ROOT/env" cp "$API_BINARY" "$next/open-platform-api" chmod 0755 "$next/open-platform-api" while IFS= read -r member; do case "$member" in /*|../*|*/../*|*/..) printf 'web archive contains unsafe path: %s\n' "$member" >&2; exit 1 ;; esac done < <(tar -tzf "$WEB_ARCHIVE") tar --no-same-owner -xzf "$WEB_ARCHIVE" -C "$next/web" test -f "$next/web/index.html" || { printf 'open portal index is missing\n' >&2; exit 1; } ln -s "$next" "$ROOT/current.next" python3 -c 'import os,sys; os.replace(sys.argv[1],sys.argv[2])' "$ROOT/current.next" "$ROOT/current" switched=true set_release_env "$RELEASE_ID" "$SYSTEMCTL_BIN" restart "$SERVICE" ready=false for _ in $(seq 1 20); do if "$SYSTEMCTL_BIN" is-active --quiet "$SERVICE" && "$CURL_BIN" -fsS "$BASE_URL/healthz" >/dev/null; then ready=true break fi sleep 1 done test "$ready" = true || { printf 'open platform service did not become ready\n' >&2; exit 1; } "$CURL_BIN" -fsS "$BASE_URL/" | grep -q '
' "$CURL_BIN" -fsS "$BASE_URL/open-api/openapi.yaml" | grep -q '^openapi: 3.0.3' "$CURL_BIN" -fsS "$BASE_URL/portal-api/catalog" | grep -q 'daily_hydrogen' python3 - "$ROOT" "$RELEASE_HISTORY_LIMIT" "$next" "$old" <<'PY' import pathlib import shutil import sys root = pathlib.Path(sys.argv[1]) limit = int(sys.argv[2]) protected = {pathlib.Path(value).resolve() for value in sys.argv[3:] if value} releases = sorted( (p for p in (root / "releases").iterdir() if p.is_dir()), key=lambda p: p.stat().st_mtime, reverse=True, ) for path in releases[limit:]: if path.resolve() not in protected: shutil.rmtree(path) PY switched=false trap - ERR printf 'open_platform_release_install=ok release=%s previous=%s\n' "$RELEASE_ID" "${old##*/}"