feat(sidecar): a Windows service, the egg and its launcher, and the first release workflow (phase 18)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m53s
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m53s
Module-rust phase 18, step 4 of docs/modules/rust/PLAN.md §34.2.7.
The Windows service (D149, §34.2.5): src/windows.rs, ported from link's fix
for error 1053. The same exe tries the SCM handshake and falls through to a
console run on 1063; it reports Running only once the listener and store are
up, and logs to a daily file beside its config. One binary serves every
RunicGatewayRust-<id> instance, because the SCM ignores the dispatcher's name
for an own-process service.
An empty environment variable now counts as unset. A Pterodactyl egg exports
every variable it declares, so a blank RUSTLINK_WEB_TOKEN arrived as "" and
overrode the saved token, and a new one was generated and persisted on every
boot. That breaks D152, which this change makes true.
The egg (R20, R22, D151, D152, §34.2.6), in egg/:
- install.sh is egg 18's script with two changes. A wipe guard moves
rust-link/ to /tmp around `rm -rf ${REMOVE_FILES}`. The bridge block then
fetches a schema-2 Rust bundle (pinnable by RUNICGATEWAY_BUNDLE), checks
every asset's sha256 and the plugin's protocol before placing anything, and
places the plugin by FRAMEWORK. Vanilla installs nothing and does not fail.
- with-sidecar.sh is the launcher. It unsets blank variables, builds the web
bind from RUSTLINK_WEB_PORT, and runs --print-config so that a newly
generated token is printed once. It prints the URL and server id for the
admin page, then execs the game. It no longer uses `set -e`: nothing the
bridge gets wrong may keep the game from booting.
- The startup's launcher prefix is conditional, so a server with no bridge
boots exactly as egg 18 does.
- build.sh assembles egg-rust-runicgateway.json. PR Checks runs it.
The release (D145, §34.2.1) reuses servuo-plugins' engine. It publishes the
static musl Linux binary, the Windows exe, the launcher, the egg and
SHA256SUMS, and dispatches the installer's bundle.yml. PR Checks gains a
clippy run for the Windows target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
86
egg/with-sidecar.sh
Executable file
86
egg/with-sidecar.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
# with-sidecar.sh — start the rust-link sidecar beside a Rust server, then become the server.
|
||||
#
|
||||
# The egg's startup is this script followed by the game's own command line:
|
||||
#
|
||||
# ./rust-link/with-sidecar.sh ./RustDedicated -batchmode …
|
||||
#
|
||||
# It ships in Rust-Link's release rather than inside the egg, so a fix here reaches a server at its
|
||||
# next reinstall without anybody re-importing the egg (docs/modules/rust/PLAN.md §34.2.6, §34.4).
|
||||
# The shape is docs/rust-link/INSTALL_RIG.md's, proven on the rigs; see that file for why each line
|
||||
# that looks optional is not.
|
||||
#
|
||||
# POSIX sh and no jq: the game image (ghcr.io/pterodactyl/games:rust) has grep and sed, not jq.
|
||||
#
|
||||
# Deliberately NOT `set -e`. Every step before the last line is the bridge's, and the last line is
|
||||
# the game's: nothing the bridge gets wrong — an unwritable log, a sidecar that will not start — may
|
||||
# keep the server from booting. Each step reports its own failure and the script goes on to `exec`.
|
||||
|
||||
RL=/home/container/rust-link
|
||||
mkdir -p "$RL" 2>/dev/null
|
||||
|
||||
export RUSTLINK_CONFIG="$RL/sidecar.toml"
|
||||
# Fixed, and never a panel variable: the install script moves this directory aside around its own
|
||||
# `rm -rf ${REMOVE_FILES}`, which is what keeps a wipe from reaching the store (§34.1).
|
||||
export RUSTLINK_DB_PATH="$RL/rust-link.db"
|
||||
|
||||
# An EMPTY panel variable is exported as `VAR=""`. The sidecar treats that as unset, and so does
|
||||
# this script, so a blank field always means "the default" (or, for the token, "the saved one").
|
||||
for v in RUSTLINK_SERVER_ID RUSTLINK_WEB_TOKEN RUSTLINK_RETAIN_DAYS RUSTLINK_WEB_PORT RUSTLINK_GAME_BIND RUSTLINK_WEB_BIND; do
|
||||
eval "val=\${$v-}"
|
||||
if [ -z "$val" ]; then unset "$v"; fi
|
||||
done
|
||||
|
||||
# The website-facing bind. Pterodactyl tells a container nothing about its extra allocations, so
|
||||
# the port is typed into the egg, and it must be one of this server's allocations — a port that is
|
||||
# not fails as a bind the website never reaches, which the lines below make visible (§34.1).
|
||||
if [ -n "${RUSTLINK_WEB_PORT-}" ]; then
|
||||
export RUSTLINK_WEB_BIND="0.0.0.0:${RUSTLINK_WEB_PORT}"
|
||||
fi
|
||||
|
||||
SIDECAR="$RL/rust-link-sidecar"
|
||||
if [ ! -x "$SIDECAR" ]; then
|
||||
echo "[rust-link] $SIDECAR is missing - reinstall the server to fetch the bridge. Starting the game without it."
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
# Provision first, so the token can be shown. `--print-config` resolves the configuration exactly as
|
||||
# a start does, writing sidecar.toml with a generated token when there is none; the JSON it prints
|
||||
# says whether it generated one on THIS call, which is what makes "print once" true (D152).
|
||||
# Tracing is off on that path, so stdout is only the document. LD_PRELOAD is dropped for the
|
||||
# sidecar in both calls: Carbon's entrypoint puts its Mono preloader in front of the whole startup.
|
||||
if CFG="$(env -u LD_PRELOAD "$SIDECAR" --print-config 2>&1)"; then
|
||||
# The Nth `"key": "value"` line of the pretty-printed JSON. `bind` appears twice — game, then web.
|
||||
field() { printf '%s\n' "$CFG" | sed -n "s/^ *\"$1\": \"\([^\"]*\)\",\{0,1\}\$/\1/p" | sed -n "${2:-1}p"; }
|
||||
WEB_BIND="$(field bind 2)"
|
||||
SERVER_ID="$(field server_id)"
|
||||
|
||||
if printf '%s\n' "$CFG" | grep -q '"token_generated": true'; then
|
||||
TOKEN="$(field auth_token)"
|
||||
echo "[rust-link] ================================================================"
|
||||
echo "[rust-link] A new sidecar token was generated. It is shown ONCE, here:"
|
||||
echo "[rust-link] ${TOKEN}"
|
||||
echo "[rust-link] It is kept in rust-link/sidecar.toml; read it there if you lose it."
|
||||
echo "[rust-link] ================================================================"
|
||||
fi
|
||||
|
||||
PORT="${WEB_BIND##*:}"
|
||||
HOST="${SERVER_IP-}"
|
||||
case "$HOST" in ""|0.0.0.0) HOST="<this node's address>" ;; esac
|
||||
echo "[rust-link] Admin -> Rust -> Servers: server id '${SERVER_ID:-main}', sidecar URL http://${HOST}:${PORT} (listening on ${WEB_BIND})"
|
||||
else
|
||||
echo "[rust-link] the sidecar could not read its configuration:"
|
||||
printf '%s\n' "$CFG" | sed 's/^/[rust-link] /'
|
||||
fi
|
||||
|
||||
# A background job's redirection fails in the child, invisibly, so writability is asked first. A
|
||||
# directory the log cannot be written to is one the store and the token cannot be written to either.
|
||||
if touch "$RL/sidecar.log" 2>/dev/null; then
|
||||
env -u LD_PRELOAD "$SIDECAR" >> "$RL/sidecar.log" 2>&1 &
|
||||
else
|
||||
echo "[rust-link] $RL is not writable - the game starts without the sidecar."
|
||||
fi
|
||||
|
||||
# The game BECOMES this process: the panel console keeps its stdin and stdout, and stop still
|
||||
# stops the server, which takes the sidecar down with the container.
|
||||
exec "$@"
|
||||
Reference in New Issue
Block a user