1
0
mirror of https://github.com/lucko/LuckPerms.git synced 2025-09-03 11:22:33 +02:00

Implement standalone/cli app

This commit is contained in:
Luck
2022-07-20 22:46:22 +01:00
parent 8b0d6fd5d2
commit d36341c139
29 changed files with 2404 additions and 3 deletions

View File

@@ -0,0 +1,41 @@
plugins {
id 'net.kyori.blossom' version '1.3.0'
id 'java-library'
}
dependencies {
implementation project(':api')
api 'org.apache.logging.log4j:log4j-core:2.17.2'
api 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.2'
api 'net.minecrell:terminalconsoleappender:1.3.0'
api 'org.jline:jline-terminal-jansi:3.20.0'
api 'com.google.code.gson:gson:2.9.0'
api 'com.google.guava:guava:31.1-jre'
api('net.kyori:adventure-api:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'checker-qual')
exclude(module: 'annotations')
}
api('net.kyori:adventure-text-serializer-gson:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'gson')
}
api('net.kyori:adventure-text-serializer-legacy:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:adventure-text-serializer-plain:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:ansi:1.0.0-SNAPSHOT')
}
blossom {
replaceTokenIn('src/main/java/me/lucko/luckperms/standalone/app/LuckPermsApplication.java')
replaceToken '@version@', project.ext.fullVersion
}

View File

@@ -0,0 +1,118 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.app.integration.DockerCommandSocket;
import me.lucko.luckperms.standalone.app.integration.ShutdownCallback;
import me.lucko.luckperms.standalone.app.integration.TerminalInterface;
import net.luckperms.api.LuckPerms;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The LuckPerms standalone application.
*/
public class LuckPermsApplication implements AutoCloseable {
/** A logger instance */
public static final Logger LOGGER = LogManager.getLogger(LuckPermsApplication.class);
/** A callback to shutdown the application via the loader bootstrap. */
private final ShutdownCallback shutdownCallback;
/** The instance of the LuckPerms API available within the app */
private LuckPerms luckPermsApi;
/** A command executor interface to run LuckPerms commands */
private CommandExecutor commandExecutor;
/** If the application is running */
private final AtomicBoolean running = new AtomicBoolean(true);
/** The docker command socket */
private DockerCommandSocket dockerCommandSocket;
public LuckPermsApplication(ShutdownCallback shutdownCallback) {
this.shutdownCallback = shutdownCallback;
}
/**
* Start the app
*/
public void start(String[] args) {
TerminalInterface terminal = new TerminalInterface(this, this.commandExecutor);
List<String> arguments = Arrays.asList(args);
if (arguments.contains("--docker")) {
this.dockerCommandSocket = DockerCommandSocket.createAndStart(3000, terminal);
}
terminal.start(); // blocking
}
public void requestShutdown() {
this.shutdownCallback.shutdown();
}
@Override
public void close() {
this.running.set(false);
if (this.dockerCommandSocket != null) {
try {
this.dockerCommandSocket.close();
} catch (IOException e) {
LOGGER.warn(e);
}
}
}
public AtomicBoolean runningState() {
return this.running;
}
// called before start()
public void setApi(LuckPerms luckPermsApi) {
this.luckPermsApi = luckPermsApi;
}
// called before start()
public void setCommandExecutor(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
public String getVersion() {
return "@version@";
}
}

View File

@@ -0,0 +1,40 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* Minimal command executor interface.
*/
public interface CommandExecutor {
CompletableFuture<Void> execute(String command);
List<String> tabComplete(String command);
}

View File

@@ -0,0 +1,93 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.function.Consumer;
/**
* Simple/dumb socket that listens for connections on a given port,
* reads the input to a string, then executes it as a command.
*
* Combined with a small sh/nc program, this makes it easy to execute
* commands against a standalone instance of LP in a Docker container.
*/
public class DockerCommandSocket extends ServerSocket implements Runnable {
private static final Logger LOGGER = LogManager.getLogger(DockerCommandSocket.class);
public static DockerCommandSocket createAndStart(int port, TerminalInterface terminal) {
DockerCommandSocket socket = null;
try {
socket = new DockerCommandSocket(port, terminal::runCommand);
Thread thread = new Thread(socket, "docker-command-socket");
thread.setDaemon(true);
thread.start();
LOGGER.info("Created Docker command socket on port 3000");
} catch (Exception e) {
LOGGER.error("Error starting docker command socket", e);
}
return socket;
}
private final Consumer<String> callback;
public DockerCommandSocket(int port, Consumer<String> callback) throws IOException {
super(port);
this.callback = callback;
}
@Override
public void run() {
while (!isClosed()) {
try (Socket socket = accept()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
String cmd;
while ((cmd = reader.readLine()) != null) {
LOGGER.info("Executing command from Docker: " + cmd);
this.callback.accept(cmd);
}
}
} catch (IOException e) {
if (e instanceof SocketException && e.getMessage().equals("Socket closed")) {
return;
}
LOGGER.error("Error processing input from the Docker socket", e);
}
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
/**
* Shutdown callback for the whole standalone app.
*
* (in practice this is always implemented by the StandaloneLoader class)
*/
public interface ShutdownCallback {
void shutdown();
}

View File

@@ -0,0 +1,58 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import me.lucko.luckperms.standalone.app.utils.AnsiUtils;
import net.kyori.adventure.text.Component;
import java.util.UUID;
/**
* Dummy/singleton player class used by the standalone plugin.
*
* In various places (ContextManager, SenderFactory, ..) the platform "player" type is used
* as a generic parameter. This class acts as this type for the standalone plugin.
*/
public class SingletonPlayer {
public static final SingletonPlayer INSTANCE = new SingletonPlayer();
private static final UUID UUID = new UUID(0, 0);
public String getName() {
return "StandaloneUser";
}
public UUID getUniqueId() {
return UUID;
}
public void printStdout(Component component) {
LuckPermsApplication.LOGGER.info(AnsiUtils.format(component));
}
}

View File

@@ -0,0 +1,99 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.ParsedLine;
import java.util.List;
/**
* The terminal/console-style interface presented to the user.
*/
public class TerminalInterface extends SimpleTerminalConsole {
private final LuckPermsApplication application;
private final CommandExecutor commandExecutor;
public TerminalInterface(LuckPermsApplication application, CommandExecutor commandExecutor) {
this.application = application;
this.commandExecutor = commandExecutor;
}
@Override
protected LineReader buildReader(LineReaderBuilder builder) {
return super.buildReader(builder
.appName("LuckPerms")
.completer(this::completeCommand)
);
}
@Override
protected boolean isRunning() {
return this.application.runningState().get();
}
@Override
protected void shutdown() {
this.application.requestShutdown();
}
@Override
public void runCommand(String command) {
command = stripSlashLp(command);
if (command.equals("stop") || command.equals("exit")) {
this.application.requestShutdown();
return;
}
this.commandExecutor.execute(command);
}
private void completeCommand(LineReader reader, ParsedLine line, List<Candidate> candidates) {
String cmdLine = stripSlashLp(line.line());
for (String suggestion : this.commandExecutor.tabComplete(cmdLine)) {
candidates.add(new Candidate(suggestion));
}
}
private static String stripSlashLp(String command) {
if (command.startsWith("/")) {
command = command.substring(1);
}
if (command.startsWith("lp ")) {
command = command.substring(3);
}
return command;
}
}

View File

@@ -0,0 +1,131 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.utils;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.flattener.FlattenerListener;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.ansi.ANSIComponentRenderer;
import net.kyori.ansi.StyleOps;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
/**
* Utility to format a {@link Component} as an ANSI string.
*/
public final class AnsiUtils {
private AnsiUtils() {}
public static String format(Component component) {
ANSIComponentRenderer.ToString<Style> formatter = ANSIComponentRenderer.toString(AdventureStyleOps.INSTANCE);
ComponentFlattener.basic().flatten(component, new AnsiFlattenerListener(formatter));
return formatter.asString();
}
private static final class AnsiFlattenerListener implements FlattenerListener {
private final ANSIComponentRenderer.ToString<Style> formatter;
AnsiFlattenerListener(ANSIComponentRenderer.ToString<Style> formatter) {
this.formatter = formatter;
}
@Override
public void pushStyle(@NotNull Style style) {
this.formatter.pushStyle(style);
}
@Override
public void component(@NotNull String text) {
this.formatter.text(text);
}
@Override
public void popStyle(@NotNull Style style) {
this.formatter.popStyle(style);
}
}
private static final class AdventureStyleOps implements StyleOps<Style> {
private static final AdventureStyleOps INSTANCE = new AdventureStyleOps();
@Override
public State bold(@NotNull Style style) {
return state(style.decoration(TextDecoration.BOLD));
}
@Override
public State italics(@NotNull Style style) {
return state(style.decoration(TextDecoration.ITALIC));
}
@Override
public State underlined(@NotNull Style style) {
return state(style.decoration(TextDecoration.UNDERLINED));
}
@Override
public State strikethrough(@NotNull Style style) {
return state(style.decoration(TextDecoration.STRIKETHROUGH));
}
@Override
public State obfuscated(@NotNull Style style) {
return state(style.decoration(TextDecoration.OBFUSCATED));
}
@Override
public @Range(from = -1L, to = 16777215L) int color(@NotNull Style style) {
TextColor color = style.color();
return color == null ? StyleOps.COLOR_UNSET : color.value();
}
@Override
public @Nullable String font(@NotNull Style style) {
Key font = style.font();
return font == null ? null : font.asString();
}
private static State state(TextDecoration.State state) {
switch (state) {
case TRUE:
return State.TRUE;
case FALSE:
return State.FALSE;
case NOT_SET:
return State.UNSET;
default:
throw new AssertionError();
}
}
}
}