From 253a38f60de99a13cb9ae512b96c05938475727e Mon Sep 17 00:00:00 2001 From: juanmuscaria Date: Thu, 2 Sep 2021 20:52:17 -0300 Subject: [PATCH] More subcommands --- README.md | 4 +- src/main/java/com/juanmuscaria/Javactl.java | 112 +++++++++++++++++- .../java/com/juanmuscaria/api/IService.java | 15 +++ .../java/com/juanmuscaria/impl/Systemd.java | 99 ++++++++++++++++ 4 files changed, 224 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 646019d..44d08d4 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Running `javactl` without any argument will perform a basic environment check to ## Todo list - [ ] Improve `javactl connect` virtual terminal. -- [ ] A way to list all java daemons. +- [x] A way to list all java daemons. - [ ] `javactl install` to allow the installation of java programs that provides metadata about how it should be installed. - [ ] Better error messages and handling. -- [ ] Wrapper for common systemctl commands. +- [x] Wrapper for common systemctl commands. - [ ] Better root detection. - [ ] Support for colors on `javactl connect`. - [ ] Custom ppa and native image for release. diff --git a/src/main/java/com/juanmuscaria/Javactl.java b/src/main/java/com/juanmuscaria/Javactl.java index 11cd56e..8a178f7 100644 --- a/src/main/java/com/juanmuscaria/Javactl.java +++ b/src/main/java/com/juanmuscaria/Javactl.java @@ -10,14 +10,19 @@ import picocli.AutoComplete; import picocli.CommandLine.Command; import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; import java.io.File; +import java.util.List; import static com.juanmuscaria.Javactl.checkEnvironment; +import static com.juanmuscaria.Javactl.warnPrivilegedAction; import static picocli.CommandLine.Help.Ansi.AUTO; @Command(name = "javactl", version = "0.1", description = "Command line utility to make and control java daemons.", - mixinStandardHelpOptions = true, subcommands = {AutoComplete.GenerateCompletion.class, CommandCreate.class, CommandConnect.class}) + mixinStandardHelpOptions = true, + subcommands = { AutoComplete.GenerateCompletion.class, CommandCreate.class, CommandConnect.class, + CommandList.class, CommandStart.class, CommandStop.class, CommandRestart.class, CommandDelete.class }) public class Javactl implements Runnable { public static void main(String[] args) throws Exception { @@ -100,6 +105,7 @@ public void run() { .autoRestart(autoRestart); File[] files = builder.build(); System.out.println("Service created as: " + files[0].getName()); + System.out.println("Enable it using javactl enable " + name); System.exit(0); } catch (IllegalArgumentException e) { System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); @@ -110,13 +116,13 @@ public void run() { @Command(name = "connect", description = "Connects to a running java service allowing you to gather information about the jvm and control over the input stream.", mixinStandardHelpOptions = true) class CommandConnect implements Runnable { - @Option(names = {"--name", "-n"}, description = "The daemon name.", required = true) - String name; @Option(names = {"--useCat", "-c"}, description = "Journalctl will be configured to use cat as it's output.") boolean useCat; - + @Parameters(paramLabel = "name", description = "The daemon name.") + String name; @Override public void run() { + checkEnvironment(); try { if (!IService.SERVICE.isServiceRunning(name)) throw new IllegalArgumentException("Service is not running!"); @@ -129,4 +135,102 @@ public void run() { System.exit(2); } } +} + +@Command(name = "list", description = "List all installed daemons.", mixinStandardHelpOptions = true) +class CommandList implements Runnable { + + @Override + public void run() { + checkEnvironment(); + try { + List services = IService.SERVICE.getInstalledServices(); + if (services.isEmpty()) { + System.out.println(AUTO.string("@|red No services installed!|@")); + } + for (String service : services) { + boolean isRunning = IService.SERVICE.isServiceRunning(service); + System.out.println(service + " - " + (isRunning?"Running":"Stopped")); + } + } catch (IllegalArgumentException e) { + System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); + System.exit(2); + } + } +} + +@Command(name = "start", description = "Start a daemon.", mixinStandardHelpOptions = true) +class CommandStart implements Runnable { + @Parameters(paramLabel = "name", description = "The daemon name.") + String name; + + @Override + public void run() { + warnPrivilegedAction(); + try { + if (IService.SERVICE.getServiceFileFor(name).isEmpty()) + throw new IllegalArgumentException("Service '" + name + "' does not exist."); + IService.SERVICE.startService(name); + } catch (IllegalArgumentException e) { + System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); + System.exit(2); + } + } +} + +@Command(name = "stop", description = "Stop a daemon.", mixinStandardHelpOptions = true) +class CommandStop implements Runnable { + @Parameters(paramLabel = "name", description = "The daemon name.") + String name; + + @Override + public void run() { + warnPrivilegedAction(); + try { + if (IService.SERVICE.getServiceFileFor(name).isEmpty()) + throw new IllegalArgumentException("Service '" + name + "' does not exist."); + IService.SERVICE.stopService(name); + } catch (IllegalArgumentException e) { + System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); + System.exit(2); + } + } +} + +@Command(name = "restart", description = "(Re)start a daemon.", mixinStandardHelpOptions = true) +class CommandRestart implements Runnable{ + @Parameters(paramLabel = "name", description = "The daemon name.") + String name; + + @Override + public void run() { + warnPrivilegedAction(); + try { + if (IService.SERVICE.getServiceFileFor(name).isEmpty()) + throw new IllegalArgumentException("Service '" + name + "' does not exist."); + IService.SERVICE.restartService(name); + } catch (IllegalArgumentException e) { + System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); + System.exit(2); + } + } +} + +@Command(name = "delete", aliases = "uninstall", description = "Stop and delete a daemon configuration.", mixinStandardHelpOptions = true) +class CommandDelete implements Runnable { + @Parameters(paramLabel = "name", description = "The daemon name.") + String name; + + @Override + public void run() { + warnPrivilegedAction(); + try { + if (IService.SERVICE.getServiceFileFor(name).isEmpty()) + throw new IllegalArgumentException("Service '" + name + "' does not exist."); + IService.SERVICE.deleteService(name); + } catch (IllegalArgumentException e) { + System.err.println(AUTO.string("@|red " + e.getMessage() + "|@")); + System.exit(2); + } + } } \ No newline at end of file diff --git a/src/main/java/com/juanmuscaria/api/IService.java b/src/main/java/com/juanmuscaria/api/IService.java index 8a82cc0..7dc9122 100644 --- a/src/main/java/com/juanmuscaria/api/IService.java +++ b/src/main/java/com/juanmuscaria/api/IService.java @@ -4,6 +4,7 @@ import javax.validation.constraints.NotNull; import java.io.File; +import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -37,4 +38,18 @@ default boolean isValidName(String name) { default String getValidationRules() { return "^[a-zA-Z0-9\\s]+$"; } + + List getInstalledServices(); + + void startService(String name); + + void stopService(String name); + + void restartService(String name); + + void deleteService(String name); + + void enableService(String name); + + void disableService(String name); } diff --git a/src/main/java/com/juanmuscaria/impl/Systemd.java b/src/main/java/com/juanmuscaria/impl/Systemd.java index 75a730a..20599eb 100644 --- a/src/main/java/com/juanmuscaria/impl/Systemd.java +++ b/src/main/java/com/juanmuscaria/impl/Systemd.java @@ -5,6 +5,8 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; public class Systemd implements IService { @@ -59,4 +61,101 @@ public boolean isServiceRunning(String name) { throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); } } + + @Override + public List getInstalledServices() { + List list = new ArrayList<>(); + File systemdServices = new File("/etc/systemd/system/"); + File[] services = systemdServices.listFiles(); + if (services == null) + throw new IllegalArgumentException("Unable to list services."); + for (File service : services) { + if (service.getName().startsWith("javactl-") && service.getName().endsWith(".service")) + list.add(removeJavactlIdent(service.getName())); + } + return list; + } + + @Override + @SneakyThrows({InterruptedException.class}) // pass it up the chain + public void startService(String name) { + try { + Process check = Runtime.getRuntime().exec("systemctl start --quiet javactl-" + name); + check.waitFor(); + if (check.exitValue() != 0) + throw new IllegalArgumentException("Systemd returned an error! Are you running with proper permissions?"); + } catch (IOException e) { + throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); + } + } + + @Override + @SneakyThrows({InterruptedException.class}) // pass it up the chain + public void stopService(String name) { + try { + Process check = Runtime.getRuntime().exec("systemctl stop --quiet javactl-" + name); + check.waitFor(); + if (check.exitValue() != 0) + throw new IllegalArgumentException("Systemd returned an error! Are you running with proper permissions?"); + } catch (IOException e) { + throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); + } + } + + @Override + @SneakyThrows({InterruptedException.class}) // pass it up the chain + public void restartService(String name) { + try { + Process check = Runtime.getRuntime().exec("systemctl restart --quiet javactl-" + name); + check.waitFor(); + if (check.exitValue() != 0) + throw new IllegalArgumentException("Systemd returned an error! Are you running with proper permissions?"); + } catch (IOException e) { + throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); + } + } + + @Override + public void deleteService(String name) { + if (isServiceRunning(name)) + stopService(name); + disableService(name); + if (!getServiceFileFor(name).orElseThrow().delete()) + throw new IllegalArgumentException("Unable to delete service file! Try running as root."); + Optional file = getServiceFileFor(name); + if (file.isPresent()) { + if (!file.get().delete()) + throw new IllegalArgumentException("Unable to delete socket file " + file.get().getAbsolutePath()); + } + } + + @Override + @SneakyThrows({InterruptedException.class}) // pass it up the chain + public void enableService(String name) { + try { + Process check = Runtime.getRuntime().exec("systemctl enable --quiet javactl-" + name); + check.waitFor(); + if (check.exitValue() != 0) + throw new IllegalArgumentException("Systemd returned an error! Are you running with proper permissions?"); + } catch (IOException e) { + throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); + } + } + + @Override + @SneakyThrows({InterruptedException.class}) // pass it up the chain + public void disableService(String name) { + try { + Process check = Runtime.getRuntime().exec("systemctl disable --quiet javactl-" + name); + check.waitFor(); + if (check.exitValue() != 0) + throw new IllegalArgumentException("Systemd returned an error! Are you running with proper permissions?"); + } catch (IOException e) { + throw new IllegalStateException("systemctl not found, this should be an unreachable exception, please open an issue if you see this message!"); + } + } + + private String removeJavactlIdent(String name) { + return name.substring(8,name.indexOf(".service")); + } }