ci: boot-test changed games before publishing
Only games whose folder changed are boot-tested (against the previous
main commit, or against main on branches; manual runs pick games). Each
one is deployed by the node-agent's real deploy code on a dedicated
game-test runner, in a network that can reach the internet but not the
LAN. Publishing to the panel runs only if they all booted.
- boottest-payload builds the DEPLOY_SERVER message the panel would send
- readyPattern/failPatterns in game.yaml; readyExample in test.yaml is
checked against the pattern during validation
- secrets in test.yaml come from CI; a game missing one is skipped
- Minecraft gets a ready pattern; Dragonwilds needs DRAGONWILDS_OWNER_ID
- RUNNER.md + scripts/runner: runner, isolated network, systemd unit
Also fixes newline values ("\n" separators/footers) that yaml.v3 wrote
as "" on import, which would have broken ARK and Dragonwilds config
files once synced, and adds a round-trip test for the importer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5fb7736be2
commit
e9ff989246
21 files changed
+585
-18
No files matched your search
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boot-tests the given games with the node-agent's real deploy code, one after another.
|
||||
# Each game is deployed on this machine's Docker daemon inside the isolated gbr-boottest network and
|
||||
# must reach its readyPattern (or stay up) before its timeout. Games needing a secret CI doesn't
|
||||
# have are skipped. Exits non-zero if any game failed.
|
||||
# boot-test.sh palworld minecraft-crossplay
|
||||
set -uo pipefail
|
||||
|
||||
AGENT_IMAGE="${AGENT_IMAGE:-www.gitea.gbreed.com/gbreed-public/gbr-node-agent:latest}"
|
||||
NETWORK=gbr-boottest
|
||||
|
||||
if ! docker network inspect "$NETWORK" > /dev/null 2>&1; then
|
||||
# Created by the runner setup with LAN access blocked; never fall back to an unisolated network
|
||||
echo "::error::Docker network $NETWORK is missing on this runner; see RUNNER.md"
|
||||
exit 1
|
||||
fi
|
||||
docker pull -q "$AGENT_IMAGE" > /dev/null
|
||||
|
||||
# Built once: "go run" would turn the skip exit code (3) into a plain failure
|
||||
gbrgames="${RUNNER_TEMP:-/tmp}/gbrgames"
|
||||
go build -o "$gbrgames" ./cmd/gbrgames || exit 1
|
||||
|
||||
passed=() failed=() skipped=()
|
||||
for id in "$@"; do
|
||||
echo "::group::boot test: $id"
|
||||
payload=$("$gbrgames" boottest-payload "$id")
|
||||
code=$?
|
||||
if [ $code -eq 3 ]; then
|
||||
skipped+=("$id")
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
elif [ $code -ne 0 ]; then
|
||||
failed+=("$id")
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
docker run --rm -i \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--entrypoint java "$AGENT_IMAGE" \
|
||||
-Dloader.main=com.gbreed.gbr.nodeagent.BootTest -cp app.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher <<< "$payload"
|
||||
if [ $? -eq 0 ]; then passed+=("$id"); else failed+=("$id"); fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
echo "passed: ${passed[*]:-none}"
|
||||
echo "skipped: ${skipped[*]:-none} (missing secrets)"
|
||||
echo "failed: ${failed[*]:-none}"
|
||||
[ ${#failed[@]} -eq 0 ]
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prints the ids of games whose folder changed, one per line.
|
||||
# changed-games.sh BASE_REF HEAD_REF games changed between two commits
|
||||
# changed-games.sh all every game
|
||||
# changed-games.sh "palworld,ark-..." just these
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${1:-}" = "all" ]; then
|
||||
ls games
|
||||
exit 0
|
||||
fi
|
||||
if [ $# -eq 1 ]; then
|
||||
tr ',' '\n' <<< "$1" | sed 's/ //g' | grep -v '^$' | while read -r id; do
|
||||
[ -d "games/$id" ] && echo "$id" || echo "unknown game: $id" >&2
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git diff --name-only "$1" "$2" -- games/ \
|
||||
| cut -d/ -f2 | sort -u \
|
||||
| while read -r id; do
|
||||
# A deleted game has nothing to boot
|
||||
[ -f "games/$id/game.yaml" ] && echo "$id"
|
||||
done || true
|
||||
@@ -0,0 +1,10 @@
|
||||
runner:
|
||||
# One boot test at a time: game servers are big, and tests must not fight over RAM
|
||||
capacity: 1
|
||||
timeout: 3h
|
||||
|
||||
container:
|
||||
# Empty = use the host's Docker and mount it into job containers, which the boot test needs
|
||||
# to start the node-agent and game containers
|
||||
docker_host: ""
|
||||
force_pull: false
|
||||
@@ -0,0 +1,17 @@
|
||||
# The game-test runner: a second act_runner next to the build runner, so long boot tests
|
||||
# (ARK installs take 20+ minutes) never hold up builds. One job at a time.
|
||||
services:
|
||||
game-test-runner:
|
||||
image: gitea/act_runner:0.6.1
|
||||
container_name: gitea-game-test-runner
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: https://www.gitea.gbreed.com
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||
GITEA_RUNNER_NAME: game-test
|
||||
GITEA_RUNNER_LABELS: game-test:docker://catthehacker/ubuntu:act-latest
|
||||
CONFIG_FILE: /config.yaml
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml:ro
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Isolated Docker network for GBR game boot tests
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
PartOf=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/gbr-boottest/setup-boottest-network.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=docker.service
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Creates the gbr-boottest Docker network and blocks it from private networks, so boot-tested game
|
||||
# servers can download what they need from the internet but never reach the LAN or the host.
|
||||
# Safe to re-run; the systemd unit in this folder runs it after every Docker start.
|
||||
set -euo pipefail
|
||||
|
||||
NETWORK=gbr-boottest
|
||||
SUBNET=172.30.99.0/24
|
||||
|
||||
if ! docker network inspect "$NETWORK" > /dev/null 2>&1; then
|
||||
docker network create --driver bridge --subnet "$SUBNET" \
|
||||
-o com.docker.network.bridge.name=br-gbrboot "$NETWORK"
|
||||
fi
|
||||
|
||||
# DOCKER-USER is evaluated before Docker's own forwarding rules. Rebuild our rules from scratch.
|
||||
iptables -N GBR-BOOTTEST 2>/dev/null || iptables -F GBR-BOOTTEST
|
||||
iptables -C DOCKER-USER -s "$SUBNET" -j GBR-BOOTTEST 2>/dev/null \
|
||||
|| iptables -I DOCKER-USER -s "$SUBNET" -j GBR-BOOTTEST
|
||||
|
||||
# Replies to connections the container opened (downloads) are fine
|
||||
iptables -A GBR-BOOTTEST -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
||||
for net in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 169.254.0.0/16; do
|
||||
iptables -A GBR-BOOTTEST -d "$net" -j DROP
|
||||
done
|
||||
iptables -A GBR-BOOTTEST -j RETURN
|
||||
|
||||
# The host itself (SSH, the Docker API, anything listening) is reached via INPUT, not FORWARD
|
||||
iptables -C INPUT -i br-gbrboot -j DROP 2>/dev/null || iptables -I INPUT -i br-gbrboot -j DROP
|
||||
|
||||
echo "gbr-boottest ready: $SUBNET, private networks and host blocked"
|
||||
Reference in new issue
Block a user