#!/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[@]}"