test(docker): add local Spigot test server
CI / build (push) Successful in 1m18s
Release / release (push) Successful in 1m56s

This commit is contained in:
dmg
2026-08-11 17:47:26 -04:00
parent 5d1577175f
commit 2e6fdb4e5b
7 changed files with 183 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
.gradle/
.docker/
build/
out/
.idea/
+34
View File
@@ -17,6 +17,40 @@ The approved behavior and implementation record are in the [OKF design bundle](d
The plugin JAR is written to `build/libs/`.
## Local Docker test server
Build the current plugin and start it on an isolated Spigot 26.2 server:
```bash
./scripts/start-test-server.sh
```
Connect to `localhost:25565` as `WindMagi`, which the harness configures as an operator. The server uses offline mode for local convenience and binds only to the loopback interface.
The first startup may take several minutes while the image downloads and Spigot is prepared. Override the 15-minute wait or local RCON password when needed:
```bash
TREE_FELLER_START_TIMEOUT=1200 \
TREE_FELLER_RCON_PASSWORD='local-secret' \
./scripts/start-test-server.sh
```
Common lifecycle commands:
```bash
# Follow server logs
docker compose -f compose.test.yml logs -f minecraft
# Stop while preserving the world and server configuration
docker compose -f compose.test.yml down
# Stop and permanently reset all generated local test state
docker compose -f compose.test.yml down
rm -rf .docker/minecraft
```
Re-run the start script after code changes to rebuild the JAR and recreate the test container. Generated server state remains under `.docker/minecraft/` and is ignored by Git.
## Player commands
```text
+32
View File
@@ -0,0 +1,32 @@
name: tree-feller-test
services:
minecraft:
image: itzg/minecraft-server:java25
container_name: tree-feller-test
ports:
- "127.0.0.1:25565:25565"
environment:
EULA: "TRUE"
TYPE: SPIGOT
VERSION: "26.2"
MEMORY: 2G
OPS: WindMagi
ONLINE_MODE: "false"
ENABLE_RCON: "true"
RCON_PASSWORD: "${TREE_FELLER_RCON_PASSWORD:-tree-feller-local-test}"
VIEW_DISTANCE: "6"
SIMULATION_DISTANCE: "6"
SPAWN_PROTECTION: "0"
volumes:
- ./.docker/minecraft:/data
- ./build/libs/tree-feller-0.1.0-SNAPSHOT.jar:/plugins/TreeFeller.jar:ro
healthcheck:
test: ["CMD", "mc-health"]
start_period: 15m
interval: 10s
timeout: 5s
retries: 90
restart: "no"
stdin_open: true
tty: true
+8
View File
@@ -2,6 +2,14 @@
## 2026-08-11
### US-010 local Docker test server completed
- Added a repository-local Docker Compose harness using `itzg/minecraft-server:java25` with Spigot 26.2, persistent ignored state, offline local testing, and loopback-only port binding.
- Added one executable script that runs the strict Gradle build, recreates the isolated container with the latest JAR, waits for health, verifies Tree Feller through RCON, and confirms `WindMagi` as an operator.
- Documented start, log, stop, persistent-state reset, timeout, and RCON-password controls.
- Live verification completed in 23 seconds: the container was healthy, RCON reported `TreeFeller`, `ops.json` contained `WindMagi` at level 4, and port 25565 was bound only to `127.0.0.1`.
- The test server remains running at `localhost:25565` for gameplay testing.
### US-009 build and release completed
- Added maintainer documentation for requirements, builds, commands, configuration, supported species, persistence, safety, compatibility, and releases.
+1
View File
@@ -9,3 +9,4 @@
7. [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
8. [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
9. [US-009: Build and release Tree Feller](us-009-build-and-release-tree-feller.md)
10. [US-010: Run a local Docker test server](us-010-run-local-docker-test-server.md)
@@ -0,0 +1,33 @@
---
type: User Story
title: "US-010: Run a local Docker test server"
description: Provide a repeatable Docker Compose harness that builds and loads Tree Feller on an isolated Spigot 26.2 server.
status: done
---
# US-010: Run a local Docker test server
As a **plugin developer**, I want one command to run the current Tree Feller build on a local containerized server so that I can perform repeatable integration and gameplay testing.
## Acceptance criteria
- [x] One repository script builds and verifies the plugin before starting an isolated Spigot 26.2 server through Docker Compose.
- [x] The current Tree Feller JAR is mounted into the server automatically whenever the container is recreated.
- [x] `WindMagi` is configured and verified as a server operator.
- [x] The Minecraft port binds only to `127.0.0.1:25565` by default.
- [x] Startup waits for server health and verifies Tree Feller through RCON.
- [x] Generated worlds, logs, configuration, and downloaded server artifacts are stored under `.docker/minecraft/` and excluded from Git.
- [x] Stopping Compose preserves generated test state unless the operator explicitly deletes it.
- [x] Existing unrelated containers are not modified.
- [x] Maintainer documentation explains start, logs, stop, reset, timeout, and RCON-password controls.
## Scope
This harness is for local development only. It does not define production deployment, publish an image, expose Minecraft or RCON publicly, or persist its runtime state in Git.
## Related
- [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
- [US-005: Undo the last felled tree](us-005-undo-the-last-felled-tree.md)
- [US-009: Build and release Tree Feller](us-009-build-and-release-tree-feller.md)
- [User-story catalog](index.md)
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
compose_file="$project_root/compose.test.yml"
wait_seconds="${TREE_FELLER_START_TIMEOUT:-900}"
if ! command -v docker >/dev/null 2>&1; then
echo "Required command not found: docker" >&2
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "Docker is not running or is not accessible." >&2
exit 1
fi
if ! docker compose version >/dev/null 2>&1; then
echo "Docker Compose v2 is required." >&2
exit 1
fi
cd "$project_root"
./gradlew clean check jar
mkdir -p .docker/minecraft
# Recreate the container so the image installs the newly built read-only plugin JAR.
docker compose -f "$compose_file" up -d --force-recreate
container_id="$(docker compose -f "$compose_file" ps -q minecraft)"
if [[ -z "$container_id" ]]; then
echo "The Tree Feller test container was not created." >&2
exit 1
fi
echo "Waiting up to ${wait_seconds}s for Spigot 26.2..."
deadline=$((SECONDS + wait_seconds))
while (( SECONDS < deadline )); do
container_state="$(docker inspect --format '{{.State.Status}}' "$container_id")"
health_state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
if [[ "$health_state" == "healthy" ]]; then
break
fi
if [[ "$container_state" == "exited" || "$container_state" == "dead" ]]; then
docker compose -f "$compose_file" logs --tail=150 minecraft >&2
echo "The Minecraft server exited during startup." >&2
exit 1
fi
sleep 5
done
health_state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
if [[ "$health_state" != "healthy" ]]; then
docker compose -f "$compose_file" logs --tail=150 minecraft >&2
echo "Timed out waiting for Spigot 26.2 (status: $health_state)." >&2
exit 1
fi
plugins="$(docker compose -f "$compose_file" exec -T minecraft rcon-cli plugins)"
if [[ "$plugins" != *"TreeFeller"* ]]; then
echo "$plugins" >&2
echo "TreeFeller was not reported by the running server." >&2
exit 1
fi
# This is idempotent and confirms the requested local test account is an operator.
docker compose -f "$compose_file" exec -T minecraft rcon-cli 'op WindMagi' >/dev/null
cat <<'MESSAGE'
Tree Feller test server is ready at localhost:25565.
Operator: WindMagi
View logs: docker compose -f compose.test.yml logs -f minecraft
Stop: docker compose -f compose.test.yml down
Reset: docker compose -f compose.test.yml down && rm -rf .docker/minecraft
MESSAGE