Use unix domain socket for Docker commands

This commit is contained in:
Luck 2022-12-10 23:41:33 +00:00
parent 5555cdac89
commit 8e1553c0ed
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
7 changed files with 48 additions and 25 deletions

View File

@ -3,6 +3,9 @@ plugins {
id 'java-library' id 'java-library'
} }
sourceCompatibility = 17
targetCompatibility = 17
dependencies { dependencies {
implementation project(':api') implementation project(':api')

View File

@ -37,7 +37,6 @@ import net.luckperms.api.LuckPerms;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -80,7 +79,7 @@ public class LuckPermsApplication implements AutoCloseable {
List<String> arguments = Arrays.asList(args); List<String> arguments = Arrays.asList(args);
if (arguments.contains("--docker")) { 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); this.heartbeatHttpServer = HeartbeatHttpServer.createAndStart(3001, this.healthReporter);
} }
@ -98,7 +97,7 @@ public class LuckPermsApplication implements AutoCloseable {
if (this.dockerCommandSocket != null) { if (this.dockerCommandSocket != null) {
try { try {
this.dockerCommandSocket.close(); this.dockerCommandSocket.close();
} catch (IOException e) { } catch (Exception e) {
LOGGER.warn(e); LOGGER.warn(e);
} }
} }

View File

@ -30,33 +30,43 @@ import org.apache.logging.log4j.Logger;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.net.StandardProtocolFamily;
import java.net.ServerSocket; import java.net.UnixDomainSocketAddress;
import java.net.Socket; import java.nio.channels.Channels;
import java.net.SocketException; 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; 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. * 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 * <p>Combined with a small sh/nc program, this makes it easy to execute
* commands against a standalone instance of LP in a Docker container. * commands against a standalone instance of LP in a Docker container.</p>
*/ */
public class DockerCommandSocket extends ServerSocket implements Runnable { public class DockerCommandSocket implements Runnable, AutoCloseable {
private static final Logger LOGGER = LogManager.getLogger(DockerCommandSocket.class); 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; DockerCommandSocket socket = null;
try { 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 thread = new Thread(socket, "docker-command-socket");
thread.setDaemon(true); thread.setDaemon(true);
thread.start(); thread.start();
LOGGER.info("Created Docker command socket on port " + port);
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Error starting docker command socket", e); LOGGER.error("Error starting docker command socket", e);
} }
@ -64,30 +74,35 @@ public class DockerCommandSocket extends ServerSocket implements Runnable {
return socket; return socket;
} }
private final ServerSocketChannel channel;
private final Consumer<String> callback; private final Consumer<String> callback;
public DockerCommandSocket(int port, Consumer<String> callback) throws IOException { public DockerCommandSocket(ServerSocketChannel channel, Consumer<String> callback) throws IOException {
super(port); this.channel = channel;
this.callback = callback; this.callback = callback;
} }
@Override @Override
public void run() { public void run() {
while (!isClosed()) { while (this.channel.isOpen()) {
try (Socket socket = accept()) { try (SocketChannel socket = this.channel.accept()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { try (BufferedReader reader = new BufferedReader(Channels.newReader(socket, StandardCharsets.UTF_8))) {
String cmd; String cmd;
while ((cmd = reader.readLine()) != null) { while ((cmd = reader.readLine()) != null) {
LOGGER.info("Executing command from Docker: " + cmd); LOGGER.info("Executing command from Docker: " + cmd);
this.callback.accept(cmd); this.callback.accept(cmd);
} }
} }
} catch (ClosedChannelException e) {
// ignore
} catch (IOException e) { } catch (IOException e) {
if (e instanceof SocketException && e.getMessage().equals("Socket closed")) {
return;
}
LOGGER.error("Error processing input from the Docker socket", e); LOGGER.error("Error processing input from the Docker socket", e);
} }
} }
} }
@Override
public void close() throws Exception {
this.channel.close();
}
} }

View File

@ -59,7 +59,7 @@ public class HeartbeatHttpServer implements HttpHandler, AutoCloseable {
try { try {
socket = new HeartbeatHttpServer(healthReporter, port); 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) { } catch (Exception e) {
LOGGER.error("Error starting Heartbeat HTTP server", e); LOGGER.error("Error starting Heartbeat HTTP server", e);
} }

View File

@ -2,6 +2,9 @@ plugins {
id 'com.github.johnrengelman.shadow' version '7.0.0' id 'com.github.johnrengelman.shadow' version '7.0.0'
} }
sourceCompatibility = 17
targetCompatibility = 17
dependencies { dependencies {
implementation project(':common') implementation project(':common')
compileOnly project(':common:loader-utils') compileOnly project(':common:loader-utils')

View File

@ -4,7 +4,7 @@ RUN apk add --no-cache openjdk17 netcat-openbsd
# create a simple 'send' command that will allow users # create a simple 'send' command that will allow users
# to run, for example: docker exec <container> send lp info # to run, for example: docker exec <container> send lp info
RUN printf '#!/bin/sh\n\ 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 # setup user
RUN addgroup -S app && adduser -S -G app app RUN addgroup -S app && adduser -S -G app app

View File

@ -11,6 +11,9 @@ dependencies {
implementation project(':standalone:app') implementation project(':standalone:app')
} }
sourceCompatibility = 17
targetCompatibility = 17
application { application {
mainClass = 'me.lucko.luckperms.standalone.loader.StandaloneLoader' mainClass = 'me.lucko.luckperms.standalone.loader.StandaloneLoader'
} }