[Dev] Add local Servatrice development profiles

Add a no-database guest profile (servatrice/dev.ini) and scripts to run the server and client side by side, with per-profile XDG directories for the client. Add a MySQL 8.4 compose stack published on loopback that loads the schema and an idempotent seed with accounts, roles, rooms, channels, reports, a warning, a ban, a deck, and chat log rows. Document both workflows in docs/DEVELOPMENT.md and link it from the README.
This commit is contained in:
Lukas Brübach 2026-11-09 15:29:10 +01:00 • committed by BruebachL
parent 214b284814
commit 969a473c62
8 changed files with 840 additions and 0 deletions

127
scripts/dev-client.sh Executable file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env bash
#
# Run a Cockatrice client with disposable, per-profile settings.
#
# Every profile gets its own XDG data, cache, and config directories under
# build/dev/profiles/<name>/, so several clients can run side by side with
# separate settings and card databases.
#
# Unrecognised arguments are passed through to the client unchanged.
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
profile="default"
connect="${COCKATRICE_DEV_CONNECT:-devplayer@127.0.0.1:4747}"
no_connect=0
passthrough=()
usage() {
cat <<'EOF'
Usage: scripts/dev-client.sh [options] [-- client-options...]
Options:
--profile NAME Isolated settings profile (default: default)
--connect URL Connect on startup (default: devplayer@127.0.0.1:4747)
--no-connect Start without connecting anywhere
-h, --help Show this help
Environment:
COCKATRICE_BIN Client binary to run
COCKATRICE_DEV_HOME Base directory for profiles (default: build/dev)
COCKATRICE_DEV_CONNECT Default --connect value
Examples:
scripts/dev-client.sh
scripts/dev-client.sh --profile second --connect devplayer2@127.0.0.1:4747
scripts/dev-client.sh --no-connect --profile scratch
EOF
}
die() {
echo "dev-client: $*" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--profile)
[[ $# -ge 2 ]] || die "--profile needs a name"
profile="$2"
shift 2
;;
--profile=*)
profile="${1#*=}"
shift
;;
--connect)
[[ $# -ge 2 ]] || die "--connect needs a value"
connect="$2"
shift 2
;;
--connect=*)
connect="${1#*=}"
shift
;;
--no-connect)
no_connect=1
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
passthrough+=("$@")
break
;;
*)
passthrough+=("$1")
shift
;;
esac
done
[[ "$profile" =~ ^[A-Za-z0-9._-]+$ ]] || die "profile name may only contain letters, numbers, '.', '_', and '-'"
if [[ -n "${COCKATRICE_BIN:-}" ]]; then
binary="$COCKATRICE_BIN"
elif [[ -x "$repo_root/build/debug/cockatrice/cockatrice" ]]; then
binary="$repo_root/build/debug/cockatrice/cockatrice"
elif [[ -x "$repo_root/build/test/cockatrice/cockatrice" ]]; then
binary="$repo_root/build/test/cockatrice/cockatrice"
else
die "no cockatrice binary found; build it with:
cmake -S . -B build/debug -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=0 -DWITH_GUI_TEST=ON
cmake --build build/debug -j 24"
fi
base="${COCKATRICE_DEV_HOME:-$repo_root/build/dev}"
profile_dir="$base/profiles/$profile"
export XDG_DATA_HOME="$profile_dir/data"
export XDG_CACHE_HOME="$profile_dir/cache"
export XDG_CONFIG_HOME="$profile_dir/config"
mkdir -p "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME"
args=()
if [[ "$no_connect" -eq 0 ]]; then
has_connect=0
for arg in "${passthrough[@]}"; do
case "$arg" in
--connect|--connect=*|-c) has_connect=1 ;;
esac
done
if [[ "$has_connect" -eq 0 ]]; then
args+=(--connect "$connect")
echo "Connecting to: $connect"
fi
else
args+=(--no-connect)
fi
args+=("${passthrough[@]}")
echo "Client profile: $profile ($profile_dir)"
exec "$binary" "${args[@]}"

117
scripts/dev-db.sh Executable file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env bash
#
# Manage the seeded MySQL development database.
#
# The stack is defined in docker-compose.dev.yml. On a fresh volume MySQL runs
# servatrice/servatrice.sql and then servatrice/dev/seed.sql, so the database
# starts with accounts, rooms, channels, and moderation fixtures already in
# place.
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
compose_file="$repo_root/docker-compose.dev.yml"
seed_file="$repo_root/servatrice/dev/seed.sql"
url="${COCKATRICE_DEV_DATABASE_URL:-mysql://servatrice:password@127.0.0.1:3306/servatrice}"
usage() {
cat <<'EOF'
Usage: scripts/dev-db.sh <command>
Commands:
up Start MySQL and wait until it is healthy
seed Re-apply servatrice/dev/seed.sql to the running database
reset Remove the container and volume, then start a fresh seeded database
stop Stop the container, keeping the database volume
url Print the DATABASE_URL servatrice expects
Environment:
COCKATRICE_DEV_DATABASE_URL Use an external MySQL instead of the compose stack
Examples:
scripts/dev-db.sh up
scripts/dev-db.sh seed
scripts/dev-db.sh reset
EOF
}
die() {
echo "dev-db: $*" >&2
exit 1
}
compose=()
detect_compose() {
if docker compose version >/dev/null 2>&1; then
compose=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
compose=(docker-compose)
else
die "Docker Compose is not available; install the docker compose plugin or set COCKATRICE_DEV_DATABASE_URL"
fi
}
compose_cmd() {
"${compose[@]}" -f "$compose_file" "$@"
}
command="${1:-}"
[[ -n "$command" ]] || {
usage
exit 1
}
case "$command" in
url)
echo "$url"
;;
up)
if [[ -n "${COCKATRICE_DEV_DATABASE_URL:-}" ]]; then
echo "Using external database: $url"
echo "Apply the schema and seed yourself if the database is empty:"
echo " mysql < servatrice/servatrice.sql"
echo " mysql < servatrice/dev/seed.sql"
exit 0
fi
detect_compose
compose_cmd up -d --wait
echo
echo "MySQL is ready."
echo "DATABASE_URL=$(bash "$repo_root/scripts/dev-db.sh" url)"
echo "Start the server with: scripts/dev-servatrice.sh --mysql"
;;
seed)
[[ -f "$seed_file" ]] || die "seed file not found: $seed_file"
if [[ -n "${COCKATRICE_DEV_DATABASE_URL:-}" ]]; then
die "seed is only automated for the compose stack; apply $seed_file manually for an external database"
fi
detect_compose
compose_cmd exec -T mysql mysql -uservatrice -ppassword servatrice < "$seed_file"
echo "Seed applied."
;;
reset)
if [[ -n "${COCKATRICE_DEV_DATABASE_URL:-}" ]]; then
die "reset only manages the compose stack; reset the external database yourself"
fi
detect_compose
compose_cmd down -v
compose_cmd up -d --wait
echo
echo "Fresh seeded database ready."
echo "Start the server with: scripts/dev-servatrice.sh --mysql"
;;
stop)
if [[ -n "${COCKATRICE_DEV_DATABASE_URL:-}" ]]; then
die "stop only manages the compose stack"
fi
detect_compose
compose_cmd down
;;
-h|--help|help)
usage
;;
*)
usage
die "unknown command: $command"
;;
esac

148
scripts/dev-servatrice.sh Executable file
View file

@ -0,0 +1,148 @@
#!/usr/bin/env bash
#
# Run a local Servatrice with the checked-in development profile.
#
# The default profile needs no database: it listens on localhost, accepts
# guest logins, and exposes the General and Testing rooms from
# servatrice/dev.ini. Pass --mysql to run against the seeded development
# database managed by scripts/dev-db.sh.
#
# Any unrecognised argument is passed through to servatrice unchanged, so
# options such as --set key=value or --test-hash keep working.
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
config="$repo_root/servatrice/dev.ini"
database_url="${DATABASE_URL:-}"
port=""
log_to_console=1
passthrough=()
usage() {
cat <<'EOF'
Usage: scripts/dev-servatrice.sh [options] [-- servatrice-options...]
Options:
--config FILE Configuration file (default: servatrice/dev.ini)
--port PORT Listen on PORT instead of the configured port
--mysql[=URL] Use the seeded MySQL dev database
--database-url URL Use an explicit mysql:// URL
--no-log-to-console Do not force --log-to-console
-h, --help Show this help
Environment:
SERVATRICE_BIN Servatrice binary to run
DATABASE_URL Database URL used by --mysql when no URL is given
Examples:
scripts/dev-servatrice.sh
scripts/dev-servatrice.sh --port 14747
scripts/dev-db.sh up && scripts/dev-servatrice.sh --mysql
EOF
}
die() {
echo "dev-servatrice: $*" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--config)
[[ $# -ge 2 ]] || die "--config needs a file"
config="$2"
shift 2
;;
--config=*)
config="${1#*=}"
shift
;;
--port)
[[ $# -ge 2 ]] || die "--port needs a value"
port="$2"
shift 2
;;
--port=*)
port="${1#*=}"
shift
;;
--mysql)
database_url="${DATABASE_URL:-$(bash "$repo_root/scripts/dev-db.sh" url)}"
shift
;;
--mysql=*)
database_url="${1#*=}"
shift
;;
--database-url)
[[ $# -ge 2 ]] || die "--database-url needs a value"
database_url="$2"
shift 2
;;
--database-url=*)
database_url="${1#*=}"
shift
;;
--no-log-to-console)
log_to_console=0
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
passthrough+=("$@")
break
;;
*)
passthrough+=("$1")
shift
;;
esac
done
[[ -f "$config" ]] || die "configuration file not found: $config"
if [[ -n "${SERVATRICE_BIN:-}" ]]; then
binary="$SERVATRICE_BIN"
elif [[ -x "$repo_root/build/test/servatrice/servatrice" ]]; then
binary="$repo_root/build/test/servatrice/servatrice"
elif [[ -x "$repo_root/build/debug/servatrice/servatrice" ]]; then
binary="$repo_root/build/debug/servatrice/servatrice"
else
die "no servatrice binary found; build it with:
cmake -S . -B build/test -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=1 -DTEST=1
cmake --build build/test -j 24"
fi
args=(--config "$config")
[[ "$log_to_console" -eq 1 ]] && args+=(--log-to-console)
[[ -n "$port" ]] && args+=("--set" "server/port=$port")
args+=("${passthrough[@]}")
# Resolve the effective host and port through the binary itself, so a port in
# the ini file and --set overrides are both reflected in the printed command.
print_args=(--config "$config")
[[ -n "$port" ]] && print_args+=(--set "server/port=$port")
effective_config="$("$binary" "${print_args[@]}" --print-config 2>/dev/null || true)"
effective_port="$(printf '%s\n' "$effective_config" | awk -F= '$1 == "server/port" { print $2; exit }')"
effective_host="$(printf '%s\n' "$effective_config" | awk -F= '$1 == "server/host" { print $2; exit }')"
effective_port="${effective_port:-${port:-4747}}"
if [[ -z "$effective_host" || "$effective_host" == "any" ]]; then
effective_host="127.0.0.1"
fi
if [[ -n "$database_url" ]]; then
export DATABASE_URL="$database_url"
redacted_url="$(printf '%s' "$database_url" | sed -E 's#(://[^:/@]+):[^@]*@#\1:***@#')"
echo "Database: $redacted_url"
fi
echo "Servatrice listening on ${effective_host}:${effective_port}"
echo "Connect with: scripts/dev-client.sh --connect devplayer@${effective_host}:${effective_port}"
echo
exec "$binary" "${args[@]}"