Skip to content
This repository has been archived by the owner on Jul 27, 2023. It is now read-only.

Commit

Permalink
0.3.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Apr 20, 2022
1 parent 9eaee08 commit c73d586
Show file tree
Hide file tree
Showing 21 changed files with 514 additions and 351 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
- Cleanup + optimizations

- Renamed `Server` to `Client`, and the former `ServerRepository` file `servers.json` to `clients.json`, just rename it...
- Renamed `Server` to `Client`, and the former `ServerRepository` file `servers.json` to `clients.json`, just rename it...

- Replaced the global whisper with a global chat, its more useful tbh...

- `JavelinServer` and `JavelinClient` instances are guaranteed to be created on startup.

- Added the ability to reconnect the client.

- Improved documentation
129 changes: 111 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,143 @@

## Description

A simple and fast cross communication system for Mindustry servers.
A simple and fast communication system for Mindustry servers, enabling powerful features such as global chats and synced moderation.

## Usage

### First set up

First, install the plugin on all the servers you wish to link and start them, it will create the necessary config files in the `./distributor/plugins/xpdustry-javelin-plugin` directory.
1. Install the plugin on all the servers you wish to link and start them, it will create the necessary config files in the `./distributor/plugins/xpdustry-javelin-plugin` directory.

Now choose a main server and generate a secret key with the `javelin server generate-secret` command, it is really important for the security.
2. Choose a main server and generate a secret key with the `javelin server generate-secret` command. Once generated, stop the main server and set the following values in the `server-config.properties` file :

Then, set the `javelin.server.enabled` property to `true` and set the `javelin.server.secret` property to the generated secret in the main server in the `server-config.properties` file.
- `javelin.server.enabled` to `true` to enable the server.

Now that the main server is ready, add new linked servers with the `javelin server add <name>` command. It will generate a token for each so copy and set the `javelin.client.token` property with it in the `client-config.properties` file in the destination server.
- `javelin.server.secret` to the generated secret key.

> You also need to create a client for the main server.
3. Start the main server again, and add the servers you wish to link with the `javelin server add <name>` command. It will generate a token for each server so copy them and set the following values in the `client-config.properties` file of the servers :

Now, you may start all the servers to see if they link correctly. After that, Javelin is ready to be used.
- `javelin.client.enabled` to `true` to enable the client.

### Registering services
- `javelin.client.token` to the generated token.

Javelin network security is handled with endpoints, they allow external servers to access your Javelin network without leaking sensitive data.
> You also need to create a client for the main server.
To add an endpoint to a server, do the command `javelin server endpoint add <name> <namespace> <subject>` where :
4. Restart all the servers and now, they all should be linked. If you have errors, turn on debug with the `config debug true` command and try again. If you can't figure out the problem, I'll be happy to help you in the **#support** channel of the [Xpdustry Discord server](https://discord.xpdustry.fr).

### Managing service access

Javelin communicates using endpoints, where messages are handled. You can regulate the access of specific endpoints with blacklists and whitelists.

To do so, use the commands `javelin server blacklist/whitelist add/remove <name> <namespace> <subject>` where :

- `name` is the server name.

- `namespace` is the internal name of the plugin responsible for the endpoint (example: `xpdustry-javelin`).

- `subject` is the name of the specific handler that will handle the message (example: `whisper`).
- `subject` is the name of the specific handler that handles the message (example: `global-chat`).

So if you want, for example to disable the built-in global chat command for a specific server, do `javelin server blacklist add <server> xpdustry-javelin global-chat`.

### Api

It's very simple.

First, add this in your `build.gradle` :

So if you want, for example to enable the built-in global whisper command of Javelin on a server, do the `javelin server endpoint add <name> xpdustry-javelin whisper` in the main server.
```gradle
repositories {
// Replace with "https://repo.xpdustry.fr/snapshots" if you want to use the snapshots
maven { url = uri("https://repo.xpdustry.fr/releases") }
}
### Api usage
dependencies {
// Add "-SNAPSHOT" after the version if you are using the snapshot repository
compileOnly("fr.xpdustry:javelin:0.3.0" )
}
```

Its terribly simple, just get the client with `Javelin.getClient()`, check if it's not null, and enjoy.
Then, update your `plugin.json` file with :

```json
{
"dependencies": [
"xpdustry-javelin"
]
}
```

Finally, in your code, get the client with `Javelin.getClient()`, check if it's connected with `isConnected()` and enjoy.

You can `send` or `broadcast` any message you like, as long as it is serializable by `Gson`.

### Tips

- In the client config, if you set `javelin.client.timeout` to `0`, Your client will always reconnect to your main server, even if it has been down for a long time.

- You can explicitly reconnect a Javelin client with the `javelin client reconnect` command.

- You can reset the token of a specific server with `javelin server token reset <name>`.

- List all the registered servers with `javelin server list`

- If you have https enabled on your server domain with a reverse proxy (nginx, Apache web server, ...), enable the secure websocket protocol by setting :

- `javelin.server.wss` to `true` in the `server-config.properties` file in the main server.

- `javelin.client.wss` to `true` in the `client-config.properties` file of each client.

Then change the config of your reverse proxy. Example with nginx + let's encrypt / certbot :

```nginx
# This is required to upgrade the websocket connection
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream javelin {
# I use pterodactyl pannel so I use the node url, if you don't, use 127.0.0.1 or localhost
# 12000 is the port of my javelin main server
server n1.xpdustry.fr:12000;
keepalive 64;
}
server {
# Don't forget to change that
server_name javelin.xpdustry.fr;
listen 443 ssl;
listen [::]:443 ssl;
# Don't forget to change that
access_log /var/log/nginx/javelin.xpdustry.fr-access.log;
error_log /var/log/nginx/javelin.xpdustry.fr-error.log;
location / {
proxy_pass http://javelin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
}
# This part is generated by certbot, so replace it with your own
# Managed by Certbot
ssl_certificate /etc/letsencrypt/live/xpdustry.fr/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/xpdustry.fr/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
```

Now, your javelin server is accessible with `wss://javelin.yourdomain.something/`.

Your server will also refuse any normal websocket connection now.

## Building

- `./gradlew jar` for a simple jar that contains only the plugin code.
Expand All @@ -67,7 +164,3 @@ This plugin is compatible with V6 and V7, but it requires the following dependen
- [xpdustry-kotlin-stdlib](https://github.com/Xpdustry/KotlinRuntimePlugin)

If you run on v135 or lower, you will need [mod-loader](https://github.com/Xpdustry/ModLoaderPlugin) for the dependency resolution.

## Support

Strange error codes ? Crashes ? Report them in the [Xpdustry discord server](https://discord.xpdustry.fr) in the **#support** channel.
78 changes: 36 additions & 42 deletions src/main/kotlin/fr/xpdustry/javelin/Javelin.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
package fr.xpdustry.javelin

import arc.Core
import arc.util.Strings
import cloud.commandframework.annotations.AnnotationParser
import cloud.commandframework.arguments.standard.StringArgument
import cloud.commandframework.services.State
import com.google.inject.AbstractModule
import com.google.inject.Guice
import com.google.inject.Injector
import com.google.inject.Provides
import fr.xpdustry.distributor.Distributor
import fr.xpdustry.distributor.command.ArcCommandManager
import fr.xpdustry.distributor.command.ArcMeta
import fr.xpdustry.distributor.command.sender.ArcCommandSender
import fr.xpdustry.distributor.message.MessageIntent
import fr.xpdustry.distributor.plugin.AbstractPlugin
import fr.xpdustry.javelin.command.ServerCommand
import fr.xpdustry.javelin.command.JavelinClientCommand
import fr.xpdustry.javelin.command.JavelinServerCommand
import fr.xpdustry.javelin.internal.JavelinClientConfig
import fr.xpdustry.javelin.internal.JavelinServerConfig
import fr.xpdustry.javelin.repository.ClientRepository
import fr.xpdustry.javelin.service.JavelinWhisperService
import fr.xpdustry.javelin.service.WhisperContext
import fr.xpdustry.javelin.service.WhisperFormatter
import fr.xpdustry.javelin.service.WhisperService
import fr.xpdustry.javelin.util.format
import fr.xpdustry.javelin.service.chat.GlobalChatContext
import fr.xpdustry.javelin.service.chat.GlobalChatFormatter
import fr.xpdustry.javelin.service.chat.GlobalChatService
import fr.xpdustry.javelin.service.chat.JavelinGlobalChatService

import fr.xpdustry.javelin.util.invoke
import fr.xpdustry.javelin.util.formatter
import fr.xpdustry.javelin.util.servicePipeline
import fr.xpdustry.javelin.util.typeToken
import mindustry.gen.Call
import net.mindustry_ddns.store.FileStore
import java.nio.charset.StandardCharsets
import java.util.*
Expand All @@ -35,11 +37,11 @@ import javax.crypto.KeyGenerator
class Javelin : AbstractPlugin() {
companion object {
@JvmStatic
var client: JavelinClient? = null
lateinit var client: JavelinClient
private set

@JvmStatic
var server: JavelinServer? = null
lateinit var server: JavelinServer
private set
}

Expand All @@ -52,59 +54,51 @@ class Javelin : AbstractPlugin() {
serverStore = getStoredConfig("server-config", JavelinServerConfig::class.java)
injector = Guice.createInjector(JavelinModule())

servicePipeline.registerServiceType(typeToken<WhisperService>(), WhisperService.local())
server = injector.getInstance(JavelinServer::class.java)
client = injector.getInstance(JavelinClient::class.java)

Distributor.getServicePipeline().registerServiceType(
typeToken<GlobalChatService>(),
JavelinGlobalChatService()
)

if (serverStore.get().enabled) {
server = injector.getInstance(JavelinServer::class.java)
Core.app.addListener(server)
}

if (clientStore.get().enabled) {
client = injector.getInstance(JavelinClient::class.java)
Core.app.addListener(client)
servicePipeline.registerServiceImplementation(
typeToken<WhisperService>(),
injector.getInstance(JavelinWhisperService::class.java),
emptyList()
)
}
}

override fun registerClientCommands(manager: ArcCommandManager) {
manager.command(manager.commandBuilder("whisper", "w")
.meta(ArcMeta.DESCRIPTION, "Whisper to somebody, I dunno...")
.argument(StringArgument.quoted("receiver"))
manager.command(manager.commandBuilder("global", "g")
.meta(ArcMeta.DESCRIPTION, "Send a message globally to all servers.")
.meta(ArcMeta.PARAMETERS, "[message...]")
.argument(StringArgument.greedy("message"))
.handler {
if (Strings.stripColors(it.sender.player.name()) == Strings.stripColors(it["receiver"])) {
it.sender.sendMessage(it.sender.formatter.format(
"You can't message yourself.", MessageIntent.ERROR)
)
val context = GlobalChatContext(it.sender.player.name(), it["message"])
val result = try {
Distributor.getServicePipeline().pump(context).through(typeToken<GlobalChatService>()).result
} catch (e: Exception) {
State.REJECTED
}

if (result == State.ACCEPTED) {
Call.sendMessage(GlobalChatFormatter.instance.format(context))
} else {
val context = WhisperContext(it.sender.player.name(), it["receiver"], it["message"])
val result = try {
servicePipeline.pump(context).through(typeToken<WhisperService>()).result
} catch (e: Exception) {
State.REJECTED
}

if (result == State.ACCEPTED) {
it.sender.player.sendMessage(WhisperFormatter.instance.format(context))
} else {
it.sender.sendMessage(
it.sender.formatter.format(
"The player ${context.receiver} is not online.", MessageIntent.ERROR
)
)
}
it.sender.sendMessage(it.sender.formatter.invoke(
"Failed to broadcast the message. Please, report it to the server owner.", MessageIntent.ERROR
))
}
}
)
}

override fun registerServerCommands(manager: ArcCommandManager) {
val annotations = AnnotationParser(manager, ArcCommandSender::class.java) { manager.createDefaultCommandMeta() }
annotations.parse(injector.getInstance(ServerCommand::class.java))
annotations.parse(injector.getInstance(JavelinServerCommand::class.java))
annotations.parse(injector.getInstance(JavelinClientCommand::class.java))

manager.command(manager.commandBuilder("javelin").literal("server").literal("generate-secret")
.meta(ArcMeta.DESCRIPTION, "Generates a secret key for your server, be aware that changing it revokes all you server tokens.")
Expand Down
Loading

0 comments on commit c73d586

Please sign in to comment.