fix: 调试发布run
This commit is contained in:
+117
-5
@@ -26,7 +26,8 @@ export PLATFORM_BOOTSTRAP_ADMIN_PASSWORD="${PLATFORM_BOOTSTRAP_ADMIN_PASSWORD:-o
|
||||
export PLATFORM_SECRET_ENVELOPE_KEY="${PLATFORM_SECRET_ENVELOPE_KEY:-local-debug-secret-envelope-key-change-me}"
|
||||
export PLATFORM_AI_PROVIDER_MODE="${PLATFORM_AI_PROVIDER_MODE:-mock}"
|
||||
|
||||
export RUN_REPO_DIR="${RUN_REPO_DIR:-$(cd "$LOCAL_DEBUG_ROOT_DIR/.." && pwd)/run}"
|
||||
export RUN_SOURCE_DIR="${RUN_SOURCE_DIR:-${RUN_REPO_DIR:-$LOCAL_DEBUG_ROOT_DIR/run}}"
|
||||
export RUN_REPO_DIR="$RUN_SOURCE_DIR"
|
||||
export RUN_MODE="${RUN_MODE:-worker}"
|
||||
export RUN_PLATFORM_URL="${RUN_PLATFORM_URL:-http://127.0.0.1:$LOCAL_DEBUG_PLATFORM_PORT}"
|
||||
export RUN_ENDPOINT_ID="${RUN_ENDPOINT_ID:-run-local-debug}"
|
||||
@@ -34,6 +35,9 @@ export RUN_DISPLAY_NAME="${RUN_DISPLAY_NAME:-Local Debug Run}"
|
||||
export RUN_VERSION="${RUN_VERSION:-0.1.0-local-debug}"
|
||||
export RUN_REGISTRATION_TOKEN="${RUN_REGISTRATION_TOKEN:-local-debug-registration}"
|
||||
export RUN_WORKSPACE_ROOT="${RUN_WORKSPACE_ROOT:-$LOCAL_DEBUG_ROOT/run/workspace}"
|
||||
export RUN_BUILD_BUCKET_ROOT="${RUN_BUILD_BUCKET_ROOT:-$LOCAL_DEBUG_ROOT/run/build-buckets}"
|
||||
export RUN_BUILD_SOURCE_ROOT="${RUN_BUILD_SOURCE_ROOT:-$RUN_BUILD_BUCKET_ROOT/source/current}"
|
||||
export RUN_BOOTSTRAP_BIN="${RUN_BOOTSTRAP_BIN:-$RUN_BUILD_BUCKET_ROOT/bootstrap/bin/run}"
|
||||
export RUN_SPOOL_ROOT="${RUN_SPOOL_ROOT:-$LOCAL_DEBUG_ROOT/run/spool}"
|
||||
export RUN_MAX_JOBS="${RUN_MAX_JOBS:-1}"
|
||||
export RUN_HEARTBEAT_INTERVAL_MS="${RUN_HEARTBEAT_INTERVAL_MS:-2000}"
|
||||
@@ -52,12 +56,120 @@ local_debug_web_url() {
|
||||
printf 'http://127.0.0.1:%s' "$LOCAL_DEBUG_WEB_PORT"
|
||||
}
|
||||
|
||||
ensure_local_run_repo() {
|
||||
if [[ ! -f "$RUN_REPO_DIR/go.mod" ]]; then
|
||||
printf 'run repo not found at %s\n' "$RUN_REPO_DIR" >&2
|
||||
printf 'clone git@git.npc0.com:admin343/run.git beside this repo or set RUN_REPO_DIR to the independent run checkout\n' >&2
|
||||
local_debug_plugin_source_dir() {
|
||||
case "$1" in
|
||||
game.example)
|
||||
printf '%s/plugins/examples/dev-game-plugin' "$LOCAL_DEBUG_ROOT_DIR"
|
||||
;;
|
||||
game.scum)
|
||||
printf '%s/plugins/examples/scum-server-plugin' "$LOCAL_DEBUG_ROOT_DIR"
|
||||
;;
|
||||
*)
|
||||
printf 'unknown local-debug plugin id: %s\n' "$1" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
local_debug_seed_lifecycle_scope() {
|
||||
local source_dir="$1"
|
||||
local scope="$2"
|
||||
local true_binary="/usr/bin/true"
|
||||
if [[ ! -x "$true_binary" ]]; then
|
||||
printf 'required local true executable not found at %s\n' "$true_binary" >&2
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "$scope/actions" "$scope/bin"
|
||||
local action
|
||||
for action in install start stop restart status; do
|
||||
if [[ -f "$source_dir/actions/$action.json" ]]; then
|
||||
cp "$source_dir/actions/$action.json" "$scope/actions/$action.json"
|
||||
fi
|
||||
done
|
||||
cp "$true_binary" "$scope/bin/install-server"
|
||||
cp "$true_binary" "$scope/bin/game-server"
|
||||
chmod +x "$scope/bin/install-server" "$scope/bin/game-server"
|
||||
}
|
||||
|
||||
local_debug_seed_plugin_lifecycle_template() {
|
||||
local plugin_id="$1"
|
||||
local profile_key="${2:-run-local}"
|
||||
local source_dir
|
||||
source_dir="$(local_debug_plugin_source_dir "$plugin_id")"
|
||||
local_debug_seed_lifecycle_scope "$source_dir" "$RUN_WORKSPACE_ROOT/plugins/$plugin_id/$profile_key"
|
||||
}
|
||||
|
||||
local_debug_seed_server_lifecycle_workspace() {
|
||||
local server_id="$1"
|
||||
local plugin_id="$2"
|
||||
local profile_key="${3:-run-local}"
|
||||
local source_dir
|
||||
source_dir="$(local_debug_plugin_source_dir "$plugin_id")"
|
||||
local_debug_seed_lifecycle_scope "$source_dir" "$RUN_WORKSPACE_ROOT/$server_id"
|
||||
local_debug_seed_lifecycle_scope "$source_dir" "$RUN_WORKSPACE_ROOT/instances/$server_id/$profile_key"
|
||||
}
|
||||
|
||||
local_debug_prepare_run_lifecycle_templates() {
|
||||
mkdir -p "$RUN_WORKSPACE_ROOT"
|
||||
local_debug_seed_plugin_lifecycle_template game.example run-local
|
||||
local_debug_seed_plugin_lifecycle_template game.scum run-local
|
||||
}
|
||||
|
||||
local_debug_require_bucket_path() {
|
||||
local target="$1"
|
||||
local parent
|
||||
parent="$(dirname "$target")"
|
||||
mkdir -p "$parent"
|
||||
local resolved_parent
|
||||
resolved_parent="$(cd "$parent" && pwd -P)"
|
||||
local resolved="$resolved_parent/$(basename "$target")"
|
||||
case "$resolved" in
|
||||
"$LOCAL_DEBUG_ROOT"/* | /private/tmp/browser-local-debug-*/* | /tmp/browser-local-debug-*/*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
printf 'refusing to clear non-local-debug build bucket path: %s\n' "$target" >&2
|
||||
printf 'set RUN_BUILD_BUCKET_ROOT/RUN_BUILD_SOURCE_ROOT under %s or an approved browser-local-debug temp root\n' "$LOCAL_DEBUG_ROOT" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_local_run_repo() {
|
||||
if [[ ! -f "$RUN_SOURCE_DIR/go.mod" ]]; then
|
||||
printf 'run source repo not found at %s\n' "$RUN_SOURCE_DIR" >&2
|
||||
printf 'clone git@git.npc0.com:admin343/run.git into %s/run or set RUN_SOURCE_DIR/RUN_REPO_DIR to another independent run checkout\n' "$LOCAL_DEBUG_ROOT_DIR" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
local_debug_prepare_run_build_source() {
|
||||
ensure_local_run_repo
|
||||
local_debug_require_bucket_path "$RUN_BUILD_SOURCE_ROOT"
|
||||
rm -rf "$RUN_BUILD_SOURCE_ROOT"
|
||||
mkdir -p "$RUN_BUILD_SOURCE_ROOT"
|
||||
rsync -a --delete \
|
||||
--exclude '.git/' \
|
||||
--exclude '.local-debug/' \
|
||||
--exclude '.run-workspace/' \
|
||||
--exclude '.tmp/' \
|
||||
--exclude 'node_modules/' \
|
||||
"$RUN_SOURCE_DIR"/ "$RUN_BUILD_SOURCE_ROOT"/
|
||||
cat >"$RUN_BUILD_SOURCE_ROOT/.local-debug-source.json" <<EOF
|
||||
{
|
||||
"sourceDir": "$RUN_SOURCE_DIR",
|
||||
"buildSourceRoot": "$RUN_BUILD_SOURCE_ROOT",
|
||||
"createdBy": "scripts/local-debug-env.sh"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
local_debug_build_bootstrap_run() {
|
||||
local_debug_prepare_run_build_source
|
||||
mkdir -p "$(dirname "$RUN_BOOTSTRAP_BIN")"
|
||||
(
|
||||
cd "$RUN_BUILD_SOURCE_ROOT"
|
||||
env GOCACHE="$GOCACHE" go build -trimpath -o "$RUN_BOOTSTRAP_BIN" ./cmd/run
|
||||
)
|
||||
chmod +x "$RUN_BOOTSTRAP_BIN"
|
||||
}
|
||||
|
||||
local_debug_forbidden_pattern() {
|
||||
|
||||
+207
-47
@@ -10,28 +10,23 @@ API_URL="$PLATFORM_URL/api/v1"
|
||||
WORK_DIR="$LOCAL_DEBUG_ROOT/smoke"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
cat >"$WORK_DIR/run-build-config.env" <<EOF
|
||||
RUN_SOURCE_DIR=$RUN_SOURCE_DIR
|
||||
RUN_REPO_DIR=$RUN_REPO_DIR
|
||||
RUN_BUILD_BUCKET_ROOT=$RUN_BUILD_BUCKET_ROOT
|
||||
RUN_BUILD_SOURCE_ROOT=$RUN_BUILD_SOURCE_ROOT
|
||||
RUN_BOOTSTRAP_BIN=$RUN_BOOTSTRAP_BIN
|
||||
RUN_WORKSPACE_ROOT=$RUN_WORKSPACE_ROOT
|
||||
RUN_SPOOL_ROOT=$RUN_SPOOL_ROOT
|
||||
RUN_MAX_JOBS=$RUN_MAX_JOBS
|
||||
EOF
|
||||
|
||||
prepare_run_workspace() {
|
||||
local workspace_root="$RUN_WORKSPACE_ROOT"
|
||||
local true_binary="/usr/bin/true"
|
||||
if [[ ! -x "$true_binary" ]]; then
|
||||
printf 'required local true executable not found at %s\n' "$true_binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
for server in server-local-debug scum-alpha scum-beta; do
|
||||
local source_dir="$ROOT_DIR/plugins/examples/dev-game-plugin"
|
||||
if [[ "$server" != "server-local-debug" ]]; then
|
||||
source_dir="$ROOT_DIR/plugins/examples/scum-server-plugin"
|
||||
fi
|
||||
for scope in "$workspace_root/$server" "$workspace_root/instances/$server/run-local"; do
|
||||
mkdir -p "$scope/actions" "$scope/bin"
|
||||
cp "$source_dir/actions/install.json" "$scope/actions/install.json"
|
||||
cp "$source_dir/actions/start.json" "$scope/actions/start.json"
|
||||
cp "$source_dir/actions/stop.json" "$scope/actions/stop.json"
|
||||
cp "$true_binary" "$scope/bin/install-server"
|
||||
cp "$true_binary" "$scope/bin/game-server"
|
||||
chmod +x "$scope/bin/install-server" "$scope/bin/game-server"
|
||||
done
|
||||
done
|
||||
local_debug_prepare_run_lifecycle_templates
|
||||
local_debug_seed_server_lifecycle_workspace server-local-debug game.example run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-alpha game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-beta game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-dynamic game.scum run-local
|
||||
}
|
||||
|
||||
prepare_run_workspace
|
||||
@@ -64,7 +59,7 @@ wait_for_url() {
|
||||
}
|
||||
|
||||
start_self_hosted_stack() {
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$PLATFORM_DATA_DIR" "$PLATFORM_LOG_DIR" "$PLATFORM_ARTIFACT_DIR" "$RUN_WORKSPACE_ROOT" "$RUN_SPOOL_ROOT" "$GOCACHE"
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$PLATFORM_DATA_DIR" "$PLATFORM_LOG_DIR" "$PLATFORM_ARTIFACT_DIR" "$RUN_WORKSPACE_ROOT" "$RUN_SPOOL_ROOT" "$RUN_BUILD_BUCKET_ROOT" "$GOCACHE"
|
||||
trap cleanup_self_started EXIT
|
||||
|
||||
printf 'self-starting platform for local debug smoke\n'
|
||||
@@ -89,9 +84,9 @@ start_self_hosted_stack() {
|
||||
wait_for_url platform "$PLATFORM_URL/healthz" 45
|
||||
|
||||
printf 'self-starting run worker for local debug smoke\n'
|
||||
ensure_local_run_repo
|
||||
local_debug_build_bootstrap_run
|
||||
(
|
||||
cd "$RUN_REPO_DIR"
|
||||
cd "$(dirname "$RUN_BOOTSTRAP_BIN")"
|
||||
exec env \
|
||||
GOCACHE="$GOCACHE" \
|
||||
RUN_MODE="$RUN_MODE" \
|
||||
@@ -101,12 +96,13 @@ start_self_hosted_stack() {
|
||||
RUN_VERSION="$RUN_VERSION" \
|
||||
RUN_REGISTRATION_TOKEN="$RUN_REGISTRATION_TOKEN" \
|
||||
RUN_WORKSPACE_ROOT="$RUN_WORKSPACE_ROOT" \
|
||||
RUN_BUILD_SOURCE_ROOT="$RUN_BUILD_SOURCE_ROOT" \
|
||||
RUN_SPOOL_ROOT="$RUN_SPOOL_ROOT" \
|
||||
RUN_MAX_JOBS="$RUN_MAX_JOBS" \
|
||||
RUN_HEARTBEAT_INTERVAL_MS="$RUN_HEARTBEAT_INTERVAL_MS" \
|
||||
RUN_POLL_INTERVAL_MS="$RUN_POLL_INTERVAL_MS" \
|
||||
RUN_RETRY_BACKOFF_MS="$RUN_RETRY_BACKOFF_MS" \
|
||||
go run ./cmd/run
|
||||
"$RUN_BOOTSTRAP_BIN"
|
||||
) >"$LOCAL_DEBUG_LOG_DIR/run.log" 2>&1 &
|
||||
SELF_STARTED_PIDS+=("$!")
|
||||
printf '%s' "$!" >"$LOCAL_DEBUG_PID_DIR/run.pid"
|
||||
@@ -178,6 +174,23 @@ register_plugin_manifest() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
create_server_workflow() {
|
||||
local label="$1"
|
||||
local server_id="$2"
|
||||
local request_file="$3"
|
||||
local response_file="$4"
|
||||
if json_post "$API_URL/server-instances/workflows/create" "$request_file" "$response_file" "${AUTH_HEADER[@]}"; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -s "$response_file" ]] && response_code_is_duplicate "$response_file"; then
|
||||
json_get "$API_URL/server-instances/$server_id" "$response_file" "${AUTH_HEADER[@]}"
|
||||
return 0
|
||||
fi
|
||||
printf '%s server workflow creation failed; platform response:\n' "$label" >&2
|
||||
sed -n '1,160p' "$response_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
@@ -217,22 +230,15 @@ const missing = [];
|
||||
const declaredPermissions = Array.isArray(plugin.declaredPermissions) ? plugin.declaredPermissions : [];
|
||||
const bridgeActions = Array.isArray(plugin.bridgeActions) ? plugin.bridgeActions : [];
|
||||
const lifecycleProfiles = plugin.runtimeProfiles?.lifecycleProfiles ?? [];
|
||||
const clientManagers = plugin.runtimeProfiles?.clientManagers ?? [];
|
||||
if (!declaredPermissions.includes("server.run.distribution")) {
|
||||
missing.push("server.run.distribution permission");
|
||||
}
|
||||
if (!declaredPermissions.includes("server.client-manager.manage")) {
|
||||
missing.push("server.client-manager.manage permission");
|
||||
}
|
||||
if (!bridgeActions.includes("run.distribution.request")) {
|
||||
missing.push("run.distribution.request bridge action");
|
||||
}
|
||||
if (!lifecycleProfiles.some((profile) => profile.key === "run-local")) {
|
||||
missing.push("run-local runtime profile");
|
||||
}
|
||||
if (!clientManagers.some((profile) => profile.key === "scum-client-manager")) {
|
||||
missing.push("scum-client-manager runtime profile");
|
||||
}
|
||||
if (!plugin.gameClientBridge?.commands?.length) {
|
||||
missing.push("game client bridge declarations");
|
||||
}
|
||||
@@ -299,6 +305,143 @@ if (artifact.id !== artifactId || artifact.state !== "available" || !/^sha256:/.
|
||||
NODE
|
||||
}
|
||||
|
||||
read_latest_run_download_content() {
|
||||
local server_id="$1"
|
||||
local download_file="$2"
|
||||
local payload_file="$3"
|
||||
local chunk_dir="$4"
|
||||
local request_file="$download_file.request.json"
|
||||
mkdir -p "$chunk_dir"
|
||||
printf '{}\n' >"$request_file"
|
||||
json_post "$API_URL/server-instances/$server_id/run/download" "$request_file" "$download_file" "${AUTH_HEADER[@]}"
|
||||
reject_forbidden_fragments "$download_file"
|
||||
|
||||
local artifact_id
|
||||
local download_url
|
||||
local total_size
|
||||
local checksum
|
||||
local chunk_size
|
||||
artifact_id="$(node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (!data.artifactId) process.exit(2); process.stdout.write(data.artifactId);' "$download_file")"
|
||||
download_url="$(node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (!data.downloadUrl) process.exit(2); process.stdout.write(data.downloadUrl);' "$download_file")"
|
||||
total_size="$(node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (!Number.isSafeInteger(data.sizeBytes) || data.sizeBytes <= 0) process.exit(2); process.stdout.write(String(data.sizeBytes));' "$download_file")"
|
||||
checksum="$(node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (!/^sha256:/.test(data.checksum || "")) process.exit(2); process.stdout.write(data.checksum);' "$download_file")"
|
||||
chunk_size="$(node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); const size=Number(data.chunkSizeBytes || 1048576); if (!Number.isSafeInteger(size) || size <= 0) process.exit(2); process.stdout.write(String(size));' "$download_file")"
|
||||
|
||||
: >"$payload_file"
|
||||
local offset=0
|
||||
local index=0
|
||||
while (( offset < total_size )); do
|
||||
local limit="$chunk_size"
|
||||
local remaining=$((total_size - offset))
|
||||
if (( remaining < limit )); then
|
||||
limit="$remaining"
|
||||
fi
|
||||
local headers_file="$chunk_dir/chunk-$index.headers"
|
||||
local chunk_file="$chunk_dir/chunk-$index.bin"
|
||||
curl -fsS -D "$headers_file" -H "Authorization: Bearer $SESSION_ID" "$PLATFORM_URL$download_url?offset=$offset&limit=$limit" -o "$chunk_file"
|
||||
reject_forbidden_fragments "$headers_file"
|
||||
cat "$chunk_file" >>"$payload_file"
|
||||
node - "$download_file" "$headers_file" "$chunk_file" "$offset" "$limit" <<'NODE'
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const reference = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const headerLines = fs.readFileSync(process.argv[3], "utf8").split(/\r?\n/);
|
||||
const body = fs.readFileSync(process.argv[4]);
|
||||
const offset = Number(process.argv[5]);
|
||||
const limit = Number(process.argv[6]);
|
||||
const headers = new Map();
|
||||
for (const line of headerLines) {
|
||||
const index = line.indexOf(":");
|
||||
if (index > 0) {
|
||||
headers.set(line.slice(0, index).trim().toLowerCase(), line.slice(index + 1).trim());
|
||||
}
|
||||
}
|
||||
const bodyChecksum = `sha256:${crypto.createHash("sha256").update(body).digest("hex")}`;
|
||||
const fail = (message) => {
|
||||
console.error(message);
|
||||
console.error({ reference, offset, limit, headers: Object.fromEntries(headers), bodyBytes: body.length });
|
||||
process.exit(1);
|
||||
};
|
||||
if (headers.get("x-artifact-id") !== reference.artifactId) {
|
||||
fail("artifact content route returned the wrong artifact id");
|
||||
}
|
||||
if (headers.get("x-artifact-checksum") !== reference.checksum) {
|
||||
fail("artifact content route returned the wrong full checksum");
|
||||
}
|
||||
if (headers.get("x-artifact-content-checksum") !== bodyChecksum) {
|
||||
fail("artifact content route returned the wrong chunk checksum");
|
||||
}
|
||||
if (Number(headers.get("content-length")) !== body.length || body.length !== limit) {
|
||||
fail("artifact content chunk size did not match request");
|
||||
}
|
||||
if (offset > 0 || body.length !== reference.sizeBytes) {
|
||||
const range = headers.get("content-range") || "";
|
||||
if (!range.includes(`/${reference.sizeBytes}`)) {
|
||||
fail("partial artifact response did not include the expected total size");
|
||||
}
|
||||
}
|
||||
NODE
|
||||
offset=$((offset + limit))
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
node - "$download_file" "$payload_file" "$index" "$artifact_id" "$checksum" <<'NODE'
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const reference = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const payload = fs.readFileSync(process.argv[3]);
|
||||
const chunkCount = Number(process.argv[4]);
|
||||
const artifactId = process.argv[5];
|
||||
const checksum = process.argv[6];
|
||||
const actualChecksum = `sha256:${crypto.createHash("sha256").update(payload).digest("hex")}`;
|
||||
if (reference.artifactId !== artifactId || reference.checksum !== checksum || payload.length !== reference.sizeBytes || actualChecksum !== reference.checksum || chunkCount < 1) {
|
||||
console.error("downloaded artifact content did not match reference metadata");
|
||||
console.error({ reference, payloadBytes: payload.length, actualChecksum, chunkCount });
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
}
|
||||
|
||||
wait_for_lifecycle_install_success() {
|
||||
local server_id="$1"
|
||||
local output_file="$2"
|
||||
local state
|
||||
rm -f "$output_file"
|
||||
for _ in $(seq 1 30); do
|
||||
json_get "$API_URL/jobs?serverInstanceId=$server_id" "$output_file" "${AUTH_HEADER[@]}" || true
|
||||
if [[ ! -s "$output_file" ]]; then
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
state="$(node - "$output_file" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const response = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const job = (response.items || []).find((candidate) => candidate.capability === "process.install");
|
||||
if (!job) {
|
||||
process.stdout.write("missing");
|
||||
} else {
|
||||
process.stdout.write(job.state || "unknown");
|
||||
}
|
||||
NODE
|
||||
)"
|
||||
case "$state" in
|
||||
succeeded)
|
||||
return 0
|
||||
;;
|
||||
failed | cancelled)
|
||||
printf 'lifecycle install job for %s reached terminal failure\n' "$server_id" >&2
|
||||
sed -n '1,160p' "$output_file" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
sleep 1
|
||||
done
|
||||
|
||||
printf 'lifecycle install job for %s did not succeed before timeout\n' "$server_id" >&2
|
||||
sed -n '1,160p' "$output_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
printf 'checking platform health at %s\n' "$PLATFORM_URL"
|
||||
json_get "$PLATFORM_URL/healthz" "$WORK_DIR/health.json"
|
||||
require_file_contains "$WORK_DIR/health.json" '"status"[[:space:]]*:[[:space:]]*"ok"'
|
||||
@@ -389,7 +532,6 @@ const localRunCapabilities = [
|
||||
"dependencies.check",
|
||||
"dependencies.install",
|
||||
"logs.backfill",
|
||||
...source.runtimeProfiles.clientManagers.flatMap((manager) => manager.deployment?.requiredRunCapabilities ?? []),
|
||||
...source.capabilities.filter((capability) => capability.startsWith("remote."))
|
||||
];
|
||||
const localRuntimeProfiles = {
|
||||
@@ -404,8 +546,10 @@ const localRuntimeProfiles = {
|
||||
}
|
||||
}],
|
||||
transportProfiles: source.runtimeProfiles?.transportProfiles ?? [],
|
||||
clientManagers: source.runtimeProfiles?.clientManagers ?? []
|
||||
clientManagers: []
|
||||
};
|
||||
const localGameClientBridge = JSON.parse(JSON.stringify(source.gameClientBridge ?? {}));
|
||||
delete localGameClientBridge.companion;
|
||||
const manifest = {
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
@@ -428,7 +572,7 @@ const manifest = {
|
||||
productionLifecycle: source.productionLifecycle,
|
||||
remoteAccess: source.remoteAccess,
|
||||
runtimeProfiles: localRuntimeProfiles,
|
||||
gameClientBridge: source.gameClientBridge
|
||||
gameClientBridge: localGameClientBridge
|
||||
};
|
||||
fs.writeFileSync(outputPath, JSON.stringify({
|
||||
manifestRef: "artifact://manifests/game.scum/0.1.0",
|
||||
@@ -487,6 +631,18 @@ cat >"$WORK_DIR/create-scum-beta.request.json" <<JSON
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/create-scum-dynamic.request.json" <<JSON
|
||||
{
|
||||
"id": "scum-dynamic",
|
||||
"pluginId": "game.scum",
|
||||
"runEndpointId": "$RUN_ENDPOINT_ID",
|
||||
"name": "SCUM Dynamic",
|
||||
"idempotencyKey": "local-debug-scum-dynamic-create",
|
||||
"profileKey": "run-local",
|
||||
"bindings": {}
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/scum-alpha-run-generate.request.json" <<JSON
|
||||
{
|
||||
"targetOs": "windows",
|
||||
@@ -496,26 +652,26 @@ cat >"$WORK_DIR/scum-alpha-run-generate.request.json" <<JSON
|
||||
JSON
|
||||
|
||||
printf 'creating server lifecycle workflow through platform API\n'
|
||||
if ! curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/create-server.request.json" "$API_URL/server-instances/workflows/create" >"$WORK_DIR/create-server.response.json"; then
|
||||
json_get "$API_URL/server-instances/server-local-debug" "$WORK_DIR/create-server.response.json" "${AUTH_HEADER[@]}"
|
||||
fi
|
||||
create_server_workflow "dev" "server-local-debug" "$WORK_DIR/create-server.request.json" "$WORK_DIR/create-server.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-server.response.json"
|
||||
|
||||
printf 'creating SCUM server lifecycle workflows through platform API\n'
|
||||
if ! curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/create-scum-alpha.request.json" "$API_URL/server-instances/workflows/create" >"$WORK_DIR/create-scum-alpha.response.json"; then
|
||||
json_get "$API_URL/server-instances/scum-alpha" "$WORK_DIR/create-scum-alpha.response.json" "${AUTH_HEADER[@]}"
|
||||
fi
|
||||
if ! curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/create-scum-beta.request.json" "$API_URL/server-instances/workflows/create" >"$WORK_DIR/create-scum-beta.response.json"; then
|
||||
json_get "$API_URL/server-instances/scum-beta" "$WORK_DIR/create-scum-beta.response.json" "${AUTH_HEADER[@]}"
|
||||
fi
|
||||
create_server_workflow "SCUM alpha" "scum-alpha" "$WORK_DIR/create-scum-alpha.request.json" "$WORK_DIR/create-scum-alpha.response.json"
|
||||
create_server_workflow "SCUM beta" "scum-beta" "$WORK_DIR/create-scum-beta.request.json" "$WORK_DIR/create-scum-beta.response.json"
|
||||
create_server_workflow "SCUM dynamic" "scum-dynamic" "$WORK_DIR/create-scum-dynamic.request.json" "$WORK_DIR/create-scum-dynamic.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-alpha.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-beta.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-dynamic.response.json"
|
||||
require_file_contains "$WORK_DIR/create-scum-alpha.response.json" '"pluginId"[[:space:]]*:[[:space:]]*"game.scum"'
|
||||
require_file_contains "$WORK_DIR/create-scum-beta.response.json" '"pluginId"[[:space:]]*:[[:space:]]*"game.scum"'
|
||||
require_file_contains "$WORK_DIR/create-scum-dynamic.response.json" '"pluginId"[[:space:]]*:[[:space:]]*"game.scum"'
|
||||
|
||||
SERVER_ID="$(json_id "$WORK_DIR/create-server.response.json")"
|
||||
SCUM_ALPHA_ID="$(json_id "$WORK_DIR/create-scum-alpha.response.json")"
|
||||
SCUM_BETA_ID="$(json_id "$WORK_DIR/create-scum-beta.response.json")"
|
||||
SCUM_DYNAMIC_ID="$(json_id "$WORK_DIR/create-scum-dynamic.response.json")"
|
||||
|
||||
wait_for_lifecycle_install_success "$SCUM_DYNAMIC_ID" "$WORK_DIR/scum-dynamic-jobs.response.json"
|
||||
|
||||
printf 'checking SCUM runtime distribution action\n'
|
||||
json_get "$API_URL/server-instances/$SCUM_ALPHA_ID/runtime/actions" "$WORK_DIR/scum-alpha-runtime-actions.response.json" "${AUTH_HEADER[@]}"
|
||||
@@ -553,6 +709,8 @@ NODE
|
||||
wait_for_distribution_build "$WORK_DIR/scum-alpha-run-generate.response.json" "$WORK_DIR/scum-alpha-run-build-job.response.json" "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-job.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
read_latest_run_download_content "$SCUM_ALPHA_ID" "$WORK_DIR/scum-alpha-run-download.response.json" "$WORK_DIR/scum-alpha-run-download-content.bin" "$WORK_DIR/scum-alpha-run-download-chunks"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-download.response.json"
|
||||
else
|
||||
printf 'SCUM run package build is unavailable because Run did not advertise distribution.build; verified without fake success\n'
|
||||
fi
|
||||
@@ -562,6 +720,7 @@ json_get "$API_URL/server-instances" "$WORK_DIR/server-instances.response.json"
|
||||
json_get "$API_URL/jobs?serverInstanceId=$SERVER_ID" "$WORK_DIR/jobs.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/jobs?serverInstanceId=$SCUM_ALPHA_ID" "$WORK_DIR/scum-alpha-jobs.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/jobs?serverInstanceId=$SCUM_BETA_ID" "$WORK_DIR/scum-beta-jobs.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/jobs?serverInstanceId=$SCUM_DYNAMIC_ID" "$WORK_DIR/scum-dynamic-jobs.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/log-streams" "$WORK_DIR/log-streams.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/artifacts" "$WORK_DIR/artifacts.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/plugin-marketplace/plugins" "$WORK_DIR/marketplace.response.json" "${AUTH_HEADER[@]}"
|
||||
@@ -572,8 +731,9 @@ if [[ "$SCUM_BUILD_AVAILABLE" == "true" ]]; then
|
||||
fi
|
||||
require_file_contains "$WORK_DIR/scum-alpha-jobs.response.json" '"serverInstanceId"[[:space:]]*:[[:space:]]*"scum-alpha"'
|
||||
require_file_contains "$WORK_DIR/scum-beta-jobs.response.json" '"serverInstanceId"[[:space:]]*:[[:space:]]*"scum-beta"'
|
||||
require_file_contains "$WORK_DIR/scum-dynamic-jobs.response.json" '"state"[[:space:]]*:[[:space:]]*"succeeded"'
|
||||
|
||||
for file in "$WORK_DIR"/server-instances.response.json "$WORK_DIR"/jobs.response.json "$WORK_DIR"/scum-alpha-jobs.response.json "$WORK_DIR"/scum-beta-jobs.response.json "$WORK_DIR"/log-streams.response.json "$WORK_DIR"/artifacts.response.json "$WORK_DIR"/marketplace.response.json "$WORK_DIR"/scum-marketplace.response.json "$WORK_DIR"/scum-alpha-runtime-actions.response.json "$WORK_DIR"/scum-alpha-run-generate.response.json; do
|
||||
for file in "$WORK_DIR"/server-instances.response.json "$WORK_DIR"/jobs.response.json "$WORK_DIR"/scum-alpha-jobs.response.json "$WORK_DIR"/scum-beta-jobs.response.json "$WORK_DIR"/scum-dynamic-jobs.response.json "$WORK_DIR"/log-streams.response.json "$WORK_DIR"/artifacts.response.json "$WORK_DIR"/marketplace.response.json "$WORK_DIR"/scum-marketplace.response.json "$WORK_DIR"/scum-alpha-runtime-actions.response.json "$WORK_DIR"/scum-alpha-run-generate.response.json "$WORK_DIR"/scum-alpha-run-download.response.json; do
|
||||
if [[ -f "$file" ]]; then
|
||||
reject_forbidden_fragments "$file"
|
||||
fi
|
||||
@@ -600,8 +760,8 @@ cat >"$WORK_DIR/local-ui-checklist.md" <<EOF
|
||||
- Confirm the login path is API-backed and no local fallback banner or fallback workspace appears.
|
||||
- Visit 首页, 服务器管理, 插件市场, 用户管理, AI 提供商管理.
|
||||
- Open server-local-debug detail and inspect lifecycle history, plugin controls, logs, and artifact references.
|
||||
- Confirm 插件市场 can find SCUM Server / game.scum, then open scum-alpha and scum-beta from 服务器管理.
|
||||
- Confirm both SCUM servers are backed by game.scum and have separate lifecycle install jobs and operation history.
|
||||
- Confirm 插件市场 can find SCUM Server / game.scum, then open scum-alpha, scum-beta, and scum-dynamic from 服务器管理.
|
||||
- Confirm all SCUM servers are backed by game.scum and have separate lifecycle install jobs and operation history.
|
||||
- Search visible text for forbidden fragments: /Users/, /private/, unix://, tcp://, Bearer , sk-, password=, apiKeyRef, rawApiKey, run session tokens, direct run URLs, plugin-owned transport details.
|
||||
- Acceptance requires platform routes, logical IDs, job refs, log refs, artifact refs, and safe metadata only.
|
||||
EOF
|
||||
|
||||
@@ -5,8 +5,9 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=scripts/local-debug-env.sh
|
||||
source "$ROOT_DIR/scripts/local-debug-env.sh"
|
||||
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$PLATFORM_DATA_DIR" "$PLATFORM_LOG_DIR" "$RUN_WORKSPACE_ROOT" "$RUN_SPOOL_ROOT"
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$PLATFORM_DATA_DIR" "$PLATFORM_LOG_DIR" "$RUN_WORKSPACE_ROOT" "$RUN_SPOOL_ROOT" "$RUN_BUILD_BUCKET_ROOT"
|
||||
mkdir -p "$GOCACHE"
|
||||
local_debug_prepare_run_lifecycle_templates
|
||||
|
||||
managed_pid_running() {
|
||||
local pid_file="$1"
|
||||
@@ -133,7 +134,10 @@ wait_for_run_registration() {
|
||||
printf 'local debug root: %s\n' "$LOCAL_DEBUG_ROOT"
|
||||
printf 'platform: %s\n' "$(local_debug_platform_url)"
|
||||
printf 'platform_web: %s\n' "$(local_debug_web_url)"
|
||||
printf 'run repo: %s\n' "$RUN_REPO_DIR"
|
||||
printf 'run source: %s\n' "$RUN_SOURCE_DIR"
|
||||
printf 'run build bucket: %s\n' "$RUN_BUILD_BUCKET_ROOT"
|
||||
printf 'run build source snapshot: %s\n' "$RUN_BUILD_SOURCE_ROOT"
|
||||
printf 'run bootstrap binary: %s\n' "$RUN_BOOTSTRAP_BIN"
|
||||
printf 'platform log: %s\n' "$LOCAL_DEBUG_LOG_DIR/platform.log"
|
||||
printf 'run log: %s\n' "$LOCAL_DEBUG_LOG_DIR/run.log"
|
||||
printf 'platform_web log: %s\n' "$LOCAL_DEBUG_LOG_DIR/platform_web.log"
|
||||
@@ -156,8 +160,8 @@ start_service platform "$ROOT_DIR/platform" env \
|
||||
|
||||
wait_for_url platform "$(local_debug_platform_url)/healthz"
|
||||
|
||||
ensure_local_run_repo
|
||||
start_service run "$RUN_REPO_DIR" env \
|
||||
local_debug_build_bootstrap_run
|
||||
start_service run "$(dirname "$RUN_BOOTSTRAP_BIN")" env \
|
||||
GOCACHE="$GOCACHE" \
|
||||
RUN_MODE="$RUN_MODE" \
|
||||
RUN_PLATFORM_URL="$RUN_PLATFORM_URL" \
|
||||
@@ -166,12 +170,13 @@ start_service run "$RUN_REPO_DIR" env \
|
||||
RUN_VERSION="$RUN_VERSION" \
|
||||
RUN_REGISTRATION_TOKEN="$RUN_REGISTRATION_TOKEN" \
|
||||
RUN_WORKSPACE_ROOT="$RUN_WORKSPACE_ROOT" \
|
||||
RUN_BUILD_SOURCE_ROOT="$RUN_BUILD_SOURCE_ROOT" \
|
||||
RUN_SPOOL_ROOT="$RUN_SPOOL_ROOT" \
|
||||
RUN_MAX_JOBS="$RUN_MAX_JOBS" \
|
||||
RUN_HEARTBEAT_INTERVAL_MS="$RUN_HEARTBEAT_INTERVAL_MS" \
|
||||
RUN_POLL_INTERVAL_MS="$RUN_POLL_INTERVAL_MS" \
|
||||
RUN_RETRY_BACKOFF_MS="$RUN_RETRY_BACKOFF_MS" \
|
||||
go run ./cmd/run
|
||||
"$RUN_BOOTSTRAP_BIN"
|
||||
|
||||
wait_for_run_registration
|
||||
|
||||
|
||||
Reference in New Issue
Block a user