ops: verify every release asset
This commit is contained in:
66
vehicle-data-platform/deploy/verify-web-release.sh
Executable file
66
vehicle-data-platform/deploy/verify-web-release.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
WEB_ROOT=${1:-/opt/lingniu-vehicle-platform/current/web}
|
||||
BASE_URL=${2:-http://127.0.0.1:20300}
|
||||
PREVIOUS_MANIFEST=${3:-}
|
||||
CURL_TIMEOUT_SEC=${CURL_TIMEOUT_SEC:-15}
|
||||
|
||||
BASE_URL=${BASE_URL%/}
|
||||
CURRENT_MANIFEST="$WEB_ROOT/.release-assets"
|
||||
response_file=$(mktemp)
|
||||
trap 'rm -f "$response_file"' EXIT
|
||||
|
||||
fail() {
|
||||
printf 'web release smoke failed: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fetch_exact() {
|
||||
local url_path=$1
|
||||
local expected_file=$2
|
||||
local label=$3
|
||||
local status
|
||||
|
||||
test -f "$expected_file" || fail "$label is missing on disk: $expected_file"
|
||||
status=$(curl --silent --show-error --max-time "$CURL_TIMEOUT_SEC" --output "$response_file" --write-out '%{http_code}' "$BASE_URL$url_path" || true)
|
||||
test "$status" = 200 || fail "$label returned HTTP ${status:-000}: $url_path"
|
||||
cmp -s "$expected_file" "$response_file" || fail "$label response differs from release file: $url_path"
|
||||
}
|
||||
|
||||
verify_manifest() {
|
||||
local manifest=$1
|
||||
local kind=$2
|
||||
local count=0
|
||||
local asset
|
||||
|
||||
test -f "$manifest" || fail "$kind manifest is missing: $manifest"
|
||||
while IFS= read -r asset || test -n "$asset"; do
|
||||
asset=${asset%$'\r'}
|
||||
test -n "$asset" || continue
|
||||
case "$asset" in
|
||||
/*|*../*|../*|*/..|..)
|
||||
fail "$kind manifest contains unsafe asset path: $asset"
|
||||
;;
|
||||
esac
|
||||
fetch_exact "/assets/$asset" "$WEB_ROOT/assets/$asset" "$kind asset"
|
||||
count=$((count + 1))
|
||||
done < "$manifest"
|
||||
test "$count" -gt 0 || fail "$kind manifest is empty: $manifest"
|
||||
printf '%s' "$count"
|
||||
}
|
||||
|
||||
fetch_exact / "$WEB_ROOT/index.html" 'root document'
|
||||
|
||||
config_status=$(curl --silent --show-error --max-time "$CURL_TIMEOUT_SEC" --output "$response_file" --write-out '%{http_code}' "$BASE_URL/app-config.js" || true)
|
||||
test "$config_status" = 200 || fail "app config returned HTTP ${config_status:-000}"
|
||||
grep -q 'window\.__LINGNIU_APP_CONFIG__' "$response_file" || fail 'app config response does not expose the runtime configuration object'
|
||||
|
||||
current_count=$(verify_manifest "$CURRENT_MANIFEST" current)
|
||||
compatibility_count=0
|
||||
if test -n "$PREVIOUS_MANIFEST"; then
|
||||
compatibility_count=$(verify_manifest "$PREVIOUS_MANIFEST" compatibility)
|
||||
fi
|
||||
|
||||
printf 'web_release_smoke=ok current_assets=%s compatibility_assets=%s\n' "$current_count" "$compatibility_count"
|
||||
60
vehicle-data-platform/deploy/verify-web-release.test.sh
Executable file
60
vehicle-data-platform/deploy/verify-web-release.test.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
fixture=$(mktemp -d)
|
||||
server_pid=
|
||||
cleanup() {
|
||||
if test -n "$server_pid"; then
|
||||
kill "$server_pid" 2>/dev/null || true
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$fixture"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$fixture/web/assets"
|
||||
printf '<!doctype html><div id="root"></div><script src="/assets/new.js"></script>\n' > "$fixture/web/index.html"
|
||||
printf 'window.__LINGNIU_APP_CONFIG__ = {};\n' > "$fixture/web/app-config.js"
|
||||
printf 'console.log("new");\n' > "$fixture/web/assets/new.js"
|
||||
printf 'console.log("old");\n' > "$fixture/web/assets/old.js"
|
||||
printf 'new.js\n' > "$fixture/web/.release-assets"
|
||||
printf 'old.js\n' > "$fixture/previous-assets"
|
||||
cp -R "$fixture/web" "$fixture/served"
|
||||
|
||||
port=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')
|
||||
python3 -m http.server "$port" --bind 127.0.0.1 --directory "$fixture/served" > "$fixture/server.log" 2>&1 &
|
||||
server_pid=$!
|
||||
for _ in $(seq 1 30); do
|
||||
if curl --silent --fail "http://127.0.0.1:$port/" >/dev/null; then break; fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
success_output=$("$SCRIPT_DIR/verify-web-release.sh" "$fixture/web" "http://127.0.0.1:$port" "$fixture/previous-assets")
|
||||
test "$success_output" = 'web_release_smoke=ok current_assets=1 compatibility_assets=1'
|
||||
|
||||
rm "$fixture/web/assets/old.js"
|
||||
if "$SCRIPT_DIR/verify-web-release.sh" "$fixture/web" "http://127.0.0.1:$port" "$fixture/previous-assets" > "$fixture/missing.out" 2>&1; then
|
||||
printf 'expected a missing compatibility asset to fail\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'compatibility asset is missing on disk' "$fixture/missing.out"
|
||||
|
||||
printf 'console.log("old");\n' > "$fixture/web/assets/old.js"
|
||||
printf '<!doctype html><div id="root"></div>\n' > "$fixture/served/assets/old.js"
|
||||
if "$SCRIPT_DIR/verify-web-release.sh" "$fixture/web" "http://127.0.0.1:$port" "$fixture/previous-assets" > "$fixture/mismatch.out" 2>&1; then
|
||||
printf 'expected a fallback response to fail byte comparison\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'response differs from release file' "$fixture/mismatch.out"
|
||||
|
||||
printf 'console.log("old");\n' > "$fixture/served/assets/old.js"
|
||||
printf '../secret.js\n' > "$fixture/previous-assets"
|
||||
if "$SCRIPT_DIR/verify-web-release.sh" "$fixture/web" "http://127.0.0.1:$port" "$fixture/previous-assets" > "$fixture/unsafe.out" 2>&1; then
|
||||
printf 'expected an unsafe manifest path to fail\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'unsafe asset path' "$fixture/unsafe.out"
|
||||
|
||||
printf 'verify-web-release tests passed\n'
|
||||
@@ -54,7 +54,17 @@ else
|
||||
fi
|
||||
```
|
||||
|
||||
Never overwrite a new hashed file. The `.release-assets` manifest prevents compatibility files from accumulating recursively: the next release copies only the immediately previous build's original assets. The React route boundary also detects a failed dynamic import, reloads once with a short cooldown and keeps the application shell usable if recovery still fails. Verify one old route asset and the new main asset both return HTTP 200 after the switch.
|
||||
Never overwrite a new hashed file. The `.release-assets` manifest prevents compatibility files from accumulating recursively: the next release copies only the immediately previous build's original assets. The React route boundary also detects a failed dynamic import, reloads once with a short cooldown and keeps the application shell usable if recovery still fails.
|
||||
|
||||
After the switch, verify the root document, runtime configuration, every current asset and every compatibility asset from the immediately previous release. The smoke downloads each asset and compares it byte-for-byte with the release file, so a SPA fallback returning `index.html` with HTTP 200 cannot hide a missing chunk:
|
||||
|
||||
```bash
|
||||
PREVIOUS_WEB=/opt/lingniu-vehicle-platform/releases/$PREVIOUS_RELEASE/web
|
||||
/opt/lingniu-vehicle-platform/current/deploy/verify-web-release.sh \
|
||||
/opt/lingniu-vehicle-platform/current/web \
|
||||
http://127.0.0.1:20300 \
|
||||
"$PREVIOUS_WEB/.release-assets"
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
@@ -210,7 +220,7 @@ curl -fsS "http://127.0.0.1:20300$MAIN_ASSET" >/dev/null
|
||||
unset PLATFORM_TOKEN AUTH_HEADER
|
||||
```
|
||||
|
||||
The release gate must include both the root document and its hashed main asset. API-only smoke checks are insufficient because an incomplete archive can leave the service healthy while the browser returns 404. Before upload, verify the archive contains `web/index.html`, `platform-api`, both evaluator binaries, `platform-migrate`, `oneos-scope-sync`, every numbered migration and all platform systemd units.
|
||||
The release gate must include the root document, runtime configuration, every asset in the current `.release-assets` manifest and every asset in the immediately previous release manifest. API-only smoke checks are insufficient because an incomplete archive can leave the service healthy while the browser returns 404 or an HTML fallback for a missing chunk. Before upload, verify the archive contains `web/index.html`, `platform-api`, both evaluator binaries, `platform-migrate`, `oneos-scope-sync`, every numbered migration, the web-release verifier and all platform systemd units.
|
||||
|
||||
When `alertStream.lastInvalidCode` reports a recent `missing_vin_jt808`, query `/api/v2/access/unresolved-identities` as a viewer and hand the masked evidence to the identity owner. The response must not contain raw `phone` or unmasked identifier fields. Confirm the evidence against the GPS provider or vehicle owner, then maintain the authoritative `vehicle_identity_binding`; never derive VIN from the terminal number. The queue excludes a terminal after a valid binding exists. Retain the invalid counter as audit evidence and verify the recent warning clears after five minutes without another invalid frame.
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ The navigation and progressive-loading direction follows mature observability sy
|
||||
2. Bound or explicitly evict other high-cardinality query families, especially arbitrary history windows and track playback ranges.
|
||||
3. Retire or migrate the remaining legacy `App.tsx` integration suite after its compatibility coverage is replaced in V2.
|
||||
4. Profile repeated large exports after the Web Worker migration and define an explicit maximum supported row/column envelope for browser-side workbooks.
|
||||
5. Add an automated release smoke that reads the previous `.release-assets` manifest and asserts every listed compatibility asset returns HTTP 200.
|
||||
5. Run the automated byte-for-byte current/compatibility asset smoke in every future ECS release and promote it into CI/CD when the deployment pipeline is formalized.
|
||||
|
||||
## 2026-07-15: query memory and vehicle-count semantics
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"web:build": "pnpm --dir apps/web run build",
|
||||
"web:test": "pnpm --dir apps/web run test",
|
||||
"api:test": "cd apps/api && go test ./...",
|
||||
"release:web-smoke:test": "bash deploy/verify-web-release.test.sh",
|
||||
"build": "pnpm run web:build && cd apps/api && go build -o ../../dist/platform-api ./cmd/platform-api && go build -o ../../dist/oneos-scope-sync ./cmd/oneos-scope-sync"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user