From c4e8c69bc332968632953c4bc9d163b00de8f9b3 Mon Sep 17 00:00:00 2001 From: Gavin Brown Date: Mon, 17 Jul 2023 17:05:20 +0300 Subject: [PATCH 001/101] Bump cryptofs.version to 2.6.5 (from 2.3.1) Hardcode some dependancies to allow compilation. --- pom.xml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1367dea..9cf5ec6 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ https://github.com/cryptomator/cli - 2.3.1 + 2.6.5 1.2.6 1.5.0 1.2.9 @@ -42,17 +42,42 @@ cryptofs ${cryptofs.version} + + + com.google.guava + guava + 30.1.1-jre + + org.cryptomator webdav-nio-adapter ${webdav-nio.version} + org.cryptomator fuse-nio-adapter ${fuse-nio.version} + + + com.google.dagger + dagger + + + + org.slf4j + slf4j-api + 1.7.31 + + + + com.google.dagger + dagger + 2.39.1 + commons-cli From 26da5a2d70a5029ce4ea1fe93c048f57899e72ca Mon Sep 17 00:00:00 2001 From: Gavin Brown Date: Mon, 17 Jul 2023 19:40:47 +0300 Subject: [PATCH 002/101] Updated webdav-nio to v2.0.3 (from v1.2.6) Updated WebDav constructor to new WebDavServer.create format from https://github.com/cryptomator/webdav-nio-adapter/pull/44/commits/1b86572b7a4713ab9fd0159004560b5e19afd0db --- pom.xml | 2 +- .../org/cryptomator/cli/frontend/WebDav.java | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9cf5ec6..fe7d7d2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 2.6.5 - 1.2.6 + 2.0.3 1.5.0 1.2.9 1.3.3 diff --git a/src/main/java/org/cryptomator/cli/frontend/WebDav.java b/src/main/java/org/cryptomator/cli/frontend/WebDav.java index 9af6a31..7eec299 100644 --- a/src/main/java/org/cryptomator/cli/frontend/WebDav.java +++ b/src/main/java/org/cryptomator/cli/frontend/WebDav.java @@ -1,5 +1,8 @@ package org.cryptomator.cli.frontend; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.file.Path; import java.util.ArrayList; @@ -13,14 +16,16 @@ public class WebDav { private static final Logger LOG = LoggerFactory.getLogger(WebDav.class); private final WebDavServer server; - private ArrayList servlets; + private ArrayList servlets = new ArrayList<>(); 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); + try { + server = WebDavServer.create(new InetSocketAddress(InetAddress.getByName(bindAddr), port)); + server.start(); + LOG.info("WebDAV server started: {}:{}", bindAddr, port); + } catch (UnknownHostException e) { + throw new IllegalStateException("Error creating WebDavServer", e); + } } public void stop() { @@ -35,4 +40,4 @@ public void addServlet(Path vaultRoot, String vaultName) { servlets.add(servlet); servlet.start(); } -} +} \ No newline at end of file From c94a1d88d386f04070749ea4eb7e909cb5b3440c Mon Sep 17 00:00:00 2001 From: Gavin Brown Date: Thu, 20 Jul 2023 17:31:22 +0300 Subject: [PATCH 003/101] cleanup: Remove uneeded dependancies in pom.xml --- pom.xml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/pom.xml b/pom.xml index fe7d7d2..e18f330 100644 --- a/pom.xml +++ b/pom.xml @@ -42,42 +42,17 @@ cryptofs ${cryptofs.version} - - - com.google.guava - guava - 30.1.1-jre - - org.cryptomator webdav-nio-adapter ${webdav-nio.version} - org.cryptomator fuse-nio-adapter ${fuse-nio.version} - - - com.google.dagger - dagger - - - - org.slf4j - slf4j-api - 1.7.31 - - - - com.google.dagger - dagger - 2.39.1 - commons-cli From ad6de16cb1b8210fbc8793dd59626801237a433b Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Wed, 18 Oct 2023 20:33:56 +0000 Subject: [PATCH 004/101] Adds --version option to Args --- src/main/java/org/cryptomator/cli/Args.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java index 492310b..5b8ab1d 100644 --- a/src/main/java/org/cryptomator/cli/Args.java +++ b/src/main/java/org/cryptomator/cli/Args.java @@ -79,6 +79,11 @@ public class Args { .valueSeparator() // .hasArgs() // .build()); + OPTIONS.addOption(Option.builder() // + .longOpt("version") // + .desc("Prints version and exits") // + .hasArg(false) + .build()); } private final String bindAddr; @@ -89,6 +94,7 @@ public class Args { private final Properties vaultPasswordFiles; private final Map passwordStrategies; private final Properties fuseMountPoints; + private final boolean hasVersion; public Args(CommandLine commandLine) throws ParseException { if (commandLine.hasOption("bind") && commandLine.hasOption("port")) { @@ -105,6 +111,7 @@ public Args(CommandLine commandLine) throws ParseException { this.vaultPasswordFiles = commandLine.getOptionProperties("passwordfile"); this.passwordStrategies = new HashMap<>(); this.fuseMountPoints = commandLine.getOptionProperties("fusemount"); + this.hasVersion = commandLine.hasOption("version"); } public boolean hasValidWebDavConf() { @@ -162,4 +169,8 @@ public Path getFuseMountPoint(String vaultName) { Path mountPointPath = Paths.get(mountPoint); return mountPointPath; } + + public boolean hasVersion() { + return hasVersion; + } } From 7b8bc33f16023c430d532f63a95382f2105349ed Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Wed, 18 Oct 2023 20:36:08 +0000 Subject: [PATCH 005/101] Adds Version Class Gets the Implentation Version by calling: .getClass() .getPackage() .getImplementationVersion(); --- src/main/java/org/cryptomator/cli/Version.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/org/cryptomator/cli/Version.java diff --git a/src/main/java/org/cryptomator/cli/Version.java b/src/main/java/org/cryptomator/cli/Version.java new file mode 100644 index 0000000..2bebb84 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/Version.java @@ -0,0 +1,12 @@ +package org.cryptomator.cli; + +public class Version { + public static final String IMPLEMENTATION_VERSION = getImplementationVersion(); + + private static String getImplementationVersion() { + return new Version() + .getClass() + .getPackage() + .getImplementationVersion(); + } +} From 7ce0ffeea1b008c032135fe1ab75269ab73c1802 Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Wed, 18 Oct 2023 20:36:53 +0000 Subject: [PATCH 006/101] Print version and exit if --version present --- src/main/java/org/cryptomator/cli/Args.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java index 5b8ab1d..cf09ef5 100644 --- a/src/main/java/org/cryptomator/cli/Args.java +++ b/src/main/java/org/cryptomator/cli/Args.java @@ -112,6 +112,10 @@ public Args(CommandLine commandLine) throws ParseException { this.passwordStrategies = new HashMap<>(); this.fuseMountPoints = commandLine.getOptionProperties("fusemount"); this.hasVersion = commandLine.hasOption("version"); + + if (hasVersion()) { + printVersionAndExit(); + } } public boolean hasValidWebDavConf() { @@ -173,4 +177,10 @@ public Path getFuseMountPoint(String vaultName) { public boolean hasVersion() { return hasVersion; } + + public void printVersionAndExit() { + String version = Version.IMPLEMENTATION_VERSION; + System.out.println(version); + System.exit(0); + } } From 56d9efc442b13ca15d914ec97e5fcbe03a134188 Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Wed, 18 Oct 2023 20:37:26 +0000 Subject: [PATCH 007/101] Log the version at startup --- src/main/java/org/cryptomator/cli/CryptomatorCli.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index ed2fc48..1162a78 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -75,6 +75,7 @@ private static void validate(Args args) throws IllegalArgumentException { } private static void startup(Args args) throws IOException { + LOG.info("Starting Cryptomator CLI {}", Version.IMPLEMENTATION_VERSION); Optional server = initWebDavServer(args); ArrayList mounts = new ArrayList<>(); From 41439f7d0fecb67ea763303239c3270e1a886c9e Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Thu, 19 Oct 2023 13:48:14 +0000 Subject: [PATCH 008/101] Use Version.class --- src/main/java/org/cryptomator/cli/Version.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/Version.java b/src/main/java/org/cryptomator/cli/Version.java index 2bebb84..b1fac09 100644 --- a/src/main/java/org/cryptomator/cli/Version.java +++ b/src/main/java/org/cryptomator/cli/Version.java @@ -4,8 +4,7 @@ public class Version { public static final String IMPLEMENTATION_VERSION = getImplementationVersion(); private static String getImplementationVersion() { - return new Version() - .getClass() + return Version.class .getPackage() .getImplementationVersion(); } From 8f4656b667ea12d2584c445912b73a8e78c0542c Mon Sep 17 00:00:00 2001 From: Angelo Pengue Date: Thu, 19 Oct 2023 13:51:14 +0000 Subject: [PATCH 009/101] Print version and return from main() Address PR comment to return from main() instead of System.exit in Args --- src/main/java/org/cryptomator/cli/Args.java | 10 ---------- .../java/org/cryptomator/cli/CryptomatorCli.java | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java index cf09ef5..5b8ab1d 100644 --- a/src/main/java/org/cryptomator/cli/Args.java +++ b/src/main/java/org/cryptomator/cli/Args.java @@ -112,10 +112,6 @@ public Args(CommandLine commandLine) throws ParseException { this.passwordStrategies = new HashMap<>(); this.fuseMountPoints = commandLine.getOptionProperties("fusemount"); this.hasVersion = commandLine.hasOption("version"); - - if (hasVersion()) { - printVersionAndExit(); - } } public boolean hasValidWebDavConf() { @@ -177,10 +173,4 @@ public Path getFuseMountPoint(String vaultName) { public boolean hasVersion() { return hasVersion; } - - public void printVersionAndExit() { - String version = Version.IMPLEMENTATION_VERSION; - System.out.println(version); - System.exit(0); - } } diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 1162a78..d416ec8 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -39,6 +39,13 @@ public class CryptomatorCli { public static void main(String[] rawArgs) throws IOException { try { Args args = Args.parse(rawArgs); + + // print version and exit if --version option present. + if (args.hasVersion()) { + printVersion(); + return; + } + validate(args); startup(args); } catch (ParseException e) { @@ -152,4 +159,9 @@ private static void waitForShutdown(Runnable runnable) { LOG.error("Main thread blocking failed."); } } + + private static void printVersion() { + String version = Version.IMPLEMENTATION_VERSION; + System.out.println(version); + } } From d453ad484981886c56e35872d0b0c325d522ff98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:46:43 +0000 Subject: [PATCH 010/101] Bump ch.qos.logback:logback-core from 1.2.9 to 1.2.13 (#60) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e18f330..026e7d1 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 2.6.5 2.0.3 1.5.0 - 1.2.9 + 1.2.13 1.3.3 17 From 8cf9bdd4522023c3339e7184df01b82b37785fd7 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 7 Jun 2024 14:13:48 +0000 Subject: [PATCH 011/101] Hardening the CI in relation to PRs --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27b3f3e..2ed9837 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,9 @@ name: Build on: - [push] + push: + pull_request_target: + types: [labeled] jobs: build: From 1a5b1835caf97397ce539db11ad9f61038d807a4 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 26 Aug 2024 18:13:14 +0200 Subject: [PATCH 012/101] add maven wrapper --- .mvn/wrapper/maven-wrapper.properties | 18 ++ mvnw | 308 ++++++++++++++++++++++++++ mvnw.cmd | 205 +++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..346d645 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8d937f4 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From 52543c2d42c4e20454f61fde6e13a6503b2a887e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 4 Sep 2024 17:13:02 +0200 Subject: [PATCH 013/101] remove old code & prepare pom --- pom.xml | 65 +++++-- src/main/java/org/cryptomator/cli/Args.java | 176 ------------------ .../org/cryptomator/cli/CryptomatorCli.java | 167 ----------------- .../java/org/cryptomator/cli/Version.java | 11 -- .../cryptomator/cli/frontend/FuseMount.java | 65 ------- .../org/cryptomator/cli/frontend/WebDav.java | 43 ----- .../cli/pwd/PasswordFromFileStrategy.java | 43 ----- .../cli/pwd/PasswordFromPropertyStrategy.java | 29 --- .../cli/pwd/PasswordFromStdInputStrategy.java | 53 ------ .../cryptomator/cli/pwd/PasswordStrategy.java | 6 - 10 files changed, 49 insertions(+), 609 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cli/Args.java delete mode 100644 src/main/java/org/cryptomator/cli/CryptomatorCli.java delete mode 100644 src/main/java/org/cryptomator/cli/Version.java delete mode 100644 src/main/java/org/cryptomator/cli/frontend/FuseMount.java delete mode 100644 src/main/java/org/cryptomator/cli/frontend/WebDav.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java diff --git a/pom.xml b/pom.xml index 026e7d1..f90328c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,20 +2,34 @@ 4.0.0 org.cryptomator cli - 0.6.0-SNAPSHOT + 1.0.0-SNAPSHOT Cryptomator CLI Command line program to access encrypted files via WebDAV. https://github.com/cryptomator/cli - 2.6.5 - 2.0.3 - 1.5.0 - 1.2.13 - 1.3.3 - - 17 UTF-8 + UTF-8 + 22 + + + org.cryptomator.hubcli.HubCli + -Ob + + + 2.7.0-beta1 + 2.0.6 + 5.0.0 + 1.5.6 + 2.0.16 + + + 24.0.1 + 4.7.6 + + + 3.13.0 + 3.7.1 @@ -34,6 +48,13 @@ cryptomator.org http://cryptomator.org + + Armin Schrenk + armin.schrenk@skymatic.de + +1 + Skymatic GmbH + https://skymatic.de + @@ -53,14 +74,18 @@ ${fuse-nio.version} - - commons-cli - commons-cli - ${commons.cli.version} + info.picocli + picocli + ${picocli.version} + + org.slf4j + slf4j-api + ${slf4j.version} + ch.qos.logback logback-core @@ -77,18 +102,26 @@ maven-compiler-plugin - 3.8.1 + ${maven-compiler.version} - ${java.version} - ${java.version} ${java.version} true + + + info.picocli + picocli-codegen + ${picocli.version} + + + + -Aproject=${project.groupId}/${project.artifactId} + maven-assembly-plugin - 3.3.0 + ${maven-assembly.version} make-assembly diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java deleted file mode 100644 index 5b8ab1d..0000000 --- a/src/main/java/org/cryptomator/cli/Args.java +++ /dev/null @@ -1,176 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Sebastian Stenzel and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE.txt. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - *******************************************************************************/ -package org.cryptomator.cli; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.cryptomator.cli.pwd.PasswordFromFileStrategy; -import org.cryptomator.cli.pwd.PasswordFromStdInputStrategy; -import org.cryptomator.cli.pwd.PasswordStrategy; -import org.cryptomator.cli.pwd.PasswordFromPropertyStrategy; - -/** - * Parses program arguments. Does not validate them. - */ -public class Args { - - private static final String USAGE = "java -jar cryptomator-cli.jar" // - + " --bind localhost --port 8080" // - + " --vault mySecretVault=/path/to/vault --password mySecretVault=FooBar3000" // - + " --vault myOtherVault=/path/to/other/vault --password myOtherVault=BarFoo4000" // - + " --vault myThirdVault=/path/to/third/vault --passwordfile myThirdVault=/path/to/passwordfile"; - private static final Options OPTIONS = new Options(); - static { - OPTIONS.addOption(Option.builder() // - .longOpt("bind") // - .argName("WebDAV bind address") // - .desc("TCP socket bind address of the WebDAV server. Use 0.0.0.0 to accept all incoming connections.") // - .hasArg() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("port") // - .argName("WebDAV port") // - .desc("TCP port, the WebDAV server should listen on.") // - .hasArg() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("vault") // - .argName("Path of a vault") // - .desc("Format must be vaultName=/path/to/vault") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("password") // - .argName("Password of a vault") // - .desc("Format must be vaultName=password") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("passwordfile") // - .argName("Passwordfile for a vault") // - .desc("Format must be vaultName=passwordfile") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("fusemount") // - .argName("mount point") // - .desc("Format must be vaultName=mountpoint") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("version") // - .desc("Prints version and exits") // - .hasArg(false) - .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; - private final boolean hasVersion; - - public Args(CommandLine commandLine) throws ParseException { - 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"); - this.hasVersion = commandLine.hasOption("version"); - } - - public boolean hasValidWebDavConf() { - return hasValidWebDavConfig; - } - - public String getBindAddr() { - return bindAddr; - } - - public int getPort() { - return port; - } - - public Set getVaultNames() { - return vaultPaths.keySet().stream().map(String.class::cast).collect(Collectors.toSet()); - } - - public String getVaultPath(String vaultName) { - return vaultPaths.getProperty(vaultName); - } - - public static Args parse(String[] arguments) throws ParseException { - CommandLine commandLine = new DefaultParser().parse(OPTIONS, arguments); - return new Args(commandLine); - } - - public static void printUsage() { - new HelpFormatter().printHelp(USAGE, OPTIONS); - } - - public PasswordStrategy addPasswortStrategy(final String vaultName) { - PasswordStrategy passwordStrategy = new PasswordFromStdInputStrategy(vaultName); - - if (vaultPasswords.getProperty(vaultName) != null) { - passwordStrategy = new PasswordFromPropertyStrategy(vaultName, vaultPasswords.getProperty(vaultName)); - } else if (vaultPasswordFiles.getProperty(vaultName) != null) { - passwordStrategy = new PasswordFromFileStrategy(vaultName, - Paths.get(vaultPasswordFiles.getProperty(vaultName))); - } - - this.passwordStrategies.put(vaultName, passwordStrategy); - return passwordStrategy; - } - - 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; - } - - public boolean hasVersion() { - return hasVersion; - } -} diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java deleted file mode 100644 index d416ec8..0000000 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Sebastian Stenzel and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE.txt. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - *******************************************************************************/ -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.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Set; - -import com.google.common.base.Preconditions; -import org.apache.commons.cli.ParseException; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; -import org.cryptomator.cryptofs.CryptoFileSystemProvider; -import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CryptomatorCli { - - private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); - - private static final byte[] PEPPER = new byte[0]; - private static final String SCHEME = "masterkeyfile"; - - public static void main(String[] rawArgs) throws IOException { - try { - Args args = Args.parse(rawArgs); - - // print version and exit if --version option present. - if (args.hasVersion()) { - printVersion(); - return; - } - - validate(args); - startup(args); - } catch (ParseException e) { - LOG.error("Invalid or missing arguments", e); - Args.printUsage(); - } catch (IllegalArgumentException e) { - LOG.error(e.getMessage()); - Args.printUsage(); - } - } - - private static void validate(Args args) throws IllegalArgumentException { - Set vaultNames = args.getVaultNames(); - if (args.hasValidWebDavConf() && (args.getPort() < 0 || args.getPort() > 65536)) { - throw new IllegalArgumentException("Invalid WebDAV Port."); - } - - if (vaultNames.size() == 0) { - throw new IllegalArgumentException("No vault specified."); - } - - for (String vaultName : vaultNames) { - Path vaultPath = Paths.get(args.getVaultPath(vaultName)); - if (!Files.isDirectory(vaultPath)) { - 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 { - LOG.info("Starting Cryptomator CLI {}", Version.IMPLEMENTATION_VERSION); - Optional server = initWebDavServer(args); - ArrayList mounts = new ArrayList<>(); - - SecureRandom secureRandom; - try { - secureRandom = SecureRandom.getInstanceStrong(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); - } - MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(PEPPER, secureRandom); - - 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() - .withKeyLoader(keyId -> { - Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME); - Path keyFilePath = vaultPath.resolve(keyId.getSchemeSpecificPart()); - return masterkeyFileAccess.load(keyFilePath, vaultPassword); - }) - .build(); - - Path vaultRoot = CryptoFileSystemProvider.newFileSystem(vaultPath, properties).getPath("/"); - - 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.ifPresent(serv -> serv.stop()); - - for (FuseMount mount : mounts) { - mount.unmount(); - } - LOG.info("Shutdown successful."); - } catch (Throwable e) { - LOG.error("Error during shutdown", e); - } - }); - } - - 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("Press Ctrl+C to terminate."); - - // Block the main thread infinitely as otherwise when using - // Fuse mounts the application quits immediately. - try { - Object mainThreadBlockLock = new Object(); - synchronized (mainThreadBlockLock) { - while (true) { - mainThreadBlockLock.wait(); - } - } - } catch (Exception e) { - LOG.error("Main thread blocking failed."); - } - } - - private static void printVersion() { - String version = Version.IMPLEMENTATION_VERSION; - System.out.println(version); - } -} diff --git a/src/main/java/org/cryptomator/cli/Version.java b/src/main/java/org/cryptomator/cli/Version.java deleted file mode 100644 index b1fac09..0000000 --- a/src/main/java/org/cryptomator/cli/Version.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.cryptomator.cli; - -public class Version { - public static final String IMPLEMENTATION_VERSION = getImplementationVersion(); - - private static String getImplementationVersion() { - return Version.class - .getPackage() - .getImplementationVersion(); - } -} diff --git a/src/main/java/org/cryptomator/cli/frontend/FuseMount.java b/src/main/java/org/cryptomator/cli/frontend/FuseMount.java deleted file mode 100644 index 40ca15a..0000000 --- a/src/main/java/org/cryptomator/cli/frontend/FuseMount.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.cryptomator.cli.frontend; - -import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; -import org.cryptomator.frontend.fuse.mount.FuseMountException; -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; - -import java.nio.file.Path; - -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()) // - .withFileNameTranscoder(mounter.defaultFileNameTranscoder()) // - .withMountPoint(mountPoint).build(); - mnt = mounter.mount(vaultRoot, envVars); - LOG.info("Mounted to {}", mountPoint); - } catch (FuseMountException 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 (FuseMountException e) { - LOG.error("Can't unmount gracefully: {}. Force unmount.", e.getMessage()); - forceUnmount(); - } - } - - private void forceUnmount() { - try { - mnt.unmountForced(); - LOG.info("Unmounted {}", mountPoint); - } catch (FuseMountException 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 deleted file mode 100644 index 7eec299..0000000 --- a/src/main/java/org/cryptomator/cli/frontend/WebDav.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cryptomator.cli.frontend; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -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 = new ArrayList<>(); - - public WebDav(String bindAddr, int port) { - try { - server = WebDavServer.create(new InetSocketAddress(InetAddress.getByName(bindAddr), port)); - server.start(); - LOG.info("WebDAV server started: {}:{}", bindAddr, port); - } catch (UnknownHostException e) { - throw new IllegalStateException("Error creating WebDavServer", e); - } - } - - 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(); - } -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java deleted file mode 100644 index cd20261..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cryptomator.cli.pwd; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; - -public class PasswordFromFileStrategy implements PasswordStrategy { - private static final Logger LOG = LoggerFactory.getLogger(PasswordFromFileStrategy.class); - - private final String vaultName; - private final Path pathToFile; - - public PasswordFromFileStrategy(final String vaultName, final Path pathToFile) { - this.vaultName = vaultName; - this.pathToFile = pathToFile; - } - - @Override - public String password() { - LOG.info("Vault " + "'" + vaultName + "'" + " password from file."); - - if (Files.isReadable(pathToFile) && Files.isRegularFile(pathToFile)) { - try (Stream lines = Files.lines(pathToFile)) { - return lines.findFirst().get().toString(); - } catch (IOException e) { - return null; - } - } - return null; - } - - @Override - public void validate() throws IllegalArgumentException { - if (!Files.isReadable(pathToFile)) { - throw new IllegalArgumentException("Cannot read password from file: " + pathToFile); - } - } - -} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java deleted file mode 100644 index 8d5909b..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.cryptomator.cli.pwd; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PasswordFromPropertyStrategy implements PasswordStrategy { - private static final Logger LOG = LoggerFactory.getLogger(PasswordFromPropertyStrategy.class); - - private final String vaultName; - private final String password; - - public PasswordFromPropertyStrategy(final String vaultName, final String password) { - this.vaultName = vaultName; - this.password = password; - } - - @Override - public String password() { - LOG.info("Vault " + "'" + vaultName + "'" + " password from property."); - return this.password; - } - - @Override - public void validate() throws IllegalArgumentException { - if (password.equals("")) { - throw new IllegalArgumentException("Invalid password"); - } - } -} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java deleted file mode 100644 index e374e7f..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.cryptomator.cli.pwd; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.Console; -import java.io.IOException; -import java.io.InputStreamReader; - -public class PasswordFromStdInputStrategy implements PasswordStrategy { - private static final Logger LOG = LoggerFactory.getLogger(PasswordFromStdInputStrategy.class); - - private final String vaultName; - private final String inputMessage = "Enter password for vault '%s': "; - - public PasswordFromStdInputStrategy(final String vaultName) { - this.vaultName = vaultName; - } - - @Override - public String password() { - LOG.info("Vault " + "'" + vaultName + "'" + " password from standard input."); - - String password = ""; - Console console = System.console(); - if (console == null) { - LOG.warn("No console: non-interactive mode, instead use insecure replacement, PW is shown!"); - - BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - System.out.println(String.format(inputMessage, vaultName)); - - try { - password = reader.readLine(); - } catch (IOException e) { - LOG.error("There was an error reading line from console."); - e.printStackTrace(); - } - } else { - System.out.println(String.format(inputMessage, vaultName)); - password = new String(console.readPassword()); - } - - return password; - } - - @Override - public void validate() throws IllegalArgumentException { - if (vaultName.equals("")) { - throw new IllegalArgumentException("Invalid vault name"); - } - } -} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java deleted file mode 100644 index adf7c89..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.cryptomator.cli.pwd; - -public interface PasswordStrategy { - String password(); - void validate() throws IllegalArgumentException; -} From aecf18ca0e59913876aae7fc41e15422aea22a84 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 5 Sep 2024 15:13:25 +0200 Subject: [PATCH 014/101] switch to JUL (for now) --- pom.xml | 12 +++--------- src/main/resources/logback.xml | 15 --------------- src/main/resources/logging.properties | 6 ++++++ 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/logging.properties diff --git a/pom.xml b/pom.xml index f90328c..25eb1a1 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,6 @@ 2.7.0-beta1 2.0.6 5.0.0 - 1.5.6 2.0.16 @@ -87,14 +86,9 @@ ${slf4j.version} - ch.qos.logback - logback-core - ${logback.version} - - - ch.qos.logback - logback-classic - ${logback.version} + org.slf4j + slf4j-jdk14 + ${slf4j.version} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index a92d372..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - true - - %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n - - - - - - - diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties new file mode 100644 index 0000000..5370608 --- /dev/null +++ b/src/main/resources/logging.properties @@ -0,0 +1,6 @@ +handlers = java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format = %4$s: %5$s + +.level = ERROR +org.cryptomator.cli.level = INFO \ No newline at end of file From 92f3cb3a249d8a690f3deb72cb891b2edf75bbcc Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 13 Sep 2024 15:58:47 +0200 Subject: [PATCH 015/101] implement mvp --- .../org/cryptomator/cli/CryptomatorCli.java | 90 ++++++++++++++++++ .../org/cryptomator/cli/MountOptions.java | 81 ++++++++++++++++ .../org/cryptomator/cli/PasswordSource.java | 92 +++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 src/main/java/org/cryptomator/cli/CryptomatorCli.java create mode 100644 src/main/java/org/cryptomator/cli/MountOptions.java create mode 100644 src/main/java/org/cryptomator/cli/PasswordSource.java diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java new file mode 100644 index 0000000..e43129b --- /dev/null +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -0,0 +1,90 @@ +package org.cryptomator.cli; + +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.integrations.mount.UnmountFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; + +import java.io.IOException; +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.concurrent.Callable; + +@Command(name = "cryptomator-cli", + mixinStandardHelpOptions = true, + version = "1.0.0", + description = "Unlocks a cryptomator vault and mounts it into the system.") +public class CryptomatorCli implements Callable { + + private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); + private static final byte[] PEPPER = new byte[0]; + + + @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") + Path pathToVault; + + @CommandLine.ArgGroup(multiplicity = "1") + PasswordSource passwordSource; + + @CommandLine.ArgGroup(exclusive = false, multiplicity = "1") + MountOptions mountOptions; + + private SecureRandom csrpg = null; + + @Override + public Integer call() throws Exception { + csrpg = SecureRandom.getInstanceStrong(); + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // + .withKeyLoader(this::loadMasterkey) // + //TODO: shortening Threshold + //TODO: maxCleartextname + .build(); + try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsProps); + var mount = mountOptions.mount(fs)) { + + while (true) { + int c = System.in.read(); + if (c == -1) { //END OF STREAM + //TODO: terminate with error? + mount.unmount(); + return 1; + } else if (c == 0x03) {//Ctrl+C + mount.unmount(); + break; + } + } + } catch (UnmountFailedException e) { + LOG.error("Regular unmount failed. Just terminating...", e); + } + return 0; + } + + private Masterkey loadMasterkey(URI keyId) { + try { + char[] passphrase = passwordSource.readPassphrase(); + Path filePath = pathToVault.resolve("masterkey.cryptomator"); + + var masterkey = new MasterkeyFileAccess(PEPPER, csrpg).load(filePath, CharBuffer.wrap(passphrase)); + Arrays.fill(passphrase, '\u0000'); + return masterkey; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void main(String... args) { + int exitCode = new CommandLine(new CryptomatorCli()) + .setPosixClusteredShortOptionsAllowed(false) + .execute(args); + System.exit(exitCode); + } +} diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountOptions.java new file mode 100644 index 0000000..1891a89 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/MountOptions.java @@ -0,0 +1,81 @@ +package org.cryptomator.cli; + +import org.cryptomator.integrations.common.IntegrationsLoader; +import org.cryptomator.integrations.mount.*; +import picocli.CommandLine; + +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +public class MountOptions { + + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; + + @CommandLine.Option(names = {"--mounter"}, paramLabel = "fully.qualified.ClassName", description = "Name of the mounter to use", required = true) + void setMountService(String value) { + var services = IntegrationsLoader.loadAll(MountService.class).toList(); + var service = services.stream().filter(s -> s.getClass().getName().equals(value)).findFirst(); + if (service.isEmpty()) { + var availableServices = services.stream().map(s -> getClass().getName()).collect(Collectors.joining(",")); + var errorMessage = String.format("Invalid value '%s' for option '--mounter': Available mounters are [%s].", value, availableServices); + throw new CommandLine.ParameterException(spec.commandLine(), errorMessage); + } + this.mountService = service.get(); + } + + private MountService mountService; + + @CommandLine.Option(names = {"--mountPoint"}, paramLabel = "/path/to/mount/point", description = "Path to the mount point. Requirements for mount point depend on the chosen mount service") + Optional mountPoint; + + @CommandLine.Option(names = {"--volumeName"}, description = "Name of the virtual volume.") + Optional volumeName; + + @CommandLine.Option(names = {"--volumeId"}, description = "Id of the virtual volume.") + String volumeId = UUID.randomUUID().toString(); + + @CommandLine.Option(names = {"--mountOption", "-mop"}, description = "Additional mount option. For a list of mountoptions, see the WinFsp, macFUSE, FUSE-T and libfuse documentation.") + List mountOptions = new ArrayList<>(); + + @CommandLine.Option(names = {"--loopbackHostName"}, description = "Name of the loopback address.") + Optional loopbackHostName; + @CommandLine.Option(names = {"--loopbackPort"}, description = "Port used at the loopback address.") + Optional loopbackPort; + + MountBuilder prepareMountBuilder(FileSystem fs) { + var builder = mountService.forFileSystem(fs.getPath("/")); + for (var capability : mountService.capabilities()) { + switch (capability) { + case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); + case LOOPBACK_PORT -> loopbackPort.ifPresent(builder::setLoopbackPort); + case LOOPBACK_HOST_NAME -> loopbackHostName.ifPresent(builder::setLoopbackHostName); + //TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); + case MOUNT_FLAGS -> { + if (mountOptions.isEmpty()) { + builder.setMountFlags(mountService.getDefaultMountFlags()); + } else { + builder.setMountFlags(String.join(" ", mountOptions)); + } + } + case VOLUME_ID -> builder.setVolumeId(volumeId); + case VOLUME_NAME -> volumeName.ifPresent(builder::setVolumeName); + } + } + return builder; + } + + Mount mount(FileSystem fs) throws MountFailedException { + if (!mountService.hasCapability(MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH) && mountPoint.isEmpty()) { + throw new CommandLine.ParameterException(spec.commandLine(), "The selected mounter %s requires a mount point. Use --mountPoint /path/to/mount/point to specify it.".formatted(mountService.displayName())); + } + var builder = prepareMountBuilder(fs); + mountPoint.ifPresent(builder::setMountpoint); + return builder.mount(); + } +} diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java new file mode 100644 index 0000000..2bacb4c --- /dev/null +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -0,0 +1,92 @@ +package org.cryptomator.cli; + +import picocli.CommandLine; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PasswordSource { + + @CommandLine.Option(names = {"--password"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") + boolean passphraseStdin; + + @CommandLine.Option(names = "--password:env", description = "Name of the environment variable containing the passphrase") + String passphraseEnvironmentVariable = null; + + @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase") + Path passphraseFile = null; + + @CommandLine.Option(names = "--password:ipc", hidden = true, description = "Used by Cryptomator GUI") + boolean passphraseIpc = false; + + + char[] readPassphrase() throws IOException { + if (passphraseStdin) { + return readPassphraseFromStdin(); + } else if (passphraseEnvironmentVariable != null) { + return readPassphraseFromEnvironment(); + } else if (passphraseFile != null) { + return readPassphraseFromFile(); + } else { + //TODO: use ipc + return new char[]{}; + } + } + + private char[] readPassphraseFromStdin() { + System.out.println("Enter a value for --password:"); + var console = System.console(); + if (console == null) { + throw new IllegalStateException("No console found to read password from."); + } + return console.readPassword(); + } + + private char[] readPassphraseFromEnvironment() { + var tmp = System.getenv(passphraseEnvironmentVariable); + if (tmp == null) { + throw new ReadingEnvironmentVariableFailedException("Environment variable " + passphraseEnvironmentVariable + " is not defined."); + } + char[] result = new char[tmp.length()]; + tmp.getChars(0, tmp.length(), result, 0); + return result; + } + + private char[] readPassphraseFromFile() throws ReadingFileFailedException { + try { + var bytes = Files.readAllBytes(passphraseFile); + var byteBuffer = ByteBuffer.wrap(bytes); + var charBuffer = StandardCharsets.UTF_8.decode(byteBuffer); + return charBuffer.array(); + } catch (IOException e) { + throw new ReadingFileFailedException(e); + } + } + + static class PasswordSourceException extends RuntimeException { + PasswordSourceException(String msg) { + super(msg); + } + + PasswordSourceException(Throwable cause) { + super(cause); + } + } + + static class ReadingFileFailedException extends PasswordSourceException { + ReadingFileFailedException(Throwable e) { + super(e); + + } + } + + static class ReadingEnvironmentVariableFailedException extends PasswordSourceException { + ReadingEnvironmentVariableFailedException(String msg) { + super(msg); + } + } + +} From ce7936ce1eb443187b4815a3412d4d299a089c0c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 13 Sep 2024 16:20:52 +0200 Subject: [PATCH 016/101] update CI --- .github/workflows/build.yml | 56 ++++++++----------------------------- 1 file changed, 11 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27b3f3e..ef55216 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,21 +7,13 @@ jobs: build: name: Build and Test runs-on: ubuntu-latest - #This check is case insensitive - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" outputs: artifactVersion: ${{ steps.setversion.outputs.version }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: - java-version: 17 - - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + java-version: 22 - name: Ensure to use tagged version run: mvn versions:set --file ./pom.xml -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' if: startsWith(github.ref, 'refs/tags/') @@ -29,44 +21,18 @@ jobs: id: setversion run: | BUILD_VERSION=$(mvn help:evaluate "-Dexpression=project.version" -q -DforceStdout) - echo "::set-output name=version::${BUILD_VERSION}" + echo "version=${BUILD_VERSION}" >> "$GITHUB_OUTPUT" - name: Build and Test run: mvn -B install - name: Upload artifact cryptomator-cli-${{ steps.setversion.outputs.version }}.jar - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: cryptomator-cli-${{ steps.setversion.outputs.version }}.jar path: target/cryptomator-cli-*.jar - - release: - name: Draft a Release on GitHub Releases and uploads the build artifacts to it - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/') - steps: - - name: Download cryptomator-cli.jar - uses: actions/download-artifact@v1 - with: - name: cryptomator-cli-${{ needs.build.outputs.artifactVersion }}.jar - path: . - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - body: | - :construction: Work in Progress - draft: true - prerelease: false - - name: Upload cryptomator-cli-${{ needs.build.outputs.artifactVersion }}.jar to GitHub Releases - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: cryptomator-cli-${{ needs.build.outputs.artifactVersion }}.jar - asset_name: cryptomator-cli-${{ needs.build.outputs.artifactVersion }}.jar - asset_content_type: application/jar + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + generate_release_notes: true + draft: true \ No newline at end of file From 067d73dd9bcaefccad98d46c877b800ff1da65e4 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 18 Sep 2024 15:15:53 +0200 Subject: [PATCH 017/101] automatically wipe password once used --- .../org/cryptomator/cli/CryptomatorCli.java | 9 +++---- .../org/cryptomator/cli/PasswordSource.java | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index e43129b..6045b20 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -69,13 +69,10 @@ public Integer call() throws Exception { } private Masterkey loadMasterkey(URI keyId) { - try { - char[] passphrase = passwordSource.readPassphrase(); + try (var passphraseContainer = passwordSource.readPassphrase()) { Path filePath = pathToVault.resolve("masterkey.cryptomator"); - - var masterkey = new MasterkeyFileAccess(PEPPER, csrpg).load(filePath, CharBuffer.wrap(passphrase)); - Arrays.fill(passphrase, '\u0000'); - return masterkey; + return new MasterkeyFileAccess(PEPPER, csrpg) + .load(filePath, CharBuffer.wrap(passphraseContainer.content())); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 2bacb4c..ebec08c 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; public class PasswordSource { @@ -23,7 +24,7 @@ public class PasswordSource { boolean passphraseIpc = false; - char[] readPassphrase() throws IOException { + Passphrase readPassphrase() throws IOException { if (passphraseStdin) { return readPassphraseFromStdin(); } else if (passphraseEnvironmentVariable != null) { @@ -32,35 +33,35 @@ char[] readPassphrase() throws IOException { return readPassphraseFromFile(); } else { //TODO: use ipc - return new char[]{}; + return new Passphrase(new char[]{}); } } - private char[] readPassphraseFromStdin() { + private Passphrase readPassphraseFromStdin() { System.out.println("Enter a value for --password:"); var console = System.console(); if (console == null) { throw new IllegalStateException("No console found to read password from."); } - return console.readPassword(); + return new Passphrase(console.readPassword()); } - private char[] readPassphraseFromEnvironment() { + private Passphrase readPassphraseFromEnvironment() { var tmp = System.getenv(passphraseEnvironmentVariable); if (tmp == null) { throw new ReadingEnvironmentVariableFailedException("Environment variable " + passphraseEnvironmentVariable + " is not defined."); } char[] result = new char[tmp.length()]; tmp.getChars(0, tmp.length(), result, 0); - return result; + return new Passphrase(result); } - private char[] readPassphraseFromFile() throws ReadingFileFailedException { + private Passphrase readPassphraseFromFile() throws ReadingFileFailedException { try { var bytes = Files.readAllBytes(passphraseFile); var byteBuffer = ByteBuffer.wrap(bytes); var charBuffer = StandardCharsets.UTF_8.decode(byteBuffer); - return charBuffer.array(); + return new Passphrase(charBuffer.array()); } catch (IOException e) { throw new ReadingFileFailedException(e); } @@ -89,4 +90,12 @@ static class ReadingEnvironmentVariableFailedException extends PasswordSourceExc } } + record Passphrase(char [] content) implements AutoCloseable { + + @Override + public void close() { + Arrays.fill(content, (char) 0); + } + } + } From f8e3e115742bf9d6d289d6a79cba3c86d9008d1f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 18 Sep 2024 15:33:17 +0200 Subject: [PATCH 018/101] use correct maven property --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 25eb1a1..14ed0bc 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ maven-compiler-plugin ${maven-compiler.version} - ${java.version} + ${jdk.version} true From 60fc1b9f054c6cea7e2f672da6f5fc3d5abb16f8 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 18 Sep 2024 15:33:33 +0200 Subject: [PATCH 019/101] update maven wrapper --- .mvn/wrapper/maven-wrapper.properties | 5 +- mvnw | 435 ++++++++++++-------------- mvnw.cmd | 304 ++++++++---------- 3 files changed, 320 insertions(+), 424 deletions(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 346d645..d58dfb7 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/mvnw b/mvnw index 8d937f4..19529dd 100644 --- a/mvnw +++ b/mvnw @@ -19,290 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false +# OS specific support. +native_path() { printf %s\\n "$1"; } case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi fi else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi +} - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" + printf %x\\n $h } -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 } -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT else - log "Couldn't find $wrapperJarPath, downloading it ..." + die "cannot create temp dir" +fi - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" +mkdir -p -- "${MAVEN_HOME%/*}" - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -########################################################################################## -# End of extension -########################################################################################## -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index c4586b5..249bdf3 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -18,188 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From efdbd9b27670a82cea2b95e5e37d69516624b80f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 19 Sep 2024 10:06:46 +0200 Subject: [PATCH 020/101] cleanup --- src/main/java/org/cryptomator/cli/CryptomatorCli.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 6045b20..512c0a3 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -16,7 +16,6 @@ import java.nio.CharBuffer; import java.nio.file.Path; import java.security.SecureRandom; -import java.util.Arrays; import java.util.concurrent.Callable; @Command(name = "cryptomator-cli", From e935ab388520fa69d3b0d1791e9b6081f9d89058 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 23 Sep 2024 14:49:05 +0200 Subject: [PATCH 021/101] modularize app --- src/main/java/module-info.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/module-info.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..74fb27c --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,8 @@ +open module org.cryptomator.cli { + requires org.cryptomator.cryptofs; + requires org.cryptomator.frontend.fuse; + requires info.picocli; + requires org.slf4j; + requires org.slf4j.jul; + requires org.cryptomator.integrations.api; +} \ No newline at end of file From 83538f8c2246a1349970493f8aa412458d0af421 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 24 Sep 2024 11:07:33 +0200 Subject: [PATCH 022/101] fix error in help message --- src/main/java/org/cryptomator/cli/MountOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountOptions.java index 1891a89..6cb4857 100644 --- a/src/main/java/org/cryptomator/cli/MountOptions.java +++ b/src/main/java/org/cryptomator/cli/MountOptions.java @@ -22,7 +22,7 @@ void setMountService(String value) { var services = IntegrationsLoader.loadAll(MountService.class).toList(); var service = services.stream().filter(s -> s.getClass().getName().equals(value)).findFirst(); if (service.isEmpty()) { - var availableServices = services.stream().map(s -> getClass().getName()).collect(Collectors.joining(",")); + var availableServices = services.stream().map(s -> s.getClass().getName()).collect(Collectors.joining(",")); var errorMessage = String.format("Invalid value '%s' for option '--mounter': Available mounters are [%s].", value, availableServices); throw new CommandLine.ParameterException(spec.commandLine(), errorMessage); } From 702afb5c75c86165c65002922a0320ffbbc7b2f1 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 24 Sep 2024 11:07:53 +0200 Subject: [PATCH 023/101] align password parameters --- src/main/java/org/cryptomator/cli/PasswordSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index ebec08c..4e8b96a 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -11,7 +11,7 @@ public class PasswordSource { - @CommandLine.Option(names = {"--password"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") + @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") boolean passphraseStdin; @CommandLine.Option(names = "--password:env", description = "Name of the environment variable containing the passphrase") @@ -38,7 +38,7 @@ Passphrase readPassphrase() throws IOException { } private Passphrase readPassphraseFromStdin() { - System.out.println("Enter a value for --password:"); + System.out.println("Enter the password:"); var console = System.console(); if (console == null) { throw new IllegalStateException("No console found to read password from."); From 3a371e1f72d00d73132fe9b9ac7a72d1e571d1fd Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 11:53:55 +0200 Subject: [PATCH 024/101] clean up pom --- pom.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 14ed0bc..ede8c7e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 22 - org.cryptomator.hubcli.HubCli + org.cryptomator.cli.CryptomatorCli -Ob @@ -28,6 +28,7 @@ 3.13.0 + 3.3.0 3.7.1 @@ -112,7 +113,19 @@ - + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar.version} + + + + true + ${mainClass} + + + + maven-assembly-plugin ${maven-assembly.version} @@ -133,7 +146,7 @@ false - org.cryptomator.cli.CryptomatorCli + ${mainClass} ${project.version} From 2ad10f29e7dd91254b8d2540b7009f382fbd4356 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 11:54:09 +0200 Subject: [PATCH 025/101] add maven-exec-plugin --- pom.xml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pom.xml b/pom.xml index ede8c7e..be8a6f6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 3.13.0 3.3.0 3.7.1 + 3.4.1 @@ -152,6 +153,33 @@ + + org.codehaus.mojo + exec-maven-plugin + ${maven-exec.version} + + + test-run + + exec + + + + + ${env.JAVA_HOME}/bin/java + + --module-path + + --enable-native-access=org.cryptomator.jfuse.linux,org.cryptomator.jfuse.win,org.cryptomator.jfuse.mac + --module + org.cryptomator.cli/${mainClass} + --password:stdin + --mounter=org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider + --mountPoint=X:\\ + T:\\vaultFormat8 + + + From 38d357ec5d01721d946a36e642597d728d9b05ca Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 11:56:53 +0200 Subject: [PATCH 026/101] Improve app behaviour: * print mount URI * terminate on EOT or ETX character --- src/main/java/org/cryptomator/cli/CryptomatorCli.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 512c0a3..f48ea81 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -49,14 +49,11 @@ public Integer call() throws Exception { .build(); try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsProps); var mount = mountOptions.mount(fs)) { - + System.out.println(mount.getMountpoint().uri()); while (true) { int c = System.in.read(); - if (c == -1) { //END OF STREAM - //TODO: terminate with error? - mount.unmount(); - return 1; - } else if (c == 0x03) {//Ctrl+C + if (c == -1 || c == 0x03 || c == 0x04) {//Ctrl+C, Ctrl+D + LOG.info("Unmounting and locking vault..."); mount.unmount(); break; } From dfd562ecd7f136177fee3c85ddfdd52fe679ff00 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 11:57:01 +0200 Subject: [PATCH 027/101] Revert "switch to JUL (for now)" This reverts commit aecf18ca0e59913876aae7fc41e15422aea22a84. --- pom.xml | 12 +++++++++--- src/main/resources/logback.xml | 15 +++++++++++++++ src/main/resources/logging.properties | 6 ------ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 src/main/resources/logback.xml delete mode 100644 src/main/resources/logging.properties diff --git a/pom.xml b/pom.xml index be8a6f6..dd017a7 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ 2.7.0-beta1 2.0.6 5.0.0 + 1.5.6 2.0.16 @@ -88,9 +89,14 @@ ${slf4j.version} - org.slf4j - slf4j-jdk14 - ${slf4j.version} + ch.qos.logback + logback-core + ${logback.version} + + + ch.qos.logback + logback-classic + ${logback.version} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..a92d372 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + true + + %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n + + + + + + + diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties deleted file mode 100644 index 5370608..0000000 --- a/src/main/resources/logging.properties +++ /dev/null @@ -1,6 +0,0 @@ -handlers = java.util.logging.ConsoleHandler -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = %4$s: %5$s - -.level = ERROR -org.cryptomator.cli.level = INFO \ No newline at end of file From 883359cbe209559987afe5c92c0e5444e807c0b7 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 12:11:06 +0200 Subject: [PATCH 028/101] colorize console --- pom.xml | 6 ++++++ src/main/java/module-info.java | 2 +- src/main/resources/logback.xml | 4 +--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index dd017a7..ea0dd8a 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,12 @@ logback-classic ${logback.version} + + org.fusesource.jansi + jansi + 2.4.1 + + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 74fb27c..5f4f545 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,6 +3,6 @@ requires org.cryptomator.frontend.fuse; requires info.picocli; requires org.slf4j; - requires org.slf4j.jul; requires org.cryptomator.integrations.api; + requires org.fusesource.jansi; } \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index a92d372..20e1b8f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,11 +1,9 @@ - - true - %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n + [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n From 2c6a42a51733f52975169302583506833a9c7fc9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 25 Sep 2024 15:57:53 +0200 Subject: [PATCH 029/101] add option to set maxCleartextNameLimit --- .../org/cryptomator/cli/CryptomatorCli.java | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index f48ea81..cd9887e 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -2,6 +2,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.integrations.mount.UnmountFailedException; @@ -14,6 +15,8 @@ import java.io.IOException; import java.net.URI; import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.security.SecureRandom; import java.util.concurrent.Callable; @@ -26,7 +29,10 @@ public class CryptomatorCli implements Callable { private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); private static final byte[] PEPPER = new byte[0]; + private static final String CONFIG_FILE_NAME = "vault.cryptomator"; + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") Path pathToVault; @@ -37,17 +43,33 @@ public class CryptomatorCli implements Callable { @CommandLine.ArgGroup(exclusive = false, multiplicity = "1") MountOptions mountOptions; + @CommandLine.Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") + void setMaxCleartextNameLength(int input) { + if (input <= 0) { + throw new CommandLine.ParameterException(spec.commandLine(), + String.format("Invalid value '%d' for option '--maxCleartextNameLength': " + + "value must be a positive Number between 1 and %d.", input, Integer.MAX_VALUE)); + } + maxCleartextNameLength = input; + } + + private int maxCleartextNameLength = 0; + private SecureRandom csrpg = null; @Override public Integer call() throws Exception { csrpg = SecureRandom.getInstanceStrong(); - CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // + + var unverifiedConfig = readConfigFromStorage(pathToVault); + var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() // .withKeyLoader(this::loadMasterkey) // - //TODO: shortening Threshold - //TODO: maxCleartextname - .build(); - try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsProps); + .withShorteningThreshold(unverifiedConfig.allegedShorteningThreshold()); //cryptofs checks, if config is signed with masterkey + if (maxCleartextNameLength > 0) { + fsPropsBuilder.withMaxCleartextNameLength(maxCleartextNameLength); + } + + try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsPropsBuilder.build()); var mount = mountOptions.mount(fs)) { System.out.println(mount.getMountpoint().uri()); while (true) { @@ -74,6 +96,17 @@ private Masterkey loadMasterkey(URI keyId) { } } + /** + * Attempts to read the vault config file and parse it without verifying its integrity. + * + * @throws IOException if reading the file fails + */ + static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException { + Path configPath = vaultPath.resolve(CONFIG_FILE_NAME); + String token = Files.readString(configPath, StandardCharsets.US_ASCII); + return VaultConfig.decode(token); + } + public static void main(String... args) { int exitCode = new CommandLine(new CryptomatorCli()) .setPosixClusteredShortOptionsAllowed(false) From 6f3ba44c63437d10e0faba2c7e5b8cce53d28625 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 26 Sep 2024 23:05:52 +0200 Subject: [PATCH 030/101] instead of assembled jar, use maven shade plugin to create an uber-jar. --- pom.xml | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index ea0dd8a..bf9980f 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 3.13.0 3.3.0 - 3.7.1 + 3.6.0 3.4.1 @@ -103,7 +103,6 @@ jansi 2.4.1 - @@ -140,30 +139,30 @@ - maven-assembly-plugin - ${maven-assembly.version} + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade.version} - make-assembly - package - single + shade + + true + + + ${mainClass} + + + META-INF/services/org.cryptomator.integrations.mount.MountService + + + META-INF/services/org.cryptomator.jfuse.api.FuseBuilder + + + - - cryptomator-cli-${project.version} - - jar-with-dependencies - - false - - - ${mainClass} - ${project.version} - - - org.codehaus.mojo From 9e444ec38dd721bc9ae09b9f00b4f748727a5b9d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Oct 2024 18:07:26 +0200 Subject: [PATCH 031/101] add verbose logging --- pom.xml | 3 + src/main/java/module-info.java | 8 ++ .../org/cryptomator/cli/CryptomatorCli.java | 13 ++++ .../cryptomator/cli/LogbackConfigurator.java | 77 +++++++++++++++++++ .../org/cryptomator/cli/MountOptions.java | 50 +++++++++--- .../org/cryptomator/cli/PasswordSource.java | 9 ++- .../ch.qos.logback.classic.spi.Configurator | 1 + src/main/resources/logback.xml | 13 ---- 8 files changed, 151 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/cryptomator/cli/LogbackConfigurator.java create mode 100644 src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator delete mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index bf9980f..d0f25be 100644 --- a/pom.xml +++ b/pom.xml @@ -159,6 +159,9 @@ META-INF/services/org.cryptomator.jfuse.api.FuseBuilder + + META-INF/services/ch.qos.logback.classic.spi.Configurator + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 5f4f545..0231ed6 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,8 +1,16 @@ +import ch.qos.logback.classic.spi.Configurator; +import org.cryptomator.cli.LogbackConfigurator; + open module org.cryptomator.cli { + uses Configurator; requires org.cryptomator.cryptofs; requires org.cryptomator.frontend.fuse; requires info.picocli; requires org.slf4j; requires org.cryptomator.integrations.api; requires org.fusesource.jansi; + requires ch.qos.logback.core; + requires ch.qos.logback.classic; + + provides Configurator with LogbackConfigurator; } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index cd9887e..60bbbda 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -37,6 +37,9 @@ public class CryptomatorCli implements Callable { @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") Path pathToVault; + @CommandLine.Option(names = {"--verbose"}, description = "Use verbose logging.") + boolean verbose = false; + @CommandLine.ArgGroup(multiplicity = "1") PasswordSource passwordSource; @@ -59,6 +62,15 @@ void setMaxCleartextNameLength(int input) { @Override public Integer call() throws Exception { + if (verbose) { + var logConfigurator = LogbackConfigurator.INSTANCE.get(); + if (logConfigurator != null) { + logConfigurator.setLogLevels(LogbackConfigurator.DEBUG_LOG_LEVELS); + LOG.debug("User verbose logging"); + } else { + throw new IllegalStateException("Logging is not configured."); + } + } csrpg = SecureRandom.getInstanceStrong(); var unverifiedConfig = readConfigFromStorage(pathToVault); @@ -103,6 +115,7 @@ private Masterkey loadMasterkey(URI keyId) { */ static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException { Path configPath = vaultPath.resolve(CONFIG_FILE_NAME); + LOG.debug("Reading vault config from file {}.", configPath); String token = Files.readString(configPath, StandardCharsets.US_ASCII); return VaultConfig.decode(token); } diff --git a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java new file mode 100644 index 0000000..7358df6 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java @@ -0,0 +1,77 @@ +package org.cryptomator.cli; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.Configurator; +import ch.qos.logback.classic.spi.ConfiguratorRank; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.spi.ContextAwareBase; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +@ConfiguratorRank(ConfiguratorRank.CUSTOM_HIGH_PRIORITY) +public class LogbackConfigurator extends ContextAwareBase implements Configurator { + + public static final AtomicReference INSTANCE = new AtomicReference<>(); + + static final Map DEFAULT_LOG_LEVELS = Map.of( // + Logger.ROOT_LOGGER_NAME, Level.INFO, // + "org.cryptomator", Level.INFO // + ); + + public static final Map DEBUG_LOG_LEVELS = Map.of( // + Logger.ROOT_LOGGER_NAME, Level.DEBUG, // + "org.cryptomator", Level.TRACE // + ); + + @Override + public ExecutionStatus configure(LoggerContext context) { + var encoder = new PatternLayoutEncoder(); + encoder.setContext(context); + encoder.setPattern("[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n"); + encoder.start(); + + var stdout = new ConsoleAppender(); + stdout.setWithJansi(true); + stdout.setContext(context); + stdout.setName("STDOUT"); + stdout.setEncoder(encoder); + stdout.start(); + + // configure loggers: + for (var loglevel : DEFAULT_LOG_LEVELS.entrySet()) { + Logger logger = context.getLogger(loglevel.getKey()); + logger.setLevel(loglevel.getValue()); + logger.setAdditive(false); + logger.addAppender(stdout); + } + + //disable fuselocking messages + Logger fuseLocking = context.getLogger("org.cryptomator.frontend.fuse.locks"); + fuseLocking.setLevel(Level.OFF); + + //make instance accessible + INSTANCE.compareAndSet(null, this); + return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY; + } + + /** + * Adjust the log levels + * + * @param logLevels new log levels to use + */ + public void setLogLevels(Map logLevels) { + if (context instanceof LoggerContext lc) { + for (var loglevel : logLevels.entrySet()) { + Logger logger = lc.getLogger(loglevel.getKey()); + System.out.println(logger.getName()); + logger.setLevel(loglevel.getValue()); + } + } + } + +} diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountOptions.java index 6cb4857..7e3825f 100644 --- a/src/main/java/org/cryptomator/cli/MountOptions.java +++ b/src/main/java/org/cryptomator/cli/MountOptions.java @@ -2,18 +2,19 @@ import org.cryptomator.integrations.common.IntegrationsLoader; import org.cryptomator.integrations.mount.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import picocli.CommandLine; import java.nio.file.FileSystem; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; public class MountOptions { + private static final Logger LOG = LoggerFactory.getLogger(MountOptions.class); + @CommandLine.Spec CommandLine.Model.CommandSpec spec; @@ -48,34 +49,65 @@ void setMountService(String value) { @CommandLine.Option(names = {"--loopbackPort"}, description = "Port used at the loopback address.") Optional loopbackPort; + MountBuilder prepareMountBuilder(FileSystem fs) { + var specifiedOptions = filterNotSpecifiedOptions(); var builder = mountService.forFileSystem(fs.getPath("/")); for (var capability : mountService.capabilities()) { switch (capability) { case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); - case LOOPBACK_PORT -> loopbackPort.ifPresent(builder::setLoopbackPort); - case LOOPBACK_HOST_NAME -> loopbackHostName.ifPresent(builder::setLoopbackHostName); + case LOOPBACK_PORT -> { + loopbackPort.ifPresent(builder::setLoopbackPort); + specifiedOptions.put("loopbackPort", false); + } + case LOOPBACK_HOST_NAME -> { + loopbackHostName.ifPresent(builder::setLoopbackHostName); + specifiedOptions.put("loopbackHostname", false); + } //TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); case MOUNT_FLAGS -> { + specifiedOptions.put("mountOptions", false); if (mountOptions.isEmpty()) { - builder.setMountFlags(mountService.getDefaultMountFlags()); + var defaultFlags = mountService.getDefaultMountFlags(); + LOG.debug("Using default mount options {}", defaultFlags); + builder.setMountFlags(defaultFlags); } else { builder.setMountFlags(String.join(" ", mountOptions)); } } - case VOLUME_ID -> builder.setVolumeId(volumeId); - case VOLUME_NAME -> volumeName.ifPresent(builder::setVolumeName); + case VOLUME_ID -> { + builder.setVolumeId(volumeId); + } + case VOLUME_NAME -> { + volumeName.ifPresent(builder::setVolumeName); + specifiedOptions.put("volumeName", false); + } } } + + var ignoredOptions = specifiedOptions.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(",")); + LOG.info("Ignoring unsupported options: {}", ignoredOptions); return builder; } + private Map filterNotSpecifiedOptions() { + var map = new HashMap(); + loopbackPort.ifPresent(_ -> map.put("loopbackPort", true)); + loopbackHostName.ifPresent(_ -> map.put("loopbackHostname", true)); + volumeName.ifPresent(_ -> map.put("volumeName", true)); + if (!mountOptions.isEmpty()) { + map.put("mountOption", true); + } + return map; + } + Mount mount(FileSystem fs) throws MountFailedException { if (!mountService.hasCapability(MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH) && mountPoint.isEmpty()) { throw new CommandLine.ParameterException(spec.commandLine(), "The selected mounter %s requires a mount point. Use --mountPoint /path/to/mount/point to specify it.".formatted(mountService.displayName())); } var builder = prepareMountBuilder(fs); mountPoint.ifPresent(builder::setMountpoint); + LOG.debug("Mounting vault using {} to {}.", mountService.displayName(), mountPoint.isPresent() ? mountPoint.get() : "system chosen location"); return builder.mount(); } } diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 4e8b96a..5a8b601 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -1,5 +1,7 @@ package org.cryptomator.cli; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import picocli.CommandLine; import java.io.IOException; @@ -11,6 +13,8 @@ public class PasswordSource { + public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class); + @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") boolean passphraseStdin; @@ -38,6 +42,7 @@ Passphrase readPassphrase() throws IOException { } private Passphrase readPassphraseFromStdin() { + LOG.debug("Reading passphrase from STDIN"); System.out.println("Enter the password:"); var console = System.console(); if (console == null) { @@ -47,6 +52,7 @@ private Passphrase readPassphraseFromStdin() { } private Passphrase readPassphraseFromEnvironment() { + LOG.debug("Reading passphrase from env variable '{}'", passphraseEnvironmentVariable); var tmp = System.getenv(passphraseEnvironmentVariable); if (tmp == null) { throw new ReadingEnvironmentVariableFailedException("Environment variable " + passphraseEnvironmentVariable + " is not defined."); @@ -57,6 +63,7 @@ private Passphrase readPassphraseFromEnvironment() { } private Passphrase readPassphraseFromFile() throws ReadingFileFailedException { + LOG.debug("Reading passphrase from file '{}'", passphraseFile); try { var bytes = Files.readAllBytes(passphraseFile); var byteBuffer = ByteBuffer.wrap(bytes); @@ -90,7 +97,7 @@ static class ReadingEnvironmentVariableFailedException extends PasswordSourceExc } } - record Passphrase(char [] content) implements AutoCloseable { + record Passphrase(char[] content) implements AutoCloseable { @Override public void close() { diff --git a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator new file mode 100644 index 0000000..e7a2757 --- /dev/null +++ b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator @@ -0,0 +1 @@ +org.cryptomator.cli.LogbackConfigurator \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 20e1b8f..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - true - - [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n - - - - - - - From 654e62015b41f9ca6c902af4f038dd40624bed9e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Oct 2024 18:11:51 +0200 Subject: [PATCH 032/101] fix ci --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef55216..9cca102 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,8 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - java-version: 22 + java-version: '22' + distribution: 'temurin' - name: Ensure to use tagged version run: mvn versions:set --file ./pom.xml -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' if: startsWith(github.ref, 'refs/tags/') From c2c260fec3c691ab9804c013a5c4dc7b961eb90d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 4 Oct 2024 17:29:45 +0200 Subject: [PATCH 033/101] clean up --- src/main/java/org/cryptomator/cli/CryptomatorCli.java | 3 ++- src/main/java/org/cryptomator/cli/MountOptions.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 60bbbda..7f086e4 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -86,6 +86,7 @@ public Integer call() throws Exception { System.out.println(mount.getMountpoint().uri()); while (true) { int c = System.in.read(); + //TODO: Password piping is currently not supported due to read() returing -1 if (c == -1 || c == 0x03 || c == 0x04) {//Ctrl+C, Ctrl+D LOG.info("Unmounting and locking vault..."); mount.unmount(); @@ -93,7 +94,7 @@ public Integer call() throws Exception { } } } catch (UnmountFailedException e) { - LOG.error("Regular unmount failed. Just terminating...", e); + LOG.error("Regular unmount failed. Just terminating process...", e); } return 0; } diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountOptions.java index 7e3825f..70a2cfa 100644 --- a/src/main/java/org/cryptomator/cli/MountOptions.java +++ b/src/main/java/org/cryptomator/cli/MountOptions.java @@ -86,7 +86,9 @@ MountBuilder prepareMountBuilder(FileSystem fs) { } var ignoredOptions = specifiedOptions.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(",")); - LOG.info("Ignoring unsupported options: {}", ignoredOptions); + if(!ignoredOptions.isEmpty()) { + LOG.info("Ignoring unsupported options: {}", ignoredOptions); + } return builder; } From c3421ca8a2e7213c300a2d7ef21a5ac39da65168 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 4 Oct 2024 17:45:02 +0200 Subject: [PATCH 034/101] Update README --- README.md | 79 +++++++++++-------------------------------------------- 1 file changed, 16 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 16c08c2..71a44b0 100644 --- a/README.md +++ b/README.md @@ -3,34 +3,32 @@ # Cryptomator CLI -This is a minimal command-line application that unlocks vaults of vault format 8. -After unlocking the vaults, its vault content can be accessed via an embedded WebDAV server. -The minimum required Java version is JDK 17. - -## Disclaimer - -:warning: This project is in an early stage and not ready for production use. We recommend using it only for testing and evaluation purposes. +This is a minimal command-line application that unlocks a single vault of vault format 8 and mounts it into the system. ## Download and Usage Download the JAR file via [GitHub Releases](https://github.com/cryptomator/cli/releases). -Cryptomator CLI requires that at least JDK 17 is present on your system. - +Cryptomator CLI requires that at least JDK/JRE 22 is present on your system. ```sh -java -jar cryptomator-cli-x.y.z.jar \ - --vault demoVault=/path/to/vault --password demoVault=topSecret \ - --vault otherVault=/path/to/differentVault --passwordfile otherVault=/path/to/fileWithPassword \ - --vault thirdVault=/path/to/thirdVault \ - --bind 127.0.0.1 --port 8080 -# You can now mount http://localhost:8080/demoVault/ -# The password for the third vault is read from stdin +java --enable-native-access="ALL-UNNAMED" -jar cryptomator-cli-x.y.z.jar \ + --password:stdin \ + --mounter=org.cryptomator.frontend.fuse.mount.FuseMountProvider \ + --mountPoint=/home/user/existing/empty/dir \ + /path/to/vault # Be aware that passing the password on the command-line typically makes it visible to anyone on your system! ``` -## Filesystem Integration +For a complete list of options, start the jar with the `--help` argument. +```shell +java --enable-native-access="ALL-UNNAMED" -jar cryptomator-cli-x.y.z.jar --help +``` + +## Block Filesystem Integration -Once the vault is unlocked and the WebDAV server started, you can access the vault by any WebDAV client or directly mounting it in your filesystem. +Depending on the chosen mounter, you the vault is automatically integrated into the os. +If you don't want a direct integration, choose `org.cryptomator.frontend.webdav.mount.FallbackMounter` for `--mounter`. +It starts a local WebDAV server started, where you can access the vault by any WebDAV client or mounting it into your filesystem manually. ### Windows via Windows Explorer @@ -64,55 +62,10 @@ sudo umount /media/your/mounted/folder ### macOS via AppleScript Mount the vault with: - ```sh osascript -e 'mount volume "http://localhost:8080/demoVault/"' ``` -Unmount the vault with: - -```sh -osascript -e 'tell application "Finder" to if "demoVault" exists then eject "demoVault"' -``` - -## Using as a Docker image - -### Bridge Network with Port Forwarding - -:warning: **WARNING: This approach should only be used to test the containerized approach, not in production.** :warning: - -The reason is that with port forwarding, you need to listen on all interfaces. Other devices on the network could also access your WebDAV server and potentially expose your secret files. - -Ideally, you would run this in a private Docker network with trusted containers built by yourself communicating with each other. **Again, the below example is for testing purposes only to understand how the container would behave in production.** - -```sh -docker run --rm -p 8080:8080 \ - -v /path/to/vault:/vaults/vault \ - -v /path/to/differentVault:/vaults/differentVault \ - -v /path/to/fileWithPassword:/passwordFile \ - cryptomator/cli \ - --bind 0.0.0.0 --port 8080 \ - --vault demoVault=/vaults/vault --password demoVault=topSecret \ - --vault otherVault=/vaults/differentVault --passwordfile otherVault=/passwordFile -# You can now mount http://localhost:8080/demoVault/ -``` - -### Host Network - -```sh -docker run --rm --network=host \ - -v /path/to/vault:/vaults/vault \ - -v /path/to/differentVault:/vaults/differentVault \ - -v /path/to/fileWithPassword:/passwordFile \ - cryptomator/cli \ - --bind 127.0.0.1 --port 8080 \ - --vault demoVault=/vaults/vault --password demoVault=topSecret \ - --vault otherVault=/vaults/differentVault --passwordfile otherVault=/passwordFile -# You can now mount http://localhost:8080/demoVault/ -``` - -Then you can access the vault using any WebDAV client. - ## License This project is dual-licensed under the AGPLv3 for FOSS projects as well as a commercial license derived from the LGPL for independent software vendors and resellers. If you want to use this library in applications, that are *not* licensed under the AGPL, feel free to contact our [support team](https://cryptomator.org/help/). From 03fde0cbe105f03cb77a4b3be0ac4ea2e8767ab1 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 4 Oct 2024 17:46:04 +0200 Subject: [PATCH 035/101] remove unsupported docker image --- Dockerfile | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 38d6fa5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM alpine:3.12.0 - -ARG CRYPTOMATOR_CLI_VERSION=0.4.0 - -RUN adduser -D cryptomator && \ - apk add --no-cache openjdk11-jre-headless && \ - wget https://github.com/cryptomator/cli/releases/download/$CRYPTOMATOR_CLI_VERSION/cryptomator-cli-$CRYPTOMATOR_CLI_VERSION.jar -O /usr/bin/cryptomator.jar - -USER cryptomator - -VOLUME ["/vaults"] - -ENTRYPOINT ["java", "-jar", "/usr/bin/cryptomator.jar"] From 4a5f9c5a1179ed579af2874197b453a3fb4a365f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 4 Oct 2024 17:46:17 +0200 Subject: [PATCH 036/101] update gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3ef5c20..1ebf12d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ out/ .idea_modules/ *.iws -*.iml \ No newline at end of file +*.iml + +#mvn shade plugin artifact +dependency-reduced-pom.xml \ No newline at end of file From 5efd135515e074552bd43238457a5d8e5db67197 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 13:26:12 +0200 Subject: [PATCH 037/101] adjust name of shaded jar (for CI integration) --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index d0f25be..47bdc7a 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,7 @@ true + cryptomator-cli-${project.version} ${mainClass} From a56172883ac116f24fb5451b4fbe17394df40efc Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 14:02:18 +0200 Subject: [PATCH 038/101] cleanup --- README.md | 2 +- .../org/cryptomator/cli/CryptomatorCli.java | 10 ++++---- .../cryptomator/cli/LogbackConfigurator.java | 11 ++++----- .../{MountOptions.java => MountSetup.java} | 24 +++++++++---------- 4 files changed, 22 insertions(+), 25 deletions(-) rename src/main/java/org/cryptomator/cli/{MountOptions.java => MountSetup.java} (86%) diff --git a/README.md b/README.md index 71a44b0..cff7a1f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ java --enable-native-access="ALL-UNNAMED" -jar cryptomator-cli-x.y.z.jar --help ## Block Filesystem Integration -Depending on the chosen mounter, you the vault is automatically integrated into the os. +Depending on the chosen mounter, the vault is automatically integrated into the os. If you don't want a direct integration, choose `org.cryptomator.frontend.webdav.mount.FallbackMounter` for `--mounter`. It starts a local WebDAV server started, where you can access the vault by any WebDAV client or mounting it into your filesystem manually. diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 7f086e4..e982f24 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -44,7 +44,7 @@ public class CryptomatorCli implements Callable { PasswordSource passwordSource; @CommandLine.ArgGroup(exclusive = false, multiplicity = "1") - MountOptions mountOptions; + MountSetup mountSetup; @CommandLine.Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") void setMaxCleartextNameLength(int input) { @@ -58,7 +58,7 @@ void setMaxCleartextNameLength(int input) { private int maxCleartextNameLength = 0; - private SecureRandom csrpg = null; + private SecureRandom csprng = null; @Override public Integer call() throws Exception { @@ -71,7 +71,7 @@ public Integer call() throws Exception { throw new IllegalStateException("Logging is not configured."); } } - csrpg = SecureRandom.getInstanceStrong(); + csprng = SecureRandom.getInstanceStrong(); var unverifiedConfig = readConfigFromStorage(pathToVault); var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() // @@ -82,7 +82,7 @@ public Integer call() throws Exception { } try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsPropsBuilder.build()); - var mount = mountOptions.mount(fs)) { + var mount = mountSetup.mount(fs)) { System.out.println(mount.getMountpoint().uri()); while (true) { int c = System.in.read(); @@ -102,7 +102,7 @@ public Integer call() throws Exception { private Masterkey loadMasterkey(URI keyId) { try (var passphraseContainer = passwordSource.readPassphrase()) { Path filePath = pathToVault.resolve("masterkey.cryptomator"); - return new MasterkeyFileAccess(PEPPER, csrpg) + return new MasterkeyFileAccess(PEPPER, csprng) .load(filePath, CharBuffer.wrap(passphraseContainer.content())); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java index 7358df6..e75f33e 100644 --- a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java +++ b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java @@ -20,12 +20,14 @@ public class LogbackConfigurator extends ContextAwareBase implements Configurato static final Map DEFAULT_LOG_LEVELS = Map.of( // Logger.ROOT_LOGGER_NAME, Level.INFO, // - "org.cryptomator", Level.INFO // + "org.cryptomator", Level.INFO, // + "org.cryptomator.frontend.fuse.locks", Level.OFF ); public static final Map DEBUG_LOG_LEVELS = Map.of( // Logger.ROOT_LOGGER_NAME, Level.DEBUG, // - "org.cryptomator", Level.TRACE // + "org.cryptomator", Level.TRACE, // + "org.cryptomator.frontend.fuse.locks", Level.OFF ); @Override @@ -50,10 +52,6 @@ public ExecutionStatus configure(LoggerContext context) { logger.addAppender(stdout); } - //disable fuselocking messages - Logger fuseLocking = context.getLogger("org.cryptomator.frontend.fuse.locks"); - fuseLocking.setLevel(Level.OFF); - //make instance accessible INSTANCE.compareAndSet(null, this); return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY; @@ -68,7 +66,6 @@ public void setLogLevels(Map logLevels) { if (context instanceof LoggerContext lc) { for (var loglevel : logLevels.entrySet()) { Logger logger = lc.getLogger(loglevel.getKey()); - System.out.println(logger.getName()); logger.setLevel(loglevel.getValue()); } } diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountSetup.java similarity index 86% rename from src/main/java/org/cryptomator/cli/MountOptions.java rename to src/main/java/org/cryptomator/cli/MountSetup.java index 70a2cfa..20512ab 100644 --- a/src/main/java/org/cryptomator/cli/MountOptions.java +++ b/src/main/java/org/cryptomator/cli/MountSetup.java @@ -11,9 +11,9 @@ import java.util.*; import java.util.stream.Collectors; -public class MountOptions { +public class MountSetup { - private static final Logger LOG = LoggerFactory.getLogger(MountOptions.class); + private static final Logger LOG = LoggerFactory.getLogger(MountSetup.class); @CommandLine.Spec CommandLine.Model.CommandSpec spec; @@ -41,7 +41,7 @@ void setMountService(String value) { @CommandLine.Option(names = {"--volumeId"}, description = "Id of the virtual volume.") String volumeId = UUID.randomUUID().toString(); - @CommandLine.Option(names = {"--mountOption", "-mop"}, description = "Additional mount option. For a list of mountoptions, see the WinFsp, macFUSE, FUSE-T and libfuse documentation.") + @CommandLine.Option(names = {"--mountOption", "-mop"}, description = "Additional mount option. For a list of mount options, see the WinFsp, macFUSE, FUSE-T and libfuse documentation.") List mountOptions = new ArrayList<>(); @CommandLine.Option(names = {"--loopbackHostName"}, description = "Name of the loopback address.") @@ -51,22 +51,22 @@ void setMountService(String value) { MountBuilder prepareMountBuilder(FileSystem fs) { - var specifiedOptions = filterNotSpecifiedOptions(); + var specifiedMOPs = listSpecifiedSpecifiedMountOptions(); var builder = mountService.forFileSystem(fs.getPath("/")); for (var capability : mountService.capabilities()) { switch (capability) { case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); case LOOPBACK_PORT -> { loopbackPort.ifPresent(builder::setLoopbackPort); - specifiedOptions.put("loopbackPort", false); + specifiedMOPs.put("loopbackPort", false); } case LOOPBACK_HOST_NAME -> { loopbackHostName.ifPresent(builder::setLoopbackHostName); - specifiedOptions.put("loopbackHostname", false); + specifiedMOPs.put("loopbackHostname", false); } //TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); case MOUNT_FLAGS -> { - specifiedOptions.put("mountOptions", false); + specifiedMOPs.put("mountOptions", false); if (mountOptions.isEmpty()) { var defaultFlags = mountService.getDefaultMountFlags(); LOG.debug("Using default mount options {}", defaultFlags); @@ -80,19 +80,19 @@ MountBuilder prepareMountBuilder(FileSystem fs) { } case VOLUME_NAME -> { volumeName.ifPresent(builder::setVolumeName); - specifiedOptions.put("volumeName", false); + specifiedMOPs.put("volumeName", false); } } } - var ignoredOptions = specifiedOptions.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(",")); - if(!ignoredOptions.isEmpty()) { - LOG.info("Ignoring unsupported options: {}", ignoredOptions); + var ignoredMOPs = specifiedMOPs.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(",")); + if(!ignoredMOPs.isEmpty()) { + LOG.info("Ignoring unsupported options: {}", ignoredMOPs); } return builder; } - private Map filterNotSpecifiedOptions() { + private Map listSpecifiedSpecifiedMountOptions() { var map = new HashMap(); loopbackPort.ifPresent(_ -> map.put("loopbackPort", true)); loopbackHostName.ifPresent(_ -> map.put("loopbackHostname", true)); From a9481f6dae1c736295ded2d56a6ec8adb471460f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 14:02:56 +0200 Subject: [PATCH 039/101] removed unused IPC for password input --- src/main/java/org/cryptomator/cli/PasswordSource.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 5a8b601..2ec3975 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -24,10 +24,6 @@ public class PasswordSource { @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase") Path passphraseFile = null; - @CommandLine.Option(names = "--password:ipc", hidden = true, description = "Used by Cryptomator GUI") - boolean passphraseIpc = false; - - Passphrase readPassphrase() throws IOException { if (passphraseStdin) { return readPassphraseFromStdin(); @@ -35,10 +31,8 @@ Passphrase readPassphrase() throws IOException { return readPassphraseFromEnvironment(); } else if (passphraseFile != null) { return readPassphraseFromFile(); - } else { - //TODO: use ipc - return new Passphrase(new char[]{}); } + throw new IllegalStateException("Passphrase location not specified, but required."); } private Passphrase readPassphraseFromStdin() { From 897dda7e15fa2db449a474942b28b5ccf17a4302 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 14:03:46 +0200 Subject: [PATCH 040/101] throw exception if password file is greater than 10KB --- src/main/java/org/cryptomator/cli/PasswordSource.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 2ec3975..53d0568 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -14,6 +14,7 @@ public class PasswordSource { public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class); + private static final int MAX_PASSPHRASE_FILE_SIZE = 10_000; //10KB @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") boolean passphraseStdin; @@ -59,6 +60,9 @@ private Passphrase readPassphraseFromEnvironment() { private Passphrase readPassphraseFromFile() throws ReadingFileFailedException { LOG.debug("Reading passphrase from file '{}'", passphraseFile); try { + if(Files.size(passphraseFile) > MAX_PASSPHRASE_FILE_SIZE){ + throw new ReadingFileFailedException("Password file is too big. Max supported size is " + MAX_PASSPHRASE_FILE_SIZE + " bytes."); + } var bytes = Files.readAllBytes(passphraseFile); var byteBuffer = ByteBuffer.wrap(bytes); var charBuffer = StandardCharsets.UTF_8.decode(byteBuffer); @@ -83,6 +87,10 @@ static class ReadingFileFailedException extends PasswordSourceException { super(e); } + + public ReadingFileFailedException(String s) { + super(s); + } } static class ReadingEnvironmentVariableFailedException extends PasswordSourceException { From fa0437bec81327c9e79fa734c9487c4891e6b31c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 17:14:08 +0200 Subject: [PATCH 041/101] fix password from file source and specify file format --- .../org/cryptomator/cli/PasswordSource.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 53d0568..6a68bb4 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -14,7 +15,7 @@ public class PasswordSource { public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class); - private static final int MAX_PASSPHRASE_FILE_SIZE = 10_000; //10KB + private static final int MAX_PASSPHRASE_FILE_SIZE = 5_000; //5KB @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") boolean passphraseStdin; @@ -22,7 +23,7 @@ public class PasswordSource { @CommandLine.Option(names = "--password:env", description = "Name of the environment variable containing the passphrase") String passphraseEnvironmentVariable = null; - @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase") + @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase. The password file must be utf-8 encoded and must not end with a new line") Path passphraseFile = null; Passphrase readPassphrase() throws IOException { @@ -58,17 +59,27 @@ private Passphrase readPassphraseFromEnvironment() { } private Passphrase readPassphraseFromFile() throws ReadingFileFailedException { - LOG.debug("Reading passphrase from file '{}'", passphraseFile); + LOG.debug("Reading passphrase from file '{}'.", passphraseFile); + byte[] fileContent = null; + CharBuffer charWrapper = null; try { - if(Files.size(passphraseFile) > MAX_PASSPHRASE_FILE_SIZE){ + if (Files.size(passphraseFile) > MAX_PASSPHRASE_FILE_SIZE) { throw new ReadingFileFailedException("Password file is too big. Max supported size is " + MAX_PASSPHRASE_FILE_SIZE + " bytes."); } - var bytes = Files.readAllBytes(passphraseFile); - var byteBuffer = ByteBuffer.wrap(bytes); - var charBuffer = StandardCharsets.UTF_8.decode(byteBuffer); - return new Passphrase(charBuffer.array()); + fileContent = Files.readAllBytes(passphraseFile); + charWrapper = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(fileContent)); + char[] content = new char[charWrapper.limit()]; + charWrapper.get(content); + return new Passphrase(content); } catch (IOException e) { throw new ReadingFileFailedException(e); + } finally { + if (fileContent != null) { + Arrays.fill(fileContent, (byte) 0); + } + if (charWrapper != null) { + Arrays.fill(charWrapper.array(), (char) 0x00); + } } } From ee9d0b6a9db30f8e8d6fcd89698428f5e7f2031a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 7 Oct 2024 17:17:05 +0200 Subject: [PATCH 042/101] cleanup --- .../org/cryptomator/cli/CryptomatorCli.java | 17 ++++++++++------- .../cryptomator/cli/LogbackConfigurator.java | 10 +++++++--- .../org/cryptomator/cli/PasswordSource.java | 2 +- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index e982f24..cd1856f 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -63,13 +63,7 @@ void setMaxCleartextNameLength(int input) { @Override public Integer call() throws Exception { if (verbose) { - var logConfigurator = LogbackConfigurator.INSTANCE.get(); - if (logConfigurator != null) { - logConfigurator.setLogLevels(LogbackConfigurator.DEBUG_LOG_LEVELS); - LOG.debug("User verbose logging"); - } else { - throw new IllegalStateException("Logging is not configured."); - } + activateVerboseMode(); } csprng = SecureRandom.getInstanceStrong(); @@ -99,6 +93,15 @@ public Integer call() throws Exception { return 0; } + private void activateVerboseMode() { + var logConfigurator = LogbackConfigurator.INSTANCE.get(); + if (logConfigurator == null) { + throw new IllegalStateException("Logging is not configured."); + } + logConfigurator.switchToDebug(); + LOG.debug("Activated debug logging"); + } + private Masterkey loadMasterkey(URI keyId) { try (var passphraseContainer = passwordSource.readPassphrase()) { Path filePath = pathToVault.resolve("masterkey.cryptomator"); diff --git a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java index e75f33e..410e0c9 100644 --- a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java +++ b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java @@ -18,13 +18,13 @@ public class LogbackConfigurator extends ContextAwareBase implements Configurato public static final AtomicReference INSTANCE = new AtomicReference<>(); - static final Map DEFAULT_LOG_LEVELS = Map.of( // + private static final Map DEFAULT_LOG_LEVELS = Map.of( // Logger.ROOT_LOGGER_NAME, Level.INFO, // "org.cryptomator", Level.INFO, // "org.cryptomator.frontend.fuse.locks", Level.OFF ); - public static final Map DEBUG_LOG_LEVELS = Map.of( // + private static final Map DEBUG_LOG_LEVELS = Map.of( // Logger.ROOT_LOGGER_NAME, Level.DEBUG, // "org.cryptomator", Level.TRACE, // "org.cryptomator.frontend.fuse.locks", Level.OFF @@ -57,12 +57,16 @@ public ExecutionStatus configure(LoggerContext context) { return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY; } + void switchToDebug() { + setLogLevels(DEBUG_LOG_LEVELS); + } + /** * Adjust the log levels * * @param logLevels new log levels to use */ - public void setLogLevels(Map logLevels) { + private void setLogLevels(Map logLevels) { if (context instanceof LoggerContext lc) { for (var loglevel : logLevels.entrySet()) { Logger logger = lc.getLogger(loglevel.getKey()); diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 6a68bb4..2d4ce93 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -34,7 +34,7 @@ Passphrase readPassphrase() throws IOException { } else if (passphraseFile != null) { return readPassphraseFromFile(); } - throw new IllegalStateException("Passphrase location not specified, but required."); + throw new IllegalStateException("Passphrase source not specified, but required."); } private Passphrase readPassphraseFromStdin() { From 0f9564d541dcad81bcba78676a184316799c95e3 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 8 Oct 2024 11:11:17 +0200 Subject: [PATCH 043/101] more renaming --- src/main/java/org/cryptomator/cli/MountSetup.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/MountSetup.java b/src/main/java/org/cryptomator/cli/MountSetup.java index 20512ab..7d96512 100644 --- a/src/main/java/org/cryptomator/cli/MountSetup.java +++ b/src/main/java/org/cryptomator/cli/MountSetup.java @@ -51,7 +51,7 @@ void setMountService(String value) { MountBuilder prepareMountBuilder(FileSystem fs) { - var specifiedMOPs = listSpecifiedSpecifiedMountOptions(); + var specifiedMOPs = listSpecifiedMountOptions(); var builder = mountService.forFileSystem(fs.getPath("/")); for (var capability : mountService.capabilities()) { switch (capability) { @@ -62,7 +62,7 @@ MountBuilder prepareMountBuilder(FileSystem fs) { } case LOOPBACK_HOST_NAME -> { loopbackHostName.ifPresent(builder::setLoopbackHostName); - specifiedMOPs.put("loopbackHostname", false); + specifiedMOPs.put("loopbackHostName", false); } //TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); case MOUNT_FLAGS -> { @@ -92,10 +92,10 @@ MountBuilder prepareMountBuilder(FileSystem fs) { return builder; } - private Map listSpecifiedSpecifiedMountOptions() { + private Map listSpecifiedMountOptions() { var map = new HashMap(); loopbackPort.ifPresent(_ -> map.put("loopbackPort", true)); - loopbackHostName.ifPresent(_ -> map.put("loopbackHostname", true)); + loopbackHostName.ifPresent(_ -> map.put("loopbackHostName", true)); volumeName.ifPresent(_ -> map.put("volumeName", true)); if (!mountOptions.isEmpty()) { map.put("mountOption", true); From e527dcad47442843da84ddcbc61d168f6b3df801 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 10 Oct 2024 22:52:26 +0200 Subject: [PATCH 044/101] use picocli built in way to read from stdin --- .../org/cryptomator/cli/PasswordSource.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 2d4ce93..a7b6140 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -17,8 +17,9 @@ public class PasswordSource { public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class); private static final int MAX_PASSPHRASE_FILE_SIZE = 5_000; //5KB - @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN") - boolean passphraseStdin; + //TODO: is the colon ":" a reserved char in Linux? + @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN", interactive = true) + char[] passphraseStdin = null; @CommandLine.Option(names = "--password:env", description = "Name of the environment variable containing the passphrase") String passphraseEnvironmentVariable = null; @@ -27,8 +28,8 @@ public class PasswordSource { Path passphraseFile = null; Passphrase readPassphrase() throws IOException { - if (passphraseStdin) { - return readPassphraseFromStdin(); + if (passphraseStdin != null) { + return new Passphrase(passphraseStdin); //readPassphraseFromStdin(); } else if (passphraseEnvironmentVariable != null) { return readPassphraseFromEnvironment(); } else if (passphraseFile != null) { @@ -37,16 +38,6 @@ Passphrase readPassphrase() throws IOException { throw new IllegalStateException("Passphrase source not specified, but required."); } - private Passphrase readPassphraseFromStdin() { - LOG.debug("Reading passphrase from STDIN"); - System.out.println("Enter the password:"); - var console = System.console(); - if (console == null) { - throw new IllegalStateException("No console found to read password from."); - } - return new Passphrase(console.readPassword()); - } - private Passphrase readPassphraseFromEnvironment() { LOG.debug("Reading passphrase from env variable '{}'", passphraseEnvironmentVariable); var tmp = System.getenv(passphraseEnvironmentVariable); From 1e246c2fd566a1bd1cf3bdbecfe42ff6376ba4dc Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 18 Oct 2024 17:19:11 +0200 Subject: [PATCH 045/101] generalize logging to be also used in subcommands --- .../org/cryptomator/cli/CryptomatorCli.java | 51 ++++++++++--------- .../java/org/cryptomator/cli/LogginMixin.java | 29 +++++++++++ 2 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/cryptomator/cli/LogginMixin.java diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index cd1856f..5da806c 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -9,8 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; -import picocli.CommandLine.Command; -import picocli.CommandLine.Parameters; +import picocli.CommandLine.*; import java.io.IOException; import java.net.URI; @@ -31,22 +30,21 @@ public class CryptomatorCli implements Callable { private static final byte[] PEPPER = new byte[0]; private static final String CONFIG_FILE_NAME = "vault.cryptomator"; - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; + @Spec + Model.CommandSpec spec; + @Mixin + LogginMixin logginMixin; @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") Path pathToVault; - @CommandLine.Option(names = {"--verbose"}, description = "Use verbose logging.") - boolean verbose = false; - - @CommandLine.ArgGroup(multiplicity = "1") + @ArgGroup(multiplicity = "1") PasswordSource passwordSource; - @CommandLine.ArgGroup(exclusive = false, multiplicity = "1") + @ArgGroup(exclusive = false, multiplicity = "1") MountSetup mountSetup; - @CommandLine.Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") + @Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") void setMaxCleartextNameLength(int input) { if (input <= 0) { throw new CommandLine.ParameterException(spec.commandLine(), @@ -62,9 +60,6 @@ void setMaxCleartextNameLength(int input) { @Override public Integer call() throws Exception { - if (verbose) { - activateVerboseMode(); - } csprng = SecureRandom.getInstanceStrong(); var unverifiedConfig = readConfigFromStorage(pathToVault); @@ -93,15 +88,6 @@ public Integer call() throws Exception { return 0; } - private void activateVerboseMode() { - var logConfigurator = LogbackConfigurator.INSTANCE.get(); - if (logConfigurator == null) { - throw new IllegalStateException("Logging is not configured."); - } - logConfigurator.switchToDebug(); - LOG.debug("Activated debug logging"); - } - private Masterkey loadMasterkey(URI keyId) { try (var passphraseContainer = passwordSource.readPassphrase()) { Path filePath = pathToVault.resolve("masterkey.cryptomator"); @@ -124,9 +110,28 @@ static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) t return VaultConfig.decode(token); } + private int executionStrategy(ParseResult parseResult) { + if (logginMixin.isVerbose) { + activateVerboseMode(); + } + return new RunLast().execute(parseResult); // default execution strategy + } + + private void activateVerboseMode() { + var logConfigurator = LogbackConfigurator.INSTANCE.get(); + if (logConfigurator == null) { + throw new IllegalStateException("Logging is not configured."); + } + logConfigurator.switchToDebug(); + LOG.debug("Activated debug logging"); + } + + public static void main(String... args) { - int exitCode = new CommandLine(new CryptomatorCli()) + var app = new CryptomatorCli(); + int exitCode = new CommandLine(app) .setPosixClusteredShortOptionsAllowed(false) + .setExecutionStrategy(app::executionStrategy) .execute(args); System.exit(exitCode); } diff --git a/src/main/java/org/cryptomator/cli/LogginMixin.java b/src/main/java/org/cryptomator/cli/LogginMixin.java new file mode 100644 index 0000000..0c7dd6b --- /dev/null +++ b/src/main/java/org/cryptomator/cli/LogginMixin.java @@ -0,0 +1,29 @@ +package org.cryptomator.cli; + +import picocli.CommandLine.Spec; +import picocli.CommandLine.Model; +import picocli.CommandLine.Option; + +public class LogginMixin { + + @Spec(Spec.Target.MIXEE) + private Model.CommandSpec mixee; + + boolean isVerbose; + + + /** + * Sets a verbose logging leven on the LoggingMixin of the top-level command. + * @param isVerbose boolean flag to activate verbose mode + */ + @Option(names = {"-v", "--verbose"}, description = { + "Activate verbose mode"}) + public void setVerbose(boolean isVerbose) { + // Each subcommand that mixes in the LoggingMixin has its own instance + // of this class, so there may be many LoggingMixin instances. + // We want to store the verbosity value in a single, central place, + // so we find the top-level command, + // and store the verbosity level on our top-level command's LoggingMixin. + ((CryptomatorCli) mixee.root().userObject()).logginMixin.isVerbose = true; + } +} From 080c17e6167604bbec2d9b8cb468d4bcd2d8754c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 18 Oct 2024 17:29:19 +0200 Subject: [PATCH 046/101] introduce "unlock" subcommand --- .../org/cryptomator/cli/CryptomatorCli.java | 109 ++---------------- .../{LogginMixin.java => LoggingMixin.java} | 5 +- src/main/java/org/cryptomator/cli/Unlock.java | 109 ++++++++++++++++++ 3 files changed, 120 insertions(+), 103 deletions(-) rename src/main/java/org/cryptomator/cli/{LogginMixin.java => LoggingMixin.java} (88%) create mode 100644 src/main/java/org/cryptomator/cli/Unlock.java diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 5da806c..dfc20be 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -1,117 +1,26 @@ package org.cryptomator.cli; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; -import org.cryptomator.cryptofs.CryptoFileSystemProvider; -import org.cryptomator.cryptofs.VaultConfig; -import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.cryptomator.integrations.mount.UnmountFailedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; -import picocli.CommandLine.*; - -import java.io.IOException; -import java.net.URI; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.SecureRandom; -import java.util.concurrent.Callable; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.RunLast; @Command(name = "cryptomator-cli", mixinStandardHelpOptions = true, version = "1.0.0", - description = "Unlocks a cryptomator vault and mounts it into the system.") -public class CryptomatorCli implements Callable { + description = "Unlocks a cryptomator vault and mounts it into the system.", + subcommands = Unlock.class) +public class CryptomatorCli { private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); - private static final byte[] PEPPER = new byte[0]; - private static final String CONFIG_FILE_NAME = "vault.cryptomator"; - - @Spec - Model.CommandSpec spec; @Mixin - LogginMixin logginMixin; - - @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") - Path pathToVault; - - @ArgGroup(multiplicity = "1") - PasswordSource passwordSource; - - @ArgGroup(exclusive = false, multiplicity = "1") - MountSetup mountSetup; - - @Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") - void setMaxCleartextNameLength(int input) { - if (input <= 0) { - throw new CommandLine.ParameterException(spec.commandLine(), - String.format("Invalid value '%d' for option '--maxCleartextNameLength': " + - "value must be a positive Number between 1 and %d.", input, Integer.MAX_VALUE)); - } - maxCleartextNameLength = input; - } - - private int maxCleartextNameLength = 0; - - private SecureRandom csprng = null; - - @Override - public Integer call() throws Exception { - csprng = SecureRandom.getInstanceStrong(); - - var unverifiedConfig = readConfigFromStorage(pathToVault); - var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() // - .withKeyLoader(this::loadMasterkey) // - .withShorteningThreshold(unverifiedConfig.allegedShorteningThreshold()); //cryptofs checks, if config is signed with masterkey - if (maxCleartextNameLength > 0) { - fsPropsBuilder.withMaxCleartextNameLength(maxCleartextNameLength); - } - - try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsPropsBuilder.build()); - var mount = mountSetup.mount(fs)) { - System.out.println(mount.getMountpoint().uri()); - while (true) { - int c = System.in.read(); - //TODO: Password piping is currently not supported due to read() returing -1 - if (c == -1 || c == 0x03 || c == 0x04) {//Ctrl+C, Ctrl+D - LOG.info("Unmounting and locking vault..."); - mount.unmount(); - break; - } - } - } catch (UnmountFailedException e) { - LOG.error("Regular unmount failed. Just terminating process...", e); - } - return 0; - } - - private Masterkey loadMasterkey(URI keyId) { - try (var passphraseContainer = passwordSource.readPassphrase()) { - Path filePath = pathToVault.resolve("masterkey.cryptomator"); - return new MasterkeyFileAccess(PEPPER, csprng) - .load(filePath, CharBuffer.wrap(passphraseContainer.content())); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Attempts to read the vault config file and parse it without verifying its integrity. - * - * @throws IOException if reading the file fails - */ - static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException { - Path configPath = vaultPath.resolve(CONFIG_FILE_NAME); - LOG.debug("Reading vault config from file {}.", configPath); - String token = Files.readString(configPath, StandardCharsets.US_ASCII); - return VaultConfig.decode(token); - } + LoggingMixin loggingMixin; private int executionStrategy(ParseResult parseResult) { - if (logginMixin.isVerbose) { + if (loggingMixin.isVerbose) { activateVerboseMode(); } return new RunLast().execute(parseResult); // default execution strategy diff --git a/src/main/java/org/cryptomator/cli/LogginMixin.java b/src/main/java/org/cryptomator/cli/LoggingMixin.java similarity index 88% rename from src/main/java/org/cryptomator/cli/LogginMixin.java rename to src/main/java/org/cryptomator/cli/LoggingMixin.java index 0c7dd6b..ab306b4 100644 --- a/src/main/java/org/cryptomator/cli/LogginMixin.java +++ b/src/main/java/org/cryptomator/cli/LoggingMixin.java @@ -4,14 +4,13 @@ import picocli.CommandLine.Model; import picocli.CommandLine.Option; -public class LogginMixin { +public class LoggingMixin { @Spec(Spec.Target.MIXEE) private Model.CommandSpec mixee; boolean isVerbose; - /** * Sets a verbose logging leven on the LoggingMixin of the top-level command. * @param isVerbose boolean flag to activate verbose mode @@ -24,6 +23,6 @@ public void setVerbose(boolean isVerbose) { // We want to store the verbosity value in a single, central place, // so we find the top-level command, // and store the verbosity level on our top-level command's LoggingMixin. - ((CryptomatorCli) mixee.root().userObject()).logginMixin.isVerbose = true; + ((CryptomatorCli) mixee.root().userObject()).loggingMixin.isVerbose = true; } } diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java new file mode 100644 index 0000000..97a1ba8 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -0,0 +1,109 @@ +package org.cryptomator.cli; + +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.integrations.mount.UnmountFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.*; + +import java.io.IOException; +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.concurrent.Callable; + +@Command(name = "unlock") +public class Unlock implements Callable { + + private static final Logger LOG = LoggerFactory.getLogger(Unlock.class); + private static final byte[] PEPPER = new byte[0]; + private static final String CONFIG_FILE_NAME = "vault.cryptomator"; + + @Spec + Model.CommandSpec spec; + @Mixin + LoggingMixin loggingMixin; + + @Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory") + Path pathToVault; + + @ArgGroup(multiplicity = "1") + PasswordSource passwordSource; + + @ArgGroup(exclusive = false, multiplicity = "1") + MountSetup mountSetup; + + @Option(names = {"--maxCleartextNameLength"}, description = "Maximum cleartext filename length limit of created files. Remark: If this limit is greater than the shortening threshold, it does not have any effect.") + void setMaxCleartextNameLength(int input) { + if (input <= 0) { + throw new CommandLine.ParameterException(spec.commandLine(), + String.format("Invalid value '%d' for option '--maxCleartextNameLength': " + + "value must be a positive Number between 1 and %d.", input, Integer.MAX_VALUE)); + } + maxCleartextNameLength = input; + } + private int maxCleartextNameLength = 0; + + private SecureRandom csprng = null; + + @Override + public Integer call() throws Exception { + csprng = SecureRandom.getInstanceStrong(); + + var unverifiedConfig = readConfigFromStorage(pathToVault); + var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() // + .withKeyLoader(this::loadMasterkey) // + .withShorteningThreshold(unverifiedConfig.allegedShorteningThreshold()); //cryptofs checks, if config is signed with masterkey + if (maxCleartextNameLength > 0) { + fsPropsBuilder.withMaxCleartextNameLength(maxCleartextNameLength); + } + + try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsPropsBuilder.build()); + var mount = mountSetup.mount(fs)) { + System.out.println(mount.getMountpoint().uri()); + while (true) { + int c = System.in.read(); + //TODO: Password piping is currently not supported due to read() returing -1 + if (c == -1 || c == 0x03 || c == 0x04) {//Ctrl+C, Ctrl+D + LOG.info("Unmounting and locking vault..."); + mount.unmount(); + break; + } + } + } catch (UnmountFailedException e) { + LOG.error("Regular unmount failed. Just terminating process...", e); + } + return 0; + } + + private Masterkey loadMasterkey(URI keyId) { + try (var passphraseContainer = passwordSource.readPassphrase()) { + Path filePath = pathToVault.resolve("masterkey.cryptomator"); + return new MasterkeyFileAccess(PEPPER, csprng) + .load(filePath, CharBuffer.wrap(passphraseContainer.content())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Attempts to read the vault config file and parse it without verifying its integrity. + * + * @throws IOException if reading the file fails + */ + static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException { + Path configPath = vaultPath.resolve(CONFIG_FILE_NAME); + LOG.debug("Reading vault config from file {}.", configPath); + String token = Files.readString(configPath, StandardCharsets.US_ASCII); + return VaultConfig.decode(token); + } + +} From 19735dbf246cb327756300ad3901a1868b6ded15 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 18 Oct 2024 18:16:16 +0200 Subject: [PATCH 047/101] remove TODO --- src/main/java/org/cryptomator/cli/PasswordSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index a7b6140..13eb956 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -17,7 +17,6 @@ public class PasswordSource { public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class); private static final int MAX_PASSPHRASE_FILE_SIZE = 5_000; //5KB - //TODO: is the colon ":" a reserved char in Linux? @CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN", interactive = true) char[] passphraseStdin = null; From 338ddccac90640afdf45dac6d5ca9d419fb11890 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 23 Oct 2024 17:09:25 +0200 Subject: [PATCH 048/101] switch from maven shade to jpackage --- .github/workflows/build-binary.yml | 122 ++++++++++++++++++++++++++++ README.md | 49 +++++++---- build.ps1 | 52 ++++++++++++ pom.xml | 126 ++++++++++++++++++----------- 4 files changed, 286 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/build-binary.yml create mode 100644 build.ps1 diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml new file mode 100644 index 0000000..3d5366c --- /dev/null +++ b/.github/workflows/build-binary.yml @@ -0,0 +1,122 @@ +name: Build java app image + +on: + release: + types: [published] + workflow_dispatch: + inputs: + sem-version: + description: 'Version' + required: false + +env: + JAVA_DIST: 'zulu' + JAVA_VERSION: '22.0.2+9' + +defaults: + run: + shell: bash + +jobs: + prepare: + name: Determines the versions strings for the binaries + runs-on: [ubuntu-latest] + outputs: + semVerStr: ${{ steps.determine-version.outputs.version }} + semVerNum: ${{steps.determine-number.outputs.number}} + steps: + - id: determine-version + shell: pwsh + run: | + if ( ${{github.event_name}} -eq 'release') { + echo "version=${{ github.event.release.tag_name}}" >> $GITHUB_OUTPUT + } else if (${{inputs.sem-version}}) { + echo "version=${{ inputs.sem-version}}" >> $GITHUB_OUTPUT + } + Write-Error "Version neither via input nor by tag specified. Aborting" + exit 1 + - id: determine-number + run: | + SEM_VER_NUM=`echo ${{ steps.determine-version.outputs.version }} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + echo "number=${SEM_VER_NUM}" >> $GITHUB_OUTPUT + + build-win: + name: Build java app image on windows + needs: [prepare] + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + architecture: x64 + native-access-lib: 'org.cryptomator.jfuse.linux.amd64' + - os: [self-hosted, Linux, ARM64] + architecture: aarch64 + native-access-lib: 'org.cryptomator.jfuse.linux.aarch64' + - os: [self-hosted, macOS, ARM64] + architecture: aarch64 + native-access-lib: 'org.cryptomator.jfuse.mac' + - os: macos-latest + architecture: x64 + native-access-lib: 'org.cryptomator.jfuse.mac' + - os: windows-latest + architecture: x64 + native-access-lib: 'org.cryptomator.jfuse.win' + runs-on: ${{ matrix.os }} + steps: + - name: prepare windows + if: startsWith(matrix.os, 'windows') + run: echo "JPACKAGE_OS_OPTS=--win-console" >> "$GITHUB_ENV" + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '22' + distribution: 'temurin' + - name: Set version + run: mvn versions:set -DnewVersion=${{ needs.prepare.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pwin -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + shell: pwsh + run: > + & $env:JAVA_HOME\bin\jlink + --verbose + --output target\runtime + --module-path "${env:JAVA_HOME}\jmods" + --add-modules java.base,java.compiler,java.naming,java.xml ` + --strip-native-commands ` + --no-header-files ` + --no-man-pages ` + --strip-debug ` + --compress zip-6 + - name: Run jpackage + shell: pwsh + #TODO: app version + run: > + & $env:JAVA_HOME\bin\jpackage + --verbose + --type app-image + --runtime-image target/runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.cli/org.cryptomator.cli.CryptomatorCli + --dest target + --name cryptomator-cli + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2024 Skymatic GmbH" + --app-version "${{ needs.prepare.outputs.semVerNum }}" + --java-options "--enable-preview" + --java-options "--enable-native-access=${{ matrix.native-access-lib }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options '-Dfile.encoding="utf-8"' + ${JPACKAGE_OS_OPTS} + - uses: upload-artifact@v4 + with: + path: .\target\cryptomator-cli + if-no-files-found: error + diff --git a/README.md b/README.md index cff7a1f..ab5a757 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,49 @@ This is a minimal command-line application that unlocks a single vault of vault ## Download and Usage -Download the JAR file via [GitHub Releases](https://github.com/cryptomator/cli/releases). +Download the zip file via [GitHub Releases](https://github.com/cryptomator/cli/releases) and unzip it to your desired directory, e.g. -Cryptomator CLI requires that at least JDK/JRE 22 is present on your system. ```sh -java --enable-native-access="ALL-UNNAMED" -jar cryptomator-cli-x.y.z.jar \ - --password:stdin \ - --mounter=org.cryptomator.frontend.fuse.mount.FuseMountProvider \ - --mountPoint=/home/user/existing/empty/dir \ - /path/to/vault -# Be aware that passing the password on the command-line typically makes it visible to anyone on your system! +curl -L https://github.com/cryptomator/cli/releases/download/0.7.0/cryptomator-cli-0.7.0-mac-arm64.dmg --output cryptomator-cli.zip +unzip cryptomator-cli.zip ``` -For a complete list of options, start the jar with the `--help` argument. +Afterwards, you can directly run Cryptomator-CLI: +```sh +cryptomator-cli unlock \ +--password:stdin \ +--mounter=org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider \ +--mountPoint=/path/to/empty/dir \ +/home/user/myVault +``` + +For a complete list of options, use the`--help` option. ```shell -java --enable-native-access="ALL-UNNAMED" -jar cryptomator-cli-x.y.z.jar --help +cryptomator-cli unlock --help` ``` -## Block Filesystem Integration +## FileSystem Integration + +For an OS integration of your unlocked vault, cryptomator-cli relies on third party libraries which must be installed seperately. +These are: +* [WinFsp](https://winfsp.dev/) for Windows +* [macFUSE](https://osxfuse.github.io/) or [FUSE-T](https://www.fuse-t.org/) for macOS +* and [libfuse](https://github.com/libfuse/libfuse) for Linux/BSD systems (normally provided by a fuse3 package of your distro, e.g. [ubuntu](https://packages.ubuntu.com/noble/fuse3)) + +As a fallback, you can [skip filesystem integration](README.md#skip-filesystem-integration). + +## Selecting the Mounter + +TODO + +## Skip Filesystem Integration + +If you don't want a direct integration in the OS, choose `org.cryptomator.frontend.webdav.mount.FallbackMounter` for `--mounter`. +It starts a local WebDAV server, where you can access the vault with any WebDAV client or mounting it into your filesystem manually. -Depending on the chosen mounter, the vault is automatically integrated into the os. -If you don't want a direct integration, choose `org.cryptomator.frontend.webdav.mount.FallbackMounter` for `--mounter`. -It starts a local WebDAV server started, where you can access the vault by any WebDAV client or mounting it into your filesystem manually. +> [!NOTE] +> The WebDAV protocol is supported by all major OSses. Hence, if other mounters fail or show errors when accessing the vault content, you can always use the legacy WebDAV option. +> WebDAV is not the default, because it has a low performance and might have OS dependent restrictions (e.g. maximum file size of 4GB on Windows) ### Windows via Windows Explorer diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..3cd46ba --- /dev/null +++ b/build.ps1 @@ -0,0 +1,52 @@ +"Building cryptomator cli..." + +$commands = 'mvn' +$envVars = 'JAVA_HOME' +foreach ($cmd in $commands) { + Invoke-Expression -Command "${cmd} --version" -ErrorAction Stop +} + +(Get-ChildItem env:* | Where-Object { $envVars -contains $_.Name} | Measure-Object).Count + +<# +foreach ($envVar in $envVars) { + if( "$env:$envVar") +} +#> + +mvn -B clean package +Copy-Item .\LICENSE.txt -Destination .\target +Move-Item .\target\cryptomator-cli-*.jar .\target\mods +## according to jdpes we only need java.base (and java.compiler due to dagger) +& $env:JAVA_HOME\bin\jlink ` +--verbose ` +--output target\runtime ` +--module-path "${env:JAVA_HOME}\jmods" ` +--add-modules java.base,java.compiler,java.naming,java.xml ` +--strip-native-commands ` +--no-header-files ` +--no-man-pages ` +--strip-debug ` +--compress zip-0 + +# jpackage +& $env:JAVA_HOME\bin\jpackage ` + --verbose ` + --type app-image ` + --runtime-image target/runtime ` + --input target/libs ` + --module-path target/mods ` + --module org.cryptomator.cli/org.cryptomator.cli.CryptomatorCli ` + --dest target ` + --name cryptomator-cli ` + --vendor "Skymatic GmbH" ` + --copyright "(C) 2016 - 2024 Skymatic GmbH" ` + --app-version "1.0.0.0" ` + --java-options "--enable-preview" ` + --java-options "--enable-native-access=org.cryptomator.jfuse.win" ` + --java-options "-Xss5m" ` + --java-options "-Xmx256m" ` + --java-options '-Dfile.encoding="utf-8"' ` + --win-console + #--resource-dir dist/win/resources + #--icon dist/win/resources/Cryptomator.ico diff --git a/pom.xml b/pom.xml index 47bdc7a..2231b1f 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,12 @@ UTF-8 22 + + + org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents + + org.cryptomator.cli.CryptomatorCli -Ob @@ -106,6 +112,7 @@ + cryptomator-cli-${project.version} maven-compiler-plugin @@ -138,63 +145,84 @@ - - org.apache.maven.plugins - maven-shade-plugin - ${maven-shade.version} + + org.codehaus.mojo + exec-maven-plugin + ${maven-exec.version} + + + test-run + + exec + + + + + ${env.JAVA_HOME}/bin/java + + --module-path + + --enable-native-access=org.cryptomator.jfuse.linux,org.cryptomator.jfuse.win,org.cryptomator.jfuse.mac + --module + org.cryptomator.cli/${mainClass} + --password:stdin + --mounter=org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider + --mountPoint=X:\\ + T:\\vaultFormat8 + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy-mods + prepare-package + + copy-dependencies + + + runtime + ${project.build.directory}/mods + ${nonModularGroupIds} + + + + copy-libs + prepare-package + + copy-dependencies + + + runtime + ${project.build.directory}/libs + ${nonModularGroupIds} + + + + + From e33b1f0d9993519ae4bd7fd0b21f2147fc368a84 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 23 Oct 2024 17:22:07 +0200 Subject: [PATCH 049/101] fis binary workflow --- .github/workflows/build-binary.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 3d5366c..6e64685 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -115,7 +115,7 @@ jobs: --java-options "-Xmx256m" --java-options '-Dfile.encoding="utf-8"' ${JPACKAGE_OS_OPTS} - - uses: upload-artifact@v4 + - uses: actions/upload-artifact@v4 with: path: .\target\cryptomator-cli if-no-files-found: error From 36a7c7747d1c25d99472838839ab930311129150 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:19:40 +0200 Subject: [PATCH 050/101] make MountSetup error resistant by using enum constants directly --- .../java/org/cryptomator/cli/MountSetup.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/MountSetup.java b/src/main/java/org/cryptomator/cli/MountSetup.java index 7d96512..7c0e313 100644 --- a/src/main/java/org/cryptomator/cli/MountSetup.java +++ b/src/main/java/org/cryptomator/cli/MountSetup.java @@ -11,6 +11,8 @@ import java.util.*; import java.util.stream.Collectors; +import static org.cryptomator.integrations.mount.MountCapability.*; + public class MountSetup { private static final Logger LOG = LoggerFactory.getLogger(MountSetup.class); @@ -46,6 +48,7 @@ void setMountService(String value) { @CommandLine.Option(names = {"--loopbackHostName"}, description = "Name of the loopback address.") Optional loopbackHostName; + @CommandLine.Option(names = {"--loopbackPort"}, description = "Port used at the loopback address.") Optional loopbackPort; @@ -58,15 +61,15 @@ MountBuilder prepareMountBuilder(FileSystem fs) { case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); case LOOPBACK_PORT -> { loopbackPort.ifPresent(builder::setLoopbackPort); - specifiedMOPs.put("loopbackPort", false); + specifiedMOPs.put(LOOPBACK_PORT, false); } case LOOPBACK_HOST_NAME -> { loopbackHostName.ifPresent(builder::setLoopbackHostName); - specifiedMOPs.put("loopbackHostName", false); + specifiedMOPs.put(LOOPBACK_HOST_NAME, false); } //TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); case MOUNT_FLAGS -> { - specifiedMOPs.put("mountOptions", false); + specifiedMOPs.put(MOUNT_FLAGS, false); if (mountOptions.isEmpty()) { var defaultFlags = mountService.getDefaultMountFlags(); LOG.debug("Using default mount options {}", defaultFlags); @@ -80,31 +83,34 @@ MountBuilder prepareMountBuilder(FileSystem fs) { } case VOLUME_NAME -> { volumeName.ifPresent(builder::setVolumeName); - specifiedMOPs.put("volumeName", false); + specifiedMOPs.put(VOLUME_NAME, false); + } + default -> { + //NO-OP } } } - var ignoredMOPs = specifiedMOPs.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(",")); + var ignoredMOPs = specifiedMOPs.entrySet().stream().filter(Map.Entry::getValue).map(e -> e.getKey().name()).collect(Collectors.joining(",")); if(!ignoredMOPs.isEmpty()) { LOG.info("Ignoring unsupported options: {}", ignoredMOPs); } return builder; } - private Map listSpecifiedMountOptions() { - var map = new HashMap(); - loopbackPort.ifPresent(_ -> map.put("loopbackPort", true)); - loopbackHostName.ifPresent(_ -> map.put("loopbackHostName", true)); - volumeName.ifPresent(_ -> map.put("volumeName", true)); + private Map listSpecifiedMountOptions() { + var map = new HashMap(); + loopbackPort.ifPresent(_ -> map.put(LOOPBACK_PORT, true)); + loopbackHostName.ifPresent(_ -> map.put(LOOPBACK_HOST_NAME, true)); + volumeName.ifPresent(_ -> map.put(VOLUME_NAME, true)); if (!mountOptions.isEmpty()) { - map.put("mountOption", true); + map.put(MOUNT_FLAGS, true); } return map; } Mount mount(FileSystem fs) throws MountFailedException { - if (!mountService.hasCapability(MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH) && mountPoint.isEmpty()) { + if (!mountService.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH) && mountPoint.isEmpty()) { throw new CommandLine.ParameterException(spec.commandLine(), "The selected mounter %s requires a mount point. Use --mountPoint /path/to/mount/point to specify it.".formatted(mountService.displayName())); } var builder = prepareMountBuilder(fs); From 63644deea1dd6fb87a95ab0b22067430ceee078a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:20:20 +0200 Subject: [PATCH 051/101] fix build-binary workflow --- .github/workflows/build-binary.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 6e64685..b435c91 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -30,8 +30,10 @@ jobs: run: | if ( ${{github.event_name}} -eq 'release') { echo "version=${{ github.event.release.tag_name}}" >> $GITHUB_OUTPUT + exit 0 } else if (${{inputs.sem-version}}) { - echo "version=${{ inputs.sem-version}}" >> $GITHUB_OUTPUT + echo "version=${{ inputs.sem-version}}" >> $GITHUB_OUTPUT + exit 0 } Write-Error "Version neither via input nor by tag specified. Aborting" exit 1 @@ -40,8 +42,8 @@ jobs: SEM_VER_NUM=`echo ${{ steps.determine-version.outputs.version }} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` echo "number=${SEM_VER_NUM}" >> $GITHUB_OUTPUT - build-win: - name: Build java app image on windows + build-binary: + name: Build java app image needs: [prepare] strategy: fail-fast: false @@ -64,7 +66,7 @@ jobs: native-access-lib: 'org.cryptomator.jfuse.win' runs-on: ${{ matrix.os }} steps: - - name: prepare windows + - name: Preparations fpr windows runner if: startsWith(matrix.os, 'windows') run: echo "JPACKAGE_OS_OPTS=--win-console" >> "$GITHUB_ENV" - uses: actions/checkout@v4 @@ -95,7 +97,6 @@ jobs: --compress zip-6 - name: Run jpackage shell: pwsh - #TODO: app version run: > & $env:JAVA_HOME\bin\jpackage --verbose From c1ea6f22660cd2a3ff0dfda181f25836a9c04a44 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:22:39 +0200 Subject: [PATCH 052/101] use a constant for masterkey file name --- src/main/java/org/cryptomator/cli/Unlock.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index 97a1ba8..85e3585 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -26,6 +26,7 @@ public class Unlock implements Callable { private static final Logger LOG = LoggerFactory.getLogger(Unlock.class); private static final byte[] PEPPER = new byte[0]; private static final String CONFIG_FILE_NAME = "vault.cryptomator"; + private static final String MASTERKEY_FILE_NAME = "masterkey.cryptomator"; @Spec Model.CommandSpec spec; @@ -86,7 +87,7 @@ public Integer call() throws Exception { private Masterkey loadMasterkey(URI keyId) { try (var passphraseContainer = passwordSource.readPassphrase()) { - Path filePath = pathToVault.resolve("masterkey.cryptomator"); + Path filePath = pathToVault.resolve(MASTERKEY_FILE_NAME); return new MasterkeyFileAccess(PEPPER, csprng) .load(filePath, CharBuffer.wrap(passphraseContainer.content())); } catch (IOException e) { From 92e6db21e2d120e5a8d90fdf8cadb911ba3ff88e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:38:21 +0200 Subject: [PATCH 053/101] fix error in loggingMixin --- src/main/java/org/cryptomator/cli/LoggingMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cli/LoggingMixin.java b/src/main/java/org/cryptomator/cli/LoggingMixin.java index ab306b4..e0baa9e 100644 --- a/src/main/java/org/cryptomator/cli/LoggingMixin.java +++ b/src/main/java/org/cryptomator/cli/LoggingMixin.java @@ -23,6 +23,6 @@ public void setVerbose(boolean isVerbose) { // We want to store the verbosity value in a single, central place, // so we find the top-level command, // and store the verbosity level on our top-level command's LoggingMixin. - ((CryptomatorCli) mixee.root().userObject()).loggingMixin.isVerbose = true; + ((CryptomatorCli) mixee.root().userObject()).loggingMixin.isVerbose = isVerbose; } } From 087d358ce91f75e22a863a35b2e0db88045fc9db Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:45:59 +0200 Subject: [PATCH 054/101] read app version from system property --- .github/workflows/build-binary.yml | 1 + src/main/java/org/cryptomator/cli/CryptomatorCli.java | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index b435c91..5200b1d 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -110,6 +110,7 @@ jobs: --vendor "Skymatic GmbH" --copyright "(C) 2016 - 2024 Skymatic GmbH" --app-version "${{ needs.prepare.outputs.semVerNum }}" + --java-options "-Dorg.cryptomator.cli.version=${{ needs.prepare.outputs.semVerStr }}" --java-options "--enable-preview" --java-options "--enable-native-access=${{ matrix.native-access-lib }}" --java-options "-Xss5m" diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index dfc20be..6204276 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -10,12 +10,17 @@ @Command(name = "cryptomator-cli", mixinStandardHelpOptions = true, - version = "1.0.0", + version = "${org.cryptomator.cli.version}", description = "Unlocks a cryptomator vault and mounts it into the system.", subcommands = Unlock.class) public class CryptomatorCli { private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); + + static { + System.getProperties().putIfAbsent("org.cryptomator.cli.version", "SNAPSHOT"); + } + @Mixin LoggingMixin loggingMixin; From bb4dbbd06c599c5e4c57b91980db7c61c10a510b Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 17:50:43 +0200 Subject: [PATCH 055/101] adjust local build script --- build.ps1 | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/build.ps1 b/build.ps1 index 3cd46ba..4ab41b4 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,23 +1,20 @@ "Building cryptomator cli..." $commands = 'mvn' -$envVars = 'JAVA_HOME' foreach ($cmd in $commands) { Invoke-Expression -Command "${cmd} --version" -ErrorAction Stop } -(Get-ChildItem env:* | Where-Object { $envVars -contains $_.Name} | Measure-Object).Count - -<# -foreach ($envVar in $envVars) { - if( "$env:$envVar") +if(-not $env:JAVA_HOME) { + throw "Environment variable JAVA_HOME not defined" } -#> +Write-Host "Building java app with maven..." mvn -B clean package Copy-Item .\LICENSE.txt -Destination .\target Move-Item .\target\cryptomator-cli-*.jar .\target\mods -## according to jdpes we only need java.base (and java.compiler due to dagger) + +Write-Host "Creating JRE with jlink..." & $env:JAVA_HOME\bin\jlink ` --verbose ` --output target\runtime ` @@ -29,7 +26,13 @@ Move-Item .\target\cryptomator-cli-*.jar .\target\mods --strip-debug ` --compress zip-0 +if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path .\target\runtime))) { + throw "JRE creation with jLink failed with exit code $LASTEXITCODE." +} + # jpackage +# app-version is hard coded, since the script is only for local test builds +Write-Host "Creating app binary with jpackage..." & $env:JAVA_HOME\bin\jpackage ` --verbose ` --type app-image ` @@ -41,12 +44,15 @@ Move-Item .\target\cryptomator-cli-*.jar .\target\mods --name cryptomator-cli ` --vendor "Skymatic GmbH" ` --copyright "(C) 2016 - 2024 Skymatic GmbH" ` - --app-version "1.0.0.0" ` + --app-version "0.0.1.0" ` + --java-options "-Dorg.cryptomator.cli.version=0.0.1-local" ` --java-options "--enable-preview" ` --java-options "--enable-native-access=org.cryptomator.jfuse.win" ` --java-options "-Xss5m" ` --java-options "-Xmx256m" ` --java-options '-Dfile.encoding="utf-8"' ` --win-console - #--resource-dir dist/win/resources - #--icon dist/win/resources/Cryptomator.ico + +if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path .\target\cryptomator-cli))) { + throw "Binary creation with jpackage failed with exit code $LASTEXITCODE." +} \ No newline at end of file From 47cb71776577950a4da83b6e00a0226d1e4a7944 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Oct 2024 18:54:07 +0200 Subject: [PATCH 056/101] add command to list available mounters --- README.md | 16 +++++++---- .../org/cryptomator/cli/CryptomatorCli.java | 2 +- .../org/cryptomator/cli/ListMounters.java | 28 +++++++++++++++++++ src/main/java/org/cryptomator/cli/Unlock.java | 14 +++++++++- 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/cryptomator/cli/ListMounters.java diff --git a/README.md b/README.md index ab5a757..0ec5680 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ This is a minimal command-line application that unlocks a single vault of vault Download the zip file via [GitHub Releases](https://github.com/cryptomator/cli/releases) and unzip it to your desired directory, e.g. -```sh +```shell curl -L https://github.com/cryptomator/cli/releases/download/0.7.0/cryptomator-cli-0.7.0-mac-arm64.dmg --output cryptomator-cli.zip unzip cryptomator-cli.zip ``` Afterwards, you can directly run Cryptomator-CLI: -```sh +```shell cryptomator-cli unlock \ --password:stdin \ --mounter=org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider \ @@ -25,22 +25,26 @@ cryptomator-cli unlock \ For a complete list of options, use the`--help` option. ```shell -cryptomator-cli unlock --help` +cryptomator-cli --help` ``` ## FileSystem Integration -For an OS integration of your unlocked vault, cryptomator-cli relies on third party libraries which must be installed seperately. +To integrate the unlocked vault into the filesystem, cryptomator-cli relies on third party libraries which must be installed separately. These are: * [WinFsp](https://winfsp.dev/) for Windows * [macFUSE](https://osxfuse.github.io/) or [FUSE-T](https://www.fuse-t.org/) for macOS * and [libfuse](https://github.com/libfuse/libfuse) for Linux/BSD systems (normally provided by a fuse3 package of your distro, e.g. [ubuntu](https://packages.ubuntu.com/noble/fuse3)) -As a fallback, you can [skip filesystem integration](README.md#skip-filesystem-integration). +As a fallback, you can [skip filesystem integration](README.md#skip-filesystem-integration) by using WebDAV. ## Selecting the Mounter -TODO +To list all available mounters, use the `list-mounters` subcommand: +```shell +cryptomator-cli list-mounters +``` +Pick one from the printed list and use it as input for the `--mounter` option. ## Skip Filesystem Integration diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 6204276..971a1e8 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -12,7 +12,7 @@ mixinStandardHelpOptions = true, version = "${org.cryptomator.cli.version}", description = "Unlocks a cryptomator vault and mounts it into the system.", - subcommands = Unlock.class) + subcommands = { Unlock.class, ListMounters.class}) public class CryptomatorCli { private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); diff --git a/src/main/java/org/cryptomator/cli/ListMounters.java b/src/main/java/org/cryptomator/cli/ListMounters.java new file mode 100644 index 0000000..7f33bbf --- /dev/null +++ b/src/main/java/org/cryptomator/cli/ListMounters.java @@ -0,0 +1,28 @@ +package org.cryptomator.cli; + +import org.cryptomator.integrations.common.IntegrationsLoader; +import org.cryptomator.integrations.mount.MountService; +import picocli.CommandLine; + +import java.util.concurrent.Callable; + +@CommandLine.Command(name = "list-mounters", + headerHeading = "Usage:%n%n", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + optionListHeading = "%nOptions:%n", + header = "Lists available mounters", + description = "Prints a list of available mounters to STDIN. A mounter is is the object to mount/integrate the unlocked vault into the local filesystem. In the GUI app, mounter is named \"volume type\".", + mixinStandardHelpOptions = true) +public class ListMounters implements Callable { + + @CommandLine.Option(names = {"--withDisplayName"}, description = "Prints also the display name of each mounter, as used in the GUI app.") + boolean withDisplayName = false; + + @Override + public Integer call() throws Exception { + IntegrationsLoader.loadAll(MountService.class) + .forEach(s -> System.out.println(s.getClass().getName() + (withDisplayName? " | " + s.displayName():""))); + return 0; + } +} diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index 85e3585..070bfc5 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -20,7 +20,18 @@ import java.security.SecureRandom; import java.util.concurrent.Callable; -@Command(name = "unlock") +@Command(name = "unlock", + header = "Unlocks a vault", + description = "Unlocks and mounts the given cryptomator vault, identified by the path to the vault directory." + + "The unlocked vault is mounted into the local filesystem by the specified mounter." + + "For a list of available mounters, use the `list-mounters` subcommand.", + parameterListHeading = "%nParameters:%n", + headerHeading = "Usage:%n%n", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + optionListHeading = "%nOptions:%n", + + mixinStandardHelpOptions = true) public class Unlock implements Callable { private static final Logger LOG = LoggerFactory.getLogger(Unlock.class); @@ -51,6 +62,7 @@ void setMaxCleartextNameLength(int input) { } maxCleartextNameLength = input; } + private int maxCleartextNameLength = 0; private SecureRandom csprng = null; From 0b378b7470375ac762711d7628dc69dd7c0c1587 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 5 Nov 2024 16:43:38 +0100 Subject: [PATCH 057/101] add shell script for unix builds --- build.ps1 | 16 ++++++------ build.sh | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 build.sh diff --git a/build.ps1 b/build.ps1 index 4ab41b4..b32fa63 100644 --- a/build.ps1 +++ b/build.ps1 @@ -11,14 +11,14 @@ if(-not $env:JAVA_HOME) { Write-Host "Building java app with maven..." mvn -B clean package -Copy-Item .\LICENSE.txt -Destination .\target -Move-Item .\target\cryptomator-cli-*.jar .\target\mods +Copy-Item ./LICENSE.txt -Destination ./target +Move-Item ./target/cryptomator-cli-*.jar ./target/mods Write-Host "Creating JRE with jlink..." -& $env:JAVA_HOME\bin\jlink ` +& $env:JAVA_HOME/bin/jlink ` --verbose ` ---output target\runtime ` ---module-path "${env:JAVA_HOME}\jmods" ` +--output target/runtime ` +--module-path "${env:JAVA_HOME}/jmods" ` --add-modules java.base,java.compiler,java.naming,java.xml ` --strip-native-commands ` --no-header-files ` @@ -26,14 +26,14 @@ Write-Host "Creating JRE with jlink..." --strip-debug ` --compress zip-0 -if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path .\target\runtime))) { +if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path ./target/runtime))) { throw "JRE creation with jLink failed with exit code $LASTEXITCODE." } # jpackage # app-version is hard coded, since the script is only for local test builds Write-Host "Creating app binary with jpackage..." -& $env:JAVA_HOME\bin\jpackage ` +& $env:JAVA_HOME/bin/jpackage ` --verbose ` --type app-image ` --runtime-image target/runtime ` @@ -53,6 +53,6 @@ Write-Host "Creating app binary with jpackage..." --java-options '-Dfile.encoding="utf-8"' ` --win-console -if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path .\target\cryptomator-cli))) { +if ( ($LASTEXITCODE -ne 0) -or (-not (Test-Path ./target/cryptomator-cli))) { throw "Binary creation with jpackage failed with exit code $LASTEXITCODE." } \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..d6ea819 --- /dev/null +++ b/build.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +echo "Building cryptomator cli..." + +# Check if Maven is installed +if ! command -v mvn &> /dev/null; then + echo "Maven is not installed. Please install Maven to proceed." + exit 1 +fi + +# Check if JAVA_HOME is set +if [ -z "$JAVA_HOME" ]; then + echo "Environment variable JAVA_HOME not defined" + exit 1 +fi + +echo "Building java app with maven..." +mvn -B clean package +cp ./LICENSE.txt ./target/ +mv ./target/cryptomator-cli-*.jar ./target/mods + +echo "Creating JRE with jlink..." +"$JAVA_HOME/bin/jlink" \ + --verbose \ + --output target/runtime \ + --module-path "${JAVA_HOME}/jmods" \ + --add-modules java.base,java.compiler,java.naming,java.xml \ + --strip-native-commands \ + --no-header-files \ + --no-man-pages \ + --strip-debug \ + --compress zip-0 + +if [ $? -ne 0 ] || [ ! -d ./target/runtime ]; then + echo "JRE creation with jlink failed." + exit 1 +fi + +NATIVE_ACCESS_PACKAGE="org.example" +_OS=$(uname -s) +if [ ! -z $(echo "$_OS" | grep Linux*) ]; then + _ARCH=$(uname -m) + if [ "$_ARCH" = "x86_64" ]; then + NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.amd64" + elif [ "$_ARCH" = "aarch64" ]; then + NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.aarch64" + fi +elif [ ! -z $(echo "$_OS" | grep Darwin*) ]; then + NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.darwin" +fi + +echo "Creating app binary with jpackage..." +"$JAVA_HOME/bin/jpackage" \ + --verbose \ + --type app-image \ + --runtime-image target/runtime \ + --input target/libs \ + --module-path target/mods \ + --module org.cryptomator.cli/org.cryptomator.cli.CryptomatorCli \ + --dest target \ + --name cryptomator-cli \ + --vendor "Skymatic GmbH" \ + --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --app-version "0.0.1.0" \ + --java-options "-Dorg.cryptomator.cli.version=0.0.1-local" \ + --java-options "--enable-preview" \ + --java-options "--enable-native-access=${NATIVE_ACCESS_PACKAGE}" \ + --java-options "-Xss5m" \ + --java-options "-Xmx256m" \ + --java-options "-Dfile.encoding=utf-8" \ + +if [ $? -ne 0 ] || [ ! -d ./target/cryptomator-cli ]; then + echo "Binary creation with jpackage failed." + exit 1 +fi From 480b1a9d9ac0c98877bdc90e6ab7fc735ddc8125 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 5 Nov 2024 17:26:01 +0100 Subject: [PATCH 058/101] use shutdown hook instead of waiting for input --- src/main/java/org/cryptomator/cli/Unlock.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index 070bfc5..6ab38ba 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -5,6 +5,7 @@ import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.integrations.mount.Mount; import org.cryptomator.integrations.mount.UnmountFailedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,21 +83,22 @@ public Integer call() throws Exception { try (var fs = CryptoFileSystemProvider.newFileSystem(pathToVault, fsPropsBuilder.build()); var mount = mountSetup.mount(fs)) { System.out.println(mount.getMountpoint().uri()); - while (true) { - int c = System.in.read(); - //TODO: Password piping is currently not supported due to read() returing -1 - if (c == -1 || c == 0x03 || c == 0x04) {//Ctrl+C, Ctrl+D - LOG.info("Unmounting and locking vault..."); - mount.unmount(); - break; - } - } + Runtime.getRuntime().addShutdownHook(new Thread(() -> teardown(mount))); + Thread.currentThread().join(); } catch (UnmountFailedException e) { LOG.error("Regular unmount failed. Just terminating process...", e); } return 0; } + private void teardown(Mount m) { + try { + m.close(); + } catch (IOException | UnmountFailedException e) { + LOG.error("Graceful unmount failed, possible cleanup not executed.", e); + } + } + private Masterkey loadMasterkey(URI keyId) { try (var passphraseContainer = passwordSource.readPassphrase()) { Path filePath = pathToVault.resolve(MASTERKEY_FILE_NAME); From 506b4add094235c8040853c268721ce08b5df60c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 5 Nov 2024 17:49:42 +0100 Subject: [PATCH 059/101] make build script more robust [ci skip] --- build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index d6ea819..4926441 100644 --- a/build.sh +++ b/build.sh @@ -36,16 +36,16 @@ if [ $? -ne 0 ] || [ ! -d ./target/runtime ]; then exit 1 fi -NATIVE_ACCESS_PACKAGE="org.example" +NATIVE_ACCESS_PACKAGE="no.native.access.needed" _OS=$(uname -s) -if [ ! -z $(echo "$_OS" | grep Linux*) ]; then +if (echo "$_OS" | grep -q "Linux.*") ; then _ARCH=$(uname -m) if [ "$_ARCH" = "x86_64" ]; then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.amd64" elif [ "$_ARCH" = "aarch64" ]; then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.aarch64" fi -elif [ ! -z $(echo "$_OS" | grep Darwin*) ]; then +elif (echo "$_OS" | grep -q "Darwin.*"); then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.darwin" fi From 2e5b27141882274e60791698bd012ce0d63ab505 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 5 Nov 2024 17:56:59 +0100 Subject: [PATCH 060/101] remove unnecessary uses expression in module-info --- src/main/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0231ed6..734046a 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,7 +2,6 @@ import org.cryptomator.cli.LogbackConfigurator; open module org.cryptomator.cli { - uses Configurator; requires org.cryptomator.cryptofs; requires org.cryptomator.frontend.fuse; requires info.picocli; From d59fecaa814e58c0ae0618a3d6d6c2e2da326e6c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 6 Nov 2024 11:27:49 +0100 Subject: [PATCH 061/101] use os dependent build scripts [ci skip] --- build.sh => build_linux.sh | 4 +-- build_mac.sh | 62 ++++++++++++++++++++++++++++++++++++++ build.ps1 => build_win.ps1 | 0 3 files changed, 63 insertions(+), 3 deletions(-) rename build.sh => build_linux.sh (93%) create mode 100644 build_mac.sh rename build.ps1 => build_win.ps1 (100%) diff --git a/build.sh b/build_linux.sh similarity index 93% rename from build.sh rename to build_linux.sh index 4926441..424f534 100644 --- a/build.sh +++ b/build_linux.sh @@ -36,7 +36,7 @@ if [ $? -ne 0 ] || [ ! -d ./target/runtime ]; then exit 1 fi -NATIVE_ACCESS_PACKAGE="no.native.access.needed" +NATIVE_ACCESS_PACKAGE="no.native.access.available" _OS=$(uname -s) if (echo "$_OS" | grep -q "Linux.*") ; then _ARCH=$(uname -m) @@ -45,8 +45,6 @@ if (echo "$_OS" | grep -q "Linux.*") ; then elif [ "$_ARCH" = "aarch64" ]; then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.aarch64" fi -elif (echo "$_OS" | grep -q "Darwin.*"); then - NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.darwin" fi echo "Creating app binary with jpackage..." diff --git a/build_mac.sh b/build_mac.sh new file mode 100644 index 0000000..f08682c --- /dev/null +++ b/build_mac.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +echo "Building cryptomator cli..." + +# Check if Maven is installed +if ! command -v mvn &> /dev/null; then + echo "Maven is not installed. Please install Maven to proceed." + exit 1 +fi + +# Check if JAVA_HOME is set +if [ -z "$JAVA_HOME" ]; then + echo "Environment variable JAVA_HOME not defined" + exit 1 +fi + +echo "Building java app with maven..." +mvn -B clean package +cp ./LICENSE.txt ./target/ +mv ./target/cryptomator-cli-*.jar ./target/mods + +echo "Creating JRE with jlink..." +"$JAVA_HOME/bin/jlink" \ + --verbose \ + --output target/runtime \ + --module-path "${JAVA_HOME}/jmods" \ + --add-modules java.base,java.compiler,java.naming,java.xml \ + --strip-native-commands \ + --no-header-files \ + --no-man-pages \ + --strip-debug \ + --compress zip-0 + +if [ $? -ne 0 ] || [ ! -d ./target/runtime ]; then + echo "JRE creation with jlink failed." + exit 1 +fi + +echo "Creating app binary with jpackage..." +"$JAVA_HOME/bin/jpackage" \ + --verbose \ + --type app-image \ + --runtime-image target/runtime \ + --input target/libs \ + --module-path target/mods \ + --module org.cryptomator.cli/org.cryptomator.cli.CryptomatorCli \ + --dest target \ + --name cryptomator-cli \ + --vendor "Skymatic GmbH" \ + --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --app-version "99.9.9" \ + --java-options "-Dorg.cryptomator.cli.version=0.0.1-local" \ + --java-options "--enable-preview" \ + --java-options "--enable-native-access=org.cryptomator.jfuse.mac" \ + --java-options "-Xss5m" \ + --java-options "-Xmx256m" \ + --java-options "-Dfile.encoding=utf-8" \ + +if [ $? -ne 0 ] || [ ! -d ./target/cryptomator-cli.app ]; then + echo "Binary creation with jpackage failed." + exit 1 +fi diff --git a/build.ps1 b/build_win.ps1 similarity index 100% rename from build.ps1 rename to build_win.ps1 From ef40c6b88210d96256d0205da407b28310a6f1b7 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 6 Nov 2024 12:09:41 +0100 Subject: [PATCH 062/101] Update README [ci skip] --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ec5680..a834fbd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is a minimal command-line application that unlocks a single vault of vault Download the zip file via [GitHub Releases](https://github.com/cryptomator/cli/releases) and unzip it to your desired directory, e.g. ```shell -curl -L https://github.com/cryptomator/cli/releases/download/0.7.0/cryptomator-cli-0.7.0-mac-arm64.dmg --output cryptomator-cli.zip +curl -L https://github.com/cryptomator/cli/releases/download/0.7.0/cryptomator-cli-0.7.0-mac-arm64.zip --output cryptomator-cli.zip unzip cryptomator-cli.zip ``` @@ -23,6 +23,8 @@ cryptomator-cli unlock \ /home/user/myVault ``` +To unmount, send a SIGTERM signal to the process, e.g. by pressing CTRL+C (macOS: CMD+C) in the terminal. + For a complete list of options, use the`--help` option. ```shell cryptomator-cli --help` From 18c74ab08eec23b1cc603cd4d74494d9995d1243 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 6 Nov 2024 16:32:24 +0100 Subject: [PATCH 063/101] Add section about possible cleanup tasks. --- README.md | 12 ++++++++++++ src/main/java/org/cryptomator/cli/Unlock.java | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a834fbd..db1d865 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,18 @@ Mount the vault with: osascript -e 'mount volume "http://localhost:8080/demoVault/"' ``` +## Manual Cleanup + +If a handle to a resource inside the unlocked vault is still open, a graceful unmount is not possible and cryptomator-cli just terminates without executing possible cleanup tasks. +In that case the message "GRACEFUL UNMOUNT FAILED" is printed to the console/stdout. + +On a linux OS with the `LinuxFuseMountProvider`, the manual cleanup task is to unmount and free the mountpoint: +``` +fusermount -u /path/to/former/mountpoint +``` + +For other OSs, there is no cleanup necessary. + ## License This project is dual-licensed under the AGPLv3 for FOSS projects as well as a commercial license derived from the LGPL for independent software vendors and resellers. If you want to use this library in applications, that are *not* licensed under the AGPL, feel free to contact our [support team](https://cryptomator.org/help/). diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index 6ab38ba..f769123 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -39,6 +39,7 @@ public class Unlock implements Callable { private static final byte[] PEPPER = new byte[0]; private static final String CONFIG_FILE_NAME = "vault.cryptomator"; private static final String MASTERKEY_FILE_NAME = "masterkey.cryptomator"; + private static final String FORCED_UNMOUNT_MSG = "GRACEFUL UNMOUNT FAILED. Please check if manual cleanups are necessary"; @Spec Model.CommandSpec spec; @@ -86,7 +87,7 @@ public Integer call() throws Exception { Runtime.getRuntime().addShutdownHook(new Thread(() -> teardown(mount))); Thread.currentThread().join(); } catch (UnmountFailedException e) { - LOG.error("Regular unmount failed. Just terminating process...", e); + LOG.error(FORCED_UNMOUNT_MSG, e); } return 0; } @@ -95,7 +96,7 @@ private void teardown(Mount m) { try { m.close(); } catch (IOException | UnmountFailedException e) { - LOG.error("Graceful unmount failed, possible cleanup not executed.", e); + LOG.error(FORCED_UNMOUNT_MSG, e); } } From da44681db80d9a24e0ca0cb40c194aba7dece855 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 17:07:07 +0100 Subject: [PATCH 064/101] use correct version number --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2231b1f..fdae914 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cli - 1.0.0-SNAPSHOT + 0.6.0-SNAPSHOT Cryptomator CLI Command line program to access encrypted files via WebDAV. https://github.com/cryptomator/cli From fc3425476b1c47852790dc77d7433fabddb222f6 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 18:33:40 +0100 Subject: [PATCH 065/101] improve error handling when loading masterkey --- src/main/java/org/cryptomator/cli/Unlock.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index f769123..06eb8ee 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -4,6 +4,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.integrations.mount.Mount; import org.cryptomator.integrations.mount.UnmountFailedException; @@ -101,12 +102,13 @@ private void teardown(Mount m) { } private Masterkey loadMasterkey(URI keyId) { + Path filePath = pathToVault.resolve(MASTERKEY_FILE_NAME); try (var passphraseContainer = passwordSource.readPassphrase()) { - Path filePath = pathToVault.resolve(MASTERKEY_FILE_NAME); return new MasterkeyFileAccess(PEPPER, csprng) .load(filePath, CharBuffer.wrap(passphraseContainer.content())); } catch (IOException e) { - throw new RuntimeException(e); + LOG.error("Reading {} failed.", filePath, e); + throw new MasterkeyLoadingFailedException("Unable to load key from file " + filePath + ": " + e.getMessage()); } } From 19409ab0aeca1a3d1d251aa9f39ed2450ba00dfe Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 19:19:24 +0100 Subject: [PATCH 066/101] handle correctly unexpected regular app shutdown. --- src/main/java/org/cryptomator/cli/Unlock.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/Unlock.java b/src/main/java/org/cryptomator/cli/Unlock.java index 06eb8ee..f569ec6 100644 --- a/src/main/java/org/cryptomator/cli/Unlock.java +++ b/src/main/java/org/cryptomator/cli/Unlock.java @@ -87,10 +87,9 @@ public Integer call() throws Exception { System.out.println(mount.getMountpoint().uri()); Runtime.getRuntime().addShutdownHook(new Thread(() -> teardown(mount))); Thread.currentThread().join(); - } catch (UnmountFailedException e) { - LOG.error(FORCED_UNMOUNT_MSG, e); } - return 0; + + throw new IllegalStateException("Application exited without error or receiving shutdown signal."); } private void teardown(Mount m) { From 2ab1e191e7a8f806fe8c08cd2c3918dbf37071c5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 19:50:12 +0100 Subject: [PATCH 067/101] follow bash standards --- .github/workflows/build-binary.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 5200b1d..5ee5018 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -29,18 +29,18 @@ jobs: shell: pwsh run: | if ( ${{github.event_name}} -eq 'release') { - echo "version=${{ github.event.release.tag_name}}" >> $GITHUB_OUTPUT + echo "version=${{ github.event.release.tag_name}}" >> "$GITHUB_OUTPUT" exit 0 } else if (${{inputs.sem-version}}) { - echo "version=${{ inputs.sem-version}}" >> $GITHUB_OUTPUT + echo "version=${{ inputs.sem-version}}" >> "$GITHUB_OUTPUT" exit 0 } Write-Error "Version neither via input nor by tag specified. Aborting" exit 1 - id: determine-number run: | - SEM_VER_NUM=`echo ${{ steps.determine-version.outputs.version }} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` - echo "number=${SEM_VER_NUM}" >> $GITHUB_OUTPUT + SEM_VER_NUM=$(echo "${{ steps.determine-version.outputs.version }}" | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/') + echo "number=${SEM_VER_NUM}" >> "$GITHUB_OUTPUT" build-binary: name: Build java app image From 99f38a71a428ce1a6755e9449b1001908f6bfa7d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 19:52:00 +0100 Subject: [PATCH 068/101] fix wrong arch variable and use github built in macos 64 runner --- .github/workflows/build-binary.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 5ee5018..539bfcb 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -55,11 +55,11 @@ jobs: - os: [self-hosted, Linux, ARM64] architecture: aarch64 native-access-lib: 'org.cryptomator.jfuse.linux.aarch64' - - os: [self-hosted, macOS, ARM64] - architecture: aarch64 + - os: macos-latest-large + architecture: x64 native-access-lib: 'org.cryptomator.jfuse.mac' - os: macos-latest - architecture: x64 + architecture: aarch64 native-access-lib: 'org.cryptomator.jfuse.mac' - os: windows-latest architecture: x64 From eb812cc43688789d44bfbc55ce9d967054d927e0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 8 Nov 2024 19:53:16 +0100 Subject: [PATCH 069/101] upload release artifacts to release page and add gpg signature --- .github/workflows/build-binary.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 539bfcb..32b0bb7 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -52,18 +52,23 @@ jobs: - os: ubuntu-latest architecture: x64 native-access-lib: 'org.cryptomator.jfuse.linux.amd64' + binary-dir-suffix: "" - os: [self-hosted, Linux, ARM64] architecture: aarch64 native-access-lib: 'org.cryptomator.jfuse.linux.aarch64' + binary-dir-suffix: "" - os: macos-latest-large architecture: x64 native-access-lib: 'org.cryptomator.jfuse.mac' + binary-dir-suffix: ".app" - os: macos-latest architecture: aarch64 native-access-lib: 'org.cryptomator.jfuse.mac' + binary-dir-suffix: ".app" - os: windows-latest architecture: x64 native-access-lib: 'org.cryptomator.jfuse.win' + binary-dir-suffix: "" runs-on: ${{ matrix.os }} steps: - name: Preparations fpr windows runner @@ -119,6 +124,26 @@ jobs: ${JPACKAGE_OS_OPTS} - uses: actions/upload-artifact@v4 with: - path: .\target\cryptomator-cli + path: .\target\cryptomator-cli${{ matrix.binary-dir-suffix }} if-no-files-found: error + - name: Zip binary for release + if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' + shell: pwsh + run: Compress-Archive -Path .\target\cryptomator-cli${{ matrix.binary-dir-suffix }} -DestinationPath .\cryptomator-cli-${{ needs.prepare.outputs.semVerStr }}.zip + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a .\cryptomator-cli-${{ needs.prepare.outputs.semVerStr }}.zip + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Publish artefact on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' + uses: softprops/action-gh-release@v2 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + cryptomator-cli-${{ needs.prepare.outputs.semVerStr }}.zip + cryptomator-cli-*.asc From 206072979954eeddfbb7b0527d5aa51a12704268 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 18 Nov 2024 13:58:06 +0100 Subject: [PATCH 070/101] make local build scripts more robust --- build_linux.sh | 12 ++++++++++++ build_mac.sh | 8 ++++++++ build_win.ps1 | 13 +++++++++++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/build_linux.sh b/build_linux.sh index 424f534..dc65472 100644 --- a/build_linux.sh +++ b/build_linux.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -euxo pipefail echo "Building cryptomator cli..." @@ -14,6 +15,14 @@ if [ -z "$JAVA_HOME" ]; then exit 1 fi +# Check Java version +MIN_JAVA_VERSION=$(mvn help:evaluate "-Dexpression=jdk.version" -q -DforceStdout):w +JAVA_VERSION=$("$JAVA_HOME/bin/java" -version | head -n1 | cut -d' ' -f2 | cut -d'.' -f1) +if [ "$JAVA_VERSION" -lt "$MIN_JAVA_VERSION" ]; then + echo "Java version $JAVA_VERSION is too old. Minimum required version is $MIN_JAVA_VERSION" + exit 1 +fi + echo "Building java app with maven..." mvn -B clean package cp ./LICENSE.txt ./target/ @@ -44,6 +53,9 @@ if (echo "$_OS" | grep -q "Linux.*") ; then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.amd64" elif [ "$_ARCH" = "aarch64" ]; then NATIVE_ACCESS_PACKAGE="org.cryptomator.jfuse.linux.aarch64" + else + echo "Warning: Unsupported Linux architecture for FUSE mounter: $_ARCH" + echo "FUSE supported architectures: x86_64, aarch64" fi fi diff --git a/build_mac.sh b/build_mac.sh index f08682c..7d8350b 100644 --- a/build_mac.sh +++ b/build_mac.sh @@ -14,6 +14,14 @@ if [ -z "$JAVA_HOME" ]; then exit 1 fi +# Check Java version +MIN_JAVA_VERSION=$(mvn help:evaluate "-Dexpression=jdk.version" -q -DforceStdout) +JAVA_VERSION=$("$JAVA_HOME/bin/java" -version | head -n1 | cut -d' ' -f2 | cut -d'.' -f1) +if [ "$JAVA_VERSION" -lt "$MIN_JAVA_VERSION" ]; then + echo "Java version $JAVA_VERSION is too old. Minimum required version is $MIN_JAVA_VERSION" + exit 1 +fi + echo "Building java app with maven..." mvn -B clean package cp ./LICENSE.txt ./target/ diff --git a/build_win.ps1 b/build_win.ps1 index b32fa63..3cafd14 100644 --- a/build_win.ps1 +++ b/build_win.ps1 @@ -1,18 +1,27 @@ "Building cryptomator cli..." +# Check if maven is installed $commands = 'mvn' foreach ($cmd in $commands) { Invoke-Expression -Command "${cmd} --version" -ErrorAction Stop } +# Check if JAVA_HOME is set if(-not $env:JAVA_HOME) { throw "Environment variable JAVA_HOME not defined" } +# Check Java version +$minJavaVersion=$(mvn help:evaluate "-Dexpression=jdk.version" -q -DforceStdout) +$javaVersion = $(& "$env:JAVA_HOME\bin\java" --version) -split ' ' | Select-Object -Index 1 +if( ($javaVersion -split '.' | Select-Object -First 1) -ne "22") { + throw "Java version $javaVersion is too old. Minimum required version is $minJavaVersion" +} + Write-Host "Building java app with maven..." mvn -B clean package -Copy-Item ./LICENSE.txt -Destination ./target -Move-Item ./target/cryptomator-cli-*.jar ./target/mods +Copy-Item ./LICENSE.txt -Destination ./target -ErrorAction Stop +Move-Item ./target/cryptomator-cli-*.jar ./target/mods -ErrorAction Stop Write-Host "Creating JRE with jlink..." & $env:JAVA_HOME/bin/jlink ` From ded3f90b1c19c736642144d25b18360ca6a0ccc8 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 18 Nov 2024 15:50:45 +0100 Subject: [PATCH 071/101] correct build scripts --- .github/workflows/build-binary.yml | 10 +++++++--- build_linux.sh | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 32b0bb7..9e72681 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -9,6 +9,10 @@ on: description: 'Version' required: false +permissions: + contents: write + packages: write + env: JAVA_DIST: 'zulu' JAVA_VERSION: '22.0.2+9' @@ -78,7 +82,7 @@ jobs: - uses: actions/setup-java@v4 with: java-version: '22' - distribution: 'temurin' + distribution: 'zulu' - name: Set version run: mvn versions:set -DnewVersion=${{ needs.prepare.outputs.semVerStr }} - name: Run maven @@ -124,7 +128,7 @@ jobs: ${JPACKAGE_OS_OPTS} - uses: actions/upload-artifact@v4 with: - path: .\target\cryptomator-cli${{ matrix.binary-dir-suffix }} + path: ./target/cryptomator-cli${{ matrix.binary-dir-suffix }} if-no-files-found: error - name: Zip binary for release if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' @@ -133,7 +137,7 @@ jobs: - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a .\cryptomator-cli-${{ needs.prepare.outputs.semVerStr }}.zip + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ./cryptomator-cli-${{ needs.prepare.outputs.semVerStr }}.zip env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} diff --git a/build_linux.sh b/build_linux.sh index dc65472..da5dc5f 100644 --- a/build_linux.sh +++ b/build_linux.sh @@ -16,7 +16,7 @@ if [ -z "$JAVA_HOME" ]; then fi # Check Java version -MIN_JAVA_VERSION=$(mvn help:evaluate "-Dexpression=jdk.version" -q -DforceStdout):w +MIN_JAVA_VERSION=$(mvn help:evaluate "-Dexpression=jdk.version" -q -DforceStdout) JAVA_VERSION=$("$JAVA_HOME/bin/java" -version | head -n1 | cut -d' ' -f2 | cut -d'.' -f1) if [ "$JAVA_VERSION" -lt "$MIN_JAVA_VERSION" ]; then echo "Java version $JAVA_VERSION is too old. Minimum required version is $MIN_JAVA_VERSION" From 60e53aef1d1e0395f55950d85597cb550150a8d0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 18 Nov 2024 16:12:35 +0100 Subject: [PATCH 072/101] strip new line from end of password file --- src/main/java/org/cryptomator/cli/PasswordSource.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java index 13eb956..d0a6784 100644 --- a/src/main/java/org/cryptomator/cli/PasswordSource.java +++ b/src/main/java/org/cryptomator/cli/PasswordSource.java @@ -23,7 +23,7 @@ public class PasswordSource { @CommandLine.Option(names = "--password:env", description = "Name of the environment variable containing the passphrase") String passphraseEnvironmentVariable = null; - @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase. The password file must be utf-8 encoded and must not end with a new line") + @CommandLine.Option(names = "--password:file", description = "Path of the file containing the passphrase. The password file must be utf-8 encoded") Path passphraseFile = null; Passphrase readPassphrase() throws IOException { @@ -58,7 +58,12 @@ private Passphrase readPassphraseFromFile() throws ReadingFileFailedException { } fileContent = Files.readAllBytes(passphraseFile); charWrapper = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(fileContent)); - char[] content = new char[charWrapper.limit()]; + //strips newline, since most files on linux end with a new line + var length = charWrapper.limit(); + if(charWrapper.get(length) == '\n') { + length--; + } + char[] content = new char[length]; charWrapper.get(content); return new Passphrase(content); } catch (IOException e) { From c35d31777f41bec9a62b8bbf0dce9aa5ce8e5452 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 18 Nov 2024 16:12:57 +0100 Subject: [PATCH 073/101] format pom --- pom.xml | 92 +++++++++++++++++++++------------------------------------ 1 file changed, 33 insertions(+), 59 deletions(-) diff --git a/pom.xml b/pom.xml index fdae914..88e0b19 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 org.cryptomator cli @@ -145,64 +146,37 @@ - - org.codehaus.mojo - exec-maven-plugin - ${maven-exec.version} - - - test-run - - exec - - - - - ${env.JAVA_HOME}/bin/java - - --module-path - - --enable-native-access=org.cryptomator.jfuse.linux,org.cryptomator.jfuse.win,org.cryptomator.jfuse.mac - --module - org.cryptomator.cli/${mainClass} - --password:stdin - --mounter=org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider - --mountPoint=X:\\ - T:\\vaultFormat8 - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - - copy-mods - prepare-package - - copy-dependencies - - - runtime - ${project.build.directory}/mods - ${nonModularGroupIds} - - - - copy-libs - prepare-package - - copy-dependencies - - - runtime - ${project.build.directory}/libs - ${nonModularGroupIds} - - - - + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy-mods + prepare-package + + copy-dependencies + + + runtime + ${project.build.directory}/mods + ${nonModularGroupIds} + + + + copy-libs + prepare-package + + copy-dependencies + + + runtime + ${project.build.directory}/libs + ${nonModularGroupIds} + + + + org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents - - - org.cryptomator.cli.CryptomatorCli - -Ob - 2.7.0-beta1 - 2.0.6 - 5.0.0 - 1.5.6 + 2.7.0 + 2.0.7 + 5.0.2 + 1.5.12 2.0.16 From 8e18943ab0d9892997af835868560abc6c46877e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 27 Nov 2024 11:31:01 +0100 Subject: [PATCH 087/101] fixes #52 --- .github/workflows/build-linux.yml | 5 ++++- .github/workflows/build-mac.yml | 3 ++- .github/workflows/build-win.yml | 4 ++++ pom.xml | 27 +++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index ca85d8e..c4fd697 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -74,7 +74,6 @@ jobs: run: mvn -B clean package -DskipTests - name: Patch target dir run: | - cp LICENSE.txt target cp target/cryptomator-*.jar target/mods - name: Run jlink run: | @@ -88,6 +87,10 @@ jobs: JP_APP_VERSION: ${{ needs.prepare.outputs.semVerNum }} APP_VERSION: ${{ needs.prepare.outputs.semVerStr }} NATIVE_ACCESS_PACKAGE: ${{ matrix.native-access-lib }} + - name: Update app dir + run: | + cp LICENSE.txt target/cryptomator-cli + cp target/cryptomator-cli_completion.sh target/cryptomator-cli - uses: actions/upload-artifact@v4 with: name: cryptomator-cli-linux-${{ matrix.architecture }} diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index 1aa2825..1f76db3 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -84,7 +84,6 @@ jobs: run: mvn -B clean package -DskipTests - name: Patch target dir run: | - cp LICENSE.txt target cp target/cryptomator-*.jar target/mods - name: Run jlink run: | @@ -100,6 +99,8 @@ jobs: NATIVE_ACCESS_PACKAGE: org.cryptomator.jfuse.mac - name: Patch .app dir run: | + cp LICENSE.txt target/cryptomator-cli.app + cp target/cryptomator-cli_completion.sh target/cryptomator-cli.app sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" cryptomator-cli.app/Contents/Info.plist sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" cryptomator-cli.app/Contents/Info.plist echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode -o "cryptomator-cli.app/Contents/embedded.provisionprofile" diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index febbdfe..e6fa55f 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -78,6 +78,10 @@ jobs: JP_APP_VERSION: ${{ needs.prepare.outputs.semVerNum }} APP_VERSION: ${{ needs.prepare.outputs.semVerStr }} NATIVE_ACCESS_PACKAGE: org.cryptomator.jfuse.win + - name: Update app dir + run: | + cp LICENSE.txt target/cryptomator-cli + cp target/cryptomator-cli_completion.sh target/cryptomator-cli - name: Fix permissions run: attrib -r target/cryptomator-cli/cryptomator-cli.exe shell: pwsh diff --git a/pom.xml b/pom.xml index f1b4513..77cda8a 100644 --- a/pom.xml +++ b/pom.xml @@ -173,6 +173,33 @@ + + org.codehaus.mojo + exec-maven-plugin + ${maven-exec.version} + + + generate-autocompletion-script + package + + exec + + + + + java + + -Dpicocli.autocomplete.systemExitOnError + -cp + + picocli.AutoComplete + --force + --completionScript + ${project.build.directory}/cryptomator-cli_completion.sh + ${mainClass} + + + - 2.7.0 + 2.7.1 2.0.7 5.0.2 1.5.12 From a32cdb231a73aa4a9658ee46aed04a773019ffba Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 16 Dec 2024 11:00:53 +0100 Subject: [PATCH 101/101] use JDK 23 --- .github/workflows/build-linux.yml | 2 +- .github/workflows/build-mac.yml | 2 +- .github/workflows/build-win.yml | 2 +- .github/workflows/build.yml | 2 +- pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index fe26b02..267a4dd 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -14,7 +14,7 @@ permissions: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_VERSION: '23.0.1+11' defaults: run: diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index 8e30577..cae2ba5 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -18,7 +18,7 @@ permissions: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_VERSION: '23.0.1+11' defaults: run: diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 6422dcc..56bb483 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -14,7 +14,7 @@ permissions: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_VERSION: '23.0.1+11' defaults: run: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50320e0..2cb1e2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - java-version: '22' + java-version: '23' distribution: 'temurin' - name: Ensure to use tagged version run: mvn versions:set --file ./pom.xml -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' diff --git a/pom.xml b/pom.xml index 1b2f941..1fa7eaf 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UTF-8 UTF-8 - 22 + 23