Updated server launch behavior to reliably treat JSON file arguments as config paths, including file-manager “Open with” cases. The CLI `serve` command now accepts an optional positional config path (equivalent to `--config`) and errors on conflicting values. `start.sh` and `start.bat` now parse config-related arguments more explicitly, validate required values, and always pass `--config` to avoid accidental positional forwarding. Added tests covering both positional and `--config` forms, plus README documentation for the new startup behavior.
213 lines
6.0 KiB
Bash
213 lines
6.0 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
SCRIPT_PATH="$(readlink -f -- "${BASH_SOURCE[0]}" 2>/dev/null || true)"
|
|
if [[ -z "${SCRIPT_PATH}" ]]; then
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
else
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_PATH}")" && pwd)"
|
|
fi
|
|
cd -- "${SCRIPT_DIR}"
|
|
|
|
SERVER_DIR="${SCRIPT_DIR}"
|
|
VENV_DIR="${SERVER_DIR}/.venv"
|
|
REQUIREMENTS_FILE="${SERVER_DIR}/requirements-server.txt"
|
|
REQUIREMENTS_HASH_FILE="${VENV_DIR}/.requirements-server.sha256"
|
|
CONFIG_FILE="${SERVER_DIR}/commonwealth-server.json"
|
|
ENTRY_POINT="${SERVER_DIR}/consumer_server_cli.py"
|
|
|
|
UPDATE_DEPENDENCIES=0
|
|
SERVER_ARGS=()
|
|
ARGS=("$@")
|
|
ARG_INDEX=0
|
|
while [[ ${ARG_INDEX} -lt ${#ARGS[@]} ]]; do
|
|
arg="${ARGS[${ARG_INDEX}]}"
|
|
case "${arg}" in
|
|
--update-dependencies)
|
|
UPDATE_DEPENDENCIES=1
|
|
;;
|
|
--config|-c)
|
|
ARG_INDEX=$((ARG_INDEX + 1))
|
|
if [[ ${ARG_INDEX} -ge ${#ARGS[@]} ]]; then
|
|
echo "ERROR: ${arg} requires a config file path." >&2
|
|
exit 1
|
|
fi
|
|
CONFIG_FILE="${ARGS[${ARG_INDEX}]}"
|
|
;;
|
|
--config=*)
|
|
CONFIG_FILE="${arg#--config=}"
|
|
;;
|
|
--host|--port|-H|-p|--interactive|-i)
|
|
SERVER_ARGS+=("${arg}")
|
|
if [[ "${arg}" == "--host" || "${arg}" == "-H" || "${arg}" == "--port" || "${arg}" == "-p" ]]; then
|
|
ARG_INDEX=$((ARG_INDEX + 1))
|
|
if [[ ${ARG_INDEX} -ge ${#ARGS[@]} ]]; then
|
|
echo "ERROR: ${arg} requires a value." >&2
|
|
exit 1
|
|
fi
|
|
SERVER_ARGS+=("${ARGS[${ARG_INDEX}]}")
|
|
fi
|
|
;;
|
|
--host=*|--port=*)
|
|
SERVER_ARGS+=("${arg}")
|
|
;;
|
|
*.json)
|
|
# File managers / "Open with" often pass the config path as $1.
|
|
CONFIG_FILE="${arg}"
|
|
;;
|
|
-*)
|
|
echo "ERROR: Unknown option: ${arg}" >&2
|
|
echo "Supported: --update-dependencies, --config PATH, --host HOST, --port PORT, --interactive" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ -f "${arg}" ]]; then
|
|
CONFIG_FILE="${arg}"
|
|
else
|
|
echo "ERROR: Unexpected argument: ${arg}" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
ARG_INDEX=$((ARG_INDEX + 1))
|
|
done
|
|
|
|
echo
|
|
echo "================================================================================"
|
|
echo " Commonwealth Online Server - Start Script"
|
|
echo "================================================================================"
|
|
echo
|
|
|
|
die() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
detect_python() {
|
|
local candidate
|
|
for candidate in python3 python; do
|
|
if command -v "${candidate}" >/dev/null 2>&1; then
|
|
if "${candidate}" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)'; then
|
|
echo "${candidate}"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
if [[ ! -f "${REQUIREMENTS_FILE}" ]]; then
|
|
die "Missing requirements file: ${REQUIREMENTS_FILE}"
|
|
fi
|
|
|
|
if [[ ! -f "${ENTRY_POINT}" ]]; then
|
|
die "Missing server entry point: ${ENTRY_POINT}"
|
|
fi
|
|
|
|
if ! BASE_PYTHON="$(detect_python)"; then
|
|
die "Python 3.9+ is required. Install python3 (and python3-venv on Debian/Ubuntu)."
|
|
fi
|
|
|
|
echo "Using system interpreter: ${BASE_PYTHON} ($("${BASE_PYTHON}" --version 2>&1))"
|
|
|
|
if [[ ! -x "${VENV_DIR}/bin/python" ]]; then
|
|
echo "Creating virtual environment at ${VENV_DIR}..."
|
|
if ! "${BASE_PYTHON}" -m venv "${VENV_DIR}"; then
|
|
die "Failed to create virtual environment. On Debian/Ubuntu install python3-venv."
|
|
fi
|
|
fi
|
|
|
|
VENV_PYTHON="${VENV_DIR}/bin/python"
|
|
if [[ ! -x "${VENV_PYTHON}" ]]; then
|
|
die "Virtual environment interpreter missing: ${VENV_PYTHON}"
|
|
fi
|
|
|
|
if ! "${VENV_PYTHON}" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)'; then
|
|
die "Virtual environment Python is older than 3.9."
|
|
fi
|
|
|
|
hash_requirements() {
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum -- "${REQUIREMENTS_FILE}" | awk '{print $1}'
|
|
else
|
|
"${VENV_PYTHON}" - <<'PY'
|
|
from hashlib import sha256
|
|
from pathlib import Path
|
|
print(sha256(Path("requirements-server.txt").read_bytes()).hexdigest())
|
|
PY
|
|
fi
|
|
}
|
|
|
|
CURRENT_HASH="$(hash_requirements)"
|
|
STORED_HASH=""
|
|
if [[ -f "${REQUIREMENTS_HASH_FILE}" ]]; then
|
|
STORED_HASH="$(tr -d '[:space:]' < "${REQUIREMENTS_HASH_FILE}")"
|
|
fi
|
|
|
|
NEED_INSTALL=0
|
|
if [[ ! -f "${REQUIREMENTS_HASH_FILE}" ]]; then
|
|
NEED_INSTALL=1
|
|
elif [[ "${CURRENT_HASH}" != "${STORED_HASH}" ]]; then
|
|
NEED_INSTALL=1
|
|
elif [[ "${UPDATE_DEPENDENCIES}" -eq 1 ]]; then
|
|
NEED_INSTALL=1
|
|
fi
|
|
|
|
if [[ "${NEED_INSTALL}" -eq 1 ]]; then
|
|
echo "Installing dedicated-server dependencies into .venv..."
|
|
if ! "${VENV_PYTHON}" -m pip install --upgrade pip >/dev/null 2>&1; then
|
|
echo "WARNING: Could not upgrade pip quietly; continuing with existing pip."
|
|
fi
|
|
if ! "${VENV_PYTHON}" -m pip install -r "${REQUIREMENTS_FILE}"; then
|
|
die "Failed to install dependencies from ${REQUIREMENTS_FILE}."
|
|
fi
|
|
printf '%s\n' "${CURRENT_HASH}" > "${REQUIREMENTS_HASH_FILE}"
|
|
echo "Dependencies installed."
|
|
else
|
|
echo "Dependencies are up to date."
|
|
fi
|
|
echo
|
|
|
|
if [[ ! -f "${CONFIG_FILE}" ]]; then
|
|
echo "Generating default configuration file..."
|
|
if ! "${VENV_PYTHON}" -u "${ENTRY_POINT}" config init "${CONFIG_FILE}"; then
|
|
die "Failed to generate config file."
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
INTERACTIVE_ARGS=()
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
INTERACTIVE_ARGS+=(--interactive)
|
|
echo "Starting Commonwealth Online Server (interactive)..."
|
|
echo "Type help for commands. Type quit or press Ctrl+C to stop."
|
|
else
|
|
echo "Starting Commonwealth Online Server (non-interactive)..."
|
|
echo "Manage the server with: ${VENV_PYTHON} -u consumer_server_cli.py status"
|
|
fi
|
|
echo
|
|
|
|
# Resolve config to an absolute path after cd'ing into the server directory.
|
|
if [[ "${CONFIG_FILE}" != /* ]]; then
|
|
CONFIG_FILE="${SERVER_DIR}/${CONFIG_FILE}"
|
|
fi
|
|
CONFIG_FILE="$(cd -- "$(dirname -- "${CONFIG_FILE}")" && pwd)/$(basename -- "${CONFIG_FILE}")"
|
|
|
|
# Do not source .venv/bin/activate — invoke the venv interpreter directly.
|
|
# Always pass --config explicitly so a bare path is never a positional serve arg.
|
|
CMD=(
|
|
"${VENV_PYTHON}"
|
|
-u
|
|
"${ENTRY_POINT}"
|
|
serve
|
|
--config
|
|
"${CONFIG_FILE}"
|
|
)
|
|
if [[ ${#INTERACTIVE_ARGS[@]} -gt 0 ]]; then
|
|
CMD+=("${INTERACTIVE_ARGS[@]}")
|
|
fi
|
|
if [[ ${#SERVER_ARGS[@]} -gt 0 ]]; then
|
|
CMD+=("${SERVER_ARGS[@]}")
|
|
fi
|
|
exec "${CMD[@]}"
|