bruh
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"role": "lobby"
|
||||
},
|
||||
"redis": {
|
||||
"url": null,
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379,
|
||||
"password": null,
|
||||
@@ -63,13 +64,14 @@
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `host` | `"127.0.0.1"` | Redis host |
|
||||
| `port` | `6379` | Redis port |
|
||||
| `password` | `null` | Redis AUTH password. Prefer the env var (below). |
|
||||
| `database` | `0` | Redis logical database (0-15 on stock Redis) |
|
||||
| `key_prefix` | `"network:"` | All keys NetworkCore writes start with this prefix. Server registry keys end up at `network:servers:<id>`. Change this only if you're sharing a Redis with something unrelated. |
|
||||
| `tls` | `false` | Enables TLS for the Redis connection (Lettuce `rediss://` equivalent). **Set to true whenever Redis is on a different host than the Hytale server**, since the connection crosses untrusted network. |
|
||||
| `verify_peer` | `true` | When `tls` is on, verifies the Redis server's certificate against the JVM's default trust store. Set to false only in dev / against self-signed certs. Logs a warning when disabled. |
|
||||
| `url` | `null` | Full Redis connection URI. Supports both `redis://` (plain) and `rediss://` (TLS). When set, **all other redis fields are ignored** (host, port, password, database, tls). This is what you want when your provider hands you a one-line connection string. Prefer the env var (below) over putting credentials in the file. |
|
||||
| `host` | `"127.0.0.1"` | Redis host. Only used when `url` is null. |
|
||||
| `port` | `6379` | Redis port. Only used when `url` is null. |
|
||||
| `password` | `null` | Redis AUTH password. Only used when `url` is null. Prefer the env var. |
|
||||
| `database` | `0` | Redis logical database (0-15 on stock Redis). Only used when `url` is null. |
|
||||
| `key_prefix` | `"network:"` | All keys NetworkCore writes start with this prefix. Server registry keys end up at `network:servers:<id>`. Change this only if you're sharing a Redis with something unrelated. **Applies regardless of `url`.** |
|
||||
| `tls` | `false` | Enables TLS for the Redis connection. Only used when `url` is null (the URI scheme picks TLS on/off when `url` is set). **Set to true whenever Redis is on a different host than the Hytale server**, since the connection crosses untrusted network. |
|
||||
| `verify_peer` | `true` | When TLS is on (via `tls: true` or a `rediss://` url), verifies the Redis server's certificate against the JVM's default trust store. Set to false only in dev / against self-signed certs. Logs a warning when disabled. |
|
||||
|
||||
### network
|
||||
|
||||
@@ -115,6 +117,7 @@ These take precedence over the config file at boot time. Use them for secrets so
|
||||
|
||||
| Env var | Overrides |
|
||||
|---|---|
|
||||
| `REDIS_URL` | `redis.url` (full connection URI) |
|
||||
| `REDIS_PASSWORD` | `redis.password` |
|
||||
| `NETWORK_AUTH_TOKEN` | `network.auth_token` |
|
||||
| `MYSQL_PASSWORD` | `mysql.password` |
|
||||
@@ -127,7 +130,7 @@ These take precedence over the config file at boot time. Use them for secrets so
|
||||
**Multi-server staging or production:**
|
||||
- `server.id` left null is fine if you're OK with the id changing on every boot. Set it to something stable like `lobby-prod-01` if you want logs and dashboards to keep continuity across restarts.
|
||||
- `server.role` set explicitly per server. This is what dashboards and routing use.
|
||||
- `redis.host` pointed at your shared Redis. `redis.password` not in the file; set `REDIS_PASSWORD` env var.
|
||||
- `redis.tls` **on** whenever Redis is on a different host than the Hytale server. `verify_peer` stays true unless Redis uses a self-signed cert you haven't loaded into the JVM trust store.
|
||||
- **Easiest path for hosted Redis**: set `REDIS_URL` env var to the `rediss://user:pass@host:port/0` string your provider gives you. Leave the host/port/password/database fields at defaults. The `rediss://` scheme already implies TLS - no need to touch `tls`.
|
||||
- **Alternative**: set `redis.host` / `redis.port` explicitly, put the password in `REDIS_PASSWORD`, and set `redis.tls: true`. Works the same; just more fields to keep in sync across servers.
|
||||
- `network.auth_token` not in the file; set `NETWORK_AUTH_TOKEN` env var to the same value on every server.
|
||||
- `metrics.bind` stays `127.0.0.1`. Run Prometheus on the same host (or use an SSH tunnel / reverse proxy with auth to expose it elsewhere).
|
||||
|
||||
+14
-4
@@ -4,6 +4,20 @@ Currently one in-game admin command: `/networkcore`.
|
||||
|
||||
All subcommands are run by an in-world player. Console support isn't wired up yet.
|
||||
|
||||
## Permissions
|
||||
|
||||
Every subcommand is gated by a permission node, checked via Hytale's `PermissionsModule`. Assign these to your staff/admin groups in the Hytale server's own permission config.
|
||||
|
||||
| Subcommand | Required permission |
|
||||
|---|---|
|
||||
| `/networkcore status` | `networkcore.status` |
|
||||
| `/networkcore servers` | `networkcore.servers` |
|
||||
| `/networkcore publish` | `networkcore.publish` |
|
||||
|
||||
You can grant the whole namespace with `networkcore.*` (Hytale's permission system handles wildcards). A player without the relevant node sees Hytale's standard `You do not have permission for this command.` message - the command system itself blocks execution before our code runs.
|
||||
|
||||
Permissions are wired via `AbstractCommand.requirePermission(node)` in each subcommand constructor, and `canGeneratePermission()` is overridden to false on every command (including the root) so Hytale doesn't auto-generate a different one and clash with ours.
|
||||
|
||||
## /networkcore status
|
||||
|
||||
Prints local server identity, Redis state, and the metrics endpoint.
|
||||
@@ -49,10 +63,6 @@ The JSON is parsed and wrapped in the standard envelope (with your auth token if
|
||||
|
||||
This is dangerous in production - it lets anyone with command access spoof any message on any channel. Lock it down with permissions before exposing to non-admins.
|
||||
|
||||
## Permissions
|
||||
|
||||
Subcommand permissions aren't enforced yet. If your server runs HytalePerms, gate `/networkcore` behind an admin group at the command-routing layer.
|
||||
|
||||
## Things this command does *not* do (and why)
|
||||
|
||||
- **Restart Redis / reload config.** Restart the server. Reload is fragile - half-loaded state is worse than no state.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.kewwbec.networkcore;
|
||||
|
||||
public final class NetworkCorePermissions {
|
||||
|
||||
public static final String ROOT = "networkcore";
|
||||
public static final String STATUS = ROOT + ".status";
|
||||
public static final String SERVERS = ROOT + ".servers";
|
||||
public static final String PUBLISH = ROOT + ".publish";
|
||||
|
||||
private NetworkCorePermissions() {
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,17 @@ public final class RedisMessageBus implements MessageBus {
|
||||
this.serverId = serverId;
|
||||
this.authToken = (authToken == null || authToken.isEmpty()) ? null : authToken;
|
||||
|
||||
RedisURI uri;
|
||||
if (redis.url != null && !redis.url.isBlank()) {
|
||||
uri = RedisURI.create(redis.url.trim());
|
||||
if (uri.getTimeout() == null) {
|
||||
uri.setTimeout(Duration.ofSeconds(5));
|
||||
}
|
||||
if (uri.isSsl() && !redis.verify_peer) {
|
||||
uri.setVerifyPeer(io.lettuce.core.SslVerifyMode.NONE);
|
||||
LOGGER.at(Level.WARNING).log("Redis TLS is on but peer verification is OFF. Only acceptable in dev.");
|
||||
}
|
||||
} else {
|
||||
RedisURI.Builder b = RedisURI.Builder.redis(redis.host, redis.port)
|
||||
.withDatabase(redis.database)
|
||||
.withTimeout(Duration.ofSeconds(5));
|
||||
@@ -68,7 +79,8 @@ public final class RedisMessageBus implements MessageBus {
|
||||
LOGGER.at(Level.WARNING).log("Redis TLS is on but peer verification is OFF. Only acceptable in dev.");
|
||||
}
|
||||
}
|
||||
RedisURI uri = b.build();
|
||||
uri = b.build();
|
||||
}
|
||||
|
||||
this.client = RedisClient.create(uri);
|
||||
this.commands = client.connect();
|
||||
@@ -99,8 +111,8 @@ public final class RedisMessageBus implements MessageBus {
|
||||
this.rejectedCounter = (metrics == null) ? null : metrics.counter("messagebus_rejected_total", Map.of());
|
||||
|
||||
LOGGER.at(Level.INFO).log("Connected to Redis at %s:%d (db %d, tls=%s, auth=%s)",
|
||||
redis.host, redis.port, redis.database,
|
||||
redis.tls ? "on" : "off",
|
||||
uri.getHost(), uri.getPort(), uri.getDatabase(),
|
||||
uri.isSsl() ? "on" : "off",
|
||||
this.authToken != null ? "on" : "off");
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||
import com.hypixel.hytale.server.core.universe.world.World;
|
||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||
import net.kewwbec.networkcore.NetworkCore;
|
||||
import net.kewwbec.networkcore.NetworkCorePermissions;
|
||||
import net.kewwbec.networkcore.api.DatabaseService;
|
||||
import net.kewwbec.networkcore.api.MessageBus;
|
||||
import net.kewwbec.networkcore.api.ServerInfo;
|
||||
@@ -33,6 +34,11 @@ public final class NetworkCoreCommand extends AbstractCommandCollection {
|
||||
addSubCommand(new PublishCommand(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGeneratePermission() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final class StatusCommand extends AbstractPlayerCommand {
|
||||
|
||||
private final NetworkCore plugin;
|
||||
@@ -40,6 +46,12 @@ public final class NetworkCoreCommand extends AbstractCommandCollection {
|
||||
public StatusCommand(@Nonnull NetworkCore plugin) {
|
||||
super("status", "Show local server status");
|
||||
this.plugin = plugin;
|
||||
requirePermission(NetworkCorePermissions.STATUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGeneratePermission() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,6 +101,12 @@ public final class NetworkCoreCommand extends AbstractCommandCollection {
|
||||
public ServersCommand(@Nonnull NetworkCore plugin) {
|
||||
super("servers", "List known servers in the network");
|
||||
this.plugin = plugin;
|
||||
requirePermission(NetworkCorePermissions.SERVERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGeneratePermission() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,6 +140,12 @@ public final class NetworkCoreCommand extends AbstractCommandCollection {
|
||||
public PublishCommand(@Nonnull NetworkCore plugin) {
|
||||
super("publish", "Publish a raw JSON payload to a channel");
|
||||
this.plugin = plugin;
|
||||
requirePermission(NetworkCorePermissions.PUBLISH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGeneratePermission() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,6 +41,10 @@ public final class ConfigLoader {
|
||||
|
||||
@Nonnull
|
||||
private static CoreConfig applyEnvOverrides(@Nonnull CoreConfig config) {
|
||||
String envUrl = System.getenv("REDIS_URL");
|
||||
if (envUrl != null && !envUrl.isEmpty()) {
|
||||
config.redis.url = envUrl;
|
||||
}
|
||||
String envPw = System.getenv("REDIS_PASSWORD");
|
||||
if (envPw != null && !envPw.isEmpty()) {
|
||||
config.redis.password = envPw;
|
||||
|
||||
@@ -18,6 +18,13 @@ public final class CoreConfig {
|
||||
}
|
||||
|
||||
public static final class RedisSection {
|
||||
/**
|
||||
* Full Redis URI ("redis://..." or "rediss://..."). When set, host/port/password/database/tls
|
||||
* fields below are ignored. Use this when your provider gives you a one-line connection string.
|
||||
*/
|
||||
@Nullable
|
||||
public String url = null;
|
||||
|
||||
public String host = "127.0.0.1";
|
||||
public int port = 6379;
|
||||
@Nullable
|
||||
|
||||
@@ -115,7 +115,7 @@ public final class RedisServerRegistry implements ServerRegistry {
|
||||
ServerInfo previous = cache.put(p.id, info);
|
||||
if (previous == null) {
|
||||
try {
|
||||
eventBus.dispatchFor(ServerJoinedNetworkEvent.class).dispatch((IBaseEvent) new ServerJoinedNetworkEvent(info));
|
||||
eventBus.dispatchFor(ServerJoinedNetworkEvent.class).dispatch(new ServerJoinedNetworkEvent(info));
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ public final class RedisServerRegistry implements ServerRegistry {
|
||||
ServerInfo removed = cache.remove(p.id);
|
||||
if (removed != null) {
|
||||
try {
|
||||
eventBus.dispatchFor(ServerLeftNetworkEvent.class).dispatch((IBaseEvent) new ServerLeftNetworkEvent(removed));
|
||||
eventBus.dispatchFor(ServerLeftNetworkEvent.class).dispatch(new ServerLeftNetworkEvent(removed));
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ public final class RedisServerRegistry implements ServerRegistry {
|
||||
ServerInfo info = latest.get(j);
|
||||
if (info != null) {
|
||||
try {
|
||||
eventBus.dispatchFor(ServerJoinedNetworkEvent.class).dispatch((IBaseEvent) new ServerJoinedNetworkEvent(info));
|
||||
eventBus.dispatchFor(ServerJoinedNetworkEvent.class).dispatch(new ServerJoinedNetworkEvent(info));
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ public final class RedisServerRegistry implements ServerRegistry {
|
||||
for (String l : left) {
|
||||
ServerInfo info = new ServerInfo(l, "unknown", null, 0, 0L, 0L);
|
||||
try {
|
||||
eventBus.dispatchFor(ServerLeftNetworkEvent.class).dispatch((IBaseEvent) new ServerLeftNetworkEvent(info));
|
||||
eventBus.dispatchFor(ServerLeftNetworkEvent.class).dispatch(new ServerLeftNetworkEvent(info));
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"Name": "NetworkCore",
|
||||
"Version": "0.1.0",
|
||||
"Description": "Cross-server messaging and metrics core",
|
||||
"IncludesAssetsPack": false,
|
||||
"IncludesAssetPack": false,
|
||||
"Authors": [
|
||||
{"Name": "kewwbec"}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user