From c769140913a8f9048fcd38b9d7b77d902e0baa51 Mon Sep 17 00:00:00 2001 From: Andrewkydev Date: Sun, 31 May 2026 14:01:34 +0300 Subject: [PATCH] feat: add bds core, locale updates and safety fixes --- CHANGELOG.md | 14 +- README.md | 22 ++- README.ru.md | 24 ++- VERSION | 1 + bot-integration-fsm-plan.md | 291 ++++++++++++++++++++++++++++++++++ cores/bds.sh | 152 ++++++++++++++++++ cores/paper.sh | 59 +++++++ cores/pmmp.sh | 62 ++++++++ cores/powernukkitx.sh | 65 ++++++++ cores/purpur.sh | 59 +++++++ cores/velocity.sh | 65 ++++++++ docs/USAGE.md | 27 +++- docs/USAGE.ru.md | 27 +++- install.sh | 10 +- lib/detect.sh | 33 ++++ lib/download.sh | 65 ++++++++ lib/firewall.sh | 30 ++++ lib/i18n.sh | 50 ++++++ lib/java.sh | 77 +++++++++ lib/logger.sh | 38 +++++ lib/packages.sh | 34 ++++ lib/report.sh | 72 +++++++++ lib/rollback.sh | 30 ++++ lib/services.sh | 132 +++++++++++++++ lib/users.sh | 31 ++++ lib/validation.sh | 4 +- locales/en.sh | 4 +- locales/ru.sh | 4 +- manifests/cores.json | 10 ++ manifests/os-support.json | 15 ++ templates/service-systemd.tpl | 15 ++ templates/service-tmux.tpl | 17 ++ templates/start-java.tpl | 4 + templates/start-pmmp.tpl | 4 + 34 files changed, 1535 insertions(+), 12 deletions(-) create mode 100644 VERSION create mode 100644 bot-integration-fsm-plan.md create mode 100644 cores/bds.sh create mode 100644 cores/paper.sh create mode 100644 cores/pmmp.sh create mode 100644 cores/powernukkitx.sh create mode 100644 cores/purpur.sh create mode 100644 cores/velocity.sh create mode 100644 lib/detect.sh create mode 100644 lib/download.sh create mode 100644 lib/firewall.sh create mode 100644 lib/i18n.sh create mode 100644 lib/java.sh create mode 100644 lib/logger.sh create mode 100644 lib/packages.sh create mode 100644 lib/report.sh create mode 100644 lib/rollback.sh create mode 100644 lib/services.sh create mode 100644 lib/users.sh create mode 100644 manifests/os-support.json create mode 100644 templates/service-systemd.tpl create mode 100644 templates/service-tmux.tpl create mode 100644 templates/start-java.tpl create mode 100644 templates/start-pmmp.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index be45d42..b45e3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to AstraDeploy are documented here. +## [Unreleased] + +### Added +- Bedrock Dedicated Server (`bds`) core support: + - `--core bds` + - version-aware install via official Bedrock download source + - `.bds-version` marker and update flow support +- Additional UI polish for beginner mode with BDS version prompt. + +### Fixed +- Improved `--force-clean` cleanup to clear hidden entries in server directories. +- Safer service unit generation for start-script paths containing special characters. + ## [v0.1.0] - 2026-05-31 ### Added @@ -39,4 +52,3 @@ All notable changes to AstraDeploy are documented here. ### Known limitations - Support matrix for OS/package-manager is intentionally MVP-focused (mainly Debian/Ubuntu family first, with extension points for others). - `--update` path is implemented per-core where available. - diff --git a/README.md b/README.md index 0969fdf..afb1747 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Current version: `v0.1.0`. | `velocity` | `/opt/minecraft/velocity` | `25577` | Java 21 | TCP | Requires Minecraft Java EULA acceptance | | `powernukkitx` | `/opt/minecraft/powernukkitx` | `19132` | Java 21 | UDP | Requires Minecraft Java EULA acceptance | | `nethergamesmc` | `/opt/minecraft/nethergamesmc` | `19132` | PHP | UDP | Prompts for PHP build and PocketMine-MCPE version | +| `bds` | `/opt/minecraft/bds` | `19132` | native | UDP | Official Bedrock Dedicated Server zip binary | > Service mode `screen` is available and handled in the service layer with the current installer version. @@ -112,9 +113,10 @@ Run output is mirrored with `tee` and saved to: ### Core selection and versions -- `--core ` +- `--core ` - `--mc-version ` - `--build ` (for `nethergamesmc`, PHP build) +- `--mc-version` for `bds` resolves required Bedrock package version (`latest` allowed) - legacy aliases: `--version`, `--core-version` for MC version ### Directory and runtime @@ -167,6 +169,24 @@ sudo bash install.sh \ 3. `PocketMine-MP` clone/build flow 4. Server startup script generation +## Example for BDS + +```bash +sudo bash install.sh \ + --easy \ + --core bds \ + --dir /opt/minecraft/bds \ + --mc-version latest \ + --service tmux-systemd +``` + +`bds` flow includes: + +1. resolve version from official Bedrock download page or provided tag; +2. download `bedrock-server-.zip`; +3. extract/update server files; +4. generate `start.sh` and configure service. + ## Update mode `--update` switches workflow to update mode. The installer validates existing directory first and replaces server artifacts via the core-specific update path where supported. diff --git a/README.ru.md b/README.ru.md index e2c4a44..36a0d5d 100644 --- a/README.ru.md +++ b/README.ru.md @@ -46,6 +46,7 @@ | `velocity` | `/opt/minecraft/velocity` | `25577` | Java 21 | TCP | Нужна явная обработка/подтверждение EULA Java | | `powernukkitx` | `/opt/minecraft/powernukkitx` | `19132` | Java 21 | UDP | Нужна явная обработка/подтверждение EULA Java | | `nethergamesmc` | `/opt/minecraft/nethergamesmc` | `19132` | PHP | UDP | Доп. вопросы по PHP build и версии PocketMine-MCPE | +| `bds` | `/opt/minecraft/bds` | `19132` | native | UDP | Официальный бинарник Bedrock Dedicated Server | > Режим `screen` доступен в текущей версии и настраивается через слой сервисов. @@ -112,9 +113,12 @@ sudo bash install.sh \ ### Ядро и версии -- `--core ` +- `--core ` - `--mc-version ` - `--build ` (для `nethergamesmc`, версия PHP) +- `--mc-version` для `bds` и `nethergamesmc`: + - `nethergamesmc`: версия/тег PocketMine-MCPE + - `bds`: версия Bedrock Dedicated Server (`latest` определяется автоматически) - legacy алиасы: `--version`, `--core-version` ### Каталог и рантайм @@ -167,6 +171,24 @@ sudo bash install.sh \ 3. клонирование `PocketMine-MP`; 4. сборка/подготовка запуска. +## Пример для BDS + +```bash +sudo bash install.sh \ + --easy \ + --core bds \ + --dir /opt/minecraft/bds \ + --mc-version latest \ + --service tmux-systemd +``` + +Путь для `bds`: + +1. разрешение версии Bedrock; +2. скачивание `bedrock-server-<версия>.zip`; +3. разархивация и обновление файлов сервера; +4. генерация `start.sh` и настройка сервиса. + ## Режим обновления `--update` переводит в режим обновления. Сначала проверяется существование и непустота директории, затем выполняется core-специфичный апдейт. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/bot-integration-fsm-plan.md b/bot-integration-fsm-plan.md new file mode 100644 index 0000000..f65290b --- /dev/null +++ b/bot-integration-fsm-plan.md @@ -0,0 +1,291 @@ +# План интеграции AstraDeploy и бота (FSM + callback API) + +## Цели + +Сделать наблюдаемую, устойчивую и воспроизводимую установку через бота с поэтапными статусами. + +Скрипт уже умеет отдавать: +- `started` +- `step` +- `success` +- `failed` + +Нужно оборачивать это в `FSM`-движок на бэкенде бота. + +## 1. Точка входа callback + +### Endpoint +- `POST /astra-deploy/report` +- Заголовок: `Authorization: Bearer ` +- Content-Type: `application/json` + +### Тело (из скрипта) +```json +{ + "status": "step", + "core": "paper", + "dir": "/opt/minecraft/paper", + "port": 25565, + "protocol": "tcp", + "service": "tmux-systemd", + "message": "Installing base packages", + "step": "preflight_packages", + "step_index": 14, + "step_total": 80, + "step_percent": 17 +} +``` + +### Рекомендуемый расширенный payload (бот-раннер может добавить) +```json +{ + "request_id": "uuid", + "job_id": "job_123", + "user_id": "123", + "locale": "ru" +} +``` + +### Ответ +- `200/204` с `{ "ok": true }` или пустым телом. +- Идемпотентность по `(request_id, status, step, step_index, step_total, ts_sec)` или аналогичной паре. + +## 2. Запуск задачи из бота + +Ключевые параметры запускаемого процесса: +- `--core ...` +- `--dir ...` +- `--port ...` +- `--service systemd|tmux-systemd|screen|none` +- `--user ...` +- `--firewall yes|no` +- `--yes` +- `--accept-license yes` (обязательно в автоматическом) +- `--accept-eula yes` для Java-ядер +- `--report-url ...` +- `--report-token ...` +- `--locale ru|en` (или `LANGUAGE`/`LANG`) +- `--dry-run`, `--update`, `--force-clean`, `--backup-existing`, `--allow-existing` +- `--build`, `--mc-version` для `nethergamesmc` + +## 3. FSM состояния + +### Состояния +- `QUEUED` +- `RUNNING` +- `VALIDATION` +- `PREPARE_ENV` +- `CORE_INSTALL` +- `POST_INSTALL` +- `WAITING_INPUT` +- `ROLLBACK` +- `DONE` +- `FAILED` +- `DRY_RUN` +- `STALLED` (таймаут между шагами) + +### Переходы +- `started` -> `RUNNING` +- `step(parse_complete|prepare_defaults|preflight_*)` -> `VALIDATION` +- `step(preflight_packages|preflight_java|preflight_user|preflight_directory)` -> `PREPARE_ENV` +- `step(install_core|deploy_core|update_core|core_*)` -> `CORE_INSTALL` +- `step(core_post|core_service|service_*|finalize)` -> `POST_INSTALL` +- `status=success` -> `DONE` +- `status=failed` -> `FAILED` +- `--dry-run` (локально определено) -> `DRY_RUN` +- долгий таймаут без новых step -> `STALLED` +- interactive без автоматических флагов и требующие ответа -> `WAITING_INPUT` + +### Идёмпотентная обработка +- Игнорировать дубликаты последнего шага в том же `request_id`. +- Обновлять состояние только если новый `step_index` выше предыдущего или статус изменился на более поздний. + +## 4. Полная карта `step` + +### Общая фаза и предзапуск +- `parse_complete` +- `prepare_defaults` +- `preflight_start` +- `install_start` +- `finalize` +- `core_runtime` +- `core_install` +- `core_update` + +### Предпроверки +- `preflight_begin` +- `preflight_validate_root` +- `preflight_detect` +- `preflight_detect_os` +- `preflight_validate_support` +- `preflight_update_mode_check` +- `preflight_validate_plan` +- `preflight_validate_licenses` +- `preflight_validate_port` +- `preflight_directory` +- `preflight_packages` +- `preflight_java` +- `preflight_user` + +### Установка/обновление ядер +- `core_resolve_channel` +- `core_resolve_download` +- `core_fetch` +- `core_render` +- `core_configure` +- `core_post` +- `core_service` +- `core_chown` +- `core_backup` +- `core_update_done` +- `core_phpbuild_resolve` +- `core_phpbuild_fetch` +- `core_composer` +- `core_repo_clone` +- `core_install` +- `install_core` +- `deploy_core` +- `update_core` + +### Системные шаги +- `packages_start` +- `packages_dry_run` +- `packages_index` +- `download_start` +- `download_completed` +- `download_swap` +- `firewall_start` +- `firewall_done` +- `firewall_manual` +- `java_install` +- `render_start_script` +- `chmod_start_script` +- `service_stop` +- `service_create` +- `service_reload_enable` +- `service_start` +- `user_create` +- `user_exists` +- `prepare_dir` +- `permission_fix` + +## 5. Маппинг на UX-сообщения (ru/en) + +### RU +- `preflight_*` → «Подготовка окружения и проверок» +- `preflight_packages` / `packages_*` → «Установка базовых пакетов» +- `core_*` → «Установка и настройка ядра» +- `service_*`, `core_service` → «Настройка автозапуска» +- `core_chown` / `prepare_dir` / `permission_fix` → «Настройка доступа» +- `success` → «Установка завершена» +- `failed` → «Ошибка установки» + +### EN +- `preflight_*` → `Environment preflight` +- `core_*` → `Core installation` +- `service_*` → `Service setup` +- `failed` → `Deployment failed` + +## 6. Edge-cases по сценариям запуска + +- **Interactive (без `--yes`)** + - может остановиться на подтверждении лицензии/ЕULA/директории + - статус `WAITING_INPUT` +- **`--easy`** + - обычный процесс, добавляется план-сводка до preflight +- **`--update`** + - `step_total` уменьшается на 6, проверяется существующая непустая директория +- **`--dry-run`** + - физически без изменений, callback может отсутствовать (как реализовано сейчас) +- **`--force-clean`** + - удаление содержимого директории перед установкой +- **`--backup-existing`** + - резервная копия `*.backup-` +- **`--allow-existing`** + - пропуск проверки непустой директории +- **Неправильный `core` / OS** + - жёсткий fail с объяснением +- **Порт занят** + - fail с деталями `tcp/udp` и предложением сменить порт +- **Сбой callback API** + - для скрипта не фатальный; бот не должен переводить задачу в `FAILED` из-за 5xx webhook-а + +## 7. Rollback и пост-факту + +На фатальной ошибке скрипт делает: +- лог ошибки +- попытку удаления созданного сервиса (если был создан) +- сообщение с путём лога (`/var/log/astra-deploy/...`) +- завершение с кодом ошибки + +Для бота: +- статусы -> `ROLLBACK` (внутренний подстейт) +- показать пользователю: + - возможная причина + - путь лога + - безопасный список ручных действий + +## 8. Минимальная модель хранения + +### `jobs` +- `id` +- `user_id` +- `core`, `dir`, `port`, `service`, `locale` +- `fsm_state` +- `status` +- `percent` +- `step`, `step_index`, `step_total`, `message` +- `request_id`, `report_token_id`, `process_pid` +- `created_at`, `updated_at` + +### `job_events` +- `job_id` +- `status`, `step`, `step_index`, `step_total`, `step_percent` +- `message` +- `raw_payload` (json) +- `created_at` + +## 9. Шаблоны уведомлений + +### Telegram (RU) +1) Запуск: `🚀 Установка AstraDeploy запущена` +2) Шаг: `⚙️ {percent}% | {state} | {step}` +3) Успех: `✅ Установка завершена` +4) Ошибка: `❌ Установка завершена с ошибкой` + +### Telegram (EN) +1) `🚀 AstraDeploy started` +2) `⚙️ {percent}% | {state} | {step}` +3) `✅ Installation completed` +4) `❌ Installation failed` + +Кнопки: +- `Получить статус` +- `Логи` +- `Запустить проверку` +- `Повторить (dry-run)` + +## 10. Безопасность + +- Токены webhook не логировать. +- Проверять `Authorization` строго. +- Проверка whitelist по разрешённым `step`. +- Rate limit на report endpoint. +- Защищённое хранение токенов (vault/secret/env). + +## 11. Проверочный набор для интеграции + +- `paper` non-interactive, валидные флаги +- `paper` без `--accept-eula` и с `--yes` -> `failed` +- `nethergamesmc --dry-run` -> без изменений, шаги в локальном логе +- занятый порт -> `failed` +- `--update` на пустой директории -> `failed` +- `--force-clean` на непустой директории -> success после очистки +- callback 5xx -> установка продолжается, без ложного FAILED в UI + +## 12. Что отдаёт разработчику бота на старте + +- список поддерживаемых `core`: `pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc` +- поддерживаемые режимы сервиса: `systemd`, `tmux-systemd`, `screen`, `none` +- список ключевых шагов из этого плана выше +- статусные правила и fallback-поведение для отсутствующего callback diff --git a/cores/bds.sh b/cores/bds.sh new file mode 100644 index 0000000..c03b728 --- /dev/null +++ b/cores/bds.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +core_name_bds() { + echo "Bedrock Dedicated Server" +} + +core_default_port_bds() { + echo "19132" +} + +core_protocol_bds() { + echo "udp" +} + +core_runtime_bds() { + echo "native" +} + +core_validate_bds() { + if [[ -z "${MC_VERSION:-}" ]]; then + MC_VERSION="latest" + fi +} + +bds_resolve_latest_download_url() { + local page + local url + + page="$(http_get "https://www.minecraft.net/en-us/download/server/bedrock")" + if [[ -z "$page" ]]; then + fail "$(t no_stable_build "Bedrock Dedicated Server" "latest")" + fi + + url="$(printf '%s\n' "$page" | grep -oE "https://minecraft\\.azureedge\\.net/bin-linux/bedrock-server-[^\" ]+\\.zip" | head -n 1)" + if [[ -z "$url" ]]; then + fail "$(t no_stable_build "Bedrock Dedicated Server" "latest")" + fi + + echo "$url" +} + +bds_resolve_download_url() { + local requested="${1:-latest}" + + if [[ "$requested" == "latest" ]]; then + bds_resolve_latest_download_url + return + fi + + if [[ "$requested" == http* ]]; then + echo "$requested" + return + fi + + echo "https://minecraft.azureedge.net/bin-linux/bedrock-server-${requested}.zip" +} + +bds_resolve_version_from_url() { + local url="$1" + local requested="${2:-latest}" + local file + + file="${url##*/}" + file="${file#bedrock-server-}" + file="${file%.zip*}" + file="${file%%\?*}" + + if [[ -z "$file" || "$file" == "$requested" ]]; then + echo "$requested" + return + fi + + echo "$file" +} + +bds_render_start_script() { + local dir="$SERVER_DIR" + emit_step "core_render" "bds start script" + run_cmd bash -c "cat > '$dir/start.sh' </dev/null || true)" + if [[ "$UPDATE_MODE" == "no" && -n "$existing_version" && "$existing_version" == "$version" && -f "$SERVER_DIR/bedrock_server" ]]; then + info "BDS already at requested version ${version}. Skipping binary download." + run_cmd chmod +x "$SERVER_DIR/bedrock_server" + bds_render_start_script + return + fi + fi + + run_cmd rm -f "$archive" + download_as_user "$download_url" "$archive" + emit_step "core_fetch" "Bedrock server archive" + run_cmd unzip -o -q "$archive" -d "$SERVER_DIR" + run_cmd rm -f "$archive" + + if [[ ! -f "$SERVER_DIR/bedrock_server" ]]; then + fail "bedrock_server not found after extraction" + fi + + run_cmd chmod +x "$SERVER_DIR/bedrock_server" + bds_render_start_script + run_cmd bash -lc "printf '%s\n' '$version' > '$SERVER_DIR/.bds-version'" + emit_step "core_configure" "BDS version ${version}" +} + +core_post_install_bds() { + emit_step "core_post" "BDS" + run_cmd bash -lc "chown -R ${SERVER_USER}:${SERVER_USER} '$SERVER_DIR'" + emit_step "core_chown" "BDS" + open_firewall_port "$SERVER_PORT" "udp" + emit_step "core_service" "BDS" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_bds() { + info "$(t core_update "$(core_name_bds)")" + emit_step "core_update" "$(core_name_bds)" + + backup_file "$SERVER_DIR/bedrock_server" + backup_file "$SERVER_DIR/start.sh" + backup_file "$SERVER_DIR/.bds-version" + + core_install_bds + emit_step "core_update_done" "BDS" +} diff --git a/cores/paper.sh b/cores/paper.sh new file mode 100644 index 0000000..caa999d --- /dev/null +++ b/cores/paper.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +core_name_paper() { + echo "Paper" +} + +core_default_port_paper() { + echo "25565" +} + +core_protocol_paper() { + echo "tcp" +} + +core_runtime_paper() { + echo "java" +} + +core_validate_paper() { + return 0 +} + +core_install_paper() { + info "$(t core_install "$(core_name_paper)")" + emit_step "core_install" "$(core_name_paper)" + + local url + emit_step "core_resolve_download" "paper" + url="$(get_papermc_download_url "paper" "${MC_VERSION:-latest}")" + download_as_user "$url" "$SERVER_DIR/server.jar" + emit_step "core_configure" "eula" + run_cmd bash -lc "printf 'eula=true\n' > '$SERVER_DIR/eula.txt'" + create_java_start_script "$SERVER_DIR" "server.jar" "$RAM" +} + +core_post_install_paper() { + emit_step "core_post" "Paper" + run_cmd bash -lc "chown -R ${SERVER_USER}:${SERVER_USER} '$SERVER_DIR'" + emit_step "core_chown" "Paper" + open_firewall_port "$SERVER_PORT" "tcp" + emit_step "core_service" "Paper" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_paper() { + info "$(t core_update "$(core_name_paper)")" + emit_step "core_update" "$(core_name_paper)" + + local target="$SERVER_DIR/server.jar" + backup_file "$target" + emit_step "core_backup" "$target" + + local url + emit_step "core_resolve_download" "paper" + url="$(get_papermc_download_url "paper" "${MC_VERSION:-latest}")" + download_as_user_atomic "$url" "$target" + emit_step "core_update_done" "Paper" +} diff --git a/cores/pmmp.sh b/cores/pmmp.sh new file mode 100644 index 0000000..64f56b9 --- /dev/null +++ b/cores/pmmp.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +core_name_pmmp() { + echo "PocketMine-MP" +} + +core_default_port_pmmp() { + echo "19132" +} + +core_protocol_pmmp() { + echo "udp" +} + +core_runtime_pmmp() { + echo "php" +} + +core_validate_pmmp() { + return 0 +} + +core_install_pmmp() { + info "$(t core_install "$(core_name_pmmp)")" + emit_step "core_install" "$(core_name_pmmp)" + emit_step "core_resolve_channel" "${BUILD:-latest}" + + local channel="${BUILD}" + if [[ "$channel" == "latest" || -z "$channel" ]]; then + channel="stable" + fi + download_url="https://get.pmmp.io" + emit_step "core_fetch" "pmmp" + run_cmd sudo -u "$SERVER_USER" bash -lc "cd '$SERVER_DIR' && curl -sL '$download_url' | bash -s - -d '$SERVER_DIR' -v '$channel'" + emit_step "core_configure" "pmmp" +} + +core_post_install_pmmp() { + emit_step "core_post" "pmmp" + open_firewall_port "$SERVER_PORT" "udp" + emit_step "core_service" "pmmp" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_pmmp() { + info "$(t core_update "$(core_name_pmmp)")" + emit_step "core_update" "$(core_name_pmmp)" + + local download_url="https://get.pmmp.io" + local channel="${BUILD}" + if [[ "$channel" == "latest" || -z "$channel" ]]; then + channel="stable" + fi + + backup_file "$SERVER_DIR/server.phar" + backup_file "$SERVER_DIR/start.sh" + + emit_step "core_fetch" "pmmp" + run_cmd sudo -u "$SERVER_USER" bash -lc "cd '$SERVER_DIR' && curl -sL '$download_url' | bash -s - -d '$SERVER_DIR' -v '$channel'" + emit_step "core_update_done" "pmmp" +} diff --git a/cores/powernukkitx.sh b/cores/powernukkitx.sh new file mode 100644 index 0000000..c6312f4 --- /dev/null +++ b/cores/powernukkitx.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +core_name_powernukkitx() { + echo "PowerNukkitX" +} + +core_default_port_powernukkitx() { + echo "19132" +} + +core_protocol_powernukkitx() { + echo "udp" +} + +core_runtime_powernukkitx() { + echo "java" +} + +core_validate_powernukkitx() { + return 0 +} + +core_install_powernukkitx() { + info "$(t core_install "$(core_name_powernukkitx)")" + emit_step "core_install" "$(core_name_powernukkitx)" + + local url + emit_step "core_resolve_download" "powernukkitx" + url="$(get_github_release_asset_url PowerNukkitX/PowerNukkitX 'powernukkitx.*\\.jar')" + if [[ -z "$url" || "$url" == "null" ]]; then + fail "$(t no_stable_build "PowerNukkitX" "$BUILD")" + fi + download_as_user "$url" "$SERVER_DIR/powernukkitx.jar" + create_powernukkit_start_script "$SERVER_DIR" "$RAM" + emit_step "core_render" "powernukkitx" +} + +core_post_install_powernukkitx() { + emit_step "core_configure" "eula" + run_cmd bash -lc "printf 'eula=true\n' > '$SERVER_DIR/eula.txt'" + emit_step "core_chown" "PowerNukkitX" + run_cmd bash -lc "chown -R ${SERVER_USER}:${SERVER_USER} '$SERVER_DIR'" + open_firewall_port "$SERVER_PORT" "udp" + emit_step "core_service" "PowerNukkitX" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_powernukkitx() { + info "$(t core_update "$(core_name_powernukkitx)")" + emit_step "core_update" "$(core_name_powernukkitx)" + + local target="$SERVER_DIR/powernukkitx.jar" + backup_file "$target" + emit_step "core_backup" "$target" + + local url + emit_step "core_resolve_download" "powernukkitx" + url="$(get_github_release_asset_url PowerNukkitX/PowerNukkitX 'powernukkitx.*\\.jar')" + if [[ -z "$url" || "$url" == "null" ]]; then + fail "$(t no_stable_build "PowerNukkitX" "$BUILD")" + fi + download_as_user_atomic "$url" "$target" + emit_step "core_update_done" "PowerNukkitX" +} diff --git a/cores/purpur.sh b/cores/purpur.sh new file mode 100644 index 0000000..95248c9 --- /dev/null +++ b/cores/purpur.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +core_name_purpur() { + echo "Purpur" +} + +core_default_port_purpur() { + echo "25565" +} + +core_protocol_purpur() { + echo "tcp" +} + +core_runtime_purpur() { + echo "java" +} + +core_validate_purpur() { + return 0 +} + +core_install_purpur() { + info "$(t core_install "$(core_name_purpur)")" + emit_step "core_install" "$(core_name_purpur)" + + local url + emit_step "core_resolve_download" "purpur" + url="$(get_purpur_download_url "${MC_VERSION:-latest}" "${BUILD:-latest}")" + download_as_user "$url" "$SERVER_DIR/server.jar" + emit_step "core_configure" "eula" + run_cmd bash -lc "printf 'eula=true\n' > '$SERVER_DIR/eula.txt'" + create_java_start_script "$SERVER_DIR" "server.jar" "$RAM" +} + +core_post_install_purpur() { + emit_step "core_post" "Purpur" + run_cmd bash -lc "chown -R ${SERVER_USER}:${SERVER_USER} '$SERVER_DIR'" + emit_step "core_chown" "Purpur" + open_firewall_port "$SERVER_PORT" "tcp" + emit_step "core_service" "Purpur" + create_service "$CORE" "$SERVER_DIR" "$SERVICE_MODE" "$SERVER_DIR/start.sh" + start_service +} + +core_update_purpur() { + info "$(t core_update "$(core_name_purpur)")" + emit_step "core_update" "$(core_name_purpur)" + + local target="$SERVER_DIR/server.jar" + backup_file "$target" + emit_step "core_backup" "$target" + + local url + emit_step "core_resolve_download" "purpur" + url="$(get_purpur_download_url "${MC_VERSION:-latest}" "${BUILD:-latest}")" + download_as_user_atomic "$url" "$target" + emit_step "core_update_done" "Purpur" +} diff --git a/cores/velocity.sh b/cores/velocity.sh new file mode 100644 index 0000000..cdca106 --- /dev/null +++ b/cores/velocity.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +core_name_velocity() { + echo "Velocity" +} + +core_default_port_velocity() { + echo "25577" +} + +core_protocol_velocity() { + echo "tcp" +} + +core_runtime_velocity() { + echo "java" +} + +core_validate_velocity() { + return 0 +} + +core_install_velocity() { + info "$(t core_install "$(core_name_velocity)")" + emit_step "core_install" "$(core_name_velocity)" + + local url + emit_step "core_resolve_download" "velocity" + url="$(get_papermc_download_url "velocity" "${MC_VERSION:-latest}")" + download_as_user "$url" "$SERVER_DIR/velocity.jar" + run_cmd bash -c "cat > '$SERVER_DIR/start.sh' < + --core --core-version (legacy alias for mc-version) --dir --port @@ -313,7 +313,7 @@ resolve_core_defaults() { 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")" + fail "$(t unsupported_core "$CORE" "pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc,bds" "${SCRIPT_DIR}/manifests/cores.json")" fi CORE_NAME="$(core_name_${CORE})" @@ -423,6 +423,8 @@ show_noob_plan() { if [[ "$CORE" == "nethergamesmc" ]]; then info "$(t easy_plan_build "$BUILD")" info "$(t easy_plan_mc_version "$MC_VERSION")" + elif [[ "$CORE" == "bds" ]]; then + info "$(t easy_plan_bds_version "$MC_VERSION")" fi if is_java_core "$CORE"; then info "$(t easy_plan_eula "$ACCEPT_EULA")" @@ -452,6 +454,8 @@ interact() { if [[ "$CORE" == "nethergamesmc" ]]; then ask_value "$(t prompt_php_build)" "${BUILD:-latest}" BUILD ask_value "$(t prompt_pocketmine_version)" "${MC_VERSION:-latest}" MC_VERSION + elif [[ "$CORE" == "bds" ]]; then + ask_value "$(t prompt_bds_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 @@ -525,7 +529,7 @@ check_existing_directory() { fi if [[ "$FORCE_CLEAN" == "yes" ]]; then - run_cmd find "$SERVER_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + + run_cmd bash -c "find \"$SERVER_DIR\" -mindepth 1 -delete" return fi diff --git a/lib/detect.sh b/lib/detect.sh new file mode 100644 index 0000000..c9f6886 --- /dev/null +++ b/lib/detect.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +detect_system() { + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + OS_ID="${ID:-unknown}" + OS_VERSION="${VERSION_ID:-unknown}" + OS_NAME="${PRETTY_NAME:-unknown}" + else + fail "$(t detect_os_failed)" + fi + + ARCHITECTURE="$(uname -m)" + + if command -v apt-get >/dev/null 2>&1; then + PKG_MANAGER="apt" + elif command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf" + elif command -v yum >/dev/null 2>&1; then + PKG_MANAGER="yum" + elif command -v pacman >/dev/null 2>&1; then + PKG_MANAGER="pacman" + else + fail "$(t no_package_manager)" + fi + + if command -v systemctl >/dev/null 2>&1; then + INIT_SYSTEM="systemd" + else + INIT_SYSTEM="none" + fi +} diff --git a/lib/download.sh b/lib/download.sh new file mode 100644 index 0000000..b676943 --- /dev/null +++ b/lib/download.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +http_get() { + local url="$1" + curl -fsSL -H "User-Agent: AstraDeploy/${DEPLOY_VERSION}" "$url" +} + +get_papermc_download_url() { + local project="$1" + local version="$2" + + if [[ "$version" == "latest" ]]; then + version="$(http_get "https://fill.papermc.io/v3/projects/${project}" | jq -r '.versions | if type=="array" then .[-1] else (to_entries | last | .value[0]) end')" + fi + + local builds_json + builds_json="$(http_get "https://fill.papermc.io/v3/projects/${project}/versions/${version}/builds")" + local url + url="$(printf '%s\n' "$builds_json" | jq -r '.builds | map(select(.channel=="STABLE")) | .[0].downloads."server:default".url // empty')" + if [[ -z "$url" || "$url" == "null" ]]; then + fail "$(t no_stable_build "$project" "$version")" + fi + echo "$url" +} + +get_purpur_download_url() { + local version="$1" + local build="${2:-latest}" + + if [[ "$version" == "latest" ]]; then + version="$(http_get "https://api.purpurmc.org/v2/purpur/" | jq -r '.versions | .[-1]')" + fi + + if [[ "$build" == "latest" ]]; then + echo "https://api.purpurmc.org/v2/purpur/${version}/latest/download" + else + echo "https://api.purpurmc.org/v2/purpur/${version}/${build}/download" + fi +} + +get_github_release_asset_url() { + local repo="$1" + local pattern="$2" + http_get "https://api.github.com/repos/${repo}/releases/latest" \ + | jq -r --arg pattern "$pattern" '.assets[] | select(.name | test($pattern)) | .browser_download_url' | head -n 1 +} + +download_as_user() { + local url="$1" + local target="$2" + + emit_step "download_start" "$url" + run_cmd sudo -u "$SERVER_USER" bash -lc "curl -fL \"$url\" -o \"${target}\"" + emit_step "download_completed" "$target" +} + +download_as_user_atomic() { + local url="$1" + local target="$2" + local tmp="${target}.tmp.${RANDOM}.$(date +%s)" + + download_as_user "$url" "$tmp" + run_cmd mv "$tmp" "$target" + emit_step "download_swap" "$target" +} diff --git a/lib/firewall.sh b/lib/firewall.sh new file mode 100644 index 0000000..c1d1085 --- /dev/null +++ b/lib/firewall.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +open_firewall_port() { + local port="$1" + local proto="$2" + + if [[ "$FIREWALL" != "yes" ]]; then + info "$(t firewall_skip)" + return 0 + fi + + emit_step "firewall_start" "$port/$proto" + + if command -v ufw >/dev/null 2>&1; then + run_cmd ufw allow "$port/$proto" || true + run_cmd ufw --force enable || true + emit_step "firewall_done" "ufw" + return 0 + fi + + if command -v firewall-cmd >/dev/null 2>&1; then + run_cmd firewall-cmd --permanent --add-port="$port/$proto" + run_cmd firewall-cmd --reload + emit_step "firewall_done" "firewalld" + return 0 + fi + + warn "$(t firewall_manual_open "$port" "$proto")" + emit_step "firewall_manual" "$port/$proto" +} diff --git a/lib/i18n.sh b/lib/i18n.sh new file mode 100644 index 0000000..a80d3a9 --- /dev/null +++ b/lib/i18n.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +LOCALE_DIRECTORY="${SCRIPT_DIR}/locales" + +load_locale() { + case "${LOCALE,,}" in + ru*|ru_*) + LOCALE="ru" + ;; + en*|en_*) + LOCALE="en" + ;; + *) + if [[ "${LANGUAGE:-${LANG:-}}" == ru* ]]; then + LOCALE="ru" + else + LOCALE="en" + fi + ;; + esac + + case "$LOCALE" in + ru) + LOCALE_FILE="$LOCALE_DIRECTORY/ru.sh" + ;; + *) + LOCALE_FILE="$LOCALE_DIRECTORY/en.sh" + ;; + esac + + if [[ ! -f "$LOCALE_FILE" ]]; then + LOCALE="en" + LOCALE_FILE="$LOCALE_DIRECTORY/en.sh" + fi + + # shellcheck source=/dev/null + . "$LOCALE_FILE" +} + +t() { + local key="$1" + shift || true + + local fn="locale_msg_${key}" + if declare -F "$fn" >/dev/null 2>&1; then + "$fn" "$@" + else + echo "$key" + fi +} diff --git a/lib/java.sh b/lib/java.sh new file mode 100644 index 0000000..4562698 --- /dev/null +++ b/lib/java.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +is_java_core() { + local core="$1" + case "$core" in + paper|purpur|velocity|powernukkitx) return 0 ;; + *) return 1 ;; + esac +} + +install_java_21() { + if command -v java >/dev/null 2>&1; then + local version + version="$(java -version 2>&1 | sed -n 's/.*version "\(.*\)".*/\1/p' | head -n1 | sed 's/"//g' | sed 's/^\([0-9][0-9]*\).*/\1/')" + if [[ "$version" =~ ^[0-9]+$ ]] && (( version >= 21 )); then + info "$(t java_already_available "$version")" + return 0 + fi + fi + + info "$(t installing_java_21)" + case "$PKG_MANAGER" in + apt) + emit_step "java_install" "apt" + run_cmd apt-get install -y openjdk-21-jre-headless + ;; + dnf|yum) + emit_step "java_install" "$PKG_MANAGER" + run_cmd "$PKG_MANAGER" install -y java-21-openjdk-headless + ;; + pacman) + emit_step "java_install" "pacman" + run_cmd pacman -Sy --noconfirm jre-openjdk-headless + ;; + esac +} + +create_java_start_script() { + local dir="$1" + local jar_file="$2" + local ram="$3" + local extra_opts="${4:-}" + + local script="$dir/start.sh" + emit_step "render_start_script" "$jar_file" + run_cmd bash -c "cat > '$script' < '$script' < step_total )); then + step_percent_json=100 + else + step_percent=$(( step_index * 100 / step_total )) + step_percent_json="$step_percent" + fi + fi + + if [[ -z "${REPORT_URL:-}" || -z "${REPORT_TOKEN:-}" ]]; then + return 0 + fi + + local payload + payload="$(printf '{ "status":"%s", "core":"%s", "dir":"%s", "port":%s, "protocol":"%s", "service":"%s", "message":"%s", "step":"%s", "step_index":%s, "step_total":%s, "step_percent":%s }' \ + "$(json_escape "$status")" \ + "$(json_escape "${CORE}")" \ + "$(json_escape "${SERVER_DIR}")" \ + "${SERVER_PORT}" \ + "$(json_escape "${CORE_PROTOCOL}")" \ + "$(json_escape "${SERVICE_MODE_ACTIVE}")" \ + "$(json_escape "$message")" \ + "$(json_escape "$step")" \ + "$step_index_json" \ + "$step_total_json" \ + "$step_percent_json")" + + local attempt + for attempt in 1 2; do + if [[ "${DRY_RUN}" == "yes" ]]; then + info "$(t report_dry_run "$payload")" + return 0 + fi + if curl -fsSL -X POST "$REPORT_URL" \ + -H "Authorization: Bearer ${REPORT_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload" >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + + warn "$(t report_failed)" +} diff --git a/lib/rollback.sh b/lib/rollback.sh new file mode 100644 index 0000000..03c4573 --- /dev/null +++ b/lib/rollback.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +ROLLBACK_IN_PROGRESS="no" + +rollback_on_error() { + local exit_code="${1:-1}" + local command_failed="${2:-unknown}" + + if [[ "$ROLLBACK_IN_PROGRESS" == "yes" ]]; then + return + fi + ROLLBACK_IN_PROGRESS="yes" + + local fail_message + fail_message="$(t deployment_failed "$exit_code" "$command_failed")" + error "$fail_message" + send_report "failed" "$fail_message" + + warn "$(t rollback_starts "$SERVER_DIR")" + + if [[ "${SERVICE_CREATED:-no}" == "yes" ]]; then + remove_service_if_exists + fi + + if [[ -n "${LOG_FILE:-}" ]]; then + info "$(t see_log "$LOG_FILE")" + fi + + exit "$exit_code" +} diff --git a/lib/services.sh b/lib/services.sh new file mode 100644 index 0000000..1861c72 --- /dev/null +++ b/lib/services.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +SERVICE_NAME="" +SERVICE_FILE="" +SERVICE_CREATED="no" +SERVICE_MODE_ACTIVE="none" + +stop_service_if_running() { + local service_name="${1:-${SERVICE_NAME:-${CORE}_minecraft}}" + + if [[ "$INIT_SYSTEM" != "systemd" ]]; then + return 0 + fi + + if [[ "${DRY_RUN:-no}" == "yes" ]]; then + info "[dry-run] systemctl stop ${service_name}" + return 0 + fi + + emit_step "service_stop" "$service_name" + systemctl stop "$service_name" >/dev/null 2>&1 || true +} + +create_service() { + local core="$1" + local dir="$2" + local mode="$3" + local start_script="$4" + local start_script_escaped="$4" + + start_script_escaped="${start_script_escaped//\\/\\\\}" + start_script_escaped="${start_script_escaped//\"/\\\"}" + start_script_escaped="${start_script_escaped//%/%%}" + + SERVICE_NAME="${core}_minecraft" + SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + + if [[ "$mode" == "none" || "$INIT_SYSTEM" != "systemd" ]]; then + warn "$(t service_create_skipped "$mode" "$INIT_SYSTEM")" + SERVICE_MODE_ACTIVE="none" + return 0 + fi + + emit_step "service_create" "$core:$mode" + if [[ "$mode" == "tmux-systemd" ]]; then + run_cmd bash -c "cat > '$SERVICE_FILE' < '$SERVICE_FILE' < '$SERVICE_FILE' </dev/null 2>&1; then + emit_step "user_create" "$user" + info "$(t creating_user "$user")" + if [[ "$DRY_RUN" == "no" ]]; then + useradd -r -m -s /bin/bash "$user" + else + info "$(t dry_run_useradd "$user")" + fi + else + emit_step "user_exists" "$user" + fi +} + +prepare_server_dir() { + local dir="$1" + local user="$2" + + emit_step "prepare_dir" "$dir" + run_cmd mkdir -p "$dir" + run_cmd chown -R "${user}:${user}" "$dir" + emit_step "permission_fix" "$dir" +} diff --git a/lib/validation.sh b/lib/validation.sh index c7adf31..a4e2faa 100644 --- a/lib/validation.sh +++ b/lib/validation.sh @@ -7,12 +7,12 @@ validate_root_usage() { } validate_core_support() { - local supported_cores="pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc" + local supported_cores="pmmp,paper,purpur,velocity,powernukkitx,nethergamesmc,bds" local supported_os="ubuntu,debian" local planned_os="arch,fedora,rocky,almalinux" case "$CORE" in - pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc) + pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc|bds) ;; *) fail "$(t unsupported_core "$CORE" "$supported_cores" "${SCRIPT_DIR}/manifests/cores.json")" diff --git a/locales/en.sh b/locales/en.sh index 97adc1e..968e510 100644 --- a/locales/en.sh +++ b/locales/en.sh @@ -85,12 +85,13 @@ 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_core() { echo "Core (pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc|bds)"; } 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_bds_version() { echo "Bedrock Dedicated Server version (latest|version)"; } 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"; } @@ -105,6 +106,7 @@ 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_bds_version() { echo "BDS version: $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."; } diff --git a/locales/ru.sh b/locales/ru.sh index 899cb27..b51551f 100644 --- a/locales/ru.sh +++ b/locales/ru.sh @@ -85,12 +85,13 @@ 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_core() { echo "Ядро (pmmp|paper|purpur|velocity|powernukkitx|nethergamesmc|bds)"; } 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_bds_version() { echo "Версия Bedrock Dedicated Server (latest|версия)"; } locale_msg_prompt_service_mode() { echo "Режим сервиса"; } locale_msg_prompt_open_firewall() { echo "Открыть firewall (yes/no)"; } locale_msg_prompt_install_user() { echo "Пользователь для установки"; } @@ -105,6 +106,7 @@ 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_bds_version() { echo "Версия BDS: $1"; } locale_msg_easy_plan_eula() { echo "EULA: $1"; } locale_msg_dir_not_empty_auto() { echo "Каталог '$1' не пустой. Укажите --allow-existing, --backup-existing или --force-clean."; } diff --git a/manifests/cores.json b/manifests/cores.json index 1d11822..04860db 100644 --- a/manifests/cores.json +++ b/manifests/cores.json @@ -62,5 +62,15 @@ "default_dir": "/opt/minecraft/nethergamesmc", "supports_protocols": ["udp"], "supported_services": ["systemd", "tmux-systemd", "screen", "none"] + }, + "bds": { + "name": "Minecraft Bedrock Dedicated Server", + "edition": "bedrock", + "runtime": "native", + "default_port": 19132, + "protocol": "udp", + "default_dir": "/opt/minecraft/bds", + "supports_protocols": ["udp"], + "supported_services": ["systemd", "tmux-systemd", "screen", "none"] } } diff --git a/manifests/os-support.json b/manifests/os-support.json new file mode 100644 index 0000000..6a0fbe0 --- /dev/null +++ b/manifests/os-support.json @@ -0,0 +1,15 @@ +{ + "supports": { + "mvp": ["ubuntu", "debian"], + "planned": ["arch", "fedora", "rocky", "almalinux"] + }, + "service": { + "systemd": ["ubuntu", "debian", "fedora", "rocky", "almalinux"] + }, + "java_21": { + "apt": "openjdk-21-jre-headless", + "dnf": "java-21-openjdk-headless", + "yum": "java-21-openjdk-headless", + "pacman": "jre-openjdk-headless" + } +} diff --git a/templates/service-systemd.tpl b/templates/service-systemd.tpl new file mode 100644 index 0000000..a24f2dd --- /dev/null +++ b/templates/service-systemd.tpl @@ -0,0 +1,15 @@ +[Unit] +Description=Minecraft {{CORE_NAME}} server +After=network.target + +[Service] +Type=simple +User={{SERVER_USER}} +Group={{SERVER_USER}} +WorkingDirectory={{SERVER_DIR}} +ExecStart={{START_SCRIPT}} +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/templates/service-tmux.tpl b/templates/service-tmux.tpl new file mode 100644 index 0000000..261ed6a --- /dev/null +++ b/templates/service-tmux.tpl @@ -0,0 +1,17 @@ +[Unit] +Description=Minecraft {{CORE_NAME}} server (tmux) +After=network.target + +[Service] +Type=forking +User={{SERVER_USER}} +Group={{SERVER_USER}} +WorkingDirectory={{SERVER_DIR}} +ExecStart=/usr/bin/tmux new-session -d -s {{SERVICE_NAME}} '{{START_SCRIPT}}' +ExecStop=/usr/bin/tmux send-keys -t {{SERVICE_NAME}} 'stop' C-m +ExecStopPost=/bin/sleep 8 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/templates/start-java.tpl b/templates/start-java.tpl new file mode 100644 index 0000000..09cb0e8 --- /dev/null +++ b/templates/start-java.tpl @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +cd "$(dirname "$0")" +exec java -Xms{{RAM}} -Xmx{{RAM}} {{JAVA_OPTS}} -jar {{JAR_FILE}} diff --git a/templates/start-pmmp.tpl b/templates/start-pmmp.tpl new file mode 100644 index 0000000..ab85d7d --- /dev/null +++ b/templates/start-pmmp.tpl @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +cd "$(dirname "$0")" +./start.sh "$@"