From a7f7d2375c1c47dcd524b9fd1b4e8bd093cc8318 Mon Sep 17 00:00:00 2001 From: Andrewkydev Date: Sun, 31 May 2026 13:39:25 +0300 Subject: [PATCH] feat: add nethergamesmc core install flow --- cores/nethergamesmc.sh | 287 +++++++++++++++++ install.sh | 708 +++++++++++++++++++++++++++++++++++++++++ lib/validation.sh | 73 +++++ locales/en.sh | 157 +++++++++ locales/ru.sh | 157 +++++++++ manifests/cores.json | 66 ++++ 6 files changed, 1448 insertions(+) create mode 100644 cores/nethergamesmc.sh create mode 100644 install.sh create mode 100644 lib/validation.sh create mode 100644 locales/en.sh create mode 100644 locales/ru.sh create mode 100644 manifests/cores.json diff --git a/cores/nethergamesmc.sh b/cores/nethergamesmc.sh new file mode 100644 index 0000000..e810060 --- /dev/null +++ b/cores/nethergamesmc.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash + +core_name_nethergamesmc() { + echo "NetherGamesMC" +} + +core_default_port_nethergamesmc() { + echo "19132" +} + +core_protocol_nethergamesmc() { + echo "udp" +} + +core_runtime_nethergamesmc() { + echo "php" +} + +core_validate_nethergamesmc() { + return 0 +} + +nethergamesmc_get_php_build_release_tag() { + local requested_tag="$1" + + if [[ "$requested_tag" == "latest" ]]; then + requested_tag="$(http_get "https://api.github.com/repos/NetherGamesMC/php-build-scripts/releases/latest" | jq -r '.tag_name // empty')" + if [[ -z "$requested_tag" || "$requested_tag" == "null" ]]; then + fail "$(t no_stable_build "NetherGamesMC/php-build-scripts" "latest")" + fi + fi + + echo "$requested_tag" +} + +nethergamesmc_resolve_php_platform() { + local os + local arch + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + arch="$(uname -m | tr '[:upper:]' '[:lower:]')" + + case "$os" in + linux) + os="linux" + ;; + darwin) + os="macos|darwin" + ;; + *) + os="$os" + ;; + esac + + case "$arch" in + x86_64|amd64) + arch="x86_64|amd64" + ;; + aarch64|arm64) + arch="aarch64|arm64" + ;; + *) + arch="$arch" + ;; + esac + + echo "$os:$arch" +} + +nethergamesmc_get_php_build_asset_url() { + local tag="$1" + local allow_fallback="${2:-no}" + local platform os_regex arch_regex + local release_json + + platform="$(nethergamesmc_resolve_php_platform)" + os_regex="${platform%%:*}" + arch_regex="${platform##*:}" + + release_json="$(http_get "https://api.github.com/repos/NetherGamesMC/php-build-scripts/releases/tags/${tag}")" + if [[ "$release_json" == *"\"message\""* && "$release_json" == *"Not Found"* && "$allow_fallback" == "no" ]]; then + return 1 + fi + if [[ -z "$release_json" || "$release_json" == "null" ]]; then + if [[ "$allow_fallback" == "yes" ]]; then + release_json="$(http_get "https://api.github.com/repos/NetherGamesMC/php-build-scripts/releases/latest")" + fi + fi + + jq -r --arg os "$os_regex" --arg arch "$arch_regex" ' + (.assets // [])[] + | select((.name|test($os;"i")) and (.name|test($arch;"i")) and (.name|test("PM5";"i")) and (.name|test("\\.tar\\.gz$";"i"))) + | .browser_download_url + ' <<<"$release_json" | head -n 1 +} + +nethergamesmc_sha256_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + echo "" + fi +} + +nethergamesmc_ensure_php_runtime() { + local build_tag="$1" + local php_dir="${SERVER_DIR}/tools/php" + local marker="${SERVER_DIR}/.nethergamesmc-php-build" + local current_build="" + local build_url + + if [[ "${DRY_RUN:-no}" == "yes" ]]; then + emit_step "core_runtime" "cache" + echo "${php_dir}/bin/php" + return 0 + fi + + if [[ -f "$marker" ]]; then + current_build="$(cat "$marker" 2>/dev/null || true)" + fi + + if [[ "$current_build" == "$build_tag" ]]; then + if [[ -x "${php_dir}/bin/php" && -f "${php_dir}/composer.phar" ]]; then + emit_step "core_runtime" "cache" + echo "${php_dir}/bin/php" + return 0 + fi + fi + + build_url="$(nethergamesmc_get_php_build_asset_url "$build_tag" "no")" + if [[ -z "$build_url" || "$build_url" == "null" ]]; then + fail "$(t no_stable_build "NetherGamesMC/php-build-scripts" "$build_tag")" + fi + + emit_step "core_phpbuild_resolve" "$build_tag" + emit_step "core_phpbuild_fetch" "$build_url" + + local tmp_dir + local tmp_archive + tmp_dir="$(mktemp -d)" + tmp_archive="${tmp_dir}/php-build-${build_tag}.tar.gz" + + run_cmd curl -fL "$build_url" -o "$tmp_archive" + run_cmd mkdir -p "$tmp_dir/extracted" + run_cmd tar -xzf "$tmp_archive" -C "$tmp_dir/extracted" + + run_cmd rm -rf "$php_dir" + run_cmd mkdir -p "$php_dir" + run_cmd cp -a "$tmp_dir/extracted"/. "$php_dir"/ + run_cmd rm -rf "$tmp_dir" + + local php_bin + php_bin="$(find "$php_dir" -type f -name 'php' | head -n 1 || true)" + if [[ -z "$php_bin" ]]; then + fail "PHP runtime binary not found after extraction" + fi + + run_cmd chmod +x "$php_bin" + run_cmd mkdir -p "$php_dir/bin" + run_cmd ln -sf "$php_bin" "$php_dir/bin/php" + + run_cmd printf '%s\n' "$build_tag" > "$marker" + echo "${php_dir}/bin/php" +} + +nethergamesmc_fetch_composer() { + local php_bin="$1" + local composer_path="$2" + + if [[ "${DRY_RUN:-no}" == "yes" ]]; then + return 0 + fi + + emit_step "core_composer" "composer.phar" + if [[ -f "$composer_path" ]]; then + if "$php_bin" -n "$composer_path" --version >/dev/null 2>&1; then + return 0 + fi + fi + + local phar_url="https://getcomposer.org/download/latest-stable/composer.phar" + local sha_url="https://getcomposer.org/download/latest-stable/composer.phar.sha256sum" + local hash_file="${composer_path}.sha256" + + run_cmd curl -fL "$phar_url" -o "$composer_path" + run_cmd curl -fL "$sha_url" -o "$hash_file" + + local expected_sum + local actual_sum + expected_sum="$(awk '{print $1}' "$hash_file" | head -n 1 || true)" + actual_sum="$(nethergamesmc_sha256_file "$composer_path" || true)" + if [[ -n "$expected_sum" && -n "$actual_sum" && "$expected_sum" == "$actual_sum" ]]; then + run_cmd rm -f "$hash_file" + return 0 + fi + + # Fallback to latest release asset when hash check is unavailable or changed + run_cmd rm -f "$composer_path" "$hash_file" + run_cmd curl -fL "https://github.com/composer/composer/releases/latest/download/composer.phar" -o "$composer_path" +} + +nethergamesmc_render_start_script() { + local php_bin="$1" + + emit_step "core_render" "nethergamesmc start script" + run_cmd bash -c "cat > '$SERVER_DIR/start.sh' < "$SERVER_DIR/.nethergamesmc-build" + emit_step "core_configure" "nethergamesmc done" +} + +core_post_install_nethergamesmc() { + emit_step "core_post" "NetherGamesMC" + run_cmd bash -lc "chown -R ${SERVER_USER}:${SERVER_USER} '$SERVER_DIR'" + emit_step "core_chown" "NetherGamesMC" + open_firewall_port "$SERVER_PORT" "udp" + emit_step "core_service" "NetherGamesMC" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_nethergamesmc() { + info "$(t core_update "$(core_name_nethergamesmc)")" + emit_step "core_update" "$(core_name_nethergamesmc)" + + backup_file "$SERVER_DIR/server.phar" + backup_file "$SERVER_DIR/start.sh" + backup_file "$SERVER_DIR/.nethergamesmc-build" + core_install_nethergamesmc + emit_step "core_update_done" "NetherGamesMC" +} diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..bdc4069 --- /dev/null +++ b/install.sh @@ -0,0 +1,708 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +DEPLOY_VERSION="0.1.0" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LIB_DIR="$SCRIPT_DIR/lib" +CORE_DIR="$SCRIPT_DIR/cores" + +CORE="" +SERVER_DIR="" +SERVER_PORT="" +MC_VERSION="latest" +BUILD="latest" +RAM="2G" +SERVICE_MODE="tmux-systemd" +FIREWALL="yes" +SERVER_USER="minecraft" +ACCEPT_EULA="no" +ACCEPT_LICENSE="no" +LOCALE="${LANGUAGE:-${LANG:-en}}" +ASSUME_YES="no" +BEGINNER_MODE="no" +DRY_RUN="no" +DEBUG="no" +REPORT_URL="" +REPORT_TOKEN="" +VERSION_REQUEST="no" +SELF_UPDATE="no" +ALLOW_EXISTING="no" +BACKUP_EXISTING="no" +FORCE_CLEAN="no" +UPDATE_MODE="no" +STEP_INDEX=0 +STEP_TOTAL=30 + +CORE_DEFAULT_PORT="" +CORE_PROTOCOL="" +CORE_RUNTIME="" +CORE_NAME="" +CORE_SERVICE_NAME="" + +OS_ID="" +OS_VERSION="" +ARCHITECTURE="" +PKG_MANAGER="" +INIT_SYSTEM="" + +LOG_DIR="/var/log/astra-deploy" +LOG_FILE="" + +if shopt -s nullglob 2>/dev/null; then + for lib in "$LIB_DIR"/*.sh; do + # shellcheck source=/dev/null + . "$lib" + done + shopt -u nullglob +else + for lib in "$LIB_DIR"/*.sh; do + . "$lib" + done +fi + +usage() { + cat < + --core-version (legacy alias for mc-version) + --dir + --port + --mc-version + --version (legacy alias for mc-version) + --build + --ram <2G> + --service + --firewall + --user + --accept-eula + --eula (legacy alias for accept-eula) + --accept-license + --license (legacy alias for accept-license) + --locale + --lang + --easy, -E + --dry-run + --yes + --non-interactive + --allow-existing + --backup-existing + --force-clean + --report-url + --report-token + --debug + --version (legacy mc-version) without value to print tool version + --self-update + --script-version + -V + --update + --help + +Examples: + # Быстрый запуск для нубов (без вопросов): + sudo bash install.sh --easy --core paper + # Полный non-interactive запуск для автоматизации (бота): + sudo bash install.sh --yes --core paper --dir /opt/minecraft/paper --service tmux-systemd --locale ru +EOF +} + +emit_step() { + local step_id="$1" + local details="${2:-}" + + STEP_INDEX=$((STEP_INDEX + 1)) + + local key="step_${step_id}" + local message + message="$(t "$key")" + if [[ "$message" == "$key" ]]; then + message="${step_id}" + fi + + if [[ -n "$details" ]]; then + message="${message}: ${details}" + fi + + info "$(t progress_step "$STEP_INDEX" "$message")" + send_report "step" "$message" "$step_id" "$STEP_INDEX" "$STEP_TOTAL" +} + +init_step_counter() { + if is_java_core "$CORE"; then + STEP_TOTAL=80 + else + STEP_TOTAL=60 + fi + + if [[ "$UPDATE_MODE" == "yes" ]]; then + STEP_TOTAL=$((STEP_TOTAL - 6)) + if (( STEP_TOTAL < 10 )); then + STEP_TOTAL=10 + fi + fi +} + +show_license() { + info "$(t license_title)" + info "$(t license_intro)" + info "$(t license_item_1)" + info "$(t license_item_2)" + info "$(t license_item_3)" + info "$(t license_item_4)" + info "$(t license_item_5)" + info "$(t license_item_6)" +} + +parse_bool() { + case "${1,,}" in + yes|y|1|true|on) echo yes ;; + no|n|0|false|off) echo no ;; + *) echo "" ;; + esac +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --core) + CORE="${2:-}"; shift 2 ;; + --core=*) + CORE="${1#*=}"; shift ;; + --core-version) + MC_VERSION="${2:-}"; shift 2 ;; + --core-version=*) + MC_VERSION="${1#*=}"; shift ;; + --dir) + SERVER_DIR="${2:-}"; shift 2 ;; + --dir=*) + SERVER_DIR="${1#*=}"; shift ;; + --port) + SERVER_PORT="${2:-}"; shift 2 ;; + --port=*) + SERVER_PORT="${1#*=}"; shift ;; + --mc-version) + MC_VERSION="${2:-}"; shift 2 ;; + --mc-version=*) + MC_VERSION="${1#*=}"; shift ;; + --version-code) + MC_VERSION="${2:-}"; shift 2 ;; + --version-code=*) + MC_VERSION="${1#*=}"; shift ;; + --version) + if [[ "${2:-}" == "" || "${2:-}" == --* ]]; then + VERSION_REQUEST="yes" + shift + else + MC_VERSION="${2:-}" + shift 2 + fi + ;; + --build) + BUILD="${2:-}"; shift 2 ;; + --build=*) + BUILD="${1#*=}"; shift ;; + --ram) + RAM="${2:-}"; shift 2 ;; + --ram=*) + RAM="${1#*=}"; shift ;; + --service) + SERVICE_MODE="${2:-}"; shift 2 ;; + --service=*) + SERVICE_MODE="${1#*=}"; shift ;; + --firewall) + FIREWALL="$(parse_bool "${2:-}")"; shift 2 ;; + --firewall=*) + FIREWALL="$(parse_bool "${1#*=}")"; shift ;; + --user) + SERVER_USER="${2:-}"; shift 2 ;; + --user=*) + SERVER_USER="${1#*=}"; shift ;; + --accept-eula) + ACCEPT_EULA="$(parse_bool "${2:-}")"; shift 2 ;; + --accept-eula=*) + ACCEPT_EULA="$(parse_bool "${1#*=}")"; shift ;; + --eula) + ACCEPT_EULA="$(parse_bool "${2:-}")"; shift 2 ;; + --eula=*) + ACCEPT_EULA="$(parse_bool "${1#*=}")"; shift ;; + --accept-license) + ACCEPT_LICENSE="$(parse_bool "${2:-}")"; shift 2 ;; + --accept-license=*) + ACCEPT_LICENSE="$(parse_bool "${1#*=}")"; shift ;; + --license) + ACCEPT_LICENSE="$(parse_bool "${2:-}")"; shift 2 ;; + --license=*) + ACCEPT_LICENSE="$(parse_bool "${1#*=}")"; shift ;; + --locale) + LOCALE="${2:-}"; shift 2 ;; + --locale=*) + LOCALE="${1#*=}"; shift ;; + --lang) + LOCALE="${2:-}"; shift 2 ;; + --lang=*) + LOCALE="${1#*=}"; shift ;; + --easy|--beginner|-E) + BEGINNER_MODE="yes" + ASSUME_YES="yes" + shift + ;; + --dry-run) + DRY_RUN="yes"; shift ;; + --yes|-y) + ASSUME_YES="yes"; shift ;; + --non-interactive) + ASSUME_YES="yes"; shift ;; + --allow-existing) + ALLOW_EXISTING="yes"; shift ;; + --backup-existing) + BACKUP_EXISTING="yes"; shift ;; + --force-clean) + FORCE_CLEAN="yes"; shift ;; + --report-url) + REPORT_URL="${2:-}"; shift 2 ;; + --report-url=*) + REPORT_URL="${1#*=}"; shift ;; + --report-token) + REPORT_TOKEN="${2:-}"; shift 2 ;; + --report-token=*) + REPORT_TOKEN="${1#*=}"; shift ;; + --debug) + DEBUG="yes"; shift ;; + --script-version|-V) + VERSION_REQUEST="yes"; shift ;; + --self-update) + SELF_UPDATE="yes"; shift ;; + --update) + UPDATE_MODE="yes"; shift ;; + --help|-h) + usage + exit 0 + ;; + *) + error "$(t unknown_argument "$1")" + usage + exit 1 + ;; + esac + done + + if [[ "$FIREWALL" == "" ]]; then + FIREWALL="yes" + fi +} + +init_logger() { + local timestamp + timestamp="$(date +%F-%H-%M-%S)" + mkdir -p "$LOG_DIR" + LOG_FILE="$LOG_DIR/install-${timestamp}.log" + exec > >(tee -a "$LOG_FILE") 2>&1 + info "$(t log_title "$DEPLOY_VERSION")" + info "$(t log_file "$LOG_FILE")" +} + +resolve_core_defaults() { + if [[ -z "$CORE" ]]; then + return 0 + fi + + local check_fn="core_default_port_${CORE}" + if [[ "$(type -t "$check_fn" 2>/dev/null)" != "function" ]]; then + fail "$(t unsupported_core "$CORE" "pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc" "${SCRIPT_DIR}/manifests/cores.json")" + fi + + CORE_NAME="$(core_name_${CORE})" + CORE_DEFAULT_PORT="$(core_default_port_${CORE})" + CORE_PROTOCOL="$(core_protocol_${CORE})" + CORE_RUNTIME="$(core_runtime_${CORE})" + CORE_SERVICE_NAME="${CORE}_minecraft" + + if [[ -z "$SERVER_PORT" ]]; then + SERVER_PORT="$CORE_DEFAULT_PORT" + fi +} + +ask_value() { + local prompt_text="$1" + local default_value="$2" + local var_name="$3" + + local value="" + if [[ -t 0 ]]; then + read -r -p "$prompt_text [$default_value]: " value < /dev/tty || true + else + read -r -p "$prompt_text [$default_value]: " value || true + fi + if [[ -z "$value" ]]; then + value="$default_value" + fi + printf -v "$var_name" "%s" "$value" +} + +ask_yes_no() { + local prompt_text="$1" + local default_value="$2" + local var_name="$3" + + local value="" + while true; do + if [[ -t 0 ]]; then + read -r -p "$prompt_text (yes/no) [$default_value]: " value < /dev/tty || true + else + read -r -p "$prompt_text (yes/no) [$default_value]: " value || true + fi + if [[ -z "$value" ]]; then + value="$default_value" + fi + value="$(parse_bool "$value" || true)" + if [[ "$value" == "yes" || "$value" == "no" ]]; then + printf -v "$var_name" "%s" "$value" + return + fi + warn "$(t prompt_invalid_yes_no)" + done +} + +apply_noob_defaults() { + if [[ -z "$CORE" ]]; then + CORE="paper" + fi + + if [[ -z "$RAM" ]]; then + RAM="2G" + fi + if [[ -z "$SERVICE_MODE" ]]; then + SERVICE_MODE="tmux-systemd" + fi + if [[ -z "$FIREWALL" ]]; then + FIREWALL="yes" + fi + if [[ -z "$SERVER_USER" ]]; then + SERVER_USER="minecraft" + fi + if [[ -z "$MC_VERSION" ]]; then + MC_VERSION="latest" + fi + if [[ -z "$BUILD" ]]; then + BUILD="latest" + fi + + resolve_core_defaults + + if [[ -z "$SERVER_DIR" ]]; then + SERVER_DIR="/opt/minecraft/$CORE" + fi + if [[ -z "$SERVER_PORT" ]]; then + SERVER_PORT="$CORE_DEFAULT_PORT" + fi + + if [[ "$BEGINNER_MODE" == "yes" ]]; then + FIREWALL="$(parse_bool "$FIREWALL" || true)" + [[ "$FIREWALL" == "" ]] && FIREWALL="yes" + ACCEPT_LICENSE="yes" + if is_java_core "$CORE"; then + ACCEPT_EULA="yes" + fi + fi +} + +show_noob_plan() { + info "$(t easy_plan_title)" + info "$(t easy_plan_core "$CORE")" + info "$(t easy_plan_dir "$SERVER_DIR")" + info "$(t easy_plan_port "$SERVER_PORT")" + info "$(t easy_plan_ram "$RAM")" + info "$(t easy_plan_service "$SERVICE_MODE")" + info "$(t easy_plan_user "$SERVER_USER")" + info "$(t easy_plan_firewall "$FIREWALL")" + if [[ "$CORE" == "nethergamesmc" ]]; then + info "$(t easy_plan_build "$BUILD")" + info "$(t easy_plan_mc_version "$MC_VERSION")" + fi + if is_java_core "$CORE"; then + info "$(t easy_plan_eula "$ACCEPT_EULA")" + fi +} + +interact() { + if [[ "$ASSUME_YES" == "yes" ]]; then + return 0 + fi + + if [[ -z "$CORE" ]]; then + ask_value "$(t prompt_core)" "paper" CORE + fi + + resolve_core_defaults + + if [[ -z "$SERVER_DIR" ]]; then + ask_value "$(t prompt_server_directory)" "/opt/minecraft/$CORE" SERVER_DIR + fi + + if [[ -z "$SERVER_PORT" ]]; then + ask_value "$(t prompt_server_port)" "$CORE_DEFAULT_PORT" SERVER_PORT + fi + + ask_value "$(t prompt_ram)" "$RAM" RAM + if [[ "$CORE" == "nethergamesmc" ]]; then + ask_value "$(t prompt_php_build)" "${BUILD:-latest}" BUILD + ask_value "$(t prompt_pocketmine_version)" "${MC_VERSION:-latest}" MC_VERSION + fi + ask_value "$(t prompt_service_mode)" "$SERVICE_MODE" SERVICE_MODE + ask_value "$(t prompt_open_firewall)" "$FIREWALL" FIREWALL + FIREWALL="$(parse_bool "$FIREWALL" || true)" + [[ "$FIREWALL" == "" ]] && FIREWALL="yes" + ask_value "$(t prompt_install_user)" "$SERVER_USER" SERVER_USER + + if is_java_core "$CORE"; then + ask_yes_no "$(t eula_prompt)" "${ACCEPT_EULA}" ACCEPT_EULA + fi +} + +configure_plan() { + if [[ -z "$CORE" ]]; then + fail "$(t core_required)" + fi + + resolve_core_defaults + + local validate_fn="core_validate_${CORE}" + if [[ "$(type -t "$validate_fn" 2>/dev/null)" != "function" ]]; then + fail "$(t core_validator_missing "$CORE")" + fi + "$validate_fn" + + if [[ -z "$SERVER_DIR" ]]; then + fail "$(t server_dir_required)" + fi + if [[ -z "$SERVER_PORT" ]]; then + fail "$(t server_port_required)" + fi + if [[ ! "$SERVER_PORT" =~ ^[0-9]+$ ]]; then + fail "$(t port_must_be_numeric)" + fi + if (( SERVER_PORT < 1 || SERVER_PORT > 65535 )); then + fail "$(t port_out_of_range "$SERVER_PORT")" + fi + + if [[ "$SERVICE_MODE" != "systemd" && "$SERVICE_MODE" != "tmux-systemd" && "$SERVICE_MODE" != "screen" && "$SERVICE_MODE" != "none" ]]; then + fail "$(t invalid_service_mode "$SERVICE_MODE")" + fi + if [[ "$FIREWALL" != "yes" && "$FIREWALL" != "no" ]]; then + fail "$(t invalid_firewall_value "$FIREWALL")" + fi +} + +validate_license() { + if [[ "${ACCEPT_LICENSE,,}" == "yes" ]]; then + return 0 + fi + + if [[ "$ASSUME_YES" == "yes" ]]; then + fail "$(t license_denied)" + fi + + show_license + ask_yes_no "$(t license_prompt)" "no" ACCEPT_LICENSE + if [[ "${ACCEPT_LICENSE,,}" != "yes" ]]; then + fail "$(t license_denied)" + fi +} + +check_existing_directory() { + if [[ ! -d "$SERVER_DIR" ]]; then + run_cmd mkdir -p "$SERVER_DIR" + return + fi + + if [[ -z "$(ls -A "$SERVER_DIR" 2>/dev/null)" ]]; then + return + fi + + if [[ "$FORCE_CLEAN" == "yes" ]]; then + run_cmd find "$SERVER_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + + return + fi + + if [[ "$BACKUP_EXISTING" == "yes" ]]; then + local backup_dir="${SERVER_DIR}.backup-$(date +%F-%H-%M-%S)" + run_cmd cp -a "$SERVER_DIR" "$backup_dir" + info "$(t dir_backed_up "$backup_dir")" + return + fi + + if [[ "$ALLOW_EXISTING" == "yes" ]]; then + return + fi + + if [[ "$ASSUME_YES" == "yes" ]]; then + fail "$(t dir_not_empty_auto "$SERVER_DIR")" + fi + + warn "$(t dir_not_empty "$SERVER_DIR")" + while true; do + local action + if [[ -t 0 ]]; then + read -r -p "$(t dir_not_empty_actions)" action < /dev/tty || true + else + read -r -p "$(t dir_not_empty_actions)" action || true + fi + case "$action" in + 1) fail "$(t action_cancelled_by_user)";; + 2) return;; + 3) + local backup_dir="${SERVER_DIR}.backup-$(date +%F-%H-%M-%S)" + run_cmd cp -a "$SERVER_DIR" "$backup_dir" + info "$(t dir_backed_up "$backup_dir")" + return + ;; + 4) + run_cmd find "$SERVER_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + + return + ;; + *) + warn "$(t dir_invalid_option)" + ;; + esac + done +} + +backup_file() { + local path="$1" + + if [[ ! -e "$path" ]]; then + return 0 + fi + + local backup_path="${path}.backup-$(date +%F-%H-%M-%S)" + run_cmd cp -a "$path" "$backup_path" + info "$(t file_backed_up "$path" "$backup_path")" +} + +preflight() { + emit_step "preflight_begin" + emit_step "preflight_validate_root" + validate_root_usage + emit_step "preflight_detect" + emit_step "preflight_detect_os" + detect_system + emit_step "preflight_validate_support" + validate_core_support + if [[ "$UPDATE_MODE" == "yes" ]]; then + emit_step "preflight_update_mode_check" + if [[ ! -d "$SERVER_DIR" ]]; then + fail "$(t update_requires_existing_dir)" + fi + if [[ -z "$(ls -A "$SERVER_DIR" 2>/dev/null)" ]]; then + fail "$(t update_requires_non_empty_dir)" + fi + fi + + emit_step "preflight_validate_plan" + configure_plan + emit_step "preflight_validate_licenses" + validate_license + validate_eula + + emit_step "preflight_validate_port" + if is_java_core "$CORE"; then + validate_port "$SERVER_PORT" "tcp" + else + validate_port "$SERVER_PORT" "udp" + fi + + emit_step "preflight_directory" + if [[ "$UPDATE_MODE" == "no" ]]; then + check_existing_directory + fi + + emit_step "preflight_packages" + install_base_packages + + if is_java_core "$CORE"; then + emit_step "preflight_java" + install_java_21 + fi + + emit_step "preflight_user" + create_minecraft_user "$SERVER_USER" + prepare_server_dir "$SERVER_DIR" "$SERVER_USER" +} + +install_core() { + emit_step "install_core" "$CORE" + + local install_fn="core_install_${CORE}" + local post_fn="core_post_install_${CORE}" + local update_fn="core_update_${CORE}" + + if [[ "$UPDATE_MODE" == "yes" && "$(type -t "$update_fn" 2>/dev/null)" == "function" ]]; then + stop_service_if_running "${CORE_SERVICE_NAME}" + emit_step "update_core" "$CORE" + "$update_fn" + else + emit_step "deploy_core" "$CORE" + "$install_fn" + fi + + if [[ ! "$(type -t "$post_fn" 2>/dev/null)" == "function" ]]; then + fail "$(t post_install_callback_missing "$CORE")" + fi + "$post_fn" +} + +finalize() { + info "$(t install_completed "$CORE_NAME")" + info "$(t result_server_dir "$SERVER_DIR")" + info "$(t result_service_mode "${SERVICE_MODE_ACTIVE:-none}")" + info "$(t result_service_name "${SERVICE_NAME:-${CORE}_minecraft}")" + info "$(t result_log_file "$LOG_FILE")" + send_report "success" "$(t install_finished)" +} + +main() { + for core_file in "$CORE_DIR"/*.sh; do + . "$core_file" + done + + load_locale + parse_args "$@" + + if [[ "$VERSION_REQUEST" == "yes" ]]; then + echo "AstraDeploy v$DEPLOY_VERSION" + exit 0 + fi + + load_locale + validate_root_usage + + init_logger + trap 'rollback_on_error "$?" "$BASH_COMMAND"' ERR + send_report "started" "Deployment started" + + if [[ "$SELF_UPDATE" == "yes" ]]; then + warn "$(t self_update_not_implemented)" + fi + + emit_step "parse_complete" + interact + emit_step "prepare_defaults" + apply_noob_defaults + if [[ "$BEGINNER_MODE" == "yes" ]]; then + show_noob_plan + fi + init_step_counter + emit_step "preflight_start" + preflight + emit_step "install_start" + install_core + emit_step "finalize" + finalize +} + +main "$@" diff --git a/lib/validation.sh b/lib/validation.sh new file mode 100644 index 0000000..c7adf31 --- /dev/null +++ b/lib/validation.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +validate_root_usage() { + if [[ "$(id -u)" -ne 0 ]]; then + fail "$(t root_required)" + fi +} + +validate_core_support() { + local supported_cores="pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc" + local supported_os="ubuntu,debian" + local planned_os="arch,fedora,rocky,almalinux" + + case "$CORE" in + pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc) + ;; + *) + fail "$(t unsupported_core "$CORE" "$supported_cores" "${SCRIPT_DIR}/manifests/cores.json")" + ;; + esac + + case "$OS_ID" in + ubuntu|debian) + return 0 + ;; + arch|fedora|rocky|almalinux) + warn "$(t os_coreplanned "$OS_ID" "${SCRIPT_DIR}/manifests/os-support.json")" + ;; + *) + fail "$(t unsupported_os "$OS_ID" "$supported_os" "$planned_os" "${SCRIPT_DIR}/manifests/os-support.json")" + ;; + esac +} + +validate_eula() { + if ! is_java_core "$CORE"; then + return 0 + fi + + if [[ "${ACCEPT_EULA,,}" != "yes" ]]; then + fail "$(t eula_denied)" + fi +} + +validate_port() { + local port="$1" + local protocol="$2" + + if ! command -v ss >/dev/null 2>&1; then + if command -v lsof >/dev/null 2>&1; then + if [[ "$protocol" == "tcp" ]]; then + if lsof -iTCP:"$port" -sTCP:LISTEN -n -P >/dev/null 2>&1; then + fail "$(t tcp_port_in_use "$port")" + fi + else + if lsof -iUDP:"$port" -n -P >/dev/null 2>&1; then + fail "$(t udp_port_in_use "$port")" + fi + fi + return 0 + fi + fi + + if [[ "$protocol" == "tcp" ]]; then + if ss -ltn | awk '{print $4}' | grep -q ":${port}$"; then + fail "$(t tcp_port_in_use "$port")" + fi + else + if ss -lun | awk '{print $5}' | grep -q ":${port}$"; then + fail "$(t udp_port_in_use "$port")" + fi + fi +} diff --git a/locales/en.sh b/locales/en.sh new file mode 100644 index 0000000..97adc1e --- /dev/null +++ b/locales/en.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +locale_msg_unknown_argument() { echo "Unknown argument: $1"; } +locale_msg_self_update_not_implemented() { echo "Self-update is not implemented in v0.1.0"; } + +locale_msg_license_title() { echo "AstraDeploy License Agreement"; } +locale_msg_license_intro() { echo "Important conditions for running this installer."; } +locale_msg_license_item_1() { echo "- Installer executes privileged actions via root/sudo."; } +locale_msg_license_item_2() { echo "- The script may modify system packages, services, firewall, user and files."; } +locale_msg_license_item_3() { echo "- Backup important data before updating or replacing server files."; } +locale_msg_license_item_4() { echo "- You are responsible for core licensing and server data."; } +locale_msg_license_item_5() { echo "- AstraDeploy does not store your data and is not liable for any data loss."; } +locale_msg_license_item_6() { echo "- Agreement: AstraDeploy is provided \"as is\" without warranties, and you use it at your own risk."; } +locale_msg_log_title() { echo "AstraDeploy v$1"; } +locale_msg_log_file() { echo "Log file: $1"; } +locale_msg_license_prompt() { echo "Continue installation after accepting terms?"; } +locale_msg_license_denied() { echo "Installation stopped: terms not accepted."; } +locale_msg_eula_prompt() { echo "Accept Minecraft EULA for Java cores (Paper/Purpur/Velocity/PowerNukkitX)?"; } +locale_msg_eula_denied() { echo "Java installation cannot continue without EULA acceptance."; } +locale_msg_progress_step() { echo "Step $1: $2"; } +locale_msg_step_parse_complete() { echo "Arguments parsed"; } +locale_msg_step_prepare_defaults() { echo "Applying plan defaults"; } +locale_msg_step_preflight_start() { echo "Starting preflight checks"; } +locale_msg_step_install_start() { echo "Starting install"; } +locale_msg_step_finalize() { echo "Finalizing"; } +locale_msg_step_preflight_begin() { echo "Start preflight"; } +locale_msg_step_preflight_detect() { echo "Detecting OS and package manager"; } +locale_msg_step_preflight_update_mode_check() { echo "Checking update mode constraints"; } +locale_msg_step_preflight_validate_plan() { echo "Validating installation plan"; } +locale_msg_step_preflight_validate_licenses() { echo "Validating license agreements"; } +locale_msg_step_preflight_validate_port() { echo "Checking port availability"; } +locale_msg_step_preflight_directory() { echo "Preparing server directory"; } +locale_msg_step_preflight_packages() { echo "Installing base packages"; } +locale_msg_step_preflight_java() { echo "Installing Java components"; } +locale_msg_step_preflight_user() { echo "Preparing service user"; } +locale_msg_step_preflight_validate_root() { echo "Checking root privileges"; } +locale_msg_step_preflight_detect_os() { echo "Detecting OS details"; } +locale_msg_step_preflight_validate_support() { echo "Checking support matrix"; } +locale_msg_step_install_core() { echo "Installing core"; } +locale_msg_step_update_core() { echo "Updating core"; } +locale_msg_step_deploy_core() { echo "Deploying core"; } +locale_msg_step_packages_start() { echo "Installing dependencies"; } +locale_msg_step_packages_dry_run() { echo "Dependencies dry-run"; } +locale_msg_step_packages_index() { echo "Refreshing package index"; } +locale_msg_step_download_start() { echo "Downloading artifact"; } +locale_msg_step_download_completed() { echo "Artifact downloaded"; } +locale_msg_step_download_swap() { echo "Swapping updated artifact"; } +locale_msg_step_firewall_start() { echo "Configuring firewall"; } +locale_msg_step_firewall_done() { echo "Firewall configured"; } +locale_msg_step_firewall_manual() { echo "Firewall not auto-configured"; } +locale_msg_step_java_install() { echo "Installing Java runtime"; } +locale_msg_step_render_start_script() { echo "Generating start script"; } +locale_msg_step_chmod_start_script() { echo "Adjusting start script permissions"; } +locale_msg_step_user_create() { echo "Creating service user"; } +locale_msg_step_user_exists() { echo "Service user already exists"; } +locale_msg_step_prepare_dir() { echo "Creating server directory"; } +locale_msg_step_permission_fix() { echo "Applying ownership and permissions"; } +locale_msg_step_service_stop() { echo "Stopping existing service"; } +locale_msg_step_service_create() { echo "Creating service unit"; } +locale_msg_step_service_reload_enable() { echo "Enabling service"; } +locale_msg_step_service_start() { echo "Starting server service"; } +locale_msg_step_core_install() { echo "Core installation start"; } +locale_msg_step_core_resolve_channel() { echo "Resolving distribution channel"; } +locale_msg_step_core_resolve_download() { echo "Resolving download source"; } +locale_msg_step_core_fetch() { echo "Fetching core files"; } +locale_msg_step_core_render() { echo "Rendering core startup files"; } +locale_msg_step_core_phpbuild_resolve() { echo "Resolving PHP build"; } +locale_msg_step_core_phpbuild_fetch() { echo "Downloading PHP build archive"; } +locale_msg_step_core_runtime() { echo "Preparing PHP runtime"; } +locale_msg_step_core_composer() { echo "Preparing Composer"; } +locale_msg_step_core_repo_clone() { echo "Cloning PocketMine-MP source"; } +locale_msg_step_core_configure() { echo "Writing core config files"; } +locale_msg_step_core_post() { echo "Core post-setup"; } +locale_msg_step_core_service() { echo "Preparing service start"; } +locale_msg_step_core_chown() { echo "Adjusting file ownership"; } +locale_msg_step_core_backup() { echo "Backing up existing files"; } +locale_msg_step_core_update_done() { echo "Core update completed"; } + +locale_msg_core_required() { echo "Core is required. Use --core."; } +locale_msg_core_validator_missing() { echo "Core validator missing for $1"; } +locale_msg_server_dir_required() { echo "Server directory is required."; } +locale_msg_server_port_required() { echo "Server port is required."; } +locale_msg_port_must_be_numeric() { echo "Port must be numeric."; } +locale_msg_port_out_of_range() { echo "Port out of range: $1"; } +locale_msg_invalid_service_mode() { echo "Invalid service mode: $1"; } +locale_msg_invalid_firewall_value() { echo "Invalid --firewall value: $1"; } + +locale_msg_prompt_core() { echo "Core (pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc)"; } +locale_msg_prompt_server_directory() { echo "Server directory"; } +locale_msg_prompt_server_port() { echo "Server port"; } +locale_msg_prompt_ram() { echo "RAM"; } +locale_msg_prompt_php_build() { echo "PHP build tag (latest|tag)"; } +locale_msg_prompt_pocketmine_version() { echo "PocketMine-MCPE version/tag (latest|tag)"; } +locale_msg_prompt_service_mode() { echo "Service mode"; } +locale_msg_prompt_open_firewall() { echo "Open firewall (yes/no)"; } +locale_msg_prompt_install_user() { echo "Install user"; } +locale_msg_prompt_invalid_yes_no() { echo "Invalid answer: use yes/no"; } +locale_msg_easy_plan_title() { echo "Easy mode enabled. Plan:"; } +locale_msg_easy_plan_core() { echo "Core: $1"; } +locale_msg_easy_plan_dir() { echo "Directory: $1"; } +locale_msg_easy_plan_port() { echo "Port: $1"; } +locale_msg_easy_plan_ram() { echo "RAM: $1"; } +locale_msg_easy_plan_service() { echo "Service: $1"; } +locale_msg_easy_plan_user() { echo "Service user: $1"; } +locale_msg_easy_plan_firewall() { echo "Firewall: $1"; } +locale_msg_easy_plan_build() { echo "PHP build: $1"; } +locale_msg_easy_plan_mc_version() { echo "PocketMine-MCPE tag: $1"; } +locale_msg_easy_plan_eula() { echo "EULA: $1"; } + +locale_msg_dir_not_empty_auto() { echo "Directory '$1' is not empty. Add --allow-existing, --backup-existing or --force-clean."; } +locale_msg_dir_not_empty() { echo "Directory '$1' is not empty"; } +locale_msg_dir_not_empty_actions() { echo "1) cancel, 2) use existing, 3) backup existing, 4) force clean: "; } +locale_msg_dir_backed_up() { echo "Existing server backed up to $1"; } +locale_msg_action_cancelled_by_user() { echo "Installation cancelled by user"; } +locale_msg_dir_invalid_option() { echo "Invalid option"; } +locale_msg_backed_up_file() { echo "Backed up existing file: $1 -> $2"; } +locale_msg_file_backed_up() { echo "Backed up existing file: $1 -> $2"; } +locale_msg_post_install_callback_missing() { echo "Post-install callback missing for core $1"; } +locale_msg_install_completed() { echo "Installation completed for $1"; } +locale_msg_result_server_dir() { echo "Server directory: $1"; } +locale_msg_result_service_mode() { echo "Service mode: $1"; } +locale_msg_result_service_name() { echo "Service name: $1"; } +locale_msg_result_log_file() { echo "Log file: $1"; } +locale_msg_install_finished() { echo "Installation finished"; } +locale_msg_core_install() { echo "Installing $1..."; } +locale_msg_core_update() { echo "Updating $1..."; } +locale_msg_core_name_java_core() { echo "Java core $1"; } +locale_msg_no_stable_build() { echo "No stable build found for ${1} ${2}"; } +locale_msg_detect_os_failed() { echo "Cannot detect OS: /etc/os-release not found"; } +locale_msg_no_package_manager() { echo "No supported package manager found"; } +locale_msg_java_already_available() { echo "Java ${1} already available"; } +locale_msg_installing_java_21() { echo "Installing Java 21"; } +locale_msg_installing_base_packages() { echo "Installing base packages"; } +locale_msg_dry_run_install() { echo "[dry-run] apt/yum/dnf/pacman install: $1"; } +locale_msg_package_manager_unsupported() { echo "Package manager ${1} is not supported"; } +locale_msg_firewall_skip() { echo "Firewall step skipped"; } +locale_msg_firewall_manual_open() { echo "No supported firewall manager found. Open port manually: $1/$2"; } +locale_msg_report_dry_run() { echo "Skipping report in dry-run: $1"; } +locale_msg_report_failed() { echo "Report callback failed and was ignored"; } +locale_msg_deployment_failed() { echo "Deployment failed (code $1) at: $2"; } +locale_msg_rollback_starts() { echo "Rollback starts. Server directory is intentionally preserved: $1"; } +locale_msg_see_log() { echo "See log: $1"; } +locale_msg_service_create_skipped() { echo "Skipping service creation (mode=$1, init=$2)"; } +locale_msg_service_start_skipped() { echo "Service start skipped (mode: $1)"; } +locale_msg_refuse_root_user() { echo "Refusing to use root as server user"; } +locale_msg_creating_user() { echo "Creating user $1"; } +locale_msg_dry_run_useradd() { echo "[dry-run] useradd -r -m -s /bin/bash $1"; } + +locale_msg_update_requires_existing_dir() { echo "Update mode requires existing directory."; } +locale_msg_update_requires_non_empty_dir() { echo "Update mode requires non-empty server directory."; } + +locale_msg_root_required() { echo "Run this script as root (example: sudo bash install.sh ...)"; } +locale_msg_unsupported_core() { echo "Unsupported core '$1'. Supported now: $2. Full matrix in $3"; } +locale_msg_os_coreplanned() { echo "OS '$1' is planned for v1.1 (manifest: $2). Proceeding best-effort."; } +locale_msg_unsupported_os() { echo "Unsupported OS '$1'. Supported now: $2. Planned in future: $3. See $4"; } +locale_msg_tcp_port_in_use() { echo "TCP port $1 is already in use."; } +locale_msg_udp_port_in_use() { echo "UDP port $1 is already in use."; } diff --git a/locales/ru.sh b/locales/ru.sh new file mode 100644 index 0000000..899cb27 --- /dev/null +++ b/locales/ru.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +locale_msg_unknown_argument() { echo "Неизвестный аргумент: $1"; } +locale_msg_self_update_not_implemented() { echo "Self-update пока не реализован в v0.1.0"; } + +locale_msg_license_title() { echo "Лицензионное соглашение AstraDeploy"; } +locale_msg_license_intro() { echo "Ниже приведены важные условия запуска утилиты."; } +locale_msg_license_item_1() { echo "- Утилита выполняет команды с повышенными привилегиями (root/sudo)."; } +locale_msg_license_item_2() { echo "- Утилита может изменять систему: пакеты, службы, firewall, пользователя и файлы."; } +locale_msg_license_item_3() { echo "- Перед обновлением или заменой файлов рекомендуется сделать резервную копию."; } +locale_msg_license_item_4() { echo "- Ответственность за лицензии ядра и данные сервера лежит на вас."; } +locale_msg_license_item_5() { echo "- AstraDeploy не хранит и не отвечает за ваши данные и их потерю."; } +locale_msg_license_item_6() { echo "- Соглашение: AstraDeploy поставляется «как есть», без каких-либо гарантий, и вы используете его на свой риск."; } +locale_msg_log_title() { echo "AstraDeploy v$1"; } +locale_msg_log_file() { echo "Файл журнала: $1"; } +locale_msg_license_prompt() { echo "Продолжить установку после принятия условий?"; } +locale_msg_license_denied() { echo "Установка остановлена: условия не приняты."; } +locale_msg_eula_prompt() { echo "Подтверждаете принятие Minecraft EULA для Java ядер (Paper/Purpur/Velocity/PowerNukkitX)?"; } +locale_msg_eula_denied() { echo "Без принятия EULA установка Java-сервера невозможна."; } +locale_msg_progress_step() { echo "Шаг $1: $2"; } +locale_msg_step_parse_complete() { echo "Аргументы обработаны"; } +locale_msg_step_prepare_defaults() { echo "Применены параметры по умолчанию"; } +locale_msg_step_preflight_start() { echo "Запуск предварительных проверок"; } +locale_msg_step_install_start() { echo "Начало установки"; } +locale_msg_step_finalize() { echo "Завершение"; } +locale_msg_step_preflight_begin() { echo "Старт preflight"; } +locale_msg_step_preflight_detect() { echo "Определение ОС и менеджера пакетов"; } +locale_msg_step_preflight_update_mode_check() { echo "Проверка параметров режима обновления"; } +locale_msg_step_preflight_validate_plan() { echo "Проверка плана установки"; } +locale_msg_step_preflight_validate_licenses() { echo "Проверка соглашений"; } +locale_msg_step_preflight_validate_port() { echo "Проверка свободного порта"; } +locale_msg_step_preflight_directory() { echo "Подготовка каталога сервера"; } +locale_msg_step_preflight_packages() { echo "Установка базовых пакетов"; } +locale_msg_step_preflight_java() { echo "Подготовка компонентов Java"; } +locale_msg_step_preflight_user() { echo "Подготовка пользователя сервиса"; } +locale_msg_step_preflight_validate_root() { echo "Проверка запуска от root"; } +locale_msg_step_preflight_detect_os() { echo "Определение параметров ОС"; } +locale_msg_step_preflight_validate_support() { echo "Проверка матрицы поддерживаемых версий"; } +locale_msg_step_install_core() { echo "Установка ядра"; } +locale_msg_step_update_core() { echo "Обновление ядра"; } +locale_msg_step_deploy_core() { echo "Развёртывание ядра"; } +locale_msg_step_packages_start() { echo "Установка зависимостей"; } +locale_msg_step_packages_dry_run() { echo "Симуляция установки зависимостей"; } +locale_msg_step_packages_index() { echo "Обновление индекса пакетов"; } +locale_msg_step_download_start() { echo "Загрузка артефакта"; } +locale_msg_step_download_completed() { echo "Артефакт загружен"; } +locale_msg_step_download_swap() { echo "Замена артефакта"; } +locale_msg_step_firewall_start() { echo "Настройка firewall"; } +locale_msg_step_firewall_done() { echo "Firewall настроен"; } +locale_msg_step_firewall_manual() { echo "Firewall не настроен автоматически"; } +locale_msg_step_java_install() { echo "Установка Java runtime"; } +locale_msg_step_render_start_script() { echo "Генерация стартового скрипта"; } +locale_msg_step_chmod_start_script() { echo "Настройка прав скрипта запуска"; } +locale_msg_step_user_create() { echo "Создание сервисного пользователя"; } +locale_msg_step_user_exists() { echo "Сервисный пользователь уже существует"; } +locale_msg_step_prepare_dir() { echo "Создание каталога сервера"; } +locale_msg_step_permission_fix() { echo "Применение владельца и прав"; } +locale_msg_step_service_stop() { echo "Остановка существующего сервиса"; } +locale_msg_step_service_create() { echo "Создание unit-файла"; } +locale_msg_step_service_reload_enable() { echo "Активация сервиса"; } +locale_msg_step_service_start() { echo "Запуск сервиса сервера"; } +locale_msg_step_core_install() { echo "Старт установки ядра"; } +locale_msg_step_core_resolve_channel() { echo "Определение канала выпуска"; } +locale_msg_step_core_resolve_download() { echo "Поиск источника загрузки"; } +locale_msg_step_core_fetch() { echo "Загрузка файлов ядра"; } +locale_msg_step_core_render() { echo "Генерация файлов запуска"; } +locale_msg_step_core_phpbuild_resolve() { echo "Определение PHP build"; } +locale_msg_step_core_phpbuild_fetch() { echo "Загрузка архива PHP build"; } +locale_msg_step_core_runtime() { echo "Подготовка PHP runtime"; } +locale_msg_step_core_composer() { echo "Подготовка Composer"; } +locale_msg_step_core_repo_clone() { echo "Клонирование источника PocketMine-MP"; } +locale_msg_step_core_configure() { echo "Запись конфигураций ядра"; } +locale_msg_step_core_post() { echo "Пост-настройка ядра"; } +locale_msg_step_core_service() { echo "Подготовка запуска сервиса"; } +locale_msg_step_core_chown() { echo "Настройка владения файлами"; } +locale_msg_step_core_backup() { echo "Резервное копирование файлов"; } +locale_msg_step_core_update_done() { echo "Обновление ядра завершено"; } + +locale_msg_core_required() { echo "Необходимо указать ядро через --core."; } +locale_msg_core_validator_missing() { echo "Для ядра $1 отсутствует валидатор."; } +locale_msg_server_dir_required() { echo "Требуется указать каталог сервера."; } +locale_msg_server_port_required() { echo "Требуется указать порт сервера."; } +locale_msg_port_must_be_numeric() { echo "Порт должен быть числом."; } +locale_msg_port_out_of_range() { echo "Порт вне диапазона: $1"; } +locale_msg_invalid_service_mode() { echo "Неподдерживаемый режим сервиса: $1"; } +locale_msg_invalid_firewall_value() { echo "Недопустимое значение --firewall: $1"; } + +locale_msg_prompt_core() { echo "Ядро (pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc)"; } +locale_msg_prompt_server_directory() { echo "Каталог сервера"; } +locale_msg_prompt_server_port() { echo "Порт сервера"; } +locale_msg_prompt_ram() { echo "RAM"; } +locale_msg_prompt_php_build() { echo "Тег PHP-билда (latest|tag)"; } +locale_msg_prompt_pocketmine_version() { echo "Версия/тег PocketMine-MCPE (latest|tag)"; } +locale_msg_prompt_service_mode() { echo "Режим сервиса"; } +locale_msg_prompt_open_firewall() { echo "Открыть firewall (yes/no)"; } +locale_msg_prompt_install_user() { echo "Пользователь для установки"; } +locale_msg_prompt_invalid_yes_no() { echo "Неверный ответ: используйте yes/no"; } +locale_msg_easy_plan_title() { echo "Режим easy активирован. План установки:"; } +locale_msg_easy_plan_core() { echo "Ядро: $1"; } +locale_msg_easy_plan_dir() { echo "Каталог: $1"; } +locale_msg_easy_plan_port() { echo "Порт: $1"; } +locale_msg_easy_plan_ram() { echo "RAM: $1"; } +locale_msg_easy_plan_service() { echo "Сервис: $1"; } +locale_msg_easy_plan_user() { echo "Пользователь сервиса: $1"; } +locale_msg_easy_plan_firewall() { echo "Firewall: $1"; } +locale_msg_easy_plan_build() { echo "PHP-билд: $1"; } +locale_msg_easy_plan_mc_version() { echo "Версия/тег PocketMine-MCPE: $1"; } +locale_msg_easy_plan_eula() { echo "EULA: $1"; } + +locale_msg_dir_not_empty_auto() { echo "Каталог '$1' не пустой. Укажите --allow-existing, --backup-existing или --force-clean."; } +locale_msg_dir_not_empty() { echo "Каталог '$1' не пустой"; } +locale_msg_dir_not_empty_actions() { echo "1) отмена, 2) использовать существующий, 3) создать backup, 4) очистить: "; } +locale_msg_dir_backed_up() { echo "Существующий сервер сохранён как $1"; } +locale_msg_action_cancelled_by_user() { echo "Установка отменена пользователем"; } +locale_msg_dir_invalid_option() { echo "Неверный вариант"; } +locale_msg_backed_up_file() { echo "Файл скопирован: $1 -> $2"; } +locale_msg_file_backed_up() { echo "Файл скопирован: $1 -> $2"; } +locale_msg_post_install_callback_missing() { echo "Отсутствует шаг пост-установки для ядра $1"; } +locale_msg_install_completed() { echo "Установка завершена для $1"; } +locale_msg_result_server_dir() { echo "Каталог сервера: $1"; } +locale_msg_result_service_mode() { echo "Режим сервиса: $1"; } +locale_msg_result_service_name() { echo "Имя сервиса: $1"; } +locale_msg_result_log_file() { echo "Файл журнала: $1"; } +locale_msg_install_finished() { echo "Установка завершена"; } +locale_msg_core_install() { echo "Установка $1..."; } +locale_msg_core_update() { echo "Обновление $1..."; } +locale_msg_core_name_java_core() { echo "Java ядро $1"; } +locale_msg_no_stable_build() { echo "Не найден стабильный build для ${1} ${2}"; } +locale_msg_detect_os_failed() { echo "Не удалось определить ОС: файл /etc/os-release отсутствует"; } +locale_msg_no_package_manager() { echo "Поддерживаемый менеджер пакетов не найден"; } +locale_msg_java_already_available() { echo "Java ${1} уже установлена"; } +locale_msg_installing_java_21() { echo "Установка Java 21"; } +locale_msg_installing_base_packages() { echo "Установка базовых пакетов"; } +locale_msg_dry_run_install() { echo "[dry-run] apt/yum/dnf/pacman install: $1"; } +locale_msg_package_manager_unsupported() { echo "Менеджер пакетов ${1} не поддерживается"; } +locale_msg_firewall_skip() { echo "Шаг настройки firewall пропущен"; } +locale_msg_firewall_manual_open() { echo "Не найден поддерживаемый менеджер firewall. Откройте порт вручную: $1/$2"; } +locale_msg_report_dry_run() { echo "Пропуск отправки отчёта в dry-run: $1"; } +locale_msg_report_failed() { echo "Не удалось отправить отчет в callback, ошибка проигнорирована"; } +locale_msg_deployment_failed() { echo "Установка завершилась ошибкой (код $1) на шаге: $2"; } +locale_msg_rollback_starts() { echo "Запущен rollback. Каталог сервера не удаляется: $1"; } +locale_msg_see_log() { echo "См. лог: $1"; } +locale_msg_service_create_skipped() { echo "Создание сервиса пропущено (mode=$1, init=$2)"; } +locale_msg_service_start_skipped() { echo "Запуск сервиса пропущен (mode: $1)"; } +locale_msg_refuse_root_user() { echo "Использование root как сервисного пользователя запрещено"; } +locale_msg_creating_user() { echo "Создание пользователя $1"; } +locale_msg_dry_run_useradd() { echo "[dry-run] useradd -r -m -s /bin/bash $1"; } + +locale_msg_update_requires_existing_dir() { echo "Для обновления нужен существующий каталог сервера."; } +locale_msg_update_requires_non_empty_dir() { echo "Для обновления нужен непустой каталог сервера."; } + +locale_msg_root_required() { echo "Запустите скрипт от root (пример: sudo bash install.sh ...)"; } +locale_msg_unsupported_core() { echo "Ядро '$1' не поддерживается. Доступны: $2. Полный список: $3"; } +locale_msg_os_coreplanned() { echo "ОС '$1' запланирована к поддержке в v1.1 (манифест: $2). Применяется best-effort."; } +locale_msg_unsupported_os() { echo "ОС '$1' не поддерживается. Сейчас поддерживается: $2. В планах: $3. См. $4"; } +locale_msg_tcp_port_in_use() { echo "TCP порт $1 уже занят."; } +locale_msg_udp_port_in_use() { echo "UDP порт $1 уже занят."; } diff --git a/manifests/cores.json b/manifests/cores.json new file mode 100644 index 0000000..1d11822 --- /dev/null +++ b/manifests/cores.json @@ -0,0 +1,66 @@ +{ + "pmmp": { + "name": "PocketMine-MP", + "edition": "bedrock", + "runtime": "php", + "default_port": 19132, + "protocol": "udp", + "default_dir": "/opt/minecraft/pmmp", + "supports_protocols": ["udp"], + "supported_services": ["systemd", "tmux-systemd", "none"] + }, + "paper": { + "name": "Paper", + "edition": "java", + "runtime": "java", + "java_version": 21, + "default_port": 25565, + "protocol": "tcp", + "default_dir": "/opt/minecraft/paper", + "supports_protocols": ["tcp"], + "supported_services": ["systemd", "tmux-systemd", "none"] + }, + "purpur": { + "name": "Purpur", + "edition": "java", + "runtime": "java", + "java_version": 21, + "default_port": 25565, + "protocol": "tcp", + "default_dir": "/opt/minecraft/purpur", + "supports_protocols": ["tcp"], + "supported_services": ["systemd", "tmux-systemd", "none"] + }, + "velocity": { + "name": "Velocity", + "edition": "proxy", + "runtime": "java", + "java_version": 21, + "default_port": 25577, + "protocol": "tcp", + "default_dir": "/opt/minecraft/velocity", + "supports_protocols": ["tcp"], + "supported_services": ["systemd", "tmux-systemd", "none"] + }, + "powernukkitx": { + "name": "PowerNukkitX", + "edition": "bedrock", + "runtime": "java", + "java_version": 21, + "default_port": 19132, + "protocol": "udp", + "default_dir": "/opt/minecraft/powernukkitx", + "supports_protocols": ["udp"], + "supported_services": ["systemd", "tmux-systemd", "none"] + }, + "nethergamesmc": { + "name": "NetherGamesMC", + "edition": "bedrock", + "runtime": "php", + "default_port": 19132, + "protocol": "udp", + "default_dir": "/opt/minecraft/nethergamesmc", + "supports_protocols": ["udp"], + "supported_services": ["systemd", "tmux-systemd", "screen", "none"] + } +}