diff --git a/pom.xml b/pom.xml index af59360..6b4d59e 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ 1.0.11 1.4 1.2.3 + 1.2.4 11 UTF-8 @@ -46,6 +47,11 @@ webdav-nio-adapter ${webdav-nio.version} + + org.cryptomator + fuse-nio-adapter + ${fuse-nio.version} + diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java index 13de342..492310b 100644 --- a/src/main/java/org/cryptomator/cli/Args.java +++ b/src/main/java/org/cryptomator/cli/Args.java @@ -8,6 +8,7 @@ *******************************************************************************/ package org.cryptomator.cli; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -71,22 +72,43 @@ public class Args { .valueSeparator() // .hasArgs() // .build()); + OPTIONS.addOption(Option.builder() // + .longOpt("fusemount") // + .argName("mount point") // + .desc("Format must be vaultName=mountpoint") // + .valueSeparator() // + .hasArgs() // + .build()); } private final String bindAddr; private final int port; + private final boolean hasValidWebDavConfig; private final Properties vaultPaths; private final Properties vaultPasswords; private final Properties vaultPasswordFiles; private final Map passwordStrategies; + private final Properties fuseMountPoints; public Args(CommandLine commandLine) throws ParseException { - this.bindAddr = commandLine.getOptionValue("bind", "localhost"); - this.port = Integer.parseInt(commandLine.getOptionValue("port", "0")); + if (commandLine.hasOption("bind") && commandLine.hasOption("port")) { + hasValidWebDavConfig = true; + this.bindAddr = commandLine.getOptionValue("bind", "localhost"); + this.port = Integer.parseInt(commandLine.getOptionValue("port", "0")); + } else { + hasValidWebDavConfig = false; + this.bindAddr = ""; + this.port = -1; + } this.vaultPaths = commandLine.getOptionProperties("vault"); this.vaultPasswords = commandLine.getOptionProperties("password"); this.vaultPasswordFiles = commandLine.getOptionProperties("passwordfile"); this.passwordStrategies = new HashMap<>(); + this.fuseMountPoints = commandLine.getOptionProperties("fusemount"); + } + + public boolean hasValidWebDavConf() { + return hasValidWebDavConfig; } public String getBindAddr() { @@ -118,15 +140,10 @@ public PasswordStrategy addPasswortStrategy(final String vaultName) { PasswordStrategy passwordStrategy = new PasswordFromStdInputStrategy(vaultName); if (vaultPasswords.getProperty(vaultName) != null) { - passwordStrategy = new PasswordFromPropertyStrategy( - vaultName, - vaultPasswords.getProperty(vaultName) - ); + passwordStrategy = new PasswordFromPropertyStrategy(vaultName, vaultPasswords.getProperty(vaultName)); } else if (vaultPasswordFiles.getProperty(vaultName) != null) { - passwordStrategy = new PasswordFromFileStrategy( - vaultName, - Paths.get(vaultPasswordFiles.getProperty(vaultName)) - ); + passwordStrategy = new PasswordFromFileStrategy(vaultName, + Paths.get(vaultPasswordFiles.getProperty(vaultName))); } this.passwordStrategies.put(vaultName, passwordStrategy); @@ -136,4 +153,13 @@ public PasswordStrategy addPasswortStrategy(final String vaultName) { public PasswordStrategy getPasswordStrategy(final String vaultName) { return passwordStrategies.get(vaultName); } + + public Path getFuseMountPoint(String vaultName) { + String mountPoint = fuseMountPoints.getProperty(vaultName); + if (mountPoint == null) { + return null; + } + Path mountPointPath = Paths.get(mountPoint); + return mountPointPath; + } } diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index ab7940b..d2cc903 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -8,17 +8,20 @@ *******************************************************************************/ package org.cryptomator.cli; +import org.cryptomator.cli.frontend.FuseMount; +import org.cryptomator.cli.frontend.WebDav; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Optional; import java.util.Set; import org.apache.commons.cli.ParseException; import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.CryptoFileSystemProvider; -import org.cryptomator.frontend.webdav.WebDavServer; -import org.cryptomator.frontend.webdav.servlet.WebDavServletController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +45,7 @@ public static void main(String[] rawArgs) throws IOException { private static void validate(Args args) throws IllegalArgumentException { Set vaultNames = args.getVaultNames(); - if (args.getPort() < 0 || args.getPort() > 65536) { + if (args.hasValidWebDavConf() && (args.getPort() < 0 || args.getPort() > 65536)) { throw new IllegalArgumentException("Invalid WebDAV Port."); } @@ -56,28 +59,45 @@ private static void validate(Args args) throws IllegalArgumentException { throw new IllegalArgumentException("Not a directory: " + vaultPath); } args.addPasswortStrategy(vaultName).validate(); + + Path mountPoint = args.getFuseMountPoint(vaultName); + if (mountPoint != null && !Files.isDirectory(mountPoint)) { + throw new IllegalArgumentException("Fuse mount point does not exist: " + mountPoint); + } } } private static void startup(Args args) throws IOException { - WebDavServer server = WebDavServer.create(); - server.bind(args.getBindAddr(), args.getPort()); - server.start(); + Optional server = initWebDavServer(args); + ArrayList mounts = new ArrayList<>(); for (String vaultName : args.getVaultNames()) { Path vaultPath = Paths.get(args.getVaultPath(vaultName)); LOG.info("Unlocking vault \"{}\" located at {}", vaultName, vaultPath); String vaultPassword = args.getPasswordStrategy(vaultName).password(); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase(vaultPassword).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties() + .withPassphrase(vaultPassword).build(); Path vaultRoot = CryptoFileSystemProvider.newFileSystem(vaultPath, properties).getPath("/"); - WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName); - servlet.start(); + + Path fuseMountPoint = args.getFuseMountPoint(vaultName); + if (fuseMountPoint != null) { + FuseMount newMount = new FuseMount(vaultRoot, fuseMountPoint); + if (newMount.mount()) { + mounts.add(newMount); + } + } + + server.ifPresent(serv -> serv.addServlet(vaultRoot, vaultName)); } waitForShutdown(() -> { LOG.info("Shutting down..."); try { - server.stop(); + server.ifPresent(serv -> serv.stop()); + + for (FuseMount mount : mounts) { + mount.unmount(); + } LOG.info("Shutdown successful."); } catch (Throwable e) { LOG.error("Error during shutdown", e); @@ -85,9 +105,23 @@ private static void startup(Args args) throws IOException { }); } + private static Optional initWebDavServer(Args args) { + Optional server = Optional.empty(); + if (args.hasValidWebDavConf()) { + server = Optional.of(new WebDav(args.getBindAddr(), args.getPort())); + } + return server; + } + private static void waitForShutdown(Runnable runnable) { Runtime.getRuntime().addShutdownHook(new Thread(runnable)); - LOG.info("Server started. Press Ctrl+C to terminate."); + LOG.info("Press Ctrl+C to terminate."); + try { + while (true) { + System.in.read(); + } + } catch (IOException e) { + e.printStackTrace(); + } } - } diff --git a/src/main/java/org/cryptomator/cli/frontend/FuseMount.java b/src/main/java/org/cryptomator/cli/frontend/FuseMount.java new file mode 100644 index 0000000..976085d --- /dev/null +++ b/src/main/java/org/cryptomator/cli/frontend/FuseMount.java @@ -0,0 +1,63 @@ +package org.cryptomator.cli.frontend; + +import java.nio.file.Path; + +import org.cryptomator.frontend.fuse.mount.CommandFailedException; +import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; +import org.cryptomator.frontend.fuse.mount.FuseMountFactory; +import org.cryptomator.frontend.fuse.mount.Mount; +import org.cryptomator.frontend.fuse.mount.Mounter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FuseMount { + private static final Logger LOG = LoggerFactory.getLogger(FuseMount.class); + + private Path vaultRoot; + private Path mountPoint; + private Mount mnt; + + public FuseMount(Path vaultRoot, Path mountPoint) { + this.vaultRoot = vaultRoot; + this.mountPoint = mountPoint; + this.mnt = null; + } + + public boolean mount() { + if (mnt != null) { + LOG.info("Already mounted to {}", mountPoint); + return false; + } + + try { + Mounter mounter = FuseMountFactory.getMounter(); + EnvironmentVariables envVars = EnvironmentVariables.create().withFlags(mounter.defaultMountFlags()) + .withMountPoint(mountPoint).build(); + mnt = mounter.mount(vaultRoot, envVars); + LOG.info("Mounted to {}", mountPoint); + } catch (CommandFailedException e) { + LOG.error("Can't mount: {}, error: {}", mountPoint, e.getMessage()); + return false; + } + return true; + } + + public void unmount() { + try { + mnt.unmount(); + LOG.info("Unmounted {}", mountPoint); + } catch (CommandFailedException e) { + LOG.error("Can't unmount gracefully: {}. Force unmount.", e.getMessage()); + forceUnmount(); + } + } + + private void forceUnmount() { + try { + mnt.unmountForced(); + LOG.info("Unmounted {}", mountPoint); + } catch (CommandFailedException e) { + LOG.error("Force unmount failed: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/org/cryptomator/cli/frontend/WebDav.java b/src/main/java/org/cryptomator/cli/frontend/WebDav.java new file mode 100644 index 0000000..9af6a31 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/frontend/WebDav.java @@ -0,0 +1,38 @@ +package org.cryptomator.cli.frontend; + +import java.nio.file.Path; +import java.util.ArrayList; + +import org.cryptomator.frontend.webdav.WebDavServer; +import org.cryptomator.frontend.webdav.servlet.WebDavServletController; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebDav { + private static final Logger LOG = LoggerFactory.getLogger(WebDav.class); + + private final WebDavServer server; + private ArrayList servlets; + + public WebDav(String bindAddr, int port) { + servlets = new ArrayList<>(); + server = WebDavServer.create(); + server.bind(bindAddr, port); + server.start(); + LOG.info("WebDAV server started: {}:{}", bindAddr, port); + } + + public void stop() { + for (WebDavServletController controller : servlets) { + controller.stop(); + } + server.stop(); + } + + public void addServlet(Path vaultRoot, String vaultName) { + WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName); + servlets.add(servlet); + servlet.start(); + } +}