From 8e1553c0ed39a3af65240b3eb1b4f831acbafdcc Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 10 Dec 2022 23:41:33 +0000 Subject: [PATCH] Use unix domain socket for Docker commands --- standalone/app/build.gradle | 3 + .../standalone/app/LuckPermsApplication.java | 5 +- .../app/utils/DockerCommandSocket.java | 55 ++++++++++++------- .../app/utils/HeartbeatHttpServer.java | 2 +- standalone/build.gradle | 3 + standalone/docker/Dockerfile | 2 +- standalone/loader/build.gradle | 3 + 7 files changed, 48 insertions(+), 25 deletions(-) diff --git a/standalone/app/build.gradle b/standalone/app/build.gradle index 91f342aab..696641afb 100644 --- a/standalone/app/build.gradle +++ b/standalone/app/build.gradle @@ -3,6 +3,9 @@ plugins { id 'java-library' } +sourceCompatibility = 17 +targetCompatibility = 17 + dependencies { implementation project(':api') diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/LuckPermsApplication.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/LuckPermsApplication.java index 2c6359947..f872f3e94 100644 --- a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/LuckPermsApplication.java +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/LuckPermsApplication.java @@ -37,7 +37,6 @@ 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; @@ -80,7 +79,7 @@ public class LuckPermsApplication implements AutoCloseable { List arguments = Arrays.asList(args); if (arguments.contains("--docker")) { - this.dockerCommandSocket = DockerCommandSocket.createAndStart(3000, terminal); + this.dockerCommandSocket = DockerCommandSocket.createAndStart("/opt/luckperms/luckperms.sock", terminal); this.heartbeatHttpServer = HeartbeatHttpServer.createAndStart(3001, this.healthReporter); } @@ -98,7 +97,7 @@ public class LuckPermsApplication implements AutoCloseable { if (this.dockerCommandSocket != null) { try { this.dockerCommandSocket.close(); - } catch (IOException e) { + } catch (Exception e) { LOGGER.warn(e); } } diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/DockerCommandSocket.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/DockerCommandSocket.java index d1e025c1d..b2435e1e4 100644 --- a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/DockerCommandSocket.java +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/DockerCommandSocket.java @@ -30,33 +30,43 @@ 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.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.Channels; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.function.Consumer; /** - * Simple/dumb socket that listens for connections on a given port, + * Simple/dumb unix domain socket that listens for connections, * 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. + *

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 { +public class DockerCommandSocket implements Runnable, AutoCloseable { private static final Logger LOGGER = LogManager.getLogger(DockerCommandSocket.class); - public static DockerCommandSocket createAndStart(int port, TerminalInterface terminal) { + public static DockerCommandSocket createAndStart(String socketPath, TerminalInterface terminal) { DockerCommandSocket socket = null; try { - socket = new DockerCommandSocket(port, terminal::runCommand); + Path path = Paths.get(socketPath); + Files.deleteIfExists(path); + + ServerSocketChannel channel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + channel.bind(UnixDomainSocketAddress.of(path)); + + socket = new DockerCommandSocket(channel, terminal::runCommand); Thread thread = new Thread(socket, "docker-command-socket"); thread.setDaemon(true); thread.start(); - - LOGGER.info("Created Docker command socket on port " + port); } catch (Exception e) { LOGGER.error("Error starting docker command socket", e); } @@ -64,30 +74,35 @@ public class DockerCommandSocket extends ServerSocket implements Runnable { return socket; } + private final ServerSocketChannel channel; private final Consumer callback; - public DockerCommandSocket(int port, Consumer callback) throws IOException { - super(port); + public DockerCommandSocket(ServerSocketChannel channel, Consumer callback) throws IOException { + this.channel = channel; this.callback = callback; } @Override public void run() { - while (!isClosed()) { - try (Socket socket = accept()) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { + while (this.channel.isOpen()) { + try (SocketChannel socket = this.channel.accept()) { + try (BufferedReader reader = new BufferedReader(Channels.newReader(socket, StandardCharsets.UTF_8))) { String cmd; while ((cmd = reader.readLine()) != null) { LOGGER.info("Executing command from Docker: " + cmd); this.callback.accept(cmd); } } + } catch (ClosedChannelException e) { + // ignore } catch (IOException e) { - if (e instanceof SocketException && e.getMessage().equals("Socket closed")) { - return; - } LOGGER.error("Error processing input from the Docker socket", e); } } } + + @Override + public void close() throws Exception { + this.channel.close(); + } } diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/HeartbeatHttpServer.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/HeartbeatHttpServer.java index 5a9067631..5338bd9f5 100644 --- a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/HeartbeatHttpServer.java +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/utils/HeartbeatHttpServer.java @@ -59,7 +59,7 @@ public class HeartbeatHttpServer implements HttpHandler, AutoCloseable { try { socket = new HeartbeatHttpServer(healthReporter, port); - LOGGER.info("Created Heartbeat HTTP server on port " + port); + LOGGER.info("Started healthcheck HTTP server on :" + port); } catch (Exception e) { LOGGER.error("Error starting Heartbeat HTTP server", e); } diff --git a/standalone/build.gradle b/standalone/build.gradle index bb699768a..88870e258 100644 --- a/standalone/build.gradle +++ b/standalone/build.gradle @@ -2,6 +2,9 @@ plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' } +sourceCompatibility = 17 +targetCompatibility = 17 + dependencies { implementation project(':common') compileOnly project(':common:loader-utils') diff --git a/standalone/docker/Dockerfile b/standalone/docker/Dockerfile index 320b645a2..70c4b3cee 100644 --- a/standalone/docker/Dockerfile +++ b/standalone/docker/Dockerfile @@ -4,7 +4,7 @@ RUN apk add --no-cache openjdk17 netcat-openbsd # create a simple 'send' command that will allow users # to run, for example: docker exec send lp info RUN printf '#!/bin/sh\n\ -echo "$@" | nc -N localhost 3000\n' >> /usr/bin/send && chmod 777 /usr/bin/send +echo "$@" | nc -NU /opt/luckperms/luckperms.sock\n' >> /usr/bin/send && chmod 777 /usr/bin/send # setup user RUN addgroup -S app && adduser -S -G app app diff --git a/standalone/loader/build.gradle b/standalone/loader/build.gradle index 0098875b2..c973a84ff 100644 --- a/standalone/loader/build.gradle +++ b/standalone/loader/build.gradle @@ -11,6 +11,9 @@ dependencies { implementation project(':standalone:app') } +sourceCompatibility = 17 +targetCompatibility = 17 + application { mainClass = 'me.lucko.luckperms.standalone.loader.StandaloneLoader' }