From 11c86f728730d995cb964079d48be8cdaeb3ac46 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 12 Jan 2026 11:59:17 +0100
Subject: [PATCH 01/33] impl idea
---
.../launcher/AdminPropertiesSetter.java | 56 +++++++++++++++++++
.../org/cryptomator/launcher/Cryptomator.java | 4 +-
2 files changed, 59 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
new file mode 100644
index 000000000..147514290
--- /dev/null
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -0,0 +1,56 @@
+package org.cryptomator.launcher;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Properties;
+
+class AdminPropertiesSetter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+
+ static {
+ final Path adminDir;
+ if (SystemUtils.IS_OS_WINDOWS) {
+ adminDir = Path.of(System.getenv().getOrDefault("ProgramData", "C:\\ProgramData"), "Cryptomator");
+ } else if (SystemUtils.IS_OS_MAC) {
+ adminDir = Path.of("/Library/Application Support/Cryptomator");
+ } else { //LINUX
+ adminDir = Path.of("/etc/cryptmator");
+ }
+ ADMIN_PROPERTIES_DIR = adminDir;
+ }
+
+ private static final Path ADMIN_PROPERTIES_DIR;
+ private static final Path ADMIN_PROPERTIES_FILE = ADMIN_PROPERTIES_DIR.resolve("config.properties");
+
+ static Properties adjustSystemProperties() {
+ var systemProps = System.getProperties();
+ var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
+ for (var key : adminProps.stringPropertyNames()) {
+ var value = adminProps.getProperty(key);
+ LOG.info("Overwriting {} with value {} from admin properties.", key, value);
+ systemProps.setProperty(key, value);
+ }
+ return systemProps;
+ }
+
+ private static Properties loadAdminProperties(Path adminPropertiesPath) {
+ var adminProps = new Properties();
+ try {
+ adminProps.load(Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ));
+ } catch (NoSuchFileException _) {
+ //NO-OP
+ } catch (IOException | IllegalArgumentException e) {
+ LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
+ }
+ return adminProps;
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 3e0a613ca..281a316db 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -27,6 +27,7 @@ import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.Properties;
import java.util.concurrent.Executors;
@Singleton
@@ -35,7 +36,8 @@ public class Cryptomator {
private static final long STARTUP_TIME = System.currentTimeMillis();
static {
- var lazyProcessedProps = new SubstitutingProperties(System.getProperties(), System.getenv());
+ var adjustedSystemProps = AdminPropertiesSetter.adjustSystemProperties();
+ var lazyProcessedProps = new SubstitutingProperties(adjustedSystemProps, System.getenv());
System.setProperties(lazyProcessedProps);
CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
LOG = LoggerFactory.getLogger(Cryptomator.class);
From b23bd0b27a9f58e059e975d79c846bfa93cb56e9 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 13 Jan 2026 13:14:40 +0100
Subject: [PATCH 02/33] Implement logic to overwrite system properties set in
app internal config file
---
.../launcher/AdminPropertiesSetter.java | 41 ++++++++++++++++---
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 147514290..d84c4f29b 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -11,28 +11,58 @@ import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
+/**
+ * Class to change JVM system properties according to an external properties file
+ *
+ * The app starts with a set of predefined properties (defined in [APPDIR]/../Cryptomator.cfg).
+ * That file is difficult to edit and edits do not persist over updates.
+ * To allow overwriting these properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add the contained properties to the {@link System} properties.
+ * The predefined location are:
+ *
+ *
Linux - {@value LINUX_DIR }
+ *
macOS - {@value MAC_DIR }
+ *
Windows - {@value WIN_DIR }
+ *
+ *
+ *
+ * @see System#getProperties()
+ */
class AdminPropertiesSetter {
private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final String LINUX_DIR = "/etc/cryptmator";
+ private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
+ private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
+ private static final String PROP_FILENAME = "config.properties";
+
static {
final Path adminDir;
if (SystemUtils.IS_OS_WINDOWS) {
adminDir = Path.of(System.getenv().getOrDefault("ProgramData", "C:\\ProgramData"), "Cryptomator");
} else if (SystemUtils.IS_OS_MAC) {
- adminDir = Path.of("/Library/Application Support/Cryptomator");
+ adminDir = Path.of(MAC_DIR);
} else { //LINUX
- adminDir = Path.of("/etc/cryptmator");
+ adminDir = Path.of(LINUX_DIR);
}
- ADMIN_PROPERTIES_DIR = adminDir;
+ ADMIN_PROPERTIES_FILE = adminDir.resolve(PROP_FILENAME);
}
- private static final Path ADMIN_PROPERTIES_DIR;
- private static final Path ADMIN_PROPERTIES_FILE = ADMIN_PROPERTIES_DIR.resolve("config.properties");
+ private static final Path ADMIN_PROPERTIES_FILE;
+ /**
+ * Adjusts the system properties by loading administrative properties from a predefined file location.
+ *
+ * If the file exists and is a valid properties file, its properties will overwrite the existing system properties.
+ *
+ * WARNING: This method modifies the global system properties and should be used with caution. Overwriting some properties has no effect, because they are read only once from the original config during JVM startup.
+ *
+ * @return The adjusted system properties.
+ */
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
+
for (var key : adminProps.stringPropertyNames()) {
var value = adminProps.getProperty(key);
LOG.info("Overwriting {} with value {} from admin properties.", key, value);
@@ -47,6 +77,7 @@ class AdminPropertiesSetter {
adminProps.load(Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ));
} catch (NoSuchFileException _) {
//NO-OP
+ LOG.debug("No admin properties found at {}.", adminPropertiesPath);
} catch (IOException | IllegalArgumentException e) {
LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
From 45633837e0e6d5cba434fb7b6b7297d245e52f9f Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 13 Jan 2026 17:26:48 +0100
Subject: [PATCH 03/33] only allow overwriting a subset of JVM properties
---
.../launcher/AdminPropertiesSetter.java | 38 ++++++++++++++-----
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index d84c4f29b..9ba18fa8c 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -10,13 +10,12 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
+import java.util.Set;
/**
- * Class to change JVM system properties according to an external properties file
+ * Class to overwrite system properties with an external properties file
*
- * The app starts with a set of predefined properties (defined in [APPDIR]/../Cryptomator.cfg).
- * That file is difficult to edit and edits do not persist over updates.
- * To allow overwriting these properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add the contained properties to the {@link System} properties.
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
* The predefined location are:
*
*
Linux - {@value LINUX_DIR }
@@ -24,6 +23,15 @@ import java.util.Properties;
*
Windows - {@value WIN_DIR }
*
*
+ *
+ * Supported properties for override are:
+ *
+ *
cryptomator.logDir
+ *
cryptomator.pluginDir
+ *
cryptomator.p12Path
+ *
cryptomator.mountPointsDir
+ *
cryptomator.disableUpdateCheck
+ *
*
* @see System#getProperties()
*/
@@ -35,6 +43,13 @@ class AdminPropertiesSetter {
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
private static final String PROP_FILENAME = "config.properties";
+ private static final Set ALLOWED_OVERRIDES = Set.of( //
+ "cryptomator.logDir", //
+ "cryptomator.pluginDir", //
+ "cryptomator.p12Path", //
+ "cryptomator.mountPointsDir", //
+ "cryptomator.disableUpdateCheck");
+
static {
final Path adminDir;
@@ -53,9 +68,8 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid properties file, its properties will overwrite the existing system properties.
- *
- * WARNING: This method modifies the global system properties and should be used with caution. Overwriting some properties has no effect, because they are read only once from the original config during JVM startup.
+ * If the file exists and is a valid properties file, its content will overwrite existing system properties.
+ * Only some properties are supported, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
*/
@@ -64,9 +78,13 @@ class AdminPropertiesSetter {
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
for (var key : adminProps.stringPropertyNames()) {
- var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ if (ALLOWED_OVERRIDES.contains(key)) {
+ var value = adminProps.getProperty(key);
+ LOG.info("Overwriting {} with value {} from admin properties.", key, value);
+ systemProps.setProperty(key, value);
+ } else {
+ LOG.debug("Property {} in admin properties is not supported for override.", key);
+ }
}
return systemProps;
}
From b1c21501a6fef1fcfffc79d0d0161b03fc33d592 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 13 Jan 2026 18:40:53 +0100
Subject: [PATCH 04/33] Add dummy admin properties file in Windows installer
---
dist/common/config.properties | 13 +++++++++++++
dist/win/resources/main.wxs | 30 +++++++++++++++++++++++++++++-
2 files changed, 42 insertions(+), 1 deletion(-)
create mode 100644 dist/common/config.properties
diff --git a/dist/common/config.properties b/dist/common/config.properties
new file mode 100644
index 000000000..6ad5dfd90
--- /dev/null
+++ b/dist/common/config.properties
@@ -0,0 +1,13 @@
+# This is the Cryptomator administrative configuration file.
+# It is a simple key-value pair file.
+# Lines starting with '#' are comments and will be ignored.
+#
+# There are some shorthands for well known folders:
+# Substitution Key | Variable Value
+# @{appdir} | The application installation directory.
+# @{appdata} | %APPDATA% (Windows only).
+# @{localappdata} | %LOCALAPPDATA% (Windows only).
+# @{userhome} | The user's home directory.
+#
+# Example:
+# cryptomator.pluginDir=@{appdata}/Cryptomator/Plugins #Sets a plugin directory and enables plugin loading
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 6310c6985..01f399b77 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -87,7 +87,7 @@
-
+
@@ -99,6 +99,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -107,6 +133,8 @@
+
+
From 42b06aa5562516901eeb284781d642b7d4a8aef2 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 14 Jan 2026 14:12:28 +0100
Subject: [PATCH 05/33] Switch to JSON as config format
Java Properties require ISO 8859-1 character encoding, leading to manual edits of file.
---
.../launcher/AdminPropertiesSetter.java | 50 +++++++++++--------
.../org/cryptomator/launcher/BufferedLog.java | 28 +++++++++++
.../org/cryptomator/launcher/Cryptomator.java | 4 +-
3 files changed, 59 insertions(+), 23 deletions(-)
create mode 100644 src/main/java/org/cryptomator/launcher/BufferedLog.java
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 9ba18fa8c..061590e6b 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -1,21 +1,24 @@
package org.cryptomator.launcher;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.commons.lang3.SystemUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Class to overwrite system properties with an external properties file
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the JSON file {@value CONFIG_NAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
* The predefined location are:
*
*
Linux - {@value LINUX_DIR }
@@ -37,12 +40,12 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final ObjectMapper JSON = JsonMapper.builder().build();
private static final String LINUX_DIR = "/etc/cryptmator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
- private static final String PROP_FILENAME = "config.properties";
+ private static final String CONFIG_NAME = "config.json";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
@@ -60,7 +63,7 @@ class AdminPropertiesSetter {
} else { //LINUX
adminDir = Path.of(LINUX_DIR);
}
- ADMIN_PROPERTIES_FILE = adminDir.resolve(PROP_FILENAME);
+ ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
}
private static final Path ADMIN_PROPERTIES_FILE;
@@ -77,29 +80,34 @@ class AdminPropertiesSetter {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
- for (var key : adminProps.stringPropertyNames()) {
- if (ALLOWED_OVERRIDES.contains(key)) {
- var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ adminProps.forEach((key, value) -> {
+ if (ALLOWED_OVERRIDES.contains(key) && value instanceof String v) {
+ log("Overwriting {} with value {} from admin properties.", List.of(key, v));
+ systemProps.setProperty(key, v);
} else {
- LOG.debug("Property {} in admin properties is not supported for override.", key);
+ var reason = value instanceof String ? "Unsupported" : "Not a string";
+ log("Property {} in admin config ignored: {}.", List.of(key, reason));
}
- }
+ });
return systemProps;
}
- private static Properties loadAdminProperties(Path adminPropertiesPath) {
- var adminProps = new Properties();
- try {
- adminProps.load(Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ));
+ private static Map loadAdminProperties(Path adminPropertiesPath) {
+ try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
+ var map = JSON.readValue(in, new TypeReference
*
- * Supported properties for override are:
+ * The overridable properties are:
*
*
cryptomator.logDir
*
cryptomator.pluginDir
@@ -71,8 +71,8 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid properties file, its content will overwrite existing system properties.
- * Only some properties are supported, see {@link AdminPropertiesSetter}
+ * If the file exists and is a valid JSON file, its content will overwrite existing system properties.
+ * Only some properties can be overridden, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
*/
@@ -100,7 +100,7 @@ class AdminPropertiesSetter {
}
} catch (NoSuchFileException _) {
//NO-OP
- log("No admin properties found at {}.", List.of(adminPropertiesPath));
+ log("No admin properties found at {}.", List.of(adminPropertiesPath));
} catch (IOException | RuntimeException e) {
log("Failed to read administrative properties from {}. Returning empty properties.", List.of(adminPropertiesPath, e));
}
diff --git a/src/main/java/org/cryptomator/launcher/BufferedLog.java b/src/main/java/org/cryptomator/launcher/BufferedLog.java
index 2e223b13b..0facf7734 100644
--- a/src/main/java/org/cryptomator/launcher/BufferedLog.java
+++ b/src/main/java/org/cryptomator/launcher/BufferedLog.java
@@ -14,15 +14,11 @@ class BufferedLog {
record Entry(String className, String message, List messageInput) {}
- static void log(String className, String message, List messageInput) {
- add(new BufferedLog.Entry(className, message, messageInput));
+ synchronized static void log(String className, String message, List messageInput) {
+ logMessages.add(new BufferedLog.Entry(className, message, messageInput));
}
- synchronized static void add(Entry e) {
- logMessages.add(e);
- }
-
- synchronized static void flush(Logger log) {
+ synchronized static void flushTo(Logger log) {
logMessages.forEach(e -> {
var message = "PRE LOG INIT Event in %s: %s".formatted(e.className, e.message);
log.info(message, e.messageInput.toArray());
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index b61dba843..7bbcb8f05 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -65,7 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
- BufferedLog.flush(LOG);
+ BufferedLog.flushTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //
From a0c9caeb21d053d5e7001623c344feced452a6ba Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 14 Jan 2026 17:57:24 +0100
Subject: [PATCH 10/33] [skip ci] update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc6402c4c..3af691b80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility labels for GUI elements ([#4064](https://github.com/cryptomator/cryptomator/issues/4064), [#4066](https://github.com/cryptomator/cryptomator/pull/4066), [#4055](https://github.com/cryptomator/cryptomator/issues/4055))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
+* Admin configuration: Allow overwriting some app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
From 287ab4e792c45a39927cd5ab1a7a9d7aee830d85 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Thu, 15 Jan 2026 15:11:12 +0100
Subject: [PATCH 11/33] [skip ci] Update changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3af691b80..1c70c2ddd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility labels for GUI elements ([#4064](https://github.com/cryptomator/cryptomator/issues/4064), [#4066](https://github.com/cryptomator/cryptomator/pull/4066), [#4055](https://github.com/cryptomator/cryptomator/issues/4055))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
-* Admin configuration: Allow overwriting some app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
+* Admin configuration: Allow overwriting certain app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
From 088b177c0e7069091ecfe920cf8425676e9a6c04 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Thu, 15 Jan 2026 15:11:46 +0100
Subject: [PATCH 12/33] Ensure that null map is also logged
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 30dcd241d..36c7dd247 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -95,9 +95,10 @@ class AdminPropertiesSetter {
private static Map loadAdminProperties(Path adminPropertiesPath) {
try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
var map = JSON.readValue(in, new TypeReference
*
* The overridable properties are:
@@ -40,12 +37,12 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final ObjectMapper JSON = JsonMapper.builder().build();
+ private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
- private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
- private static final String CONFIG_NAME = "config.json";
+ private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
+ private static final String CONFIG_NAME = "config.properties";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
@@ -57,7 +54,7 @@ class AdminPropertiesSetter {
static {
final Path adminDir;
if (SystemUtils.IS_OS_WINDOWS) {
- adminDir = Path.of(System.getenv().getOrDefault("ProgramData", "C:\\ProgramData"), "Cryptomator");
+ adminDir = Path.of(WIN_DIR);
} else if (SystemUtils.IS_OS_MAC) {
adminDir = Path.of(MAC_DIR);
} else { //LINUX
@@ -71,7 +68,7 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid JSON file, its content will overwrite existing system properties.
+ * If the file exists and is a valid properties file, its content will overwrite existing system properties.
* Only some properties can be overridden, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
@@ -80,35 +77,29 @@ class AdminPropertiesSetter {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
- adminProps.forEach((key, value) -> {
- if (ALLOWED_OVERRIDES.contains(key) && value instanceof String v) {
- log("Overwriting {} with value {} from admin properties.", List.of(key, v));
- systemProps.setProperty(key, v);
+ for (var key : adminProps.stringPropertyNames()) {
+ if (ALLOWED_OVERRIDES.contains(key)) {
+ var value = adminProps.getProperty(key);
+ LOG.info("Overwriting {} with value {} from admin properties.", key, value);
+ systemProps.setProperty(key, value);
} else {
- var reason = value instanceof String ? "Unsupported" : "Not a string";
- log("Property {} in admin config ignored: {}.", List.of(key, reason));
+ LOG.debug("Property {} in admin properties is not supported for override.", key);
}
- });
+ }
return systemProps;
}
- static Map loadAdminProperties(Path adminPropertiesPath) {
- try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
- var map = JSON.readValue(in, new TypeReference>() {});
- if (map == null) {
- throw new NullPointerException("JSON parsing returned null");
- }
- return map;
+ private static Properties loadAdminProperties(Path adminPropertiesPath) {
+ var adminProps = new Properties();
+ try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
+ adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
- log("No admin properties found at {}.", List.of(adminPropertiesPath));
- } catch (IOException | RuntimeException e) {
- log("Failed to read administrative properties from {}. Returning empty properties.", List.of(adminPropertiesPath, e));
+ LOG.debug("No admin properties found at {}.", adminPropertiesPath);
+ } catch (IOException | IllegalArgumentException e) {
+ LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
- return Map.of();
+ return adminProps;
}
- static void log(String message, List messageInput) {
- BufferedLog.log(AdminPropertiesSetter.class.getName(), message, messageInput);
- }
}
From 046372f95bea9035d97598ff46e397252afd3c8e Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:33:26 +0100
Subject: [PATCH 15/33] refactored BufferedLog
* rename to EventualLogger
* adhere to slf4j API
* ensure single instance
---
.../launcher/AdminPropertiesSetter.java | 3 +-
.../org/cryptomator/launcher/BufferedLog.java | 28 -----
.../org/cryptomator/launcher/Cryptomator.java | 2 +-
.../cryptomator/launcher/EventualLogger.java | 115 ++++++++++++++++++
.../launcher/AdminPropertiesSetterTest.java | 81 ++++--------
5 files changed, 138 insertions(+), 91 deletions(-)
delete mode 100644 src/main/java/org/cryptomator/launcher/BufferedLog.java
create mode 100644 src/main/java/org/cryptomator/launcher/EventualLogger.java
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 69121cd6e..3f35529ec 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -2,7 +2,6 @@ package org.cryptomator.launcher;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -37,7 +36,7 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final Logger LOG = EventualLogger.getInstance();
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
diff --git a/src/main/java/org/cryptomator/launcher/BufferedLog.java b/src/main/java/org/cryptomator/launcher/BufferedLog.java
deleted file mode 100644
index 68eb75e26..000000000
--- a/src/main/java/org/cryptomator/launcher/BufferedLog.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.cryptomator.launcher;
-
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class BufferedLog {
-
- private final static List logMessages = new ArrayList<>();
-
- private BufferedLog() {
- }
-
- record Entry(String className, String message, List messageInput) {}
-
- synchronized static void log(String className, String message, List messageInput) {
- logMessages.add(new Entry(className, message, messageInput));
- }
-
- synchronized static void flushTo(Logger log) {
- logMessages.forEach(e -> {
- var message = "PRE LOG INIT Event in %s: %s".formatted(e.className, e.message);
- log.info(message, e.messageInput.toArray());
- });
- logMessages.clear();
- }
-}
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 7bbcb8f05..70e34a2bd 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -65,7 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
- BufferedLog.flushTo(LOG);
+ EventualLogger.getInstance().drainTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
new file mode 100644
index 000000000..0d3a0ffaa
--- /dev/null
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -0,0 +1,115 @@
+package org.cryptomator.launcher;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+import org.slf4j.event.DefaultLoggingEvent;
+import org.slf4j.event.Level;
+import org.slf4j.event.LoggingEvent;
+import org.slf4j.helpers.AbstractLogger;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+class EventualLogger extends AbstractLogger {
+
+ static EventualLogger getInstance() {
+ return Wrapped.INSTANCE.get();
+ }
+
+
+ private final Queue bufferedEvents = new ArrayDeque<>();
+
+ private EventualLogger() {
+ }
+
+ synchronized void drainTo(Logger gutter) {
+ for (var event : bufferedEvents) {
+ gutter.atLevel(event.getLevel()).log(event.getMessage(), event.getArgumentArray());
+ }
+ bufferedEvents.clear();
+ }
+
+ @Override
+ protected synchronized void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
+ var event = new DefaultLoggingEvent(level, this);
+ if (marker != null) {
+ event.addMarker(marker);
+ }
+ event.setMessage(messagePattern);
+ for (var arg : arguments) {
+ event.addArgument(arg);
+ }
+ bufferedEvents.add(event);
+ }
+
+ //Unclear, unused and undocumented method of slf4j, see also https://github.com/qos-ch/slf4j/discussions/348
+ @Override
+ protected String getFullyQualifiedCallerName() {
+ return getClass().getCanonicalName();
+ }
+
+
+ @Override
+ public boolean isTraceEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isTraceEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isDebugEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isInfoEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled(Marker marker) {
+ return true;
+ }
+
+ private enum Wrapped {
+ INSTANCE;
+
+ EventualLogger actualInstance;
+
+ Wrapped() {
+ actualInstance = new EventualLogger();
+ }
+
+ public EventualLogger get() {
+ return actualInstance;
+ }
+ }
+}
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index 31b6be658..bb2ae97eb 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -1,98 +1,59 @@
package org.cryptomator.launcher;
-import com.fasterxml.jackson.databind.json.JsonMapper;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import org.mockito.Answers;
-import org.mockito.Mockito;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
-import java.util.Map;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
public class AdminPropertiesSetterTest {
- private static final Map NO_STRING_CONFIG = Map.of("list", List.of("a", "b", "c"), //
- "map", Map.of("a", 1, "b", 2));
-
- private static final Map CONFIG = Map.of("kack", "dudel", //
- "list", List.of("a", "b", "c"), //
- "map", Map.of("a", 1, "b", 2));
+ private static final String PROPS = """
+ fruit=banana
+ vegetable:kärrot
+ method=scan寧""";
@Test
- @DisplayName("Loading valid JSON")
- void loadValidJson(@TempDir Path path) throws IOException {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
- setupValidJson(configPath);
+ @DisplayName("UTF-8 is supported")
+ void loadUTF8Properties(@TempDir Path path) throws IOException {
+ var config = path.resolve("config.properties");
+ setupValidProperties(config);
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- Assertions.assertAll(CONFIG.entrySet().stream().map((e) -> //
- () -> MatcherAssert.assertThat(result, hasEntry(e.getKey(), e.getValue()))));
- }
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ Assertions.assertAll(List.of( //
+ () -> MatcherAssert.assertThat(properties, hasEntry("fruit", "banana")), //
+ () -> MatcherAssert.assertThat(properties, hasEntry("vegetable", "kärrot")), //
+ () -> MatcherAssert.assertThat(properties, hasEntry("method", "scan寧"))));
}
@Test
@DisplayName("Loading not existing file")
void loadNotExistingFile(@TempDir Path path) {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
-
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- MatcherAssert.assertThat(result, anEmptyMap());
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()));
- }
+ var config = path.resolve("config.properties");
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
}
@Test
- @DisplayName("Loading empty file")
+ @DisplayName("Loading invalid properties file")
void loadEmptyFile(@TempDir Path path) throws IOException {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
- Files.createFile(configPath);
-
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- MatcherAssert.assertThat(result, anEmptyMap());
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()));
- }
}
- void setupValidJson(Path p) throws IOException {
- var json = JsonMapper.builder().build();
+ void setupValidProperties(Path p) throws IOException {
try (var out = Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
- json.writerWithDefaultPrettyPrinter().writeValue(out, CONFIG);
+ var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ out.write(bytes);
}
}
- @Test
- @DisplayName("Keys with non-String values are ignored")
- void ignoreValues(@TempDir Path path) {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(NO_STRING_CONFIG);
- adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
-
- AdminPropertiesSetter.adjustSystemProperties();
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()), Mockito.times(NO_STRING_CONFIG.size()));
- }
- }
-
-
}
From 7ee0606306e302b2c66feefc52f8303dd77ec079 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:08 +0100
Subject: [PATCH 16/33] adjust unit tests
---
.../launcher/AdminPropertiesSetter.java | 7 +++-
.../launcher/AdminPropertiesSetterTest.java | 34 ++++++++++++++-----
2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 3f35529ec..53863e4e3 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -37,6 +37,7 @@ import java.util.Set;
class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.getInstance();
+ private static final long MAX_CONFIG_SIZE_BYTES = 8192;
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
@@ -88,9 +89,13 @@ class AdminPropertiesSetter {
return systemProps;
}
- private static Properties loadAdminProperties(Path adminPropertiesPath) {
+ //visible for testing
+ static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
+ if(Files.size(adminPropertiesPath) >= MAX_CONFIG_SIZE_BYTES) {
+ throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
+ }
adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index bb2ae97eb..9e8b4962b 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -27,7 +28,10 @@ public class AdminPropertiesSetterTest {
@DisplayName("UTF-8 is supported")
void loadUTF8Properties(@TempDir Path path) throws IOException {
var config = path.resolve("config.properties");
- setupValidProperties(config);
+ try (var out = Files.newOutputStream(config, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+ var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ out.write(bytes);
+ }
var properties = AdminPropertiesSetter.loadAdminProperties(config);
Assertions.assertAll(List.of( //
@@ -37,7 +41,7 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("Loading not existing file")
+ @DisplayName("Loading not existing file returns empty properties")
void loadNotExistingFile(@TempDir Path path) {
var config = path.resolve("config.properties");
var properties = AdminPropertiesSetter.loadAdminProperties(config);
@@ -45,15 +49,29 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("Loading invalid properties file")
+ @DisplayName("Loading invalid file returns empty properties")
void loadEmptyFile(@TempDir Path path) throws IOException {
- }
-
- void setupValidProperties(Path p) throws IOException {
- try (var out = Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
- var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ var config = path.resolve("config.properties");
+ try (var out = Files.newOutputStream(config, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+ var bytes = "method=\\u2u20".getBytes(StandardCharsets.UTF_8); //only one "u" is allowed in a Unicode escape sequence
out.write(bytes);
}
+
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
+ }
+
+ @Test
+ @DisplayName("Loading too big file returns empty properties")
+ void loadTooBigFile(@TempDir Path path) throws IOException {
+ var config = path.resolve("config.properties");
+ try (var channel = Files.newByteChannel(config, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ channel.position(10_000);
+ channel.write(ByteBuffer.wrap("test=test".getBytes()));
+ }
+
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
}
}
From b00c81c20a2bd88f3e0a399ee5652cc866c16df1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:24 +0100
Subject: [PATCH 17/33] use "cryptomator.config" for config file
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 53863e4e3..0aefb35ad 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -42,7 +42,7 @@ class AdminPropertiesSetter {
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
- private static final String CONFIG_NAME = "config.properties";
+ private static final String CONFIG_NAME = "cryptomator.config";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
From 35c2141fd6dfbba748ca9003f263a207159cd3e7 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:34 +0100
Subject: [PATCH 18/33] cleanup
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 0aefb35ad..590a68b57 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -57,7 +57,7 @@ class AdminPropertiesSetter {
adminDir = Path.of(WIN_DIR);
} else if (SystemUtils.IS_OS_MAC) {
adminDir = Path.of(MAC_DIR);
- } else { //LINUX
+ } else {
adminDir = Path.of(LINUX_DIR);
}
ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
From 5cac6b81141fd45e3462f08b452d7b8d630add0f Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:55:04 +0100
Subject: [PATCH 19/33] also adjust distribution
---
dist/common/config.json | 3 ---
dist/common/cryptomator.config | 8 ++++++++
dist/win/resources/main.wxs | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
delete mode 100644 dist/common/config.json
create mode 100644 dist/common/cryptomator.config
diff --git a/dist/common/config.json b/dist/common/config.json
deleted file mode 100644
index d077d240e..000000000
--- a/dist/common/config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "ignored": "This is the Cryptomator administrative configuration file. For more info, read the docs at https:\\docs.cryptomator.org. This entry can be removed."
-}
\ No newline at end of file
diff --git a/dist/common/cryptomator.config b/dist/common/cryptomator.config
new file mode 100644
index 000000000..c0ab0f54f
--- /dev/null
+++ b/dist/common/cryptomator.config
@@ -0,0 +1,8 @@
+# This is the Cryptomator administrative configuration file.
+# It is a simple key-value pair file.
+# Lines starting with '#' are comments and will be ignored.
+# For more info, read the docs at https://docs.cryptomator.org.
+#
+# Example:
+# Sets the plugin directory and enables plugin loading
+# cryptomator.pluginDir=@{userhome}/Cryptomator/Plugins
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 143e5a1a2..0d6f0d8f1 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -116,7 +116,7 @@
-
+
From 29e76e7f93b99e08c258009380b883e04237d6c1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:07:17 +0100
Subject: [PATCH 20/33] keep throwables in EventualLogger
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 0d3a0ffaa..597b2f1e9 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -24,7 +24,9 @@ class EventualLogger extends AbstractLogger {
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
- gutter.atLevel(event.getLevel()).log(event.getMessage(), event.getArgumentArray());
+ gutter.atLevel(event.getLevel())
+ .setCause(event.getThrowable())
+ .log(event.getMessage(), event.getArgumentArray());
}
bufferedEvents.clear();
}
@@ -39,6 +41,7 @@ class EventualLogger extends AbstractLogger {
for (var arg : arguments) {
event.addArgument(arg);
}
+ event.setThrowable(throwable);
bufferedEvents.add(event);
}
From b651b9ac26d85f077bba1a8b31cad8ea214554c3 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:08:00 +0100
Subject: [PATCH 21/33] config file can be exactly the max size
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 590a68b57..5c1c2ed60 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -93,7 +93,7 @@ class AdminPropertiesSetter {
static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
- if(Files.size(adminPropertiesPath) >= MAX_CONFIG_SIZE_BYTES) {
+ if(Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
adminProps.load(reader);
From 300cac5441e38b3dc0a59217bc4740a9725746e7 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:36:56 +0100
Subject: [PATCH 22/33] don't forget log markers
---
.../java/org/cryptomator/launcher/EventualLogger.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 597b2f1e9..64c741725 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -24,9 +24,11 @@ class EventualLogger extends AbstractLogger {
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
- gutter.atLevel(event.getLevel())
- .setCause(event.getThrowable())
- .log(event.getMessage(), event.getArgumentArray());
+ var builder = gutter.atLevel(event.getLevel());
+ builder.setCause(event.getThrowable());
+ builder.setMessage(event.getMessage());
+ event.getArguments().forEach(builder::addArgument);
+ event.getMarkers().forEach(builder::addMarker);
}
bufferedEvents.clear();
}
From efbd107fb5b2bee1c8a26a9c50c2797a462ffe0e Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:57:42 +0100
Subject: [PATCH 23/33] increase null safety
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 64c741725..06f5e24c5 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -8,6 +8,8 @@ import org.slf4j.event.LoggingEvent;
import org.slf4j.helpers.AbstractLogger;
import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Objects;
import java.util.Queue;
class EventualLogger extends AbstractLogger {
@@ -28,7 +30,8 @@ class EventualLogger extends AbstractLogger {
builder.setCause(event.getThrowable());
builder.setMessage(event.getMessage());
event.getArguments().forEach(builder::addArgument);
- event.getMarkers().forEach(builder::addMarker);
+ Objects.requireNonNullElse(event.getMarkers(), List.of()).forEach(builder::addMarker);
+ builder.log();
}
bufferedEvents.clear();
}
@@ -40,7 +43,7 @@ class EventualLogger extends AbstractLogger {
event.addMarker(marker);
}
event.setMessage(messagePattern);
- for (var arg : arguments) {
+ for (var arg : Objects.requireNonNullElse(arguments, new Object[]{})) {
event.addArgument(arg);
}
event.setThrowable(throwable);
From 19c9eada9d44aa1e4ffaa36193de77c86ef3e1c1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 28 Jan 2026 17:32:10 +0100
Subject: [PATCH 24/33] check first the filesize before opening a Reader to the
config file
---
.../org/cryptomator/launcher/AdminPropertiesSetter.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 5c1c2ed60..c32687226 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -92,11 +92,13 @@ class AdminPropertiesSetter {
//visible for testing
static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
- try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
- if(Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
+ try {
+ if (Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
- adminProps.load(reader);
+ try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
+ adminProps.load(reader);
+ }
} catch (NoSuchFileException _) {
//NO-OP
LOG.debug("No admin properties found at {}.", adminPropertiesPath);
From e9b3b505a84d84d5a251b38f098776bc1f046acb Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 28 Jan 2026 17:35:34 +0100
Subject: [PATCH 25/33] apply suggestions from code review
---
dist/common/cryptomator.config | 2 +-
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 5 ++---
.../org/cryptomator/launcher/AdminPropertiesSetterTest.java | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/dist/common/cryptomator.config b/dist/common/cryptomator.config
index c0ab0f54f..98c5f753f 100644
--- a/dist/common/cryptomator.config
+++ b/dist/common/cryptomator.config
@@ -1,7 +1,7 @@
# This is the Cryptomator administrative configuration file.
# It is a simple key-value pair file.
# Lines starting with '#' are comments and will be ignored.
-# For more info, read the docs at https://docs.cryptomator.org.
+# For more info, read the docs at https://docs.cryptomator.org/desktop/advanced-settings/
#
# Example:
# Sets the plugin directory and enables plugin loading
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index c32687226..26de38f10 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -38,7 +38,6 @@ class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.getInstance();
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
-
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
@@ -50,6 +49,7 @@ class AdminPropertiesSetter {
"cryptomator.mountPointsDir", //
"cryptomator.disableUpdateCheck");
+ private static final Path ADMIN_PROPERTIES_FILE;
static {
final Path adminDir;
@@ -63,7 +63,6 @@ class AdminPropertiesSetter {
ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
}
- private static final Path ADMIN_PROPERTIES_FILE;
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
@@ -101,7 +100,7 @@ class AdminPropertiesSetter {
}
} catch (NoSuchFileException _) {
//NO-OP
- LOG.debug("No admin properties found at {}.", adminPropertiesPath);
+ LOG.debug("No admin properties found at {}.", adminPropertiesPath);
} catch (IOException | IllegalArgumentException e) {
LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index 9e8b4962b..f33bd4d80 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -50,7 +50,7 @@ public class AdminPropertiesSetterTest {
@Test
@DisplayName("Loading invalid file returns empty properties")
- void loadEmptyFile(@TempDir Path path) throws IOException {
+ void loadInvalidFile(@TempDir Path path) throws IOException {
var config = path.resolve("config.properties");
try (var out = Files.newOutputStream(config, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
var bytes = "method=\\u2u20".getBytes(StandardCharsets.UTF_8); //only one "u" is allowed in a Unicode escape sequence
From a4836f6528c72637ce51da2da210cdbfa99090e3 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 28 Jan 2026 17:36:52 +0100
Subject: [PATCH 26/33] cleanup
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 06f5e24c5..dee2af53b 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -26,9 +26,9 @@ class EventualLogger extends AbstractLogger {
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
- var builder = gutter.atLevel(event.getLevel());
- builder.setCause(event.getThrowable());
- builder.setMessage(event.getMessage());
+ var builder = gutter.atLevel(event.getLevel()) //
+ .setCause(event.getThrowable()) //
+ .setMessage(event.getMessage());
event.getArguments().forEach(builder::addArgument);
Objects.requireNonNullElse(event.getMarkers(), List.of()).forEach(builder::addMarker);
builder.log();
From 77983fe00a8f3024d8aa29175871e21866c6d59e Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 28 Jan 2026 17:43:52 +0100
Subject: [PATCH 27/33] simplify EventualLogger init
---
.../launcher/AdminPropertiesSetter.java | 2 +-
.../org/cryptomator/launcher/Cryptomator.java | 2 +-
.../cryptomator/launcher/EventualLogger.java | 19 +------------------
3 files changed, 3 insertions(+), 20 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 26de38f10..76638cd70 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -36,7 +36,7 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final Logger LOG = EventualLogger.getInstance();
+ private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 70e34a2bd..855ef6e1d 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -65,7 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
- EventualLogger.getInstance().drainTo(LOG);
+ EventualLogger.INSTANCE.drainTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index dee2af53b..fac429cfe 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -14,10 +14,7 @@ import java.util.Queue;
class EventualLogger extends AbstractLogger {
- static EventualLogger getInstance() {
- return Wrapped.INSTANCE.get();
- }
-
+ static final EventualLogger INSTANCE = new EventualLogger();
private final Queue bufferedEvents = new ArrayDeque<>();
@@ -106,18 +103,4 @@ class EventualLogger extends AbstractLogger {
public boolean isErrorEnabled(Marker marker) {
return true;
}
-
- private enum Wrapped {
- INSTANCE;
-
- EventualLogger actualInstance;
-
- Wrapped() {
- actualInstance = new EventualLogger();
- }
-
- public EventualLogger get() {
- return actualInstance;
- }
- }
}
From cad7b4580863b7ceac711324ade84718309845d5 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 9 Feb 2026 16:16:26 +0100
Subject: [PATCH 28/33] Refactor to first check system property
cryptomator.adminConfig for config path
---
.github/workflows/appimage.yml | 1 +
.github/workflows/mac-dmg-x64.yml | 1 +
.github/workflows/mac-dmg.yml | 1 +
.github/workflows/win-exe.yml | 1 +
dist/linux/appimage/build.sh | 1 +
dist/linux/debian/rules | 1 +
dist/mac/dmg/build.sh | 1 +
dist/win/build.ps1 | 1 +
.../launcher/AdminPropertiesSetter.java | 24 ++++++++++++-------
9 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 2d4e47b08..00eddfbe1 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -122,6 +122,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
+ --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index b1677458e..fc995f565 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -128,6 +128,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
+ --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 8b341b56a..482aa61b6 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -126,6 +126,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
+ --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 749cab0c2..9cf8eab91 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -140,6 +140,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
+ --java-options "-Dcryptomator.adminConfig=\"C:/ProgramData/Cryptomator/cryptomator.config\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index 99249cb35..847da6626 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -88,6 +88,7 @@ ${JAVA_HOME}/bin/jpackage \
--app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
+ --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index 456a97e89..d4bd6c40b 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -51,6 +51,7 @@ override_dh_auto_build:
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
+ --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index fbe81931c..0e3be2add 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -114,6 +114,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
+ --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index 6fdee1c9a..35cc8e512 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -155,6 +155,7 @@ $javaOptions = @(
"--java-options", "-Djava.net.useSystemProxies=true"
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
+"--java-options", "-Dcryptomator.adminConfig=`"C:/ProgramData/$AppName/cryptomator.config`""
"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 76638cd70..508ad2c49 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -14,8 +14,8 @@ import java.util.Set;
/**
* Class to overwrite system properties with an external properties file
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value CONFIG_NAME} from an OS-dependent location and adds all supported properties to the {@link System} properties.
- * The predefined location is for
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value CONFIG_NAME} from the file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
+ * If {@value #ADMIN_PROP_FILE_KEY} is {@code null} OS-specific, predefined paths are used:
*
*
Linux: {@value LINUX_DIR }
*
macOS: {@value MAC_DIR }
@@ -42,6 +42,7 @@ class AdminPropertiesSetter {
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
private static final String CONFIG_NAME = "cryptomator.config";
+ private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfig";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
@@ -52,15 +53,20 @@ class AdminPropertiesSetter {
private static final Path ADMIN_PROPERTIES_FILE;
static {
- final Path adminDir;
- if (SystemUtils.IS_OS_WINDOWS) {
- adminDir = Path.of(WIN_DIR);
- } else if (SystemUtils.IS_OS_MAC) {
- adminDir = Path.of(MAC_DIR);
+ final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
+ if (systemPropertyDefinedAdminFile != null) {
+ ADMIN_PROPERTIES_FILE = Path.of(systemPropertyDefinedAdminFile);
} else {
- adminDir = Path.of(LINUX_DIR);
+ final Path defaultAdminDir;
+ if (SystemUtils.IS_OS_WINDOWS) {
+ defaultAdminDir = Path.of(WIN_DIR);
+ } else if (SystemUtils.IS_OS_MAC) {
+ defaultAdminDir = Path.of(MAC_DIR);
+ } else {
+ defaultAdminDir = Path.of(LINUX_DIR);
+ }
+ ADMIN_PROPERTIES_FILE = defaultAdminDir.resolve(CONFIG_NAME);
}
- ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
}
From 5e52811c743fc1cfd371cf19a700dc6ef09c4c07 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 9 Feb 2026 16:46:07 +0100
Subject: [PATCH 29/33] Remove hard coded default locations for admin config
---
.../launcher/AdminPropertiesSetter.java | 41 ++++---------------
.../launcher/AdminPropertiesSetterTest.java | 19 +++++++++
2 files changed, 27 insertions(+), 33 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 508ad2c49..29113bec3 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -1,6 +1,5 @@
package org.cryptomator.launcher;
-import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import java.io.IOException;
@@ -14,14 +13,7 @@ import java.util.Set;
/**
* Class to overwrite system properties with an external properties file
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value CONFIG_NAME} from the file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
- * If {@value #ADMIN_PROP_FILE_KEY} is {@code null} OS-specific, predefined paths are used:
- *
- *
Linux: {@value LINUX_DIR }
- *
macOS: {@value MAC_DIR }
- *
Windows: {@value WIN_DIR }
- *
- *
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} reads the properties file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
*
* The overridable properties are:
*
@@ -38,10 +30,6 @@ class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
- private static final String LINUX_DIR = "/etc/cryptomator";
- private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
- private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
- private static final String CONFIG_NAME = "cryptomator.config";
private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfig";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
@@ -50,25 +38,6 @@ class AdminPropertiesSetter {
"cryptomator.mountPointsDir", //
"cryptomator.disableUpdateCheck");
- private static final Path ADMIN_PROPERTIES_FILE;
-
- static {
- final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
- if (systemPropertyDefinedAdminFile != null) {
- ADMIN_PROPERTIES_FILE = Path.of(systemPropertyDefinedAdminFile);
- } else {
- final Path defaultAdminDir;
- if (SystemUtils.IS_OS_WINDOWS) {
- defaultAdminDir = Path.of(WIN_DIR);
- } else if (SystemUtils.IS_OS_MAC) {
- defaultAdminDir = Path.of(MAC_DIR);
- } else {
- defaultAdminDir = Path.of(LINUX_DIR);
- }
- ADMIN_PROPERTIES_FILE = defaultAdminDir.resolve(CONFIG_NAME);
- }
- }
-
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
@@ -80,7 +49,13 @@ class AdminPropertiesSetter {
*/
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
- var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
+
+ final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
+ if (systemPropertyDefinedAdminFile == null) {
+ LOG.debug("Path to admin properties file is not defined.");
+ return systemProps;
+ }
+ var adminProps = loadAdminProperties(Path.of(systemPropertyDefinedAdminFile));
for (var key : adminProps.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index f33bd4d80..e49942950 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -13,9 +13,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
+import java.util.Properties;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
public class AdminPropertiesSetterTest {
@@ -74,4 +78,19 @@ public class AdminPropertiesSetterTest {
MatcherAssert.assertThat(properties, anEmptyMap());
}
+ @Test
+ @DisplayName("If system property for config path is null, skip do not load anything")
+ void skipAdjustSystemPropertiesOnUndefinedProperty() {
+ Assertions.assertNull(System.getProperty("cryptomator.adminConfig"));
+
+ try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
+ adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
+ adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(new Properties());
+
+ AdminPropertiesSetter.adjustSystemProperties();
+
+ adminPropSetterMock.verify(() -> AdminPropertiesSetter.loadAdminProperties(any()), never());
+ }
+ }
+
}
From 53f368108ab1ee8c2ac0bbba67bc2a03d7f5bd6b Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 9 Feb 2026 16:54:04 +0100
Subject: [PATCH 30/33] Rename jvm property and actual config file
---
.github/workflows/appimage.yml | 2 +-
.github/workflows/mac-dmg-x64.yml | 2 +-
.github/workflows/mac-dmg.yml | 2 +-
.github/workflows/win-exe.yml | 2 +-
dist/linux/appimage/build.sh | 2 +-
dist/linux/debian/rules | 2 +-
dist/mac/dmg/build.sh | 2 +-
dist/win/build.ps1 | 2 +-
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
.../org/cryptomator/launcher/AdminPropertiesSetterTest.java | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 00eddfbe1..a1c57d91c 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -122,7 +122,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index fc995f565..d8047462e 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -128,7 +128,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 482aa61b6..9a06024a0 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -126,7 +126,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 9cf8eab91..23846ad5d 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -140,7 +140,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
- --java-options "-Dcryptomator.adminConfig=\"C:/ProgramData/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index 847da6626..ee72b538b 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -88,7 +88,7 @@ ${JAVA_HOME}/bin/jpackage \
--app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index d4bd6c40b..0d2242f40 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -51,7 +51,7 @@ override_dh_auto_build:
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index 0e3be2add..438de7b9a 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -114,7 +114,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index 35cc8e512..54ee8946e 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -155,7 +155,7 @@ $javaOptions = @(
"--java-options", "-Djava.net.useSystemProxies=true"
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
-"--java-options", "-Dcryptomator.adminConfig=`"C:/ProgramData/$AppName/cryptomator.config`""
+"--java-options", "-Dcryptomator.adminConfigPath=`"C:/ProgramData/$AppName/config.properties`""
"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 29113bec3..693ea402e 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -30,7 +30,7 @@ class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
- private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfig";
+ private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfigPath";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index e49942950..ef855dd5e 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -81,7 +81,7 @@ public class AdminPropertiesSetterTest {
@Test
@DisplayName("If system property for config path is null, skip do not load anything")
void skipAdjustSystemPropertiesOnUndefinedProperty() {
- Assertions.assertNull(System.getProperty("cryptomator.adminConfig"));
+ Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
From 5db05d8bc75a38f2bcf1b2ee0b1f547a1f0d6d1d Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 14:33:01 +0100
Subject: [PATCH 31/33] Apply suggestions from codereview
---
dist/win/resources/main.wxs | 13 +++----------
.../launcher/AdminPropertiesSetter.java | 14 ++++++++------
2 files changed, 11 insertions(+), 16 deletions(-)
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 9cd56f746..ac5e6a3d1 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -103,16 +103,9 @@
-
-
-
+
+
+
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 693ea402e..127d5b263 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -3,10 +3,13 @@ package org.cryptomator.launcher;
import org.slf4j.Logger;
import java.io.IOException;
+import java.io.Reader;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.Properties;
import java.util.Set;
@@ -72,13 +75,12 @@ class AdminPropertiesSetter {
//visible for testing
static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
- try {
- if (Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
+ try (FileChannel ch = FileChannel.open(adminPropertiesPath, StandardOpenOption.READ); //
+ Reader reader = Channels.newReader(ch, StandardCharsets.UTF_8)) {
+ if (ch.size() > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
- try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
- adminProps.load(reader);
- }
+ adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
LOG.debug("No admin properties found at {}.", adminPropertiesPath);
From cff47b1c7389ee96de106cd98ac0d365ae3d1c97 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 14:36:55 +0100
Subject: [PATCH 32/33] wrap system properties instead of direct modification
---
.../launcher/AdminPropertiesSetter.java | 16 +++++++++-------
.../launcher/AdminPropertiesSetterTest.java | 2 +-
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 127d5b263..17c52025e 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -53,23 +53,25 @@ class AdminPropertiesSetter {
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
- final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
- if (systemPropertyDefinedAdminFile == null) {
+ final String adminCfgPath = System.getProperty(ADMIN_PROP_FILE_KEY);
+ if (adminCfgPath == null) {
LOG.debug("Path to admin properties file is not defined.");
return systemProps;
}
- var adminProps = loadAdminProperties(Path.of(systemPropertyDefinedAdminFile));
+ var adminProps = loadAdminProperties(Path.of(adminCfgPath));
+ var newSystemProps = new Properties(systemProps);
for (var key : adminProps.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ LOG.info("Overwriting {} with value {} from admin config.", key, value);
+ newSystemProps.setProperty(key, value);
} else {
- LOG.debug("Property {} in admin properties is not supported for override.", key);
+ LOG.debug("Property {} in admin config is not supported for override.", key);
}
}
- return systemProps;
+ System.setProperties(newSystemProps);
+ return newSystemProps;
}
//visible for testing
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index ef855dd5e..ae1d940f3 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -79,7 +79,7 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("If system property for config path is null, skip do not load anything")
+ @DisplayName("If system property for config path is null, skip loading and replacing")
void skipAdjustSystemPropertiesOnUndefinedProperty() {
Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
From 33851a85593427eeb0cdbf58b9757c77c7eea6d6 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:12:52 +0100
Subject: [PATCH 33/33] Refactor admin props
* rename class to AdminPropertiesFactory
* rename factory method to "create"
* remove side effects from methods
* returned properties default to system properties
---
...etter.java => AdminPropertiesFactory.java} | 38 ++++++++++---------
.../org/cryptomator/launcher/Cryptomator.java | 4 +-
...t.java => AdminPropertiesFactoryTest.java} | 25 ++++++------
3 files changed, 35 insertions(+), 32 deletions(-)
rename src/main/java/org/cryptomator/launcher/{AdminPropertiesSetter.java => AdminPropertiesFactory.java} (62%)
rename src/test/java/org/cryptomator/launcher/{AdminPropertiesSetterTest.java => AdminPropertiesFactoryTest.java} (74%)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
similarity index 62%
rename from src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
rename to src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
index 17c52025e..6a77f31cd 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
@@ -14,9 +14,11 @@ import java.util.Properties;
import java.util.Set;
/**
- * Class to overwrite system properties with an external properties file
+ * Factory to generate admin properties.
+ *
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} reads the properties file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
+ * Admin properties are {@link Properties} using system properties as defaults, but allow overwriting a specific set of properties with an external config file.
+ * Those properties are created by calling {@link #create()}. The method first reads system property {@value #ADMIN_PROP_FILE_KEY}. If it contains a path to a valid properties file, all overridable properties from the file are loaded into the returned admin properties.
*
* The overridable properties are:
*
@@ -27,9 +29,10 @@ import java.util.Set;
*
cryptomator.disableUpdateCheck
*
*
+ * @see Properties
* @see System#getProperties()
*/
-class AdminPropertiesSetter {
+class AdminPropertiesFactory {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
@@ -43,39 +46,38 @@ class AdminPropertiesSetter {
/**
- * Adjusts the system properties by loading administrative properties from a predefined file location.
+ * Creates new {@link Properties} containing overridable properties from the admin config.
*
- * If the file exists and is a valid properties file, its content will overwrite existing system properties.
- * Only some properties can be overridden, see {@link AdminPropertiesSetter}
+ * The returned properties object uses as default the {@link System} properties.
+ * For a list of overridable properties, see {@link AdminPropertiesFactory}
*
- * @return The adjusted system properties.
+ * @return {@link Properties} containing overridable properties from the admin config and defaulting to system properties.
*/
- static Properties adjustSystemProperties() {
+ static Properties create() {
var systemProps = System.getProperties();
+ var adminProps = new Properties(systemProps);
final String adminCfgPath = System.getProperty(ADMIN_PROP_FILE_KEY);
if (adminCfgPath == null) {
- LOG.debug("Path to admin properties file is not defined.");
- return systemProps;
+ LOG.debug("Admin config property is not defined. Skipping.");
+ return adminProps;
}
- var adminProps = loadAdminProperties(Path.of(adminCfgPath));
+ var propsFromFile = loadPropertiesFromFile(Path.of(adminCfgPath));
- var newSystemProps = new Properties(systemProps);
- for (var key : adminProps.stringPropertyNames()) {
+ for (var key : propsFromFile.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
- var value = adminProps.getProperty(key);
+ var value = propsFromFile.getProperty(key);
LOG.info("Overwriting {} with value {} from admin config.", key, value);
- newSystemProps.setProperty(key, value);
+ adminProps.setProperty(key, value);
} else {
LOG.debug("Property {} in admin config is not supported for override.", key);
}
}
- System.setProperties(newSystemProps);
- return newSystemProps;
+ return adminProps;
}
//visible for testing
- static Properties loadAdminProperties(Path adminPropertiesPath) {
+ static Properties loadPropertiesFromFile(Path adminPropertiesPath) {
var adminProps = new Properties();
try (FileChannel ch = FileChannel.open(adminPropertiesPath, StandardOpenOption.READ); //
Reader reader = Channels.newReader(ch, StandardCharsets.UTF_8)) {
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 855ef6e1d..4a6d0750d 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -35,8 +35,8 @@ public class Cryptomator {
private static final long STARTUP_TIME = System.currentTimeMillis();
static {
- var adjustedSystemProps = AdminPropertiesSetter.adjustSystemProperties();
- var lazyProcessedProps = new SubstitutingProperties(adjustedSystemProps, System.getenv());
+ var adminProps = AdminPropertiesFactory.create();
+ var lazyProcessedProps = new SubstitutingProperties(adminProps, System.getenv());
System.setProperties(lazyProcessedProps);
CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
LOG = LoggerFactory.getLogger(Cryptomator.class);
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
similarity index 74%
rename from src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
rename to src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
index ae1d940f3..d75dc7fba 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
-public class AdminPropertiesSetterTest {
+public class AdminPropertiesFactoryTest {
private static final String PROPS = """
fruit=banana
@@ -37,7 +37,7 @@ public class AdminPropertiesSetterTest {
out.write(bytes);
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
Assertions.assertAll(List.of( //
() -> MatcherAssert.assertThat(properties, hasEntry("fruit", "banana")), //
() -> MatcherAssert.assertThat(properties, hasEntry("vegetable", "kärrot")), //
@@ -48,7 +48,7 @@ public class AdminPropertiesSetterTest {
@DisplayName("Loading not existing file returns empty properties")
void loadNotExistingFile(@TempDir Path path) {
var config = path.resolve("config.properties");
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@@ -61,7 +61,7 @@ public class AdminPropertiesSetterTest {
out.write(bytes);
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@@ -74,22 +74,23 @@ public class AdminPropertiesSetterTest {
channel.write(ByteBuffer.wrap("test=test".getBytes()));
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@Test
- @DisplayName("If system property for config path is null, skip loading and replacing")
- void skipAdjustSystemPropertiesOnUndefinedProperty() {
+ @DisplayName("If system properties do not contain config path, skip loading")
+ void skipLoadIfFilePathIsNotDefined() {
Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
- try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(new Properties());
+ try (var adminPropSetterMock = mockStatic(AdminPropertiesFactory.class)) {
+ adminPropSetterMock.when(AdminPropertiesFactory::create).thenCallRealMethod();
+ adminPropSetterMock.when(() -> AdminPropertiesFactory.loadPropertiesFromFile(any())).thenReturn(new Properties());
- AdminPropertiesSetter.adjustSystemProperties();
+ var adminProps = AdminPropertiesFactory.create();
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.loadAdminProperties(any()), never());
+ adminPropSetterMock.verify(() -> AdminPropertiesFactory.loadPropertiesFromFile(any()), never());
+ Assertions.assertEquals(System.getProperty("user.home"), adminProps.getProperty("user.home"));
}
}