From 10221b7e2011d3922b73d887b392289d367c36bc Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 30 Sep 2026 08:11:34 -0500 Subject: [PATCH] feat(egg): install RunicNPC when the bundle carries it (runicnpc stage 4, D224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- egg/install.sh | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/egg/install.sh b/egg/install.sh index ce78fea..036ba9f 100755 --- a/egg/install.sh +++ b/egg/install.sh @@ -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