build: 代理发行包改为单个可运行的 moonbridge.jar

发行包去掉 bin/ 启动脚本和 lib/ 下的 42 个依赖 jar,改为与其他代理服务端一样
用 java -jar moonbridge.jar 启动:
- proxy-core 用 shadow 打出含全部依赖的 moonbridge.jar(合并 META-INF/services,
  清单写入 Main-Class、Multi-Release 与 Enable-Native-Access: ALL-UNNAMED)
- 泄漏检测默认关闭改由 ProxyMain 在加载 Netty 前设置,显式 -D 仍优先
- 移除不再使用的 application 约定插件
- 安装包检查、烟测与基准脚本改用发行包根目录作 classpath;文档同步

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
layue13andClaude Opus 5.5 committed 2026-10-22 15:00:14 +08:00
1 parent 789648c82f
commit afea023bce
23 files changed
+79 -99

No files matched your search

+3 -3
View File
@@ -17,7 +17,7 @@ The LuckPerms platform plugin compiles LuckPerms from the `proxy/vendor/luckperm
./gradlew :proxy-core:test # proxy-core tests only ./gradlew :proxy-core:test # proxy-core tests only
./gradlew :proxy-core:test --tests '*RawRelayTest' # one class ./gradlew :proxy-core:test --tests '*RawRelayTest' # one class
./gradlew :proxy-core:test --tests '*RawRelayTest.m' # one method ./gradlew :proxy-core:test --tests '*RawRelayTest.m' # one method
./gradlew :proxy-core:installDist # runnable distribution in proxy/core/build/install/moonbridge ./gradlew :proxy-core:installDist # proxy/core/build/install/moonbridge: java -jar moonbridge.jar
``` ```
The slow tests are the session integration tests (`ProxySessionListenerTest`, `SessionTransferTest`, `InitialRoutingTest`, `SourceReleaseTransferTest`); iterate with `--tests` and run `check` once before pushing. The slow tests are the session integration tests (`ProxySessionListenerTest`, `SessionTransferTest`, `InitialRoutingTest`, `SourceReleaseTransferTest`); iterate with `--tests` and run `check` once before pushing.
@@ -57,7 +57,7 @@ testing/smoke/ smoke scripts and dated records; testing/benchmarks/ likewis
**Transport.** `NetworkTransport` picks epoll / kqueue / NIO and supplies the channel types for the listener, backend dials and the DNS resolver together. Create event loops only through it: a bare `MultiThreadIoEventLoopGroup` silently uses a `LinkedBlockingQueue` instead of the MPSC queue, which showed up as a measurable per-frame cost. **Transport.** `NetworkTransport` picks epoll / kqueue / NIO and supplies the channel types for the listener, backend dials and the DNS resolver together. Create event loops only through it: a bare `MultiThreadIoEventLoopGroup` silently uses a `LinkedBlockingQueue` instead of the MPSC queue, which showed up as a measurable per-frame cost.
**Buffer ownership.** Production runs with Netty leak detection disabled (the start script passes `-Dio.netty.leakDetection.level=disabled`), so refcounting must be right by construction. Do not write new `try/finally release()` blocks. Use `ByteBufs.fill/use` for allocate-write-release-on-failure, extend `FrameTransformHandler` for per-frame inbound handlers (its `transform` borrows the frame and returns what to forward), and use Netty's `MessageToMessageEncoder/Decoder` for codecs. Tests run every `proxy-core` test class under PARANOID leak detection: `LeakGate` (auto-registered via `junit-platform.properties`) fails a class that leaks a buffer. It cannot see `Unpooled.wrappedBuffer(byte[])` buffers, so assert `refCnt()` directly for those. **Buffer ownership.** Production runs with Netty leak detection disabled (`ProxyMain` sets `io.netty.leakDetection.level=disabled` before Netty loads unless the property is given), so refcounting must be right by construction. Do not write new `try/finally release()` blocks. Use `ByteBufs.fill/use` for allocate-write-release-on-failure, extend `FrameTransformHandler` for per-frame inbound handlers (its `transform` borrows the frame and returns what to forward), and use Netty's `MessageToMessageEncoder/Decoder` for codecs. Tests run every `proxy-core` test class under PARANOID leak detection: `LeakGate` (auto-registered via `junit-platform.properties`) fails a class that leaks a buffer. It cannot see `Unpooled.wrappedBuffer(byte[])` buffers, so assert `refCnt()` directly for those.
**Backends and plugins.** Backends come from static config, plugin registration (`Servers.register`, owner-scoped handles with generations) or the optional control channel (`BackendControlService`: separate authenticated blocking sockets on virtual threads, leases that outlive short disconnects). Initial routing is fail-closed: with no placement plugin it uses only `initialRouting.servers`. Permissions are a provider interface (`PermissionService`) with per-connection subjects that are prepared before login admission and released on any exit. **Backends and plugins.** Backends come from static config, plugin registration (`Servers.register`, owner-scoped handles with generations) or the optional control channel (`BackendControlService`: separate authenticated blocking sockets on virtual threads, leases that outlive short disconnects). Initial routing is fail-closed: with no placement plugin it uses only `initialRouting.servers`. Permissions are a provider interface (`PermissionService`) with per-connection subjects that are prepared before login admission and released on any exit.
@@ -68,4 +68,4 @@ testing/smoke/ smoke scripts and dated records; testing/benchmarks/ likewis
## Performance work ## Performance work
Benchmarks live in `testing/benchmarks/` (`ProxySessionBenchmark`, PowerShell runners; on Linux compile it against `proxy/core/build/install/moonbridge/lib` and run `java --enable-native-access=ALL-UNNAMED -cp ... dev.moonbridge.core.session.ProxySessionBenchmark --burst 32 --window 4 --transport epoll`). The existing records show the house method: set the decision rule first, rotate variant order across JVM runs, report median and range, keep raw output next to a dated `testing/benchmarks/results/*.md` that states what it does not cover, and prefer profile samples (JFR) over overlapping throughput ranges. Single-host loopback numbers are never capacity claims. Benchmarks live in `testing/benchmarks/` (`ProxySessionBenchmark`, PowerShell runners; on Linux compile it against `proxy/core/build/install/moonbridge/moonbridge.jar` and run `java --enable-native-access=ALL-UNNAMED -cp ... dev.moonbridge.core.session.ProxySessionBenchmark --burst 32 --window 4 --transport epoll`). The existing records show the house method: set the decision rule first, rotate variant order across JVM runs, report median and range, keep raw output next to a dated `testing/benchmarks/results/*.md` that states what it does not cover, and prefer profile samples (JFR) over overlapping throughput ranges. Single-host loopback numbers are never capacity claims.
+2 -2
View File
@@ -20,8 +20,8 @@
```sh ```sh
./gradlew check :proxy-core:installDist # Windows: .\gradlew.bat check :proxy-core:installDist ./gradlew check :proxy-core:installDist # Windows: .\gradlew.bat check :proxy-core:installDist
cd proxy/core/build/install/moonbridge cd proxy/core/build/install/moonbridge
bin/moonbridge --validate-config config/moonbridge.yml # Windows: bin\moonbridge.bat java -jar moonbridge.jar --validate-config config/moonbridge.yml
bin/moonbridge --config config/moonbridge.yml java -jar moonbridge.jar # 默认读取 config/moonbridge.yml
``` ```
需要全部部署物(服务器包、后端插件、客户端 mod)时运行 `./gradlew release`,说明见[发行物与安装位置](docs/release-layout.md)。LuckPerms 平台插件从 `proxy/vendor/luckperms` 子模块编译,默认不构建;需要时先 `git submodule update --init --recursive`,再加 `-Pluckperms`。 需要全部部署物(服务器包、后端插件、客户端 mod)时运行 `./gradlew release`,说明见[发行物与安装位置](docs/release-layout.md)。LuckPerms 平台插件从 `proxy/vendor/luckperms` 子模块编译,默认不构建;需要时先 `git submodule update --init --recursive`,再加 `-Pluckperms`。
-4
View File
@@ -16,9 +16,5 @@ gradlePlugin {
id = "moonbridge.publishing-conventions" id = "moonbridge.publishing-conventions"
implementationClass = "MoonBridgePublishingConventionsPlugin" implementationClass = "MoonBridgePublishingConventionsPlugin"
} }
register("applicationConventions") {
id = "moonbridge.application-conventions"
implementationClass = "MoonBridgeApplicationConventionsPlugin"
}
} }
} }
@@ -1,10 +0,0 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ApplicationPlugin
class MoonBridgeApplicationConventionsPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.apply(MoonBridgeJavaLibraryConventionsPlugin::class.java)
project.plugins.apply(ApplicationPlugin::class.java)
}
}
-1
View File
@@ -1,6 +1,5 @@
plugins { plugins {
id("moonbridge.java-library-conventions") apply false id("moonbridge.java-library-conventions") apply false
id("moonbridge.application-conventions") apply false
} }
val releaseVersion = providers.gradleProperty("releaseVersion").orElse("0.1.0-SNAPSHOT").get() val releaseVersion = providers.gradleProperty("releaseVersion").orElse("0.1.0-SNAPSHOT").get()
+1 -1
View File
@@ -22,7 +22,7 @@ PDS、AetherShard 与 LuckPerms 可以使用同一台 MariaDB 服务,各自保
git submodule update --init --recursive git submodule update --init --recursive
bash ./gradlew clean check :proxy-core:distZip --no-daemon --max-workers=2 bash ./gradlew clean check :proxy-core:distZip --no-daemon --max-workers=2
mkdir -p build/permission-network-classes mkdir -p build/permission-network-classes
javac -cp 'proxy/core/build/install/moonbridge/lib/*' -d build/permission-network-classes testing/smoke/PermissionNetworkAcceptance.java javac -cp 'proxy/core/build/install/moonbridge/*' -d build/permission-network-classes testing/smoke/PermissionNetworkAcceptance.java
node testing/smoke/permission-services.cjs start node testing/smoke/permission-services.cjs start
``` ```
+4 -3
View File
@@ -10,8 +10,9 @@
```powershell ```powershell
.\gradlew.bat :proxy-core:installDist .\gradlew.bat :proxy-core:installDist
.\proxy-core\build\install\moonbridge\bin\moonbridge.bat --validate-config .\proxy-core\build\install\moonbridge\config\moonbridge.yml cd .\proxy\core\build\install\moonbridge
.\proxy-core\build\install\moonbridge\bin\moonbridge.bat --config .\proxy-core\build\install\moonbridge\config\moonbridge.yml java -jar moonbridge.jar --validate-config config\moonbridge.yml
java -jar moonbridge.jar --config config\moonbridge.yml
``` ```
`--validate-config` 校验核心 YAML、静态后端注册和服务器列表图标;它不会加载插件,所以不会验证已启用插件的设置、后端连通性或登录流程。未知核心配置键会报错。 `--validate-config` 校验核心 YAML、静态后端注册和服务器列表图标;它不会加载插件,所以不会验证已启用插件的设置、后端连通性或登录流程。未知核心配置键会报错。
@@ -44,7 +45,7 @@ status:
- 可选的 `websocket` 段在 TCP `listen` 之外再开一个 WebSocket 玩家监听,供经 CDN 接入的客户端使用;两种入口同时可用,见 [WebSocket 接入](websocket.md)。OFFLINE 模式对 `websocket.listen` 同样要求字面量回环 IP,除非设置 `allowOfflinePublicAccess: true`。 - 可选的 `websocket` 段在 TCP `listen` 之外再开一个 WebSocket 玩家监听,供经 CDN 接入的客户端使用;两种入口同时可用,见 [WebSocket 接入](websocket.md)。OFFLINE 模式对 `websocket.listen` 同样要求字面量回环 IP,除非设置 `allowOfflinePublicAccess: true`。
- `maxConnections` 默认 4096,范围为 1–1,000,000;计数包括登录中的客户端,以及尚未完成 WebSocket 升级的连接。达到上限时新连接直接关闭。按部署资源和连接规模配置,不将其解释为后端容量。 - `maxConnections` 默认 4096,范围为 1–1,000,000;计数包括登录中的客户端,以及尚未完成 WebSocket 升级的连接。达到上限时新连接直接关闭。按部署资源和连接规模配置,不将其解释为后端容量。
- `transport` 选择玩家连接、后端连接和 DNS 使用的网络传输层,默认 `AUTO`:Linux 上用 epoll,macOS 上用 kqueue,其他平台(如 Windows)或原生库无法加载时(如 ARM/x86 以外的架构、musl 发行版)回退到 NIO,启动日志会输出实际选用的传输层。发行包附带 Linux x86_64/aarch64 的 epoll 与 macOS x86_64/aarch64 的 kqueue 原生库,启动脚本已加 `--enable-native-access=ALL-UNNAMED`。显式写 `NIO`、`EPOLL` 或 `KQUEUE` 会强制使用对应实现;指定的原生传输层不可用时启动失败,不会静默回退。后端控制通道使用独立的阻塞套接字,不受此项影响。 - `transport` 选择玩家连接、后端连接和 DNS 使用的网络传输层,默认 `AUTO`:Linux 上用 epoll,macOS 上用 kqueue,其他平台(如 Windows)或原生库无法加载时(如 ARM/x86 以外的架构、musl 发行版)回退到 NIO,启动日志会输出实际选用的传输层。发行包附带 Linux x86_64/aarch64 的 epoll 与 macOS x86_64/aarch64 的 kqueue 原生库,启动脚本已加 `--enable-native-access=ALL-UNNAMED`。显式写 `NIO`、`EPOLL` 或 `KQUEUE` 会强制使用对应实现;指定的原生传输层不可用时启动失败,不会静默回退。后端控制通道使用独立的阻塞套接字,不受此项影响。
- 发行包默认关闭 Netty 的内存泄漏检测(`-Dio.netty.leakDetection.level=disabled`):它在每个手写转发/编码路径上都有逐帧开销,合成基准中 burst 场景每帧 CPU 中位数约低 26%(范围有重叠,见[记录](../testing/benchmarks/results/2026-10-05-event-loop-queue-and-netty.md))。代替它的是测试期的 `LeakGate`:`proxy-core` 的每个测试类都以 PARANOID 级别运行,发现未释放的缓冲区即让该类失败,且有自测证明它能抓到故意的泄漏。引用计数的手写位置已集中到 `ByteBufs`、`FrameTransformHandler` 与 Netty 自带的编解码基类里;已评估的手写 release 位置都有测试守护,范围与方法见[验证说明](testing.md)。需要在某个部署上重新启用时,设置 `JAVA_OPTS=-Dio.netty.leakDetection.level=simple`(启动脚本里后出现的 `-D` 覆盖默认值)。 - 发行包默认关闭 Netty 的内存泄漏检测(`-Dio.netty.leakDetection.level=disabled`):它在每个手写转发/编码路径上都有逐帧开销,合成基准中 burst 场景每帧 CPU 中位数约低 26%(范围有重叠,见[记录](../testing/benchmarks/results/2026-10-05-event-loop-queue-and-netty.md))。代替它的是测试期的 `LeakGate`:`proxy-core` 的每个测试类都以 PARANOID 级别运行,发现未释放的缓冲区即让该类失败,且有自测证明它能抓到故意的泄漏。引用计数的手写位置已集中到 `ByteBufs`、`FrameTransformHandler` 与 Netty 自带的编解码基类里;已评估的手写 release 位置都有测试守护,范围与方法见[验证说明](testing.md)。这个默认值由 `ProxyMain` 在加载 Netty 前设置;需要在某个部署上重新启用时,启动时传入 `java -Dio.netty.leakDetection.level=simple -jar moonbridge.jar`,显式给出的值优先。
- `plugins.eventTimeoutSeconds` 为统一事件链期限,默认 5 秒,可设为 1–30 秒。玩家准入事件按订阅注册顺序共享一个期限;无订阅者时跳过派发。准入队列容量为 128,最多 1024 个未决请求。队列饱和、超时、异常或空结果都会拒绝当前访问:连接准入阶段关闭 TCP 连接,玩家准入阶段返回通用错误文本;插件可通过显式拒绝结果提供登录断开原因。生命周期通知有独立的单线程 FIFO 队列,容量 128,最多一个事件正在派发;单事件链受同一事件期限约束。过载时尽力丢弃并聚合记录,不影响连接处理。 - `plugins.eventTimeoutSeconds` 为统一事件链期限,默认 5 秒,可设为 1–30 秒。玩家准入事件按订阅注册顺序共享一个期限;无订阅者时跳过派发。准入队列容量为 128,最多 1024 个未决请求。队列饱和、超时、异常或空结果都会拒绝当前访问:连接准入阶段关闭 TCP 连接,玩家准入阶段返回通用错误文本;插件可通过显式拒绝结果提供登录断开原因。生命周期通知有独立的单线程 FIFO 队列,容量 128,最多一个事件正在派发;单事件链受同一事件期限约束。过载时尽力丢弃并聚合记录,不影响连接处理。
- 未认证帧上限为 4 KiB;后端 Login Success 后切换到协议 5 PLAY 帧上限。握手/身份验证与后端连接/登录各自有 15 秒期限;`initialRouting.timeoutSeconds` 默认 15 秒,可设为 1–120 秒,覆盖插件异步选服和所有候选服务器的 DNS/TCP 建连。`initialRouting.servers` 是无插件策略时显式配置的有序候选服务器名,最多 16 个;默认空列表会拒绝首次登录,不会从动态注册目录猜一个服务器。有初始选服插件时,该插件的选择完全接管路由,失败不会回退到配置列表。只有发送任何 Minecraft 握手/登录字节前的 DNS/TCP 失败可继续尝试候选;开始后端登录后失败会结束本次登录,不重放协议流量。成功登录后,客户端须在两分钟内完成首次 PLAY/Forge 握手。 - 未认证帧上限为 4 KiB;后端 Login Success 后切换到协议 5 PLAY 帧上限。握手/身份验证与后端连接/登录各自有 15 秒期限;`initialRouting.timeoutSeconds` 默认 15 秒,可设为 1–120 秒,覆盖插件异步选服和所有候选服务器的 DNS/TCP 建连。`initialRouting.servers` 是无插件策略时显式配置的有序候选服务器名,最多 16 个;默认空列表会拒绝首次登录,不会从动态注册目录猜一个服务器。有初始选服插件时,该插件的选择完全接管路由,失败不会回退到配置列表。只有发送任何 Minecraft 握手/登录字节前的 DNS/TCP 失败可继续尝试候选;开始后端登录后失败会结束本次登录,不重放协议流量。成功登录后,客户端须在两分钟内完成首次 PLAY/Forge 握手。
- 登录拒绝、插件主动断开以及后端关闭/读失败的已提交前端写入最多排空等待 5 秒。转服切换阶段有 15 秒期限;Forge 客户端切换后目标握手最多等待 30 秒。各期限届满可能使当前登录失败或关闭当前会话。 - 登录拒绝、插件主动断开以及后端关闭/读失败的已提交前端写入最多排空等待 5 秒。转服切换阶段有 15 秒期限;Forge 客户端切换后目标握手最多等待 30 秒。各期限届满可能使当前登录失败或关闭当前会话。
+4 -4
View File
@@ -56,9 +56,9 @@ dependencies {
`:proxy-core:distZip` 生成 `proxy/core/build/distributions/moonbridge-<version>.zip`。CI 把这个 ZIP 作为 `moonbridge-development` 下载工件的一部分上传,保留 30 天;它不是 Maven 模块,也不自动成为 Git Release。解压后从发行包目录运行: `:proxy-core:distZip` 生成 `proxy/core/build/distributions/moonbridge-<version>.zip`。CI 把这个 ZIP 作为 `moonbridge-development` 下载工件的一部分上传,保留 30 天;它不是 Maven 模块,也不自动成为 Git Release。解压后从发行包目录运行:
```powershell ```sh
.\bin\moonbridge.bat --validate-config .\config\moonbridge.yml java -jar moonbridge.jar --validate-config config/moonbridge.yml
.\bin\moonbridge.bat --config .\config\moonbridge.yml java -jar moonbridge.jar --config config/moonbridge.yml
``` ```
Linux/macOS 使用 `bin/moonbridge`。发行包根目录只有运行需要的内容:`bin/` 启动脚本、`lib/` 依赖、`config/` 配置模板、README,以及带 `-Pluckperms` 构建时的 `plugins/`。文档、验收与基准记录留在仓库里;后端宿主 jar 不在代理包内,见[发行物与安装位置](release-layout.md)。 不带参数时读取当前目录下的 `config/moonbridge.yml`。发行包根目录只有运行需要的内容:可直接运行的 `moonbridge.jar`(含全部依赖,清单里已允许 Netty 加载 epoll/kqueue 本地库)、`config/` 配置模板、README,以及带 `-Pluckperms` 构建时的 `plugins/`。文档、验收与基准记录留在仓库里;后端宿主 jar 不在代理包内,见[发行物与安装位置](release-layout.md)。
+1 -1
View File
@@ -4,7 +4,7 @@
| 路径 | 是什么 | 装在哪里 | | 路径 | 是什么 | 装在哪里 |
| --- | --- | --- | | --- | --- | --- |
| `server/moonbridge-<版本>.zip` | 代理发行包:`bin/` 启动脚本、`lib/`、`config/` 示例配置、README;带 `-Pluckperms` 构建时 `plugins/` 里含 LuckPerms 平台插件 | 代理服务器,解压后用 JDK 25 运行 `bin/moonbridge --config config/moonbridge.yml` | | `server/moonbridge-<版本>.zip` | 代理发行包:可直接运行的 `moonbridge.jar`、`config/` 示例配置、README;带 `-Pluckperms` 构建时 `plugins/` 里含 LuckPerms 平台插件 | 代理服务器,解压后在该目录用 JDK 25 运行 `java -jar moonbridge.jar` |
| `backend/moonbridge-backend-<版本>.jar` | 后端共享宿主 `MoonBridgeBackend`(Bukkit/Uranium 插件,Java 8),内含消息 API 与控制通道客户端 | 每台后端服务器的 `plugins/`;后端业务插件对它 `compileOnly` 并在 `plugin.yml` 里 `depend: [MoonBridgeBackend]` | | `backend/moonbridge-backend-<版本>.jar` | 后端共享宿主 `MoonBridgeBackend`(Bukkit/Uranium 插件,Java 8),内含消息 API 与控制通道客户端 | 每台后端服务器的 `plugins/`;后端业务插件对它 `compileOnly` 并在 `plugin.yml` 里 `depend: [MoonBridgeBackend]` |
| `client/moonbridgews-<版本>.jar` | 1.7.10 Forge WebSocket 客户端 mod | 玩家客户端 `mods/`,另需 UniMixins;只在使用 `ws://`/`wss://` 地址时需要 | | `client/moonbridgews-<版本>.jar` | 1.7.10 Forge WebSocket 客户端 mod | 玩家客户端 `mods/`,另需 UniMixins;只在使用 `ws://`/`wss://` 地址时需要 |
+41 -56
View File
@@ -1,7 +1,10 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Exec
plugins { plugins {
id("moonbridge.application-conventions") id("moonbridge.java-library-conventions")
distribution
id("com.gradleup.shadow") version "9.6.1"
} }
val runtimeJavaHome = System.getProperty("java.home") val runtimeJavaHome = System.getProperty("java.home")
@@ -37,54 +40,26 @@ dependencies {
testImplementation(libs.findLibrary("netty-pkitesting").get()) testImplementation(libs.findLibrary("netty-pkitesting").get())
} }
val installedDistSmokeTest = tasks.register<Exec>("installedDistSmokeTest") { // The distribution is started the way an operator starts it: java -jar moonbridge.jar from the install directory.
group = "verification" fun installedDistCheck(name: String, description: String, vararg arguments: String) =
description = "Runs the installed MoonBridge distribution in --validate-config mode." tasks.register<Exec>(name) {
dependsOn(tasks.named("installDist")) group = "verification"
this.description = description
dependsOn(tasks.named("installDist"))
val installDir = layout.buildDirectory.dir("install/moonbridge") val installDir = layout.buildDirectory.dir("install/moonbridge")
inputs.dir(installDir) inputs.dir(installDir)
environment("JAVA_HOME", runtimeJavaHome) workingDir(installDir)
commandLine(File(runtimeJavaHome, "bin/java").absolutePath, "-jar", "moonbridge.jar", *arguments)
doFirst {
val windows = System.getProperty("os.name").lowercase().contains("windows")
val script = installDir.get().file("bin/" + if (windows) "moonbridge.bat" else "moonbridge").asFile
val config = installDir.get().file("config/moonbridge.yml").asFile
commandLine(script.absolutePath, "--validate-config", config.absolutePath)
} }
}
val installedDistHelpSmokeTest = tasks.register<Exec>("installedDistHelpSmokeTest") { val installedDistSmokeTest = installedDistCheck("installedDistSmokeTest",
group = "verification" "Runs the installed MoonBridge distribution in --validate-config mode.",
description = "Runs the installed MoonBridge distribution help command." "--validate-config", "config/moonbridge.yml")
dependsOn(tasks.named("installDist")) val installedDistHelpSmokeTest = installedDistCheck("installedDistHelpSmokeTest",
"Runs the installed MoonBridge distribution help command.", "--help")
val installDir = layout.buildDirectory.dir("install/moonbridge") val installedDistVersionSmokeTest = installedDistCheck("installedDistVersionSmokeTest",
inputs.dir(installDir) "Runs the installed MoonBridge distribution version command.", "--version")
environment("JAVA_HOME", runtimeJavaHome)
doFirst {
val windows = System.getProperty("os.name").lowercase().contains("windows")
val script = installDir.get().file("bin/" + if (windows) "moonbridge.bat" else "moonbridge").asFile
commandLine(script.absolutePath, "--help")
}
}
val installedDistVersionSmokeTest = tasks.register<Exec>("installedDistVersionSmokeTest") {
group = "verification"
description = "Runs the installed MoonBridge distribution version command."
dependsOn(tasks.named("installDist"))
val installDir = layout.buildDirectory.dir("install/moonbridge")
inputs.dir(installDir)
environment("JAVA_HOME", runtimeJavaHome)
doFirst {
val windows = System.getProperty("os.name").lowercase().contains("windows")
val script = installDir.get().file("bin/" + if (windows) "moonbridge.bat" else "moonbridge").asFile
commandLine(script.absolutePath, "--version")
}
}
// The benchmarks in testing/benchmarks/ declare proxy-core packages to reach package-private classes and are // The benchmarks in testing/benchmarks/ declare proxy-core packages to reach package-private classes and are
// normally compiled by their PowerShell runners. Compiling them here keeps them building with main. // normally compiled by their PowerShell runners. Compiling them here keeps them building with main.
@@ -127,16 +102,6 @@ tasks.named("check") {
dependsOn(compileBenchmarks) dependsOn(compileBenchmarks)
} }
application {
mainClass.set("dev.moonbridge.app.ProxyMain")
applicationName = "moonbridge"
// Netty's epoll/kqueue transports load a JNI library; allow it without JDK restricted-method warnings.
// Leak detection is off in the distribution; every proxy-core test class runs with PARANOID detection
// through LeakGate instead. Re-enable per deployment with JAVA_OPTS=-Dio.netty.leakDetection.level=simple.
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED",
"-Dio.netty.leakDetection.level=disabled")
}
tasks.withType<Test>().configureEach { tasks.withType<Test>().configureEach {
jvmArgs("--enable-native-access=ALL-UNNAMED") jvmArgs("--enable-native-access=ALL-UNNAMED")
// Tests track every buffer so LeakGate can fail on a leak; the distribution runs with detection off. // Tests track every buffer so LeakGate can fail on a leak; the distribution runs with detection off.
@@ -148,11 +113,31 @@ tasks.jar {
manifest.attributes("Implementation-Title" to "MoonBridge", "Implementation-Version" to project.version) manifest.attributes("Implementation-Title" to "MoonBridge", "Implementation-Version" to project.version)
} }
// The server ships as one runnable jar, like other Minecraft proxies: java -jar moonbridge.jar.
val serverJar = tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("moonbridge.jar")
// Netty, Jackson and Logback register implementations through META-INF/services.
mergeServiceFiles()
exclude("module-info.class", "META-INF/versions/*/module-info.class",
"META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
manifest.attributes(
"Main-Class" to "dev.moonbridge.app.ProxyMain",
"Implementation-Title" to "MoonBridge",
"Implementation-Version" to project.version,
// Jackson ships version-specific classes; keep them selected at runtime.
"Multi-Release" to "true",
// Netty's epoll/kqueue transports load a JNI library; allow it without restricted-method warnings.
"Enable-Native-Access" to "ALL-UNNAMED",
)
}
distributions { distributions {
main { main {
distributionBaseName.set("moonbridge")
// Only what the proxy needs to run. Docs and test records stay in the repository, and the // Only what the proxy needs to run. Docs and test records stay in the repository, and the
// backend jar ships separately in build/release/backend (see docs/release-layout.md). // backend jar ships separately in build/release/backend (see docs/release-layout.md).
contents { contents {
from(serverJar)
from(rootProject.file("README.md")) from(rootProject.file("README.md"))
if (luckPerms != null) { if (luckPerms != null) {
from(luckPerms.tasks.named("pluginJar")) { from(luckPerms.tasks.named("pluginJar")) {
@@ -19,6 +19,15 @@ import java.util.concurrent.Executors;
/** Command line entry point for the new proxy runtime. */ /** Command line entry point for the new proxy runtime. */
public final class ProxyMain { public final class ProxyMain {
static {
// Leak detection costs per frame on every hand-written forwarding path, so the proxy runs without it;
// every proxy-core test class runs with PARANOID detection through LeakGate instead. This must run
// before Netty is loaded. Re-enable per deployment with -Dio.netty.leakDetection.level=simple.
if (System.getProperty("io.netty.leakDetection.level") == null) {
System.setProperty("io.netty.leakDetection.level", "disabled");
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyMain.class); private static final Logger LOGGER = LoggerFactory.getLogger(ProxyMain.class);
private ProxyMain() { private ProxyMain() {
+1 -1
View File
@@ -7,7 +7,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge\lib" $installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge"
$classes = Join-Path $PSScriptRoot "build" $classes = Join-Path $PSScriptRoot "build"
Push-Location $repoRoot Push-Location $repoRoot
+1 -1
View File
@@ -13,7 +13,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge\lib" $installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge"
$classes = Join-Path $PSScriptRoot "build" $classes = Join-Path $PSScriptRoot "build"
Push-Location $repoRoot Push-Location $repoRoot
+1 -1
View File
@@ -9,7 +9,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge\lib" $installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge"
$classes = Join-Path $PSScriptRoot "build" $classes = Join-Path $PSScriptRoot "build"
Push-Location $repoRoot Push-Location $repoRoot
+1 -1
View File
@@ -6,7 +6,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$candidateLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge\lib" $candidateLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge"
$baselineLib = (Resolve-Path -LiteralPath $BaselineLibDirectory).Path $baselineLib = (Resolve-Path -LiteralPath $BaselineLibDirectory).Path
if (-not (Test-Path -LiteralPath $baselineLib -PathType Container)) { if (-not (Test-Path -LiteralPath $baselineLib -PathType Container)) {
throw "baseline library directory does not exist: $baselineLib" throw "baseline library directory does not exist: $baselineLib"
+1 -1
View File
@@ -7,7 +7,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge\lib" $installLib = Join-Path $repoRoot "proxy\core\build\install\moonbridge"
$classes = Join-Path $PSScriptRoot "build" $classes = Join-Path $PSScriptRoot "build"
Push-Location $repoRoot Push-Location $repoRoot
+2 -2
View File
@@ -6,9 +6,9 @@ $ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repo = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$fixtureClasses = Join-Path $repo 'build/channel-smoke/router' $fixtureClasses = Join-Path $repo 'build/channel-smoke/router'
$probeClasses = Join-Path $repo 'build/channel-smoke/probe' $probeClasses = Join-Path $repo 'build/channel-smoke/probe'
$proxyClasspath = Join-Path $repo 'proxy/core/build/install/moonbridge/lib/*' $proxyClasspath = Join-Path $repo 'proxy/core/build/install/moonbridge/*'
$backendClasspath = Join-Path $repo 'backend/build/libs/*' $backendClasspath = Join-Path $repo 'backend/build/libs/*'
if (-not (Test-Path (Join-Path $repo 'proxy/core/build/install/moonbridge/lib'))) { if (-not (Test-Path (Join-Path $repo 'proxy/core/build/install/moonbridge'))) {
throw 'Run gradlew :proxy-core:installDist :backend:jar first.' throw 'Run gradlew :proxy-core:installDist :backend:jar first.'
} }
New-Item -ItemType Directory -Force $fixtureClasses, $probeClasses | Out-Null New-Item -ItemType Directory -Force $fixtureClasses, $probeClasses | Out-Null
+1 -1
View File
@@ -55,7 +55,7 @@ if [ "$ready" -ne 1 ]; then docker logs "$backend"; exit 1; fi
docker run -d --network "$network" --name "$proxy" \ docker run -d --network "$network" --name "$proxy" \
-v "$install:/opt/moonbridge:ro" -v "$work/config.yml:/opt/moonbridge.yml:ro" \ -v "$install:/opt/moonbridge:ro" -v "$work/config.yml:/opt/moonbridge.yml:ro" \
--entrypoint java "$java_image" -cp '/opt/moonbridge/lib/*' \ --entrypoint java "$java_image" -cp '/opt/moonbridge/*' \
dev.moonbridge.app.ProxyMain --config /opt/moonbridge.yml >/dev/null dev.moonbridge.app.ProxyMain --config /opt/moonbridge.yml >/dev/null
if ! docker run --rm --network "$network" -v "$repo_root:/work:ro" \ if ! docker run --rm --network "$network" -v "$repo_root:/work:ro" \
-e "MOONBRIDGE_CHANNEL_SECRET=$secret" --entrypoint python3 "$python_image" \ -e "MOONBRIDGE_CHANNEL_SECRET=$secret" --entrypoint python3 "$python_image" \
+1 -1
View File
@@ -98,7 +98,7 @@ $controlConfig
WaitBackend $backend WaitBackend $backend
InvokeDocker @('run', '-d', '--network', $network, '--name', $proxy, InvokeDocker @('run', '-d', '--network', $network, '--name', $proxy,
'-v', "${install}:/opt/moonbridge:ro", '-v', "${config}:/opt/moonbridge.yml:ro", '-v', "${install}:/opt/moonbridge:ro", '-v', "${config}:/opt/moonbridge.yml:ro",
'--entrypoint', 'java', $javaImage, '-cp', '/opt/moonbridge/lib/*', '--entrypoint', 'java', $javaImage, '-cp', '/opt/moonbridge/*',
'dev.moonbridge.app.ProxyMain', '--config', '/opt/moonbridge.yml') | Out-Null 'dev.moonbridge.app.ProxyMain', '--config', '/opt/moonbridge.yml') | Out-Null
$clientArgs = @('run', '--rm', '--network', $network, '-v', "${repoRoot}:/work:ro") $clientArgs = @('run', '--rm', '--network', $network, '-v', "${repoRoot}:/work:ro")
if ($mode -eq 'control') { $clientArgs += @('-e', "MOONBRIDGE_CHANNEL_SECRET=$secret") } if ($mode -eq 'control') { $clientArgs += @('-e', "MOONBRIDGE_CHANNEL_SECRET=$secret") }
+1 -1
View File
@@ -26,7 +26,7 @@ if ($ReturnToOld -and -not $InstalledPlugin) {
if (-not $PrismInstanceFolder) { $PrismInstanceFolder = $PrismInstance } if (-not $PrismInstanceFolder) { $PrismInstanceFolder = $PrismInstance }
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$bundle = (Resolve-Path -LiteralPath $BundlePath).Path $bundle = (Resolve-Path -LiteralPath $BundlePath).Path
$proxyLib = Join-Path $repoRoot 'proxy\core\build\install\moonbridge\lib' $proxyLib = Join-Path $repoRoot 'proxy\core\build\install\moonbridge'
if (-not (Test-Path -LiteralPath $Java8Path) -or -not (Test-Path -LiteralPath $proxyLib)) { if (-not (Test-Path -LiteralPath $Java8Path) -or -not (Test-Path -LiteralPath $proxyLib)) {
throw 'Java 8 and the installed MoonBridge distribution are required' throw 'Java 8 and the installed MoonBridge distribution are required'
} }
+1 -1
View File
@@ -25,7 +25,7 @@ $probeClasses = if ($PrismClient) { $null } else {
if (-not $ProbeClassesPath) { throw 'ProbeClassesPath is required without -PrismClient' } if (-not $ProbeClassesPath) { throw 'ProbeClassesPath is required without -PrismClient' }
(Resolve-Path -LiteralPath $ProbeClassesPath).Path (Resolve-Path -LiteralPath $ProbeClassesPath).Path
} }
$proxyLib = Join-Path $repoRoot 'proxy\core\build\install\moonbridge\lib' $proxyLib = Join-Path $repoRoot 'proxy\core\build\install\moonbridge'
if (-not (Test-Path -LiteralPath $Java8Path) -or -not (Test-Path -LiteralPath $proxyLib)) { if (-not (Test-Path -LiteralPath $Java8Path) -or -not (Test-Path -LiteralPath $proxyLib)) {
throw 'Java 8 and the installed MoonBridge distribution are required' throw 'Java 8 and the installed MoonBridge distribution are required'
} }
+2 -2
View File
@@ -14,8 +14,8 @@ $bundle = (Resolve-Path -LiteralPath $BundlePath).Path
$distribution = (Resolve-Path -LiteralPath $DistributionPath).Path $distribution = (Resolve-Path -LiteralPath $DistributionPath).Path
$template = Join-Path (Join-Path $PrismCacheRoot 'instances') $PrismTemplateInstance $template = Join-Path (Join-Path $PrismCacheRoot 'instances') $PrismTemplateInstance
$directHelper = Join-Path $PSScriptRoot 'permission-client-direct.cjs' $directHelper = Join-Path $PSScriptRoot 'permission-client-direct.cjs'
$distributionLib = Join-Path $distribution 'lib' $distributionLib = $distribution
$proxyLib = Join-Path $distribution 'lib\*' $proxyLib = Join-Path $distribution '*'
$packagedPlugins = Join-Path $distribution 'plugins' $packagedPlugins = Join-Path $distribution 'plugins'
$java25 = (Get-Command java.exe -ErrorAction Stop).Source $java25 = (Get-Command java.exe -ErrorAction Stop).Source
$javac25 = (Get-Command javac.exe -ErrorAction Stop).Source $javac25 = (Get-Command javac.exe -ErrorAction Stop).Source
+1 -1
View File
@@ -7,7 +7,7 @@ const mode = process.argv[2];
if (!['sql','redis'].includes(mode)) throw new Error('Usage: node testing/smoke/permission-network.cjs sql|redis [outage]'); if (!['sql','redis'].includes(mode)) throw new Error('Usage: node testing/smoke/permission-network.cjs sql|redis [outage]');
const outage = process.argv[3] === 'outage'; const outage = process.argv[3] === 'outage';
const log = fs.createWriteStream(path.join(root,'build',`permission-network-${mode}${outage?'-outage':''}.log`)); const log = fs.createWriteStream(path.join(root,'build',`permission-network-${mode}${outage?'-outage':''}.log`));
const args = ['-cp',`${path.join(root,'proxy/core/build/install/moonbridge/lib/*')}${path.delimiter}${path.join(root,'build/permission-network-classes')}`, const args = ['-cp',`${path.join(root,'proxy/core/build/install/moonbridge/*')}${path.delimiter}${path.join(root,'build/permission-network-classes')}`,
'PermissionNetworkAcceptance','proxy/core/build/install/moonbridge',`build/permission-services/${mode==='sql'?'mariadb':'redis'}-base.yml`,'build/permission-network',mode]; 'PermissionNetworkAcceptance','proxy/core/build/install/moonbridge',`build/permission-services/${mode==='sql'?'mariadb':'redis'}-base.yml`,'build/permission-network',mode];
if (outage) args.push('outage'); if (outage) args.push('outage');
const child = spawn('java',args,{cwd:root,windowsHide:true,stdio:['pipe','pipe','pipe']}); const child = spawn('java',args,{cwd:root,windowsHide:true,stdio:['pipe','pipe','pipe']});