Skip to content

Commit 7c1747b

Browse files
committed
Port bind fix
1 parent 197c640 commit 7c1747b

5 files changed

Lines changed: 111 additions & 39 deletions

File tree

.github/workflows/maven.yml

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Java CI with Maven
1+
name: Build & Publish
22

33
on:
44
push:
@@ -12,6 +12,9 @@ permissions:
1212
contents: read
1313

1414
jobs:
15+
# ============================================================
16+
# 1. Сборка и тесты — всегда
17+
# ============================================================
1518
build:
1619
runs-on: ubuntu-latest
1720
steps:
@@ -23,22 +26,25 @@ jobs:
2326
java-version: '21'
2427
cache: maven
2528

26-
- name: Build with Maven
27-
run: mvn -B clean package -DskipTests
29+
- name: Build & test
30+
run: mvn -B clean package
2831

29-
- name: Upload plugin jar
32+
- name: Upload jar
3033
uses: actions/upload-artifact@v4
3134
with:
3235
name: PlayerStatsAPI
3336
path: |
3437
target/*.jar
3538
!target/original-*.jar
3639
37-
publish-modrinth:
40+
# ============================================================
41+
# 2. Публикация на Modrinth — только при push тега v*
42+
# ============================================================
43+
publish:
3844
runs-on: ubuntu-latest
3945
needs: build
40-
# Публикация только при push тега (v1.0, v2.1 и т.д.)
4146
if: startsWith(github.ref, 'refs/tags/v')
47+
4248
steps:
4349
- uses: actions/checkout@v4
4450

@@ -48,22 +54,30 @@ jobs:
4854
java-version: '21'
4955
cache: maven
5056

51-
- name: Build with Maven
57+
- name: Build jar
5258
run: mvn -B clean package -DskipTests
5359

60+
# Читаем версию из pom.xml и кладём в переменную окружения
61+
- name: Extract project version
62+
run: echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
63+
5464
- name: Publish to Modrinth
5565
uses: Kir-Antipov/mc-publish@v3.3
5666
with:
67+
# --- Modrinth credentials (задаются в Settings → Secrets) ---
5768
modrinth-id: ${{ secrets.MODRINTH_PROJECT_ID }}
5869
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
5970

60-
name: PlayerStatsAPI ${{ github.ref_name }}
61-
version: ${{ github.ref_name }}
71+
# --- Мета-информация о релизе ---
72+
name: "PlayerStatsAPI ${{ env.PROJECT_VERSION }}"
73+
version: "${{ env.PROJECT_VERSION }}"
6274
version-type: release
6375

64-
files: target/PlayerStats-API-*.jar
76+
# --- Файл для загрузки (без original- shade-артефакта) ---
77+
files: "target/PlayerStats-API-${{ env.PROJECT_VERSION }}.jar"
6578

66-
# Автодетект поддерживаемых версий из plugin.yml api-version
79+
# --- Поддерживаемые версии Minecraft (указываем явно) ---
80+
# mc-publish поддерживает диапазоны: 1.21.x = все патчи 1.21
6781
game-versions: |
6882
1.21
6983
1.21.1
@@ -77,8 +91,11 @@ jobs:
7791
1.21.9
7892
1.21.10
7993
1.21.11
94+
95+
# --- Поддерживаемые загрузчики ---
8096
loaders: |
8197
paper
8298
purpur
8399
100+
# --- Changelog из файла ---
84101
changelog-file: CHANGELOG.md

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A lightweight **Paper/Purpur plugin** that exposes Minecraft vanilla player stat
99
| Requirement | Version |
1010
|---|---|
1111
| Java | 21+ |
12-
| Paper / Purpur | 1.21 – 1.21.4 |
12+
| Paper / Purpur | 1.21 – 1.21.11 |
1313

1414
---
1515

@@ -283,12 +283,33 @@ mvn test
283283

284284
### Publishing to Modrinth
285285

286-
Push a tag matching `v*` (e.g. `v2.1`). The GitHub Actions workflow will build and publish automatically. Requires repository secrets:
286+
> **Important:** Modrinth has **no auto-detection** of supported Minecraft versions from `plugin.yml`.
287+
> Versions must always be declared explicitly — either in the upload form or via a publish tool.
288+
> This project uses **`mc-publish`** in GitHub Actions to handle that automatically on every release.
287289
288-
| Secret | Description |
290+
#### Setup (one time)
291+
292+
1. Create a project on [modrinth.com](https://modrinth.com) if you haven't already.
293+
2. Go to **modrinth.com/settings/pats** → create a token with `VERSION_CREATE` scope.
294+
3. In your GitHub repo: **Settings → Secrets and variables → Actions → New repository secret**:
295+
296+
| Secret | Value |
289297
|---|---|
290-
| `MODRINTH_PROJECT_ID` | Your project ID on Modrinth |
291-
| `MODRINTH_TOKEN` | API token from modrinth.com/settings/pats |
298+
| `MODRINTH_PROJECT_ID` | Your project's ID or slug from Modrinth |
299+
| `MODRINTH_TOKEN` | The PAT token you just created |
300+
301+
#### Releasing
302+
303+
```bash
304+
git tag v2.1
305+
git push origin v2.1
306+
```
307+
308+
The workflow (`build & publish`) will trigger automatically:
309+
1. Builds the jar and runs tests.
310+
2. Publishes to Modrinth with the game versions and loaders declared in `.github/workflows/maven.yml`.
311+
312+
To support a new Minecraft version (e.g. `1.21.5`), add it to the `game-versions` list in the workflow file and push a new tag.
292313

293314
---
294315

src/main/java/com/plp/statsplugin/StatsPlugin.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,24 @@ private void startWebServer() {
314314
bindAddress, maxPlayers, maxTop, corsEnabled, corsOrigin, rlEnabled, rlRequests, rlWindowMs);
315315

316316
webServer = new WebServer(statsManager, getLogger(), settings);
317-
webServer.start(port);
318-
getLogger().info("Web API запущен на " + bindAddress.getHostAddress() + ":" + port);
317+
318+
// Генерируем fallback-порты: port+1, port+2, ... port+9
319+
int[] fallbacks = new int[9];
320+
for (int i = 0; i < fallbacks.length; i++) fallbacks[i] = port + i + 1;
321+
322+
int boundPort = webServer.start(port, fallbacks);
323+
if (boundPort == -1) {
324+
getLogger()
325+
.severe("Не удалось занять ни один порт из диапазона "
326+
+ port + "–" + (port + fallbacks.length)
327+
+ ". Web API не запущен. Измените web.port в config.yml.");
328+
} else {
329+
if (boundPort != port) {
330+
getLogger()
331+
.warning("Порт " + port + " был занят. Web API запущен на резервном порту " + boundPort + ".");
332+
}
333+
getLogger().info("Web API запущен на " + bindAddress.getHostAddress() + ":" + boundPort);
334+
}
319335
}
320336

321337
private File resolveStatsFolder() {

src/main/java/com/plp/statsplugin/WebServer.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,45 @@ public WebServer(StatsManager statsManager, Logger logger, Settings settings) {
3636
: null;
3737
}
3838

39-
public void start(int port) {
40-
try {
41-
server = HttpServer.create(new InetSocketAddress(settings.bindAddress(), port), 0);
42-
43-
server.createContext("/moss/players/", this::handlePlayerByUUID);
44-
server.createContext("/moss/players", this::handleAllPlayers);
45-
server.createContext("/moss/player/", this::handlePlayerByName);
46-
server.createContext("/moss/online", this::handleOnline);
47-
server.createContext("/moss/summary", this::handleSummary);
48-
server.createContext("/moss/top/", this::handleTop);
49-
server.createContext("/moss/health", this::handleHealth);
50-
51-
server.setExecutor(Executors.newFixedThreadPool(4, r -> {
52-
Thread t = new Thread(r, "PlayerStatsAPI-http");
53-
t.setDaemon(true);
54-
return t;
55-
}));
56-
server.start();
57-
} catch (IOException e) {
58-
logger.log(Level.SEVERE, "Не удалось запустить Web-сервер.", e);
39+
/**
40+
* Attempts to bind on {@code port}. If that port is busy and
41+
* {@code fallbackPorts} are provided, tries each in order.
42+
*
43+
* @return the port actually bound, or -1 if every candidate failed
44+
*/
45+
public int start(int port, int... fallbackPorts) {
46+
int[] candidates = new int[1 + fallbackPorts.length];
47+
candidates[0] = port;
48+
System.arraycopy(fallbackPorts, 0, candidates, 1, fallbackPorts.length);
49+
50+
for (int candidate : candidates) {
51+
try {
52+
server = HttpServer.create(new InetSocketAddress(settings.bindAddress(), candidate), 0);
53+
54+
server.createContext("/moss/players/", this::handlePlayerByUUID);
55+
server.createContext("/moss/players", this::handleAllPlayers);
56+
server.createContext("/moss/player/", this::handlePlayerByName);
57+
server.createContext("/moss/online", this::handleOnline);
58+
server.createContext("/moss/summary", this::handleSummary);
59+
server.createContext("/moss/top/", this::handleTop);
60+
server.createContext("/moss/health", this::handleHealth);
61+
62+
server.setExecutor(Executors.newFixedThreadPool(4, r -> {
63+
Thread t = new Thread(r, "PlayerStatsAPI-http");
64+
t.setDaemon(true);
65+
return t;
66+
}));
67+
server.start();
68+
return candidate;
69+
} catch (java.net.BindException e) {
70+
logger.warning("Порт " + candidate + " занят, пробую следующий...");
71+
server = null;
72+
} catch (IOException e) {
73+
logger.log(Level.SEVERE, "Не удалось запустить Web-сервер на порту " + candidate + ".", e);
74+
server = null;
75+
}
5976
}
77+
return -1;
6078
}
6179

6280
public void stop() {

src/main/resources/plugin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: PlayerStatsAPI
22
version: ${project.version}
33
main: com.plp.statsplugin.StatsPlugin
4-
api-version: "1.21"
4+
api-version: '1.21'
55
description: Vanilla statistics web API and command helper.
66

77
commands:

0 commit comments

Comments
 (0)