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'
}
sourceCompatibility = 17
targetCompatibility = 17
dependencies {
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.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<String> 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);
}
}

View File

@ -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.
* <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.</p>
*/
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<String> callback;
public DockerCommandSocket(int port, Consumer<String> callback) throws IOException {
super(port);
public DockerCommandSocket(ServerSocketChannel channel, Consumer<String> 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();
}
}

View File

@ -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);
}

View File

@ -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')

View File

@ -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 <container> 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

View File

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