Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions docs/zh/guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ SFMC 采用**约定优于配置(Convention over Configuration)**的设计理

---

### `bds_updater.json`:BDS 核心与进程守护
### `bds_updater.json`:BDS 核心更新

管理 Bedrock Dedicated Server 的自动版本检测、热补丁下载、升级前安全备份与进程崩溃自启。
管理 Bedrock Dedicated Server 的自动版本检测、热补丁下载与升级前安全备份。
进程崩溃拉起由 `sfmc` 后台守护进程负责,不再在此配置开关。

```json title="configs/bds_updater.json"
{
Expand All @@ -109,23 +110,19 @@ SFMC 采用**约定优于配置(Convention over Configuration)**的设计理
"backup_dir": "./backups",
"channel": "release",
"auto_check": true,
"crash_restart": true,
"crash_restart_delay": 5,
"preserve": ["server.properties", "whitelist.json", "permissions.json", "worlds"],
"qq_notify": true
}
```

| 字段 | 类型 | 说明 |
| :-------------------- | :--------- | :---------------------------------------------------------------------------------- |
| `bds_path` | `string` | BDS 核心可执行程序所在目录。 |
| `backup_dir` | `string` | 升级或回滚时的全量备份落盘路径(必须位于 `bds_path` 目录之外)。 |
| `channel` | `string` | 更新通道:`"release"`(正式稳定版)或 `"preview"`(预览测试版)。 |
| `auto_check` | `boolean` | 服务启动时是否静默请求 Mojang 官方源检测最新版本。 |
| `crash_restart` | `boolean` | 当 `bedrock_server` 异常崩溃退出时,是否自动尝试拉起恢复。 |
| `crash_restart_delay` | `number` | 崩溃重启前的缓冲等待时间(秒),避免因端口未释放引发频繁颠簸。 |
| `preserve` | `string[]` | 覆盖更新时**绝对予以保留并自动复原**的文件/目录白名单(避免存档或基础配置被冲洗)。 |
| `qq_notify` | `boolean` | 更新启动、完成或崩溃告警时,是否通过 QQ 群同步广播进度。 |
| 字段 | 类型 | 说明 |
| :----------- | :--------- | :---------------------------------------------------------------------------------- |
| `bds_path` | `string` | BDS 核心可执行程序所在目录。 |
| `backup_dir` | `string` | 升级或回滚时的全量备份落盘路径(必须位于 `bds_path` 目录之外)。 |
| `channel` | `string` | 更新通道:`"release"`(正式稳定版)或 `"preview"`(预览测试版)。 |
| `auto_check` | `boolean` | 服务启动时是否静默请求 Mojang 官方源检测最新版本。 |
| `preserve` | `string[]` | 覆盖更新时**绝对予以保留并自动复原**的文件/目录白名单(避免存档或基础配置被冲洗)。 |
| `qq_notify` | `boolean` | 更新启动、完成或崩溃告警时,是否通过 QQ 群同步广播进度。 |

---

Expand Down
6 changes: 6 additions & 0 deletions modules/sdk/@sfmc-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @sfmc-bds/sdk

## 0.2.1

### Patch Changes

- 5a25300: 新增 CLI 守护进程监管:退出 CLI 不停服,经命名管道 RPC 连接;移除 bds_updater 崩溃自启配置项,改由 daemon 固定拉起。正式版补丁 0.2.3。

## 0.2.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk/@sfmc-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sfmc-bds/sdk",
"version": "0.2.0",
"version": "0.2.1",
"description": "ScriptsForMinecraftServer unified SDK — SAPI/Node umbrella covering contracts, runtime, module-loader, and db/config/service capabilities.",
"author": "Shiroha7z",
"license": "ISC",
Expand Down
14 changes: 1 addition & 13 deletions modules/sdk/@sfmc-sdk/schemas/bds_updater.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/DogeLakeDev/ScriptsForMinecraftServer/schemas/bds_updater.schema.json",
"title": "SFMC bds_updater",
"description": "BDS 自动更新与进程管理配置。由 bds-tools(check-update / bds-manager)读取;路径请用双反斜杠或正斜杠。",
"description": "BDS 自动更新配置。由 bds-tools(check-update)读取;崩溃拉起由 sfmc daemon 负责。路径请用双反斜杠或正斜杠。",
"type": "object",
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -39,18 +39,6 @@
"type": "boolean",
"description": "启动时是否自动检查 BDS 更新"
},
"crash_restart": {
"type": "boolean",
"description": "BDS 异常退出时是否自动拉起"
},
"crash_restart_delay": {
"type": "number",
"description": "崩溃重启前等待秒数(可选)"
},
"auto_restart": {
"type": "boolean",
"description": "旧字段:兼容保留,用于推断是否启用 QQ 等相关自动行为"
},
"version_mode": {
"type": "string",
"enum": ["bedrock-oss", "endstone"],
Expand Down
5 changes: 0 additions & 5 deletions modules/sdk/@sfmc-sdk/src/node/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ export interface BdsUpdaterConfig {
bds_path?: string;
backup_dir?: string;
channel?: "release" | "preview" | string;
auto_restart?: boolean;
auto_check?: boolean;
crash_restart?: boolean;
crash_restart_delay?: number;
qq_notify?: boolean;
qq_config?: string;
preserve?: string[];
Expand Down Expand Up @@ -239,8 +236,6 @@ export const DEFAULT_BDS_UPDATER_CONFIG: BdsUpdaterConfig = {
qq_notify: false,
qq_config: "../configs/qq_config.json",
auto_check: true,
crash_restart: true,
auto_restart: true,
};

/** permissions.json 根为数组;默认空表 */
Expand Down
8 changes: 8 additions & 0 deletions packages/bds-tools/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @sfmc-bds/bds-tools

## 0.2.1

### Patch Changes

- 5a25300: 新增 CLI 守护进程监管:退出 CLI 不停服,经命名管道 RPC 连接;移除 bds_updater 崩溃自启配置项,改由 daemon 固定拉起。正式版补丁 0.2.3。
- Updated dependencies [5a25300]
- @sfmc-bds/sdk@0.2.1

## 0.2.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/bds-tools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sfmc-bds/bds-tools",
"version": "0.2.0",
"version": "0.2.1",
"description": "SFMC BDS auto-updater, lifecycle manager, and behavior-pack assembler",
"author": "Shiroha7z",
"license": "AGPL-3.0-only",
Expand Down
10 changes: 6 additions & 4 deletions packages/bds-tools/src/bds-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ export interface BdsManagerOptions {
interface CachedProc {
process: ReturnType<typeof spawn> | null;
isManualStop: boolean;
crashRestart: boolean;
/** 独立运行 bds-manager 时的崩溃拉起延迟(秒);守护由 sfmc daemon 负责时不读配置 */
crashDelayMs: number;
exePath: string;
bdsPath: string;
}

let cached: CachedProc | null = null;

/** 独立入口崩溃拉起默认等待(毫秒);配置项已移除,固定值避免与 daemon 监管重复暴露开关 */
const STANDALONE_CRASH_DELAY_MS = 5_000;

function ensureProc(): CachedProc {
if (cached) return cached;
const cfg = loadConfig();
Expand All @@ -74,8 +77,7 @@ function ensureProc(): CachedProc {
cached = {
process: null,
isManualStop: false,
crashRestart: cfg.crash_restart !== false,
crashDelayMs: (cfg.crash_restart_delay ?? 5) * 1000,
crashDelayMs: STANDALONE_CRASH_DELAY_MS,
exePath,
bdsPath: bds_path,
};
Expand Down Expand Up @@ -224,7 +226,7 @@ export function createBdsManager(options: BdsManagerOptions = {}): BdsManager {
} else {
void postBdsLifecycleEvent("stop");
}
if (!wasManual && p.crashRestart && isMain()) {
if (!wasManual && isMain()) {
log.info(`BDS 意外退出,${p.crashDelayMs / 1000}s 后自动重启...`);
setTimeout(() => {
start().catch((e) => log.error(`自动重启失败: ${e.message}`));
Expand Down
2 changes: 1 addition & 1 deletion packages/bds-tools/src/check-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function buildContext(): UpdateContext {
bdsPath: bds_path,
backupDir: backup_dir,
preserve,
autoRestart: !args["no-start"] && cfg.auto_restart !== false,
autoRestart: !args["no-start"],
qqNotify: !!cfg.qq_notify,
};
}
Expand Down
3 changes: 0 additions & 3 deletions packages/bds-tools/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ export interface BdsUpdaterConfig {
bds_path?: string;
backup_dir?: string;
channel?: "release" | "preview" | string;
auto_restart?: boolean;
auto_check?: boolean;
crash_restart?: boolean;
crash_restart_delay?: number;
qq_notify?: boolean;
qq_config?: string;
preserve?: string[];
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @sfmc-bds/cli

## 0.2.3

### Patch Changes

- 5a25300: 新增 CLI 守护进程监管:退出 CLI 不停服,经命名管道 RPC 连接;移除 bds_updater 崩溃自启配置项,改由 daemon 固定拉起。正式版补丁 0.2.3。
- Updated dependencies [5a25300]
- @sfmc-bds/sdk@0.2.1
- @sfmc-bds/bds-tools@0.2.1

## 0.2.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sfmc-bds/cli",
"version": "0.2.2",
"version": "0.2.3",
"description": "SFMC management CLI — REPL, service supervisor, module install, behavior-pack build/deploy",
"author": "Shiroha7z",
"license": "AGPL-3.0-only",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/command-surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ const TOP_DESC: Record<string, string> = {
locale: "help.locale",
debug: "help.debug.status",
ui: "help.ui",
daemon: "help.daemon",
install: "help.module.install",
uninstall: "help.module.uninstall",
search: "help.module.search",
Expand Down Expand Up @@ -346,6 +347,7 @@ export const COMMAND_SPECS: readonly CommandSpec[] = [
{ id: "init", name: "init", channel: "both", needsTty: true },
{ id: "update", name: "update", channel: "both" },
{ id: "locale", name: "locale", channel: "both" },
{ id: "daemon", name: "daemon", channel: "both" },
{ id: "debug", name: "debug", channel: "both", accent: "dev" },
{ id: "ui", name: "ui", channel: "both", accent: "dev" },

Expand Down
80 changes: 67 additions & 13 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,29 @@ import {
type QQBridgeConfig,
} from "@sfmc-bds/sdk/node/config";
import fs from "node:fs";
import { isDaemonServer } from "./daemon/role.js";
import { t } from "./i18n/index.js";
import { pushLog as pushUnifiedLog } from "./logs.js";
import { findNodeServicePids, killNodeServiceByScript, type NodeServiceName } from "./node-service-probe.js";
import { spawnService } from "./runtime.js";
import { queryServicesRuntime, ROOT, SERVICE_NAMES, services, type ServiceName } from "./services.js";
import { c, DIVIDER, highlightLogLine, padRight } from "./theme.js";

/**
* CLI 侧经守护进程执行并取回 text;守护进程内直接跑 localFn。
* 使用场景:status/start/stop/send/update 等运维命令的统一分流,避免 CLI 进程持有子进程。
*/
async function viaDaemonText(
method: "status" | "start" | "stop" | "restart" | "startAll" | "stopAll" | "send" | "update",
localFn: () => Promise<string>,
params?: { name?: string; message?: string; args?: string[] }
): Promise<string> {
if (isDaemonServer()) return localFn();
const { textViaDaemon, statusTextViaDaemon } = await import("./daemon/client.js");
if (method === "status") return statusTextViaDaemon();
return textViaDaemon(method, params);
}

function parseService(raw: string): ServiceName | null {
const s = raw.toLowerCase() as ServiceName;
if (SERVICE_NAMES.includes(s)) return s;
Expand Down Expand Up @@ -76,17 +92,19 @@ function qqBridgeStatusFooter(): string {
* @returns 格式化后的状态摘要文本表格。
*/
export async function cmdStatus(): Promise<string> {
const rows = await queryServicesRuntime();
const lines = rows.map((row) => statusLine(row.title, row.running, row.pid, row.uptime, row.ownership));
const nameH = t("svc.col.name");
const statusH = t("svc.col.status");
const ownerH = t("svc.col.owner");
const pidH = t("svc.col.pid");
const upH = t("svc.col.uptime");
const header = ` ${padRight(nameH, 16)}${padRight(statusH, 8)}${padRight(ownerH, 6)}${padRight(pidH, 8)}${upH}`;
return (
`\n${c.bold(t("svc.header"))}\n` + c.dim(header) + "\n" + DIVIDER + "\n" + lines.join("\n") + qqBridgeStatusFooter()
);
return viaDaemonText("status", async () => {
const rows = await queryServicesRuntime();
const lines = rows.map((row) => statusLine(row.title, row.running, row.pid, row.uptime, row.ownership));
const nameH = t("svc.col.name");
const statusH = t("svc.col.status");
const ownerH = t("svc.col.owner");
const pidH = t("svc.col.pid");
const upH = t("svc.col.uptime");
const header = ` ${padRight(nameH, 16)}${padRight(statusH, 8)}${padRight(ownerH, 6)}${padRight(pidH, 8)}${upH}`;
return (
`\n${c.bold(t("svc.header"))}\n` + c.dim(header) + "\n" + DIVIDER + "\n" + lines.join("\n") + qqBridgeStatusFooter()
);
});
}

/**
Expand Down Expand Up @@ -168,6 +186,11 @@ function clearQqRuntimeFile(): void {
* @returns 启动结果描述文本。
*/
export async function cmdStart(raw: string): Promise<string> {
return viaDaemonText("start", () => cmdStartLocal(raw), { name: raw });
}

/** 守护进程内启动单服务(含外部探活防双开) */
async function cmdStartLocal(raw: string): Promise<string> {
const svc = parseService(raw);
if (!svc) return c.red(t("svc.unknown", { name: raw, list: SERVICE_NAMES.join(", ") }));
const svcObj = services[svc];
Expand Down Expand Up @@ -215,6 +238,11 @@ export async function cmdStart(raw: string): Promise<string> {
* @returns 停止操作结果文本。
*/
export async function cmdStop(raw: string): Promise<string> {
return viaDaemonText("stop", () => cmdStopLocal(raw), { name: raw });
}

/** 守护进程内停止单服务(含外部实例清理) */
async function cmdStopLocal(raw: string): Promise<string> {
const svc = parseService(raw);
if (!svc) return c.red(t("svc.unknown", { name: raw, list: SERVICE_NAMES.join(", ") }));
const svcObj = services[svc];
Expand Down Expand Up @@ -281,6 +309,11 @@ export async function cmdStop(raw: string): Promise<string> {
}

export async function cmdSend(raw: string, message: string): Promise<string> {
return viaDaemonText("send", () => cmdSendLocal(raw, message), { name: raw, message });
}

/** 守护进程内向服务 stdin 写入一行 */
async function cmdSendLocal(raw: string, message: string): Promise<string> {
const svc = parseService(raw);
if (!svc) return c.red(t("svc.unknown", { name: raw, list: SERVICE_NAMES.join(", ") }));
if (svc === "tunnel") return c.yellow(t("svc.stdinUnavailable", { title: services[svc].title }));
Expand Down Expand Up @@ -314,12 +347,17 @@ export async function cmdSend(raw: string, message: string): Promise<string> {
* @returns 重启操作结果文本。
*/
export async function cmdRestart(raw: string): Promise<string> {
return viaDaemonText("restart", () => cmdRestartLocal(raw), { name: raw });
}

/** 守护进程内重启单服务 */
async function cmdRestartLocal(raw: string): Promise<string> {
const svc = parseService(raw);
if (!svc) return c.red(t("svc.unknown", { name: raw, list: SERVICE_NAMES.join(", ") }));
// bds / db / qq:统一走 stop→start,确保外部实例也被清掉
if (svc === "bds" || isNodeSvc(svc)) {
const stopMsg = await cmdStop(svc);
const startMsg = await cmdStart(svc);
const stopMsg = await cmdStopLocal(svc);
const startMsg = await cmdStartLocal(svc);
// 若 stop 已报错文案含「失败」,原样带回
if (/失败|failed/i.test(stopMsg) && !/已停止|stopped|already/i.test(stopMsg)) {
return stopMsg;
Expand All @@ -345,6 +383,11 @@ export async function cmdRestart(raw: string): Promise<string> {
* @returns 批量启动结果统计报告文本。
*/
export async function cmdStartAll(): Promise<string> {
return viaDaemonText("startAll", cmdStartAllLocal);
}

/** 守护进程内批量启动 */
async function cmdStartAllLocal(): Promise<string> {
const { startAll } = await import("./services.js");
const result = await startAll();
const parts: string[] = [];
Expand Down Expand Up @@ -382,6 +425,11 @@ export async function cmdStartAll(): Promise<string> {
* @returns 批量停止确认文本。
*/
export async function cmdStopAll(): Promise<string> {
return viaDaemonText("stopAll", cmdStopAllLocal);
}

/** 守护进程内批量停止 */
async function cmdStopAllLocal(): Promise<string> {
const { stopAll } = await import("./services.js");
await stopAll();
return c.dim(t("svc.allStopped"));
Expand All @@ -398,6 +446,11 @@ export async function cmdStopAll(): Promise<string> {
* @returns 更新执行结果摘要文本。
*/
export async function cmdUpdate(args: string[] = []): Promise<string> {
return viaDaemonText("update", () => cmdUpdateLocal(args), { args });
}

/** 守护进程内执行 BDS 更新(需持有 bds Service 句柄以 beginUpdate/endUpdate) */
async function cmdUpdateLocal(args: string[] = []): Promise<string> {
const userNoStart = args.includes("--no-start");
const checkOnly = args.includes("--check-only");
const spawnArgs = userNoStart ? [...args] : [...args, "--no-start"];
Expand All @@ -417,6 +470,7 @@ export async function cmdUpdate(args: string[] = []): Promise<string> {
const proc = spawnService("update", spawnArgs, {
cwd: ROOT,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
let out = "";
const pushChunk = (raw: string, level: "info" | "error"): void => {
Expand Down
Loading
Loading