Skip to content

Commit

Permalink
More subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
juanmuscaria committed Sep 2, 2021
1 parent bb0d4a2 commit 253a38f
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 6 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
112 changes: 108 additions & 4 deletions src/main/java/com/juanmuscaria/Javactl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() + "|@"));
Expand All @@ -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!");
Expand All @@ -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<String> 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);
}
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/juanmuscaria/api/IService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -37,4 +38,18 @@ default boolean isValidName(String name) {
default String getValidationRules() {
return "^[a-zA-Z0-9\\s]+$";
}

List<String> 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);
}
99 changes: 99 additions & 0 deletions src/main/java/com/juanmuscaria/impl/Systemd.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String> getInstalledServices() {
List<String> 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> 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"));
}
}

0 comments on commit 253a38f

Please sign in to comment.