feat(egg): install RunicNPC when the bundle carries it (runicnpc stage 4, D224)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m53s

The install script fetches RunicNPC's tarball with the rest, checks it against
the bundle's sha256, its API against the bundle and its file against its own
manifest, and places RunicNPC.cs before the bridge. Its data directory is left
for RunicNPC to make (runicnpc PLAN.md §1.5). rust-link/bundle.json names it.
Walked against a mock Gitea with and without RunicNPC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-30 08:11:34 -05:00
parent 3b9a986e3a
commit 10221b7e20

View File

@@ -138,6 +138,21 @@ rg_install() {
fetch '.sidecar.assets["linux-x86_64"]' rust-link-sidecar
fetch '.sidecar.launcher' with-sidecar.sh
fetch '.payload.asset' plugin.tar.gz
# RunicNPC (docs/runicnpc/PLAN.md D224), when the bundle carries it: a third
# file beside the bridge, checked the same way before anything is placed.
local npc=""
if jq -e '.npc != null' "${work}/bundle.json" >/dev/null; then
npc="$(jq -r '.npc.tag' "${work}/bundle.json")"
fetch '.npc.asset' runicnpc.tar.gz
tar -xzf "${work}/runicnpc.tar.gz" -C "${work}"
local npc_manifest="${work}/runicnpc/manifest.json"
[ -f "${npc_manifest}" ] || { echo "Runic Gateway: the RunicNPC tarball has no manifest.json"; return 1; }
[ "$(jq -r '.api' "${npc_manifest}")" = "$(jq -r '.npc.api' "${work}/bundle.json")" ] \
|| { echo "Runic Gateway: RunicNPC answers API $(jq -r '.api' "${npc_manifest}"), the bundle says $(jq -r '.npc.api' "${work}/bundle.json") - refusing it"; return 1; }
[ -f "${work}/runicnpc/RunicNPC.cs" ] || { echo "Runic Gateway: the RunicNPC tarball has no RunicNPC.cs"; return 1; }
echo "$(jq -r '.files["RunicNPC.cs"]' "${npc_manifest}") ${work}/runicnpc/RunicNPC.cs" | sha256sum -c --quiet - \
|| { echo "Runic Gateway: RunicNPC.cs does not match its manifest's sha256 - refusing it"; return 1; }
fi
tar -xzf "${work}/plugin.tar.gz" -C "${work}"
local manifest="${work}/runicgateway-rust-plugin/manifest.json"
@@ -164,15 +179,22 @@ rg_install() {
|| { echo "Runic Gateway: ${file} does not match the plugin manifest's sha256 - refusing it"; return 1; }
done
[ -f "${work}/runicgateway-rust-plugin/RunicGateway.cs" ] || { echo "Runic Gateway: the plugin tarball has no RunicGateway.cs"; return 1; }
# RunicNPC before the bridge: the bridge reports it at hello. Its data directory
# is NOT made here - one made from outside the game is not writable by it
# (runicnpc PLAN.md §1.5); RunicNPC makes its own on first load.
if [ -n "${npc}" ]; then
install -m 644 "${work}/runicnpc/RunicNPC.cs" "${plugins}/RunicNPC.cs"
fi
for file in $(jq -r '.files | keys[]' "${manifest}"); do
install -m 644 "${work}/runicgateway-rust-plugin/${file}" "${plugins}/${file}"
done
# What is installed, readable from the panel's file manager.
jq --arg framework "${FRAMEWORK}" --arg installed "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{ bundle, protocol, framework: $framework, installed: $installed,
sidecar: { tag: .sidecar.tag }, plugin: { tag: .payload.tag, commit: .payload.commit } }' \
sidecar: { tag: .sidecar.tag }, plugin: { tag: .payload.tag, commit: .payload.commit } }
+ (if .npc == null then {} else { runicnpc: { tag: .npc.tag, api: .npc.api } } end)' \
"${work}/bundle.json" > /mnt/server/rust-link/bundle.json
echo "Runic Gateway: installed sidecar $(jq -r '.sidecar.tag' "${work}/bundle.json") and plugin $(jq -r '.payload.tag' "${work}/bundle.json") (${FRAMEWORK})"
echo "Runic Gateway: installed sidecar $(jq -r '.sidecar.tag' "${work}/bundle.json") and plugin $(jq -r '.payload.tag' "${work}/bundle.json")${npc:+ and RunicNPC ${npc}} (${FRAMEWORK})"
echo "Runic Gateway: add this server under Admin -> Rust -> Servers; the console prints its URL and, on first boot, its token."
}
# In a subshell so `set -e` inside cannot leak into the rest of this script, and