All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 29s
The 2026-11-05 weekend never cut over: gate 1 blocked every hourly run. Both of
its checks rested on assumptions the wipe disproved:
- The wipe build went live Thursday evening, but its depot manifest is dated
11-04: the date DepotDownloader prints is when Facepunch built it.
- Public never names staging's manifest. Facepunch promotes a hidden `release`
branch ("Release, before public"), and staging had moved on before the wipe.
Gate 1 now reads Steam's app info (steamcmd +app_info_print 258550, kept by
static.sh as appinfo.txt with BRANCH=public): branches.public.timeupdated must be
on the wipe day or later, and the Linux depot manifest Steam names for public
must be the one the static half downloaded. The staging static half is gone from
edge's cutover.yml; --staging-work is still accepted, since main's copy passes it
until the cutover merge carries the new wiring over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014HJEmgsXtLR5kLfh7f9y8P
178 lines
8.0 KiB
Bash
178 lines
8.0 KiB
Bash
#!/bin/bash
|
|
# The staging drill's static half (docs/runicnpc/PLAN.md D308). Never shipped.
|
|
#
|
|
# tools/drill/static.sh (WORK=drill-work BRANCH=staging by default)
|
|
#
|
|
# Needs no server. It downloads only RustDedicated_Data/Managed/*.dll from a Steam branch, and
|
|
# Oxide's build for that branch, and:
|
|
#
|
|
# 1. asks whether Oxide's build has caught up with Rust's (tools/fieldlist --oxide-behind). Until it
|
|
# has, an Oxide server on that branch dies at boot, so the drill waits for it (D317);
|
|
# 2. regenerates the swap's field list (tools/fieldlist, D232) from Rust's own Assembly-CSharp.dll
|
|
# and compares it with the one plugin/RunicNPC.cs carries;
|
|
# 3. compiles plugin/RunicNPC.cs against those assemblies with Oxide's laid over them, as an Oxide
|
|
# server would. Oxide's patched Assembly-CSharp.dll is needed: the plugin uses members Rust
|
|
# ships non-public (IAISenses, for one).
|
|
#
|
|
# It writes $WORK/static.json and, beside it, oxide-behind.txt, fields.diff and compile.txt. In
|
|
# static.json, oxideBehind is a count (-1: not measured), fields "same"/"differs", compile
|
|
# "ok"/"errors", and either is "error" when its step could not run. The script exits 0 whenever it
|
|
# ran; what it found is in static.json. DEPOTDOWNLOADER and DOTNET point at other copies (on
|
|
# Windows, DEPOTDOWNLOADER=…/DepotDownloader.exe from Git Bash).
|
|
|
|
set -uo pipefail
|
|
|
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
|
WORK=${WORK:-$ROOT/drill-work}
|
|
BRANCH=${BRANCH:-staging}
|
|
if [ "$BRANCH" = public ]; then
|
|
OXIDE_URL=${OXIDE_URL:-https://github.com/OxideMod/Oxide.Rust/releases/latest/download/Oxide.Rust-linux.zip}
|
|
else
|
|
OXIDE_URL=${OXIDE_URL:-https://downloads.oxidemod.com/artifacts/Oxide.Rust/$BRANCH/Oxide.Rust-linux.zip}
|
|
fi
|
|
DD_URL=https://github.com/SteamRE/DepotDownloader/releases/download/DepotDownloader_3.4.0/DepotDownloader-linux-x64.zip
|
|
DOTNET=${DOTNET:-dotnet}
|
|
|
|
mkdir -p "$WORK"
|
|
rm -rf "$WORK/depot" "$WORK/oxide" "$WORK/refs" "$WORK/fields"
|
|
rm -f "$WORK"/static.json "$WORK"/oxide-behind.txt "$WORK"/fields.diff "$WORK"/compile.txt "$WORK"/appinfo.txt
|
|
|
|
managed=error
|
|
behind=-1
|
|
fields=error
|
|
compile=error
|
|
note=""
|
|
oxide_modified=""
|
|
manifest=""
|
|
manifest_date=""
|
|
steam_note=""
|
|
|
|
finish() {
|
|
printf '{"branch":"%s","manifest":"%s","manifestDate":"%s","managed":"%s","oxideBehind":%s,"fields":"%s","compile":"%s","oxideModified":"%s","note":"%s","steamNote":"%s"}\n' \
|
|
"$BRANCH" "$manifest" "$manifest_date" "$managed" "$behind" "$fields" "$compile" "$oxide_modified" "$note" "$steam_note" > "$WORK/static.json"
|
|
cat "$WORK/static.json"
|
|
exit 0
|
|
}
|
|
|
|
# Git Bash does not translate paths inside a response file; elsewhere this is the path as it is.
|
|
native() { if command -v cygpath > /dev/null; then cygpath -m "$1"; else printf '%s' "$1"; fi; }
|
|
|
|
# ---- When Steam set the branch to its build (BRANCH=public only: the cutover's gate 1) ----
|
|
# The depot manifest's date below is when Facepunch BUILT the build, not when it went live (the
|
|
# 2026-11-05 wipe build was dated 11-04). Steam's app info says when the branch was SET to it
|
|
# (depots.branches.<branch>.timeupdated) and which Linux depot manifest that is. steamcmd's whole
|
|
# output goes to appinfo.txt; tools/drill/cutover.js reads it. A failure only leaves steamNote set,
|
|
# and gate 1 blocks. STEAMCMD points at another steamcmd (steamcmd.sh, or steamcmd.exe on Windows).
|
|
|
|
steam_info() {
|
|
local sc=${STEAMCMD:-} i
|
|
if [ -z "$sc" ]; then
|
|
mkdir -p "$WORK/steamcmd"
|
|
curl -sSL --fail https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz | tar -xz -C "$WORK/steamcmd" \
|
|
|| { steam_note="steamcmd could not be fetched"; return; }
|
|
sc=$WORK/steamcmd/steamcmd.sh
|
|
fi
|
|
# steamcmd updates itself on its first start and then may print nothing, so it gets three tries.
|
|
# It is 32-bit: a bare runner image lacks its libgcc, which is installed after the first miss.
|
|
for i in 1 2 3; do
|
|
"$sc" +login anonymous +app_info_update 1 +app_info_print 258550 +quit > "$WORK/appinfo.txt" 2>&1
|
|
grep -q '^"258550"' "$WORK/appinfo.txt" && return
|
|
if [ "$i" = 1 ] && command -v apt-get > /dev/null && [ "$(id -u)" = 0 ]; then
|
|
{ apt-get update -qq && apt-get install -y -qq lib32gcc-s1; } > "$WORK/steamcmd-deps.txt" 2>&1
|
|
fi
|
|
done
|
|
steam_note="steamcmd printed no app info: $(tail -2 "$WORK/appinfo.txt" | tr '\n"\\' ' ')"
|
|
}
|
|
|
|
if [ "$BRANCH" = public ]; then
|
|
steam_info
|
|
fi
|
|
|
|
# ---- Rust's managed assemblies, from Steam (no login: the dedicated server is anonymous) ----
|
|
|
|
DD=${DEPOTDOWNLOADER:-}
|
|
if [ -z "$DD" ]; then
|
|
curl -sSL --fail -o "$WORK/dd.zip" "$DD_URL" && unzip -oq "$WORK/dd.zip" -d "$WORK/dd" && chmod +x "$WORK/dd/DepotDownloader" \
|
|
|| { note="DepotDownloader could not be fetched"; finish; }
|
|
DD=$WORK/dd/DepotDownloader
|
|
fi
|
|
|
|
printf 'regex:^RustDedicated_Data/Managed/[^/]+\\.dll$\n' > "$WORK/files.txt"
|
|
"$DD" -app 258550 -depot 258552 -branch "$BRANCH" -filelist "$WORK/files.txt" -dir "$WORK/depot" -os linux \
|
|
> "$WORK/depot.txt" 2>&1
|
|
MANAGED=$WORK/depot/RustDedicated_Data/Managed
|
|
# Which build this is, from Steam itself: the Linux depot's manifest and its date. (api.steamcmd.net
|
|
# answered a build id a day stale on 2026-10-06, so the drill does not ask it.)
|
|
manifest=$(tr -d '\r' < "$WORK/depot.txt" | sed -n 's/^Manifest \([0-9]*\) (\(.*\))$/\1/p' | head -1)
|
|
manifest_date=$(tr -d '\r' < "$WORK/depot.txt" | sed -n 's/^Manifest \([0-9]*\) (\(.*\))$/\2/p' | head -1)
|
|
if [ ! -f "$MANAGED/Assembly-CSharp.dll" ]; then
|
|
note="no Assembly-CSharp.dll from Steam's $BRANCH branch: $(tail -3 "$WORK/depot.txt" | tr '\n"' ' ')"
|
|
finish
|
|
fi
|
|
managed=ok
|
|
|
|
# ---- Oxide's build for the branch ----
|
|
|
|
oxide_modified=$(curl -sSIL "$OXIDE_URL" | tr -d '\r' | sed -n 's/^[Ll]ast-[Mm]odified: //p' | tail -1)
|
|
if ! { curl -sSL --fail -o "$WORK/oxide.zip" "$OXIDE_URL" && unzip -oq "$WORK/oxide.zip" -d "$WORK/oxide"; }; then
|
|
note="Oxide's $BRANCH build could not be downloaded"
|
|
finish
|
|
fi
|
|
OXIDE_MANAGED=$WORK/oxide/RustDedicated_Data/Managed
|
|
|
|
# The tool is built once, here, so a build failure is not mistaken for anything it reports.
|
|
if ! "$DOTNET" build "$ROOT/tools/fieldlist" -c Release -o "$WORK/fieldlist" > "$WORK/fieldlist-build.txt" 2>&1; then
|
|
note="tools/fieldlist did not build: $(grep -m1 'error' "$WORK/fieldlist-build.txt" | tr '"' ' ')"
|
|
finish
|
|
fi
|
|
FIELDLIST=("$DOTNET" "$WORK/fieldlist/FieldList.dll")
|
|
|
|
# ---- 1. Has Oxide caught up? ----
|
|
|
|
if "${FIELDLIST[@]}" --oxide-behind "$MANAGED" "$OXIDE_MANAGED/Assembly-CSharp.dll" > "$WORK/oxide-behind.txt" 2>&1; then
|
|
behind=$(sed -n 's/^behind \([0-9]*\)$/\1/p' "$WORK/oxide-behind.txt" | head -1)
|
|
behind=${behind:--1}
|
|
fi
|
|
|
|
# ---- 2. The swap's field list (D232) ----
|
|
|
|
mkdir -p "$WORK/fields"
|
|
cp "$ROOT/plugin/RunicNPC.cs" "$WORK/fields/RunicNPC.cs"
|
|
block() { sed -n '/\/\/ <fieldlist>/,/\/\/ <\/fieldlist>/p' "$1" | grep -v "Generated by tools/fieldlist" | tr -d '\r'; }
|
|
if "${FIELDLIST[@]}" "$MANAGED" "$WORK/fields/RunicNPC.cs" > "$WORK/fields/run.txt" 2>&1; then
|
|
if diff -u --label carried <(block "$ROOT/plugin/RunicNPC.cs") --label "$BRANCH" <(block "$WORK/fields/RunicNPC.cs") > "$WORK/fields.diff"; then
|
|
fields=same
|
|
else
|
|
fields=differs
|
|
fi
|
|
else
|
|
cp "$WORK/fields/run.txt" "$WORK/fields.diff"
|
|
fi
|
|
|
|
# ---- 3. Compile against the branch with Oxide's build laid over ----
|
|
|
|
mkdir -p "$WORK/refs"
|
|
cp "$MANAGED"/*.dll "$WORK/refs/"
|
|
cp "$OXIDE_MANAGED"/*.dll "$WORK/refs/"
|
|
# Oxide.References.dll bundles its own older copy of Newtonsoft.Json, which Oxide's compiler never
|
|
# references; with it, every JObject is ambiguous.
|
|
rm -f "$WORK/refs/Oxide.References.dll"
|
|
|
|
DOTNET_DIR=$(dirname "$(readlink -f "$(command -v "$DOTNET")")")
|
|
CSC=$(ls "$DOTNET_DIR"/sdk/*/Roslyn/bincore/csc.dll 2>/dev/null | tail -1)
|
|
if [ -z "$CSC" ]; then
|
|
note="no csc.dll under $DOTNET_DIR/sdk"
|
|
finish
|
|
fi
|
|
|
|
# A response file: some 250 references outgrow a Windows command line.
|
|
for f in "$WORK/refs"/*.dll; do printf '"-r:%s"\n' "$(native "$f")"; done > "$WORK/refs.rsp"
|
|
if "$DOTNET" "$CSC" -nologo -noconfig -nostdlib -target:library -nowarn:1701,1702 -warn:0 \
|
|
-out:"$WORK/RunicNPC.dll" "@$WORK/refs.rsp" "$ROOT/plugin/RunicNPC.cs" > "$WORK/compile.txt" 2>&1; then
|
|
compile=ok
|
|
else
|
|
compile=errors
|
|
fi
|
|
|
|
finish
|