Compare commits

..
Author SHA1 Message Date
Armin Schrenk 4b6afb5fd6 if linked hub vault is alread present unlock/reveal
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-08-31 18:59:34 +02:00
Armin Schrenk d2b1f86d85 Refactor Keyloading to use only the required info
* new record KeyLoadingRef(config, name)
* masterkey vaults still need Vault.java
* hub relies on KeyLoadingRef

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-08-31 15:53:54 +02:00
Armin Schrenk b01478f0fa Remove VaultTemplate stuff
including jdk.zipfs as dependency in jlink

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-08-26 17:08:29 +02:00
Armin Schrenk 27eb6298ae Refactor VaultCreationEvent to OpenHubVaultEvent
and change deeplink URL scheme to org.cryptomator

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-08-26 16:50:26 +02:00
Armin Schrenk 2c53afd70f simplify
there is no intermediate directory in the zip. Also clean up tests

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-21 17:06:17 +02:00
Armin Schrenk ae84f2f2fd move package related tasks to the importtemplate package
keeps FXApplicationWindows slim

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-21 16:22:52 +02:00
Armin Schrenk ad17f0b835 Extract zip once
and show error if extraction fails

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-21 14:21:02 +02:00
Armin Schrenk e7db172a89 restrict value of name parameter in deeplink
no path separator, no invisible chars, no trailing whitespaces

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-21 11:50:31 +02:00
Armin Schrenk 222e1f18ad use fragements section for parameters
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-20 13:52:25 +02:00
Armin Schrenk 9a3baf7119 Move VaultTemplateExtractor to package where it is actually used.
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-20 12:09:23 +02:00
Armin Schrenk 53baf3c648 Improve error handling
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-15 17:25:49 +02:00
Armin Schrenk c8a6b7cf13 show dialog on VaultCreationEvent
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-15 16:34:38 +02:00
Armin Schrenk 5d12a62e38 Replace ImportTemplate stubs with impls
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-14 17:22:45 +02:00
Armin Schrenk 40bb0079ac add ui to import a vault from template
add scaffolding, controller are stubs for now

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-14 17:00:25 +02:00
Armin Schrenk fbdf511f05 add jdk.zipfs to jlinked modules
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-14 15:31:59 +02:00
Armin Schrenk 410900ed51 add contraints to the zip'ed template
at most 2MiB, at most 30 files

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-14 13:36:55 +02:00
Armin Schrenk 1302c1fc34 add vault template extraction
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-13 16:25:59 +02:00
Armin Schrenk ea806ec97a Refactor event handling
AppLauchEvent became a sealed interface with implemented Events RevealRunningApp-, OpenFile- and ValtCreateEvent.

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-13 13:45:21 +02:00
Armin Schrenk 77dbf9414a Add DeeplinkAction
for handling URIs

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-13 10:27:23 +02:00
Armin Schrenk a3fc50fc7e remove and exclude build artifact
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-07-13 10:12:52 +02:00
Armin Schrenk 4b0e8b5841 Merge branch 'develop' into feature/deeplinking 2026-07-07 16:23:09 +02:00
Andreas OpferkuchandGitHub 688d1a4a24 README: Update JDK version from 25 to 26 (#4269)
[ci skip]
2026-07-02 21:05:18 +02:00
Tobias Hagemann 56e655c5a9 Add ApiVoid as silver sponsor [ci skip] 2026-07-01 12:15:15 +02:00
Armin Schrenk 4c9dbb60fe fix b681c26d72
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-29 14:44:13 +02:00
Armin Schrenk 7ddedea5c2 [skip ci] Merge branch 'main' into develop
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-29 13:54:55 +02:00
Armin Schrenk fc76f5f83f Merge branch 'release/1.19.3' 2026-06-29 13:52:10 +02:00
Armin Schrenk bfc8542551 remove dependency-check from release-check workflow
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-29 13:50:24 +02:00
Armin Schrenk 24a20911f1 finalize 1.19.3
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-29 13:39:27 +02:00
Armin Schrenk b681c26d72 Replace some slim runners by regular ones
Slim ubuntu runners don't have docker, thus failing executing certain actions. (i.e. https://github.com/rtCamp/action-slack-notify/issues/240)

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 13:16:17 +02:00
Armin Schrenk 34f5c06795 [skip ci] Update changelog
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 13:11:11 +02:00
Armin Schrenk f4928fd981 prepare 1.19.3
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 11:23:48 +02:00
Armin SchrenkandGitHub f748bfd953 replace zulu by temurin (#4263)
Closes #4242

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 11:13:18 +02:00
Armin SchrenkandGitHub cb689946ed Merge pull request #4262 from cryptomator/feature/4260-fix-unsigned-ps1
Sign Powershell scripts (again)
2026-06-25 10:21:47 +02:00
Armin Schrenk 38bdf6d0e4 cleanup workflow
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 10:14:42 +02:00
Armin Schrenk 409209fd89 [skip ci] update changelog
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 10:09:48 +02:00
Armin Schrenk 1fd44ff651 for scripts, do not append signature
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 01:10:10 +02:00
Armin Schrenk 9bb19b0130 extra sign steps for ps1
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 00:51:46 +02:00
Armin Schrenk 1b5319f706 check ps1 scripts after signing
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-25 00:31:19 +02:00
Armin Schrenk 33b4aa5ba3 Revert "Revert #4169 and fix ps1 files not signed"
This reverts commit 1879c6fa77.
2026-06-25 00:23:58 +02:00
Armin Schrenk 1879c6fa77 Revert #4169 and fix ps1 files not signed 2026-06-24 17:31:42 +02:00
Armin Schrenk 8b4c4dca8e Ensure also exe build uses JDK 26.0.1+8 2026-06-24 17:22:18 +02:00
Armin Schrenk 32c0327058 Update code-signing action to version 2.0.0 2026-06-24 17:22:04 +02:00
Armin SchrenkandGitHub 3be6b6eceb [skip ci] Update changelog 2026-06-24 15:51:39 +02:00
Armin Schrenk 1ff01e17f4 [skip ci] Update changelog 2026-06-24 15:43:43 +02:00
CryptobotandGitHub c932810bd8 New Crowdin updates (#4223)
New translations strings.properties

Afrikaans; Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Lithuanian; Macedonian; Marathi; Norwegian; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Sinhala; Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Urdu (Pakistan); Uyghur; Vietnamese; 

[ci skip]
2026-06-24 15:09:58 +02:00
Armin SchrenkandGitHub 4b6c4d1ce4 Merge pull request #4258 from cryptomator/feature/automatic-maven-profile
Automatically activate os specific maven profiles
2026-06-24 15:08:01 +02:00
Armin Schrenk 3d76ed5048 [skip ci] update readme 2026-06-24 15:04:48 +02:00
Armin Schrenk 47d2900f37 automatically activate os specific maven profiles 2026-06-24 14:55:38 +02:00
Armin SchrenkandGitHub 95f5c7aecd Merge pull request #4255 from cryptomator/feature/javafx25.0.3
Update to openjfx 25.0.3
2026-06-24 13:24:41 +02:00
Armin SchrenkandGitHub bda019f0b4 Merge branch 'develop' into feature/javafx25.0.3 2026-06-24 08:55:33 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
69e87c067e Bump the maven-dependencies group across 1 directory with 16 updates (#4257)
Bumps the maven-dependencies group with 16 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [org.cryptomator:webdav-nio-adapter](https://github.com/cryptomator/webdav-nio-adapter) | `3.0.1` | `3.0.2` |
| [org.cryptomator:integrations-api](https://github.com/cryptomator/integrations-api) | `1.8.0` | `1.9.0` |
| org.slf4j:slf4j-api | `2.0.17` | `2.0.18` |
| [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) | `1.5.32` | `1.5.35` |
| [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.5.32` | `1.5.35` |
| [com.auth0:java-jwt](https://github.com/auth0/java-jwt) | `4.5.1` | `4.5.2` |
| [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.21.1` | `2.21.4` |
| com.fasterxml.jackson.datatype:jackson-datatype-jsr310 | `2.21.1` | `2.21.4` |
| [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) | `3.2.3` | `3.2.4` |
| [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework) | `6.0.3` | `6.1.0` |
| [org.mockito:mockito-core](https://github.com/mockito/mockito) | `5.22.0` | `5.23.0` |
| [org.cryptomator:integrations-win](https://github.com/cryptomator/integrations-win) | `1.6.0` | `1.6.1` |
| [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin) | `3.10.0` | `3.11.0` |
| [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.5.3` | `3.5.6` |
| [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) | `0.8.14` | `0.8.15` |
| [org.owasp:dependency-check-maven](https://github.com/dependency-check/DependencyCheck) | `12.2.0` | `12.2.2` |



Updates `org.cryptomator:webdav-nio-adapter` from 3.0.1 to 3.0.2
- [Release notes](https://github.com/cryptomator/webdav-nio-adapter/releases)
- [Changelog](https://github.com/cryptomator/webdav-nio-adapter/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/webdav-nio-adapter/compare/3.0.1...3.0.2)

Updates `org.cryptomator:integrations-api` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/cryptomator/integrations-api/releases)
- [Changelog](https://github.com/cryptomator/integrations-api/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/integrations-api/compare/1.8.0...1.9.0)

Updates `org.slf4j:slf4j-api` from 2.0.17 to 2.0.18

Updates `ch.qos.logback:logback-core` from 1.5.32 to 1.5.35
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.35)

Updates `ch.qos.logback:logback-classic` from 1.5.32 to 1.5.35
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.35)

Updates `ch.qos.logback:logback-classic` from 1.5.32 to 1.5.35
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.35)

Updates `com.auth0:java-jwt` from 4.5.1 to 4.5.2
- [Release notes](https://github.com/auth0/java-jwt/releases)
- [Changelog](https://github.com/auth0/java-jwt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/java-jwt/compare/4.5.1...4.5.2)

Updates `com.fasterxml.jackson.core:jackson-databind` from 2.21.1 to 2.21.4
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.21.1 to 2.21.4

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.21.1 to 2.21.4

Updates `com.github.ben-manes.caffeine:caffeine` from 3.2.3 to 3.2.4
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.3...v3.2.4)

Updates `org.junit.jupiter:junit-jupiter` from 6.0.3 to 6.1.0
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.3...r6.1.0)

Updates `org.mockito:mockito-core` from 5.22.0 to 5.23.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.22.0...v5.23.0)

Updates `org.cryptomator:integrations-win` from 1.6.0 to 1.6.1
- [Release notes](https://github.com/cryptomator/integrations-win/releases)
- [Changelog](https://github.com/cryptomator/integrations-win/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/integrations-win/compare/1.6.0...1.6.1)

Updates `org.apache.maven.plugins:maven-dependency-plugin` from 3.10.0 to 3.11.0
- [Release notes](https://github.com/apache/maven-dependency-plugin/releases)
- [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.10.0...maven-dependency-plugin-3.11.0)

Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.3 to 3.5.6
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.3...surefire-3.5.6)

Updates `org.jacoco:jacoco-maven-plugin` from 0.8.14 to 0.8.15
- [Release notes](https://github.com/jacoco/jacoco/releases)
- [Commits](https://github.com/jacoco/jacoco/compare/v0.8.14...v0.8.15)

Updates `org.owasp:dependency-check-maven` from 12.2.0 to 12.2.2
- [Release notes](https://github.com/dependency-check/DependencyCheck/releases)
- [Changelog](https://github.com/dependency-check/DependencyCheck/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dependency-check/DependencyCheck/compare/v12.2.0...v12.2.2)

---
updated-dependencies:
- dependency-name: org.cryptomator:webdav-nio-adapter
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: org.cryptomator:integrations-api
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: maven-dependencies
- dependency-name: org.slf4j:slf4j-api
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: ch.qos.logback:logback-core
  dependency-version: 1.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: com.auth0:java-jwt
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.21.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-version: 2.21.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-version: 2.21.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: com.github.ben-manes.caffeine:caffeine
  dependency-version: 3.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-version: 6.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: maven-dependencies
- dependency-name: org.mockito:mockito-core
  dependency-version: 5.23.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: maven-dependencies
- dependency-name: org.cryptomator:integrations-win
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: org.apache.maven.plugins:maven-dependency-plugin
  dependency-version: 3.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: maven-dependencies
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: org.jacoco:jacoco-maven-plugin
  dependency-version: 0.8.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
- dependency-name: org.owasp:dependency-check-maven
  dependency-version: 12.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: maven-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 17:25:42 +02:00
Armin Schrenk fc7cf0da2d manually update javafx
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-23 16:38:32 +02:00
Armin SchrenkandGitHub c3de54f256 Merge pull request #4254 from cryptomator/dependabot/github_actions/github-actions-0c3dbf5312
Bump the github-actions group across 1 directory with 11 updates
2026-06-23 16:31:21 +02:00
Armin Schrenk 40f0a363c9 Update to openjfx 25.0.3 2026-06-23 11:55:42 +02:00
dependabot[bot]andGitHub 09e8cc7fdf Bump the github-actions group across 1 directory with 11 updates
Bumps the github-actions group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.2` | `7.0.0` |
| [actions/setup-java](https://github.com/actions/setup-java) | `5.2.0` | `5.3.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.0` | `7.0.1` |
| [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2.5.0` | `3.0.1` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.0` | `8.0.1` |
| [rtCamp/action-slack-notify](https://github.com/rtcamp/action-slack-notify) | `2.3.3` | `2.4.0` |
| [SamKirkland/FTP-Deploy-Action](https://github.com/samkirkland/ftp-deploy-action) | `4.3.6` | `4.4.0` |
| [actions/cache](https://github.com/actions/cache) | `5.0.4` | `5.0.5` |
| [skymatic/workflows/.github/workflows/run-dependency-check.yml](https://github.com/skymatic/workflows) | `3.0.3` | `3.1.0` |
| [actions/github-script](https://github.com/actions/github-script) | `8.0.0` | `9.0.0` |
| [actions/stale](https://github.com/actions/stale) | `10.2.0` | `10.3.0` |



Updates `actions/checkout` from 6.0.2 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

Updates `actions/setup-java` from 5.2.0 to 5.3.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/be666c2fcd27ec809703dec50e508c2fdc7f6654...ad2b38190b15e4d6bdf0c97fb4fca8412226d287)

Updates `actions/upload-artifact` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

Updates `softprops/action-gh-release` from 2.5.0 to 3.0.1
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2.5.0...718ea10b132b3b2eba29c1007bb80653f286566b)

Updates `actions/download-artifact` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v8...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

Updates `rtCamp/action-slack-notify` from 2.3.3 to 2.4.0
- [Release notes](https://github.com/rtcamp/action-slack-notify/releases)
- [Commits](https://github.com/rtcamp/action-slack-notify/compare/e31e87e03dd19038e411e38ae27cbad084a90661...33ca3be66c6f378fe1610fd1d5258632dbed5e58)

Updates `SamKirkland/FTP-Deploy-Action` from 4.3.6 to 4.4.0
- [Release notes](https://github.com/samkirkland/ftp-deploy-action/releases)
- [Commits](https://github.com/samkirkland/ftp-deploy-action/compare/a51268f67f6605236975928ae28b0f7e9971d50a...110f9186c050f71550953127052e77650219c287)

Updates `actions/cache` from 5.0.4 to 5.0.5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/668228422ae6a00e4ad889ee87cd7109ec5666a7...27d5ce7f107fe9357f9df03efb73ab90386fccae)

Updates `skymatic/workflows/.github/workflows/run-dependency-check.yml` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/skymatic/workflows/releases)
- [Commits](https://github.com/skymatic/workflows/compare/957d3c2c08c56855fdac41e5afb9a7aca8c30dd9...8356563bf7b8d1c8d693f75ca487e8f57573cec9)

Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/ed597411d8f924073f98dfc5c65a23a2325f34cd...3a2844b7e9c422d3c10d287c895573f7108da1b3)

Updates `actions/stale` from 10.2.0 to 10.3.0
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/b5d41d4e1d5dceea10e7104786b73624c18a190f...eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-java
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: rtCamp/action-slack-notify
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: SamKirkland/FTP-Deploy-Action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: skymatic/workflows/.github/workflows/run-dependency-check.yml
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/github-script
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/stale
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 09:54:10 +00:00
Armin Schrenk b4ea03a7cd remove maven-surefire-junit5-tree-reporter
incompatible with surefire 3.5.4 and above
2026-06-23 11:50:15 +02:00
Armin Schrenk a30e887177 Stay on Jackson 2.21.x
LTS release supported until 2028
2026-06-23 11:47:58 +02:00
Armin Schrenk a428ae6260 Implement uri handling inside app.
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-10 15:48:25 +02:00
Armin Schrenk 9c41e348fa register url scheme "cryptomator" in installers
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-10 11:53:56 +02:00
Armin SchrenkandGitHub 331f66db0c Merge pull request #4243 from cryptomator/feature/not-a-vault-dir
Feature: Improve UX when importing vaults with invalid structure
2026-06-10 11:24:00 +02:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
dd5827204c Bump Maven Wrapper distribution to Apache Maven 3.9.16 (#4247)
* Add Maven Wrapper and migrate Maven invocations

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-03 18:03:47 +02:00
Armin Schrenk 8bee60975e Merge branch 'develop' into feature/not-a-vault-dir 2026-06-03 16:59:19 +02:00
Armin SchrenkandGitHub f96a03ae30 Upgrade project JDK version from 25 to 26
references #4244
2026-06-03 16:35:03 +02:00
Armin SchrenkandGitHub 41ce350b61 Merge pull request #4244 from cryptomator/feature/jdk26
Feature: JDK 26
2026-06-03 10:44:40 +02:00
Armin Schrenk 64b0e63b21 cleaner errro report for invalid vaults 2026-06-03 10:41:45 +02:00
Armin Schrenk 6c62b4d293 prevent duplicates when adding same vault several times
do checking and adding on the fx thread.
2026-06-03 10:41:23 +02:00
Armin Schrenk 3f312b183e Prevent any blocking questions
when adding coffeelibs repo
2026-06-03 09:13:26 +02:00
Armin Schrenk 719a91692a cleanup 2026-06-02 20:21:16 +02:00
Armin Schrenk 85b7265b95 Place vault storage location check in separate functions 2026-06-02 20:21:02 +02:00
Armin Schrenk fbdf1b1bdb Replace openjdk by coffeelibs-jdk again
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-06-02 18:27:39 +02:00
Armin Schrenk ae2fd74411 Revert "test if signing issue is fixed with temurin"
This reverts commit 602c206dae.
2026-06-01 11:52:41 +02:00
Armin Schrenk 49ca7f4891 Bump jdk to 26 (or 26.0.1+8) 2026-06-01 09:52:57 +02:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>infeo
d5d992b1c2 Add dependency-check suppressions for new Jetty CVEs (#4241)
Agent-Logs-Url: https://github.com/cryptomator/cryptomator/sessions/7fec8174-cf7f-4860-9a62-1299e45a5ca6

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: infeo <9036915+infeo@users.noreply.github.com>
2026-05-18 11:23:09 +02:00
Armin Schrenk 01b30b173e enhance message 2026-05-12 12:09:50 +02:00
Armin Schrenk be4a39c628 Run validation on non-fx thread
old code can still run it on fx thread
2026-05-12 09:39:07 +02:00
Armin Schrenk e63a0e8ee8 Show user improved error message 2026-05-12 08:30:34 +02:00
Armin Schrenk 6c925d6a1f Throw more specific error about vault directory 2026-05-11 12:12:58 +02:00
Armin Schrenk 602c206dae test if signing issue is fixed with temurin 2026-05-05 11:23:14 +02:00
Armin Schrenk 426b33ae1a fix win-update-url 2026-05-04 11:55:40 +02:00
jose-cardoso-a22506616andGitHub 472b10a8a4 Merge pull request #4228 from jose-cardoso-a22506616/issue-3993
Closes #3993
2026-05-04 08:50:10 +02:00
Armin Schrenk 8e52161fa4 Use JDK 26.0.1+8 2026-04-29 12:01:03 +02:00
Armin Schrenk f1540e13eb Also use new template properties file in CI 2026-04-29 11:58:45 +02:00
Armin Schrenk 2c2165cb22 Fix wixhelper.dll not found
file is named now msica.dll
2026-04-29 11:58:07 +02:00
Armin Schrenk 03cb5f6be9 Closes #4219 2026-04-28 18:23:44 +02:00
Armin Schrenk 8a98048835 update windows build script to stop on errors 2026-04-28 16:14:39 +02:00
Armin SchrenkandGitHub 2fe7fe886c Merge pull request #4227 from yugui923/fix/vault-options-mount-scroll
fix(ui): make vault options mounting tab scrollable
2026-04-27 14:50:46 +02:00
Armin Schrenk 459112f5b8 Fix release workflow
"workflow_call" is not a event name
2026-04-27 13:01:13 +02:00
Armin SchrenkandGitHub ea86124c7d Merge pull request #4205 from cryptomator/feature/immutable-releases
Feature: Refactor Release pipeline to allow immutable releases
2026-04-27 09:22:01 +02:00
Yuri Gui cd9e315447 fix(ui): make Vault Options "Mounting" tab scrollable
Wrap the mounting-options VBox in a ScrollPane so that conditionally
shown rows (drive-letter selector, custom mount-point picker, custom
mount flags) remain reachable when the Vault Options window is at or
near its 300px minimum height. Previously the rows were clipped past
the bottom of the tab with no scrollbar, forcing the user to enlarge
the window manually before the controls could be seen or interacted
with.

Fixes #3150

Co-Authored-By: Yuri Gui <yugui923@users.noreply.github.com>
2026-04-25 14:41:28 +00:00
Armin Schrenk 1266926ebb fix docs
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-04-24 15:39:05 +02:00
Armin Schrenk 1e6bc1f043 Update RELEASE doc
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-04-24 15:10:57 +02:00
Armin Schrenk 1230e787a9 Update RELEASE.md
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2026-04-21 15:36:54 +02:00
Armin Schrenk da8493f25f add release documentation 2026-04-16 17:57:39 +02:00
Armin Schrenk 4a0a4309c2 Replace get-version workflow call with slim job 2026-04-16 17:53:08 +02:00
Armin Schrenk c52ae0120d update changelog 2026-04-16 17:51:15 +02:00
Armin Schrenk e6cef947c1 rename job 2026-04-16 17:51:06 +02:00
Armin Schrenk 08009ca1a0 Merge branch 'develop' into feature/immutable-releases
# Conflicts:
#	.github/workflows/appimage.yml
#	.github/workflows/mac-dmg-x64.yml
#	.github/workflows/mac-dmg.yml
#	.github/workflows/win-exe.yml
2026-04-14 12:14:23 +02:00
Armin Schrenk d27a52752b improve readability of version splitting 2026-04-14 11:40:02 +02:00
Armin Schrenk c745fca2ea update draft-release.yml
adhere to new workflow_call api
2026-04-13 17:58:18 +02:00
Armin Schrenk d1fcf528b0 use pwsh to move reattach burn engine 2026-04-13 17:40:43 +02:00
Armin Schrenk b6133e481c Refactor appimage, dmg and msi/exe workflows
* unify inputs handling for worklfow dispatch and call
* simplify conditions for signing/release steps
2026-04-13 16:27:12 +02:00
LamTrinh.DevandGitHub d67aa9c10a + Update Copyright year from "2016 - 2025" to "2016-2026". (#4218)
* + Update Copyright year from "2016 - 2025" to "2016-2026".
2026-04-13 15:30:33 +02:00
Armin Schrenk 19a9595f2e rename worfklow to clearly indicate what it does 2026-04-13 11:46:03 +02:00
Armin Schrenk f21ae0e11c fix notarization of dmg files on workflow calls 2026-04-13 11:37:51 +02:00
Armin Schrenk 54f805a0c9 fix exe installer not found after 2026-04-09 17:18:07 +02:00
Armin Schrenk aa239418cf use more local environment instead of copying data into script 2026-04-09 16:57:30 +02:00
Armin Schrenk 8f0c6eb994 fix bugs
* win-exe still checked for release event
* only perform appimage checksum step on release
* remove unused input in appimage flow
2026-04-09 16:55:08 +02:00
Armin Schrenk 4751c81d06 adjust hub-url in share vault dialog 2026-04-08 16:49:11 +02:00
Armin Schrenk 0e1132133c replace guava cache with caffeine 2026-04-08 16:23:25 +02:00
Armin Schrenk c7d77091f5 Merge branch 'develop' into feature/immutable-releases
# Conflicts:
#	.github/workflows/appimage.yml
#	.github/workflows/build.yml
#	.github/workflows/mac-dmg-x64.yml
#	.github/workflows/mac-dmg.yml
#	.github/workflows/post-publish.yml
2026-04-08 16:08:23 +02:00
Armin Schrenk 3a4039d663 use ubuntu-slim runner for small tasks 2026-04-08 13:00:23 +02:00
Armin Schrenk f94d204604 cleanup 2026-04-08 12:57:32 +02:00
Armin Schrenk d76dbe0ddb Replace automatic version file update
by simple notification
2026-04-08 12:54:27 +02:00
Armin Schrenk 235a23ce77 fix check for tag belongs to main or release branch 2026-04-08 12:38:23 +02:00
Armin Schrenk 357b30684b point to correct release workflow file in template 2026-04-08 12:37:50 +02:00
Armin Schrenk 517e12a586 pin jdk version for release 2026-04-08 12:35:54 +02:00
Armin Schrenk 96aa8fe180 add pkgrel update step for aur-bin 2026-04-07 17:20:08 +02:00
Armin Schrenk 7bffc317ff rename workflow from release to create-release
to prevent confusion with github release.yml standard
2026-04-07 17:05:30 +02:00
Armin Schrenk 7dde389671 move aur-bin PR creation into own workflow 2026-04-07 17:04:56 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a40f17e8f1 Bump the github-actions group with 5 updates (#4207)
Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2.5.0` | `2.6.1` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.0` | `8.0.1` |
| [actions/cache](https://github.com/actions/cache) | `5.0.3` | `5.0.4` |
| [skymatic/semver-validation-action](https://github.com/skymatic/semver-validation-action) | `3.0.0` | `4.0.0` |
| [vedantmgoyal2009/winget-releaser](https://github.com/vedantmgoyal2009/winget-releaser) | `19e706d4c9121098010096f9c495a70a7518b30f` | `7bd472be23763def6e16bd06cc8b1cdfab0e2fd5` |


Updates `softprops/action-gh-release` from 2.5.0 to 2.6.1
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/a06a81a03ee405af7f2048a818ed3f03bbf83c7b...153bb8e04406b158c6c84fc1615b65b24149a1fe)

Updates `actions/download-artifact` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

Updates `actions/cache` from 5.0.3 to 5.0.4
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/cdf6c1fa76f9f475f3d7449005a359c84ca0f306...668228422ae6a00e4ad889ee87cd7109ec5666a7)

Updates `skymatic/semver-validation-action` from 3.0.0 to 4.0.0
- [Release notes](https://github.com/skymatic/semver-validation-action/releases)
- [Commits](https://github.com/skymatic/semver-validation-action/compare/7a6ae1c9e121540d11c9c7e4e667c83d583aa153...7c80b6b03a18b42884761daa9862ff5683ec8c8a)

Updates `vedantmgoyal2009/winget-releaser` from 19e706d4c9121098010096f9c495a70a7518b30f to 7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
- [Release notes](https://github.com/vedantmgoyal2009/winget-releaser/releases)
- [Commits](https://github.com/vedantmgoyal2009/winget-releaser/compare/19e706d4c9121098010096f9c495a70a7518b30f...7bd472be23763def6e16bd06cc8b1cdfab0e2fd5)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/cache
  dependency-version: 5.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: skymatic/semver-validation-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: vedantmgoyal2009/winget-releaser
  dependency-version: 7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
  dependency-type: direct:production
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 11:39:43 +02:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>infeo
699e22d31a chore: Consolidate dependabot maven groups and switch to monthly schedule (#4208)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: infeo <9036915+infeo@users.noreply.github.com>
2026-04-02 16:58:12 +02:00
Armin Schrenk 5d5b9137cc fix workflows 2026-04-01 16:38:07 +02:00
Armin Schrenk 02767b0caf pin aws-configure action 2026-04-01 16:29:59 +02:00
Armin Schrenk ddd8c572e7 prevent accidental releases of unsigend tags 2026-04-01 16:27:20 +02:00
Armin Schrenk aa8ccf53df remove manual trigger for workflow 2026-04-01 16:16:46 +02:00
Armin Schrenk c199561a9e Link to the changelog of the git tag
in the release notes
2026-04-01 16:10:26 +02:00
Armin Schrenk e341983ffe add update-latest-version job to post-publish 2026-04-01 14:14:33 +02:00
Armin Schrenk f10b28ecb1 fix typos 2026-03-31 18:11:41 +02:00
Armin Schrenk 95eed96a37 gate windows specifc post-publishes task with check
for windows artifacts in release
2026-03-31 16:02:32 +02:00
mindmonkandGitHub 9de0981594 Merge pull request #4164 from cryptomator/feature/decrypt-name-single-dialog-per-vault
Fix Decrypt File Name dialog to reuse a single window
2026-03-31 11:02:13 +02:00
Armin Schrenk 4d6ce70afe Merge branch 'develop' into feature/immutable-releases
# Conflicts:
#	.github/workflows/appimage.yml
#	.github/workflows/mac-dmg-x64.yml
#	.github/workflows/mac-dmg.yml
#	.github/workflows/win-exe.yml
2026-03-30 15:37:06 +02:00
Armin Schrenk e75deb282c use correct cmd
for computing sha sum
2026-03-30 15:33:24 +02:00
Jan-Peter Klein 87fe209b54 Merge branch 'develop' into feature/decrypt-name-single-dialog-per-vault 2026-03-30 12:47:55 +02:00
Jan-Peter Klein 21cffb13c7 remove unused initialList field from DecryptFileNamesViewController 2026-03-30 12:46:11 +02:00
Armin Schrenk ee92cc4e9f Add docs update workflow 2026-03-30 12:22:03 +02:00
Jan-Peter Klein 9eca05aeb7 address infeos review: drop paths injection, close on locked vault, clear mapping on close 2026-03-30 12:15:09 +02:00
Armin Schrenk 57c84627f9 Update release draft with sed and envsubst
instead of awk
2026-03-30 12:07:08 +02:00
Armin Schrenk 309a8fc705 adjust repository dispatch for website update
* change event type to desktop-release
* add client payload
2026-03-26 10:48:47 +01:00
Armin Schrenk 0b77e6ce04 use release object in event payload 2026-03-25 23:15:13 +01:00
Armin Schrenk d22e912ca4 fix wrong webhook 2026-03-25 15:14:35 +01:00
Armin Schrenk 690726efc3 adjust post-publish
* run av-whitelist after publish
* notify winget
* remove gpg signature creation
2026-03-25 11:43:58 +01:00
Armin Schrenk d9c0c4980b add release flow to create releases 2026-03-25 11:42:26 +01:00
Armin Schrenk 4e5eef5a16 remove release drafting from build flow 2026-03-25 11:41:54 +01:00
Armin Schrenk 81b2081033 fix outputs typo in appimage flow 2026-03-25 11:41:38 +01:00
Armin Schrenk d9134b49ad add workflow_call to release artifact workflows 2026-03-06 17:11:11 +01:00
Jan-Peter Klein 92d11d8bd5 Merge branch 'develop' into feature/decrypt-name-single-dialog-per-vault 2026-03-02 14:18:15 +01:00
Jan-Peter Klein 300a811510 fix decrypt file name dialog to reuse one window per vault 2026-03-02 13:39:38 +01:00
151 changed files with 4412 additions and 1080 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ runs:
echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT" echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT"
shell: bash shell: bash
- name: Sign DLLs with Azure Trusted Signing - name: Sign DLLs with Azure Trusted Signing
uses: azure/artifact-signing-action@87c2e83e6868da99d3380aa309851b32ed9a8346 # v1.1.0 uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with: with:
files-folder: ${{ inputs.base-dir }} files-folder: ${{ inputs.base-dir }}
files-folder-filter: ${{ inputs.file-extensions }} files-folder-filter: ${{ inputs.file-extensions }}
+4 -36
View File
@@ -3,51 +3,19 @@ updates:
- package-ecosystem: "maven" - package-ecosystem: "maven"
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "monthly"
day: "monday"
time: "06:00"
timezone: "Etc/UTC"
ignore: ignore:
- dependency-name: "org.cryptomator:integrations-api" - dependency-name: "org.cryptomator:integrations-api"
versions: ["2.0.0-alpha1"] versions: ["2.0.0-alpha1"]
- dependency-name: "jakarta.inject:jakarta.inject-api" - dependency-name: "jakarta.inject:jakarta.inject-api"
versions: ["2.0.1.MR"] versions: ["2.0.1.MR"]
- dependency-name: "org.openjfx:*" - dependency-name: "org.openjfx:*"
update-types: ["version-update:semver-major"] - dependency-name: "com.fasterxml.jackson.*"
# due to https://github.com/fabriciorby/maven-surefire-junit5-tree-reporter/issues/68 versions: [ "[2.22,)" ] # 2.21.x is LTS, see https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.21
- dependency-name: "org.apache.maven.plugins:maven-surefire-plugin"
versions: [ "3.5.4", "3.5.5" ]
groups: groups:
java-test-dependencies: maven-dependencies:
patterns:
- "org.junit.jupiter:*"
- "org.mockito:*"
- "org.hamcrest:*"
- "com.google.jimfs:jimfs"
maven-build-plugins:
patterns:
- "org.apache.maven.plugins:*"
- "org.jacoco:jacoco-maven-plugin"
- "org.owasp:dependency-check-maven"
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
- "org.codehaus.mojo:license-maven-plugin"
javafx:
patterns:
- "org.openjfx:*"
java-production-dependencies:
patterns: patterns:
- "*" - "*"
exclude-patterns:
- "org.openjfx:*"
- "org.apache.maven.plugins:*"
- "org.jacoco:jacoco-maven-plugin"
- "org.owasp:dependency-check-maven"
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
- "org.codehaus.mojo:license-maven-plugin"
- "org.junit.jupiter:*"
- "org.mockito:*"
- "org.hamcrest:*"
- "com.google.jimfs:jimfs"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" # even for `.github/workflows` directory: "/" # even for `.github/workflows`
+34
View File
@@ -0,0 +1,34 @@
<!-- HEADER -->
> [!WARN]
> 🚧 DO NOT EDIT 🚧
>
> The [builds are still running](https://github.com/cryptomator/cryptomator/actions/workflows/create-release.yml).
> This banner will be replaced after the builds are finished.
<!-- /HEADER -->
<!--REPLACE with auto-generated release notes (see below)
### What's New 🎉
### Bugfixes 🐛
### Other Changes 📎
END REPLACE-->
For a comprehensive view of changes, read the [CHANGELOG](https://github.com/cryptomator/cryptomator/blob/$VERSION/CHANGELOG.md).
---
💾 SHA-256 checksums of release artifacts:
```
$TARBALL
$EXE
$MSI
$DMG_x64
$DMG_arm64
$APPIMAGE_x86_64
$APPIMAGE_aarch64
```
> [!TIP]
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
<!-- Auto-Generated Release Notes: -->
+189
View File
@@ -0,0 +1,189 @@
# Cryptomator Release Workflow
This document describes the automated release pipeline defined in [`draft-release.yml`](draft-release.yml) and [`post-publish.yml`](post-publish.yml).
## Overview
The release process has two phases:
1. **Draft phase** (`draft-release.yml`) -- triggered by pushing a signed git tag. Compiles, tests, builds platform installers, and creates a **draft** GitHub Release.
2. **Post-publish phase** (`post-publish.yml`) -- triggered when the draft release is manually **published**. Submits Windows installers for AV whitelisting, notifies the team for DEB build and latest-version update, and triggers downstream updates (website, docs, winget).
```mermaid
---
config:
htmlLabels: false
---
flowchart TD
%% ── Trigger ──────────────────────────────────────────────
push_tag([🏷 Signed tag pushed])
%% ── Draft phase ──────────────────────────────────────────
push_tag --> get-version
subgraph draft["draft-release.yml"]
get-version["get-version
*parse semver from tag*"]
get-version --> create-release-draft
create-release-draft["create-release-draft
*compile & test (Linux)
create draft release
sign source tarball*"]
create-release-draft --> build-exe-and-msi
create-release-draft --> build-dmg-arm64
create-release-draft --> build-dmg-x64
create-release-draft --> build-appimages
build-exe-and-msi["build-exe-and-msi
*calls win-exe.yml
MSI + EXE (x64)
code-signed & GPG-signed*"]
build-dmg-arm64["build-dmg-arm64
*calls mac-dmg.yml
DMG (arm64)
notarized & GPG-signed*"]
build-dmg-x64["build-dmg-x64
*calls mac-dmg-x64.yml
DMG (x64)
notarized & GPG-signed*"]
build-appimages["build-appimages
*calls appimage.yml
AppImage (x86_64 + aarch64)
GPG-signed*"]
build-exe-and-msi --> update-sha256sums
build-dmg-arm64 --> update-sha256sums
build-dmg-x64 --> update-sha256sums
build-appimages --> update-sha256sums
update-sha256sums["update-sha256sums
*compute checksums
update release body*"]
end
update-sha256sums --> manual_review
%% ── Manual gate ──────────────────────────────────────────
manual_review{{Manual review
& publish}}
%% ── Post-publish phase ───────────────────────────────────
manual_review --> published([📢 Release published])
published --> post-publish
subgraph post-publish["post-publish.yml"]
direction TB
check-release["check-release
*classify release tag
stable, alpha, beta, rc, unknown*"]
notify["notify
*Slack notifications
deb build & version check*"]
get-asset-urls["get-asset-urls
*extract MSI & EXE
download URLs*"]
check-release --> notify-winget
check-release --> trigger-website
check-release --> trigger-docs
get-asset-urls --> allowlist-msi
allowlist-msi --> allowlist-exe
allowlist-msi["allowlist-msi-x64
*av-whitelist.yml
Kaspersky & Avast*"]
allowlist-exe["allowlist-exe-x64
*av-whitelist.yml
Kaspersky & Avast*"]
notify-winget["notify-winget
*Slack: ready for winget
stable only*"]
trigger-website["trigger-website-update
*dispatch to
cryptomator.github.io
stable only*"]
trigger-docs["trigger-docs-update
*dispatch to
cryptomator/docs
stable only, Windows*"]
end
```
## Phase 1: Draft Release (`draft-release.yml`)
**Trigger:** push of any tag (`*`)
### Jobs
| Job | Runs on | Description |
|-----|---------|-------------|
| **get-version** | ubuntu | Parses the tag into semver components (`semVerNum`, `semVerSuffix`, `revNum`, `versionType`). The release is aborted if not an alpha, beta, rc or 'stable' release. |
| **create-release-draft** | ubuntu | Checks out the repo, verifies the tag is **signed** and lives on a `main` or `release/*` branch. Runs `./mvnw verify` (with `xvfb-run`). Creates a GitHub Release **draft** using the [release body template](../release-body.md.template). Downloads and GPG-signs the source tarball. |
| **build-exe-and-msi** | windows | Calls [`win-exe.yml`](win-exe.yml). Builds the MSI and EXE bundle installer for x64 Windows. Code-signed via Azure Trusted Signing, GPG-signed, and uploaded to the draft release. Outputs SHA-256 checksums. |
| **build-dmg-arm64** | macos-15 | Calls [`mac-dmg.yml`](mac-dmg.yml). Builds the DMG for Apple Silicon. Code-signed, notarized with Apple, GPG-signed, and uploaded. Outputs SHA-256 checksum. |
| **build-dmg-x64** | macos-15-large | Calls [`mac-dmg-x64.yml`](mac-dmg-x64.yml). Same as above but for Intel Macs. Uses macFUSE instead of FUSE-T. |
| **build-appimages** | ubuntu | Calls [`appimage.yml`](appimage.yml). Builds AppImages for x86_64 and aarch64 (matrix). GPG-signed and uploaded with `.zsync` delta-update files. Outputs SHA-256 checksums. |
| **update-sha256sums** | ubuntu | Runs after all builds complete. Computes the source tarball checksum, collects all artifact checksums, and updates the draft release body via `envsubst`. Replaces the "builds still running" banner with a success notice. |
### Release Artifacts
After the draft phase, the GitHub Release contains:
| Artifact | Platform |
|----------|----------|
| `cryptomator-<ver>.tar.gz.asc` | Source (GPG signature) |
| `Cryptomator-<ver>-x64.msi` + `.asc` | Windows |
| `Cryptomator-<ver>-x64.exe` + `.asc` | Windows |
| `Cryptomator-<ver>-arm64.dmg` + `.asc` | macOS (Apple Silicon) |
| `Cryptomator-<ver>-x64.dmg` + `.asc` | macOS (Intel) |
| `cryptomator-<ver>-x86_64.AppImage` + `.zsync` + `.asc` | Linux (x86_64) |
| `cryptomator-<ver>-aarch64.AppImage` + `.zsync` + `.asc` | Linux (aarch64) |
All artifacts are signed with GPG key [`615D449FE6E6A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
## Manual Review Gate
After the draft phase completes, a maintainer reviews the draft release on GitHub. This is the point to:
- Verify all artifacts are present and checksums look correct.
- Edit the auto-generated release notes (What's New, Bugfixes, Other Changes).
- **Publish** the release when ready, which triggers phase 2.
## Phase 2: Post-Publish (`post-publish.yml`)
**Trigger:** `release: [published]`
### Jobs
| Job | Condition | Description |
|-----|-----------|-------------|
| **notify** | always | Sends Slack notifications to `#cryptomator-desktop`: ready to build `.deb` package, and reminder to update `latest-version.json` on S3. |
| **get-asset-urls** | always | Extracts MSI and EXE download URLs from the release assets. |
| **check-release** | always | Classifies the published release tag as `stable`, `alpha`, `beta`, `rc`, or `unknown`. Stable-only follow-up jobs depend on this output. Unlike `get-version.yml` workflow, this job does not perform semver validation. |
| **allowlist-msi-x64** | Windows release | Calls [`av-whitelist.yml`](av-whitelist.yml). Uploads the MSI to Kaspersky and Avast for whitelisting. |
| **allowlist-exe-x64** | Windows release | Same as above for the EXE. Runs sequentially after MSI. |
| **notify-winget** | stable + Windows | Sends a Slack notification that the release is ready for [winget submission](winget.yml). |
| **trigger-website-update** | stable | Dispatches `desktop-release` event to `cryptomator/cryptomator.github.io`. |
| **trigger-docs-update** | stable + Windows | Dispatches `desktop-release` event to `cryptomator/docs`. |
### Manual Follow-ups
These steps are triggered by team members after Slack notifications:
- **Debian package** -- Run the [`debian.yml`](debian.yml) workflow to build `.deb` and optionally upload to the PPA.
- **winget** -- Run the [`winget.yml`](winget.yml) workflow to submit to the Windows Package Manager.
- **latest-version.json** -- Update the version-check file on S3 (`static.cryptomator.org/desktop/latest-version.json`).
## Signing & Security
- **Git tag** must be SSH-signed and reside on `main` or `release/*`.
- **Windows** installers are code-signed using Azure Trusted Signing.
- **macOS** DMGs are code-signed with an Apple Developer certificate and notarized via `notarytool`.
- **All artifacts** receive a detached GPG signature (`.asc`) using key `615D449FE6E6A235`.
- **AV whitelisting** is submitted to Kaspersky and Avast after publish (Windows installers only).
- The draft release is created using `CRYPTOBOT_RELEASE_TOKEN`, not `GITHUB_TOKEN`, to ensure proper permissions and trigger downstream workflows.
+73 -108
View File
@@ -1,19 +1,44 @@
name: Build AppImage name: Build AppImage
on: on:
release:
types: [published]
schedule: schedule:
- cron: '0 23 20 * *' - cron: '0 23 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
upload-to-draft:
type: boolean
default: true
outputs:
sha256-appimage-x64:
description: "SHA256 sum of the x64 appimage"
value: ${{ jobs.collect-sha256sums.outputs.x64-sha256sum}}
sha256-appimage-aarch64:
description: "SHA256 sum of the aarch64 appimage"
value: ${{ jobs.collect-sha256sums.outputs.aarch64-sha256sum}}
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: semVerNum:
description: 'Version' description: 'The Major.Minor.Patch part of the version'
required: false required: false
create-pr: revisionNum:
description: 'Create a PR for aur-bin repo' description: 'The revision number'
type: boolean required: false
default: false semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
@@ -25,40 +50,34 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS' JAVA_VERSION: '26.0.1+8'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs: jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }} #okay if not defined
build: build:
name: Build AppImage name: Build AppImage
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [get-version]
env:
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
SEMVER_NUM: ${{ needs.get-version.outputs.semVerNum }}
REV_NUM: ${{ needs.get-version.outputs.revNum }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-latest - os: ubuntu-latest
arch: x86_64 arch: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-x64_bin-jmods.zip'
openjfx-sha: 'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b' openjfx-sha: '47035c653863a8e4be3dc6f142b8dbd84b4bb1efc9a8cbc68413e6a5ff5e9f50'
appimagetool-sha: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0' appimagetool-sha: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0'
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
arch: aarch64 arch: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646' openjfx-sha: 'e3fd682354346845d2944a2da2b1ff2b6cb9259d92027f2f9c121b9b93c5e42f'
appimagetool-sha: 'f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158' appimagetool-sha: 'f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -76,7 +95,7 @@ jobs:
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(./mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -85,9 +104,9 @@ jobs:
exit 1 exit 1
fi fi
- name: Set version - name: Set version
run : mvn versions:set -DnewVersion="$SEMVER_STR" run : ./mvnw versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
- name: Run maven - name: Run maven
run: mvn -B clean package -Plinux -DskipTests run: ./mvnw -B clean package -DskipTests
- name: Patch target dir - name: Patch target dir
run: | run: |
cp LICENSE.txt target cp LICENSE.txt target
@@ -127,13 +146,13 @@ jobs:
--dest appdir --dest appdir
--name Cryptomator --name Cryptomator
--vendor "Skymatic GmbH" --vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2025 Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${SEMVER_NUM}.${REV_NUM}" --app-version "${VERSION_NUM}.${REVISION_NUM}"
--java-options "--enable-preview" --java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" --java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
@@ -144,7 +163,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${REV_NUM}\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\""
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\""
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" --java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
@@ -183,7 +202,7 @@ jobs:
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Build AppImage - name: Build AppImage
run: > run: >
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${SEMVER_STR}-${{ matrix.arch }}.AppImage ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.arch }}.AppImage
-u "gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.arch }}.AppImage.zsync" -u "gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.arch }}.AppImage.zsync"
--sign --sign-key=615D449FE6E6A235 --sign --sign-key=615D449FE6E6A235
- name: Create detached GPG signatures - name: Create detached GPG signatures
@@ -191,7 +210,7 @@ jobs:
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: appimage-${{ matrix.arch }} name: appimage-${{ matrix.arch }}
path: | path: |
@@ -200,9 +219,10 @@ jobs:
cryptomator-*.asc cryptomator-*.asc
if-no-files-found: error if-no-files-found: error
- name: Publish AppImage on GitHub Releases - name: Publish AppImage on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' if: inputs.upload-to-draft
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with: with:
draft: true
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: | files: |
@@ -210,79 +230,24 @@ jobs:
cryptomator-*.zsync cryptomator-*.zsync
cryptomator-*.asc cryptomator-*.asc
create-aur-bin-pr: collect-sha256sums:
name: Create PR for aur-bin repo name: Collect AppImage checksums
if: github.event_name == 'workflow_dispatch' && inputs.create-pr || github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build, get-version] needs: [build]
container: if: inputs.upload-to-draft
image: archlinux:base-devel outputs:
env: x64-sha256sum: ${{ steps.sha256sum.outputs.x64-sha256sum }}
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }} aarch64-sha256sum: ${{ steps.sha256sum.outputs.aarch64-sha256sum }}
PKGDEST: ${{ github.workspace }}/pkgdest
SRCDEST: ${{ github.workspace }}/srcdest
steps: steps:
- name: Prepare pacman - name: Download AppImage artifacts
run: | uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
pacman-key --init
pacman-key --populate archlinux
pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip github-cli curl pacman-contrib
- name: Checkout cryptomator/aur-bin
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
repository: 'cryptomator/aur-bin' pattern: appimage-*
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} path: appimage-artifacts
- name: Create build user - name: Compute SHA256 sums
id: sha256sum
run: | run: |
useradd -m builder read -ra X64_SUM < <(sha256sum appimage-artifacts/appimage-x86_64/cryptomator-*-x86_64.AppImage)
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/builder read -ra AARCH64_SUM < <(sha256sum appimage-artifacts/appimage-aarch64/cryptomator-*-aarch64.AppImage)
chown -R builder:builder "$GITHUB_WORKSPACE" echo "x64-sha256sum=${X64_SUM[0]}" >> "$GITHUB_OUTPUT"
install -d -m 0755 -o builder -g builder "$PKGDEST" "$SRCDEST" echo "aarch64-sha256sum=${AARCH64_SUM[0]}" >> "$GITHUB_OUTPUT"
- name: Import Cryptomator release signing key
# try first ubuntu. on failure try openpgp keyservers
run: >
sudo -u builder gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
|| sudo -u builder gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
- name: Checkout release branch
run: |
git config --global safe.directory '*'
git checkout -b "release/${SEMVER_STR}"
- name: Update build file
run: |
sed -i -e "s|^pkgver=.*$|pkgver=${SEMVER_STR}|" PKGBUILD
sed -i -e 's|^pkgrel=.*$|pkgrel=1|' PKGBUILD
sudo -u builder updpkgsums
sudo -u builder makepkg --printsrcinfo > .SRCINFO
- name: Build package with makepkg
run: >
sudo -u builder
env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST"
makepkg --syncdeps --cleanbuild --noconfirm --log
- name: Commit and push
run: |
git config user.name "cryptobot"
git config user.email "cryptobot@users.noreply.github.com"
git config push.autoSetupRemote true
git stage PKGBUILD .SRCINFO
git commit -m "Prepare release ${SEMVER_STR}"
git push
- name: Create pull request
id: create-pr
run: |
printf "Created by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > pr_body.md
PR_URL=$(gh pr create --title "Release ${SEMVER_STR}" --body-file pr_body.md)
echo "url=$PR_URL" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR-bin release PR for ${{ github.event.repository.name }} ${{ needs.get-version.outputs.semVerStr }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: false
MSG_MINIMAL: true
+115
View File
@@ -0,0 +1,115 @@
name: PR for aur-bin repo
on:
release:
types: [published]
workflow_dispatch:
inputs:
src-tag:
description: 'Source or Release tag'
required: false
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.src-tag }}
create-aur-bin-pr:
name: Create PR for aur-bin repo
if: (github.event_name == 'workflow_dispatch') || (github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable')
runs-on: ubuntu-latest
needs: [get-version]
container:
image: archlinux:base-devel
env:
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
PKGDEST: ${{ github.workspace }}/pkgdest
SRCDEST: ${{ github.workspace }}/srcdest
steps:
- name: Prepare pacman
run: |
pacman-key --init
pacman-key --populate archlinux
pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip github-cli curl pacman-contrib
- name: Checkout cryptomator/aur-bin
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Create build user
run: |
useradd -m builder
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/builder
chown -R builder:builder "$GITHUB_WORKSPACE"
install -d -m 0755 -o builder -g builder "$PKGDEST" "$SRCDEST"
- name: Import Cryptomator release signing key
# try first ubuntu. on failure try openpgp keyservers
run: >
sudo -u builder gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
|| sudo -u builder gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
- name: Checkout release branch
run: |
git config --global safe.directory '*'
git checkout -b "release/${SEMVER_STR}"
- name: Determine pkgrel
id: pkgrel
run: |
CURRENT_VERSION="$(sed -nE 's/^pkgver=(.*)$/\1/p' PKGBUILD | head -n1)"
CURRENT_REL="$(sed -nE 's/^pkgrel=([0-9]+).*$/\1/p' PKGBUILD | head -n1)"
if [[ "$CURRENT_VERSION" == "$TARGET_VERSION" && "$CURRENT_REL" =~ ^[0-9]+$ ]]; then
NEXT_REL=$((CURRENT_REL + 1))
else
NEXT_REL=1
fi
echo "value=${NEXT_REL}" >> "$GITHUB_OUTPUT"
echo "dist-version=${TARGET_VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT"
env:
TARGET_VERSION: ${{ needs.get-version.outputs.semVerStr }}
- name: Update build file
run: |
sed -i -e "s|^pkgver=.*$|pkgver=${PKG_VERSION}|" PKGBUILD
sed -i -e "s|^pkgrel=.*$|pkgrel=${PKG_RELEASE}|" PKGBUILD
sudo -u builder updpkgsums
sudo -u builder makepkg --printsrcinfo > .SRCINFO
env:
PKG_VERSION: ${{ needs.get-version.outputs.semVerNum }}
PKG_RELEASE: ${{ steps.pkgrel.outputs.value }}
- name: Build package with makepkg
run: >
sudo -u builder
env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST"
makepkg --syncdeps --cleanbuild --noconfirm --log
- name: Commit and push
run: |
git config user.name "cryptobot"
git config user.email "cryptobot@users.noreply.github.com"
git config push.autoSetupRemote true
git stage PKGBUILD .SRCINFO
git commit -m "Prepare release ${DIST_VERSION}"
git push
env:
DIST_VERSION: ${{ steps.pkgrel.outputs.dist-version }}
- name: Create pull request
id: create-pr
run: |
printf "Created by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > pr_body.md
PR_URL=$(gh pr create --title "Release ${DIST_VERSION}" --body-file pr_body.md)
echo "url=$PR_URL" >> "$GITHUB_OUTPUT"
env:
DIST_VERSION: ${{ steps.pkgrel.outputs.dist-version }}
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR-bin release PR for ${{ github.event.repository.name }} ${{ needs.get-version.outputs.semVerStr }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: ''
MSG_MINIMAL: true
+6 -6
View File
@@ -37,7 +37,7 @@ on:
jobs: jobs:
download-file: download-file:
name: Downloads the file into the VM name: Downloads the file into the VM
runs-on: ubuntu-latest runs-on: ubuntu-slim
outputs: outputs:
fileName: ${{ steps.extractName.outputs.fileName}} fileName: ${{ steps.extractName.outputs.fileName}}
env: env:
@@ -51,24 +51,24 @@ jobs:
- name: Download file - name: Download file
run: curl --silent --fail-with-body --proto "=https" -L "${INPUT_URL}" -o "${{steps.extractName.outputs.fileName}}" run: curl --silent --fail-with-body --proto "=https" -L "${INPUT_URL}" -o "${{steps.extractName.outputs.fileName}}"
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: ${{ steps.extractName.outputs.fileName }} name: ${{ steps.extractName.outputs.fileName }}
path: ${{ steps.extractName.outputs.fileName }} path: ${{ steps.extractName.outputs.fileName }}
if-no-files-found: error if-no-files-found: error
allowlist-kaspersky: allowlist-kaspersky:
name: Anti Virus Allowlisting Kaspersky name: Anti Virus Allowlisting Kaspersky
runs-on: ubuntu-latest runs-on: ubuntu-slim
needs: download-file needs: download-file
if: inputs.kaspersky if: inputs.kaspersky
steps: steps:
- name: Download artifact - name: Download artifact
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: ${{ needs.download-file.outputs.fileName }} name: ${{ needs.download-file.outputs.fileName }}
path: upload path: upload
- name: Upload to Kaspersky - name: Upload to Kaspersky
uses: SamKirkland/FTP-Deploy-Action@a51268f67f6605236975928ae28b0f7e9971d50a # v4.6.3 uses: SamKirkland/FTP-Deploy-Action@110f9186c050f71550953127052e77650219c287 # v4.6.3
with: with:
protocol: ftps protocol: ftps
server: allowlist.kaspersky-labs.com server: allowlist.kaspersky-labs.com
@@ -83,7 +83,7 @@ jobs:
if: inputs.avast if: inputs.avast
steps: steps:
- name: Download artifact - name: Download artifact
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: ${{ needs.download-file.outputs.fileName }} name: ${{ needs.download-file.outputs.fileName }}
path: upload path: upload
+5 -42
View File
@@ -11,7 +11,7 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: 25 JAVA_VERSION: 26
defaults: defaults:
run: run:
@@ -22,14 +22,14 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
cache: 'maven' cache: 'maven'
- name: Cache SonarCloud packages - name: Cache SonarCloud packages
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with: with:
path: ~/.sonar/cache path: ~/.sonar/cache
key: ${{ runner.os }}-sonar key: ${{ runner.os }}-sonar
@@ -37,7 +37,7 @@ jobs:
- name: Build and Test - name: Build and Test
run: > run: >
xvfb-run xvfb-run
mvn -B verify ./mvnw -B verify
jacoco:report jacoco:report
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
-Pcoverage -Pcoverage
@@ -47,40 +47,3 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
body: |-
> [!NOTE]
> 🚧 Work in Progress 🚧
>
> Please be patient, the [builds are still running](https://github.com/cryptomator/cryptomator/actions). Binary packages can be found here in a few moments.
<!--REPLACE with auto-generated release notes (see below)
### What's New 🎉
### Bugfixes 🐛
### Other Changes 📎
END REPLACE-->
For a comprehensive view of changes, read the [CHANGELOG](https://github.com/cryptomator/cryptomator/blob/develop/CHANGELOG.md).
---
<!-- Don't forget to include the
💾 SHA-256 checksums of release artifacts:
```
```
-->
> [!TIP]
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
<!-- Auto-Generated Release Notes: -->
+4 -4
View File
@@ -26,7 +26,7 @@ jobs:
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV" run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
shell: pwsh shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }} - name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
java-version: ${{ env.JDK_MAJOR_VERSION}} java-version: ${{ env.JDK_MAJOR_VERSION}}
distribution: ${{ env.JDK_VENDOR }} distribution: ${{ env.JDK_VENDOR }}
@@ -70,14 +70,14 @@ jobs:
} }
- name: Notify - name: Notify
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true' if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
env: env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot' SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:' SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop' SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "JDK update available" SLACK_TITLE: "JDK update available"
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions." SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions."
SLACK_FOOTER: false SLACK_FOOTER: ''
MSG_MINIMAL: true MSG_MINIMAL: true
+12 -11
View File
@@ -25,12 +25,12 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS' JAVA_VERSION: '26.0.1+8'
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1' DEB_BUILD_DEPENDS: 'debhelper (>=10), coffeelibs-jdk-26 (>= 26.0.1+8-0ppa1), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b' OPENJFX_JMODS_AMD64_HASH: '47035c653863a8e4be3dc6f142b8dbd84b4bb1efc9a8cbc68413e6a5ff5e9f50'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip' OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: 'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646' OPENJFX_JMODS_AARCH64_HASH: 'e3fd682354346845d2944a2da2b1ff2b6cb9259d92027f2f9c121b9b93c5e42f'
jobs: jobs:
get-version: get-version:
@@ -45,7 +45,7 @@ jobs:
env: env:
INPUT_PPAVER: ${{ inputs.ppaver }} INPUT_PPAVER: ${{ inputs.ppaver }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- id: deb-version - id: deb-version
name: Determine deb-version name: Determine deb-version
run: | run: |
@@ -56,20 +56,21 @@ jobs:
fi fi
- name: Install build tools - name: Install build tools
run: | run: |
sudo add-apt-repository -y ppa:coffeelibs/openjdk
sudo apt-get update sudo apt-get update
sudo apt-get install devscripts dput sudo apt-get install devscripts dput
sudo apt-get satisfy "${DEB_BUILD_DEPENDS}" sudo apt-get satisfy "${DEB_BUILD_DEPENDS}"
env: env:
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }} DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
check-latest: true check-latest: true
cache: 'maven' cache: 'maven'
- name: Run maven - name: Run maven
run: mvn -B clean package -Plinux -DskipTests run: ./mvnw -B clean package -DskipTests
- name: Download OpenJFX jmods - name: Download OpenJFX jmods
id: download-jmods id: download-jmods
run: | run: |
@@ -89,7 +90,7 @@ jobs:
JMOD_VERSION_AARCH64=$(jmod describe jmods/aarch64/javafx.base.jmod | head -1) JMOD_VERSION_AARCH64=$(jmod describe jmods/aarch64/javafx.base.jmod | head -1)
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64#*@} JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64#*@}
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64%%.*} JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(./mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -145,7 +146,7 @@ jobs:
run: | run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: linux-deb-package name: linux-deb-package
path: | path: |
+2 -2
View File
@@ -7,11 +7,11 @@ on:
jobs: jobs:
check-dependencies: check-dependencies:
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # v3.0.3 uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@8356563bf7b8d1c8d693f75ca487e8f57573cec9 # v3.1.0
with: with:
runner-os: 'ubuntu-latest' runner-os: 'ubuntu-latest'
java-distribution: 'temurin' java-distribution: 'temurin'
java-version: 25 java-version: 26
secrets: secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }} nvd-api-key: ${{ secrets.NVD_API_KEY }}
ossindex-username: ${{ secrets.OSSINDEX_USERNAME }} ossindex-username: ${{ secrets.OSSINDEX_USERNAME }}
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps: steps:
- name: Get download count of latest releases - name: Get download count of latest releases
id: get-stats id: get-stats
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
script: | script: |
const query = `query($owner:String!, $name:String!) { const query = `query($owner:String!, $name:String!) {
+157
View File
@@ -0,0 +1,157 @@
name: Draft a Cryptomator Release
on:
push:
tags:
- '*'
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '26.0.1+8'
defaults:
run:
shell: bash
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ''
create-release-draft:
name: Compile and Test
runs-on: ubuntu-latest
needs: get-version
if: needs.get-version.outputs.versionType != 'unknown'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Check the git tag is signed
run: git cat-file -p "${GITHUB_REF_NAME}" | grep "BEGIN SSH SIGNATURE"
- name: Check the git tag is on release or main branch
run: git branch -r --contains "${GITHUB_REF_NAME}" | grep -E '^\s*origin/(main|release/.*)\s*$'
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and Test
run: xvfb-run ./mvnw -B verify
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
- name: Draft a release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
draft: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
body_path: .github/release-body.md.template
- name: Download source tarball
run: |
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" https://github.com/cryptomator/cryptomator/archive/${{ github.ref }}.tar.gz --output cryptomator-${{ github.ref_name }}.tar.gz
- name: Sign source tarball with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
build-exe-and-msi:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/win-exe.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-dmg-arm64:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/mac-dmg.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-dmg-x64:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/mac-dmg-x64.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-appimages:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/appimage.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
update-sha256sums:
runs-on: ubuntu-latest
needs: [get-version, build-exe-and-msi, build-dmg-arm64, build-dmg-x64, build-appimages]
env:
TAG: ${{ github.ref_name }}
SEMVER: ${{ needs.get-version.outputs.semVerStr }}
GH_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Compute source tarball SHA256
id: src-sha256
run: |
curl --silent --fail-with-body --proto "=https" -L \
-H "Accept: application/vnd.github+json" \
"https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz" \
--output "cryptomator-${SEMVER}.tar.gz"
read -ra CMD_OUTPUT < <(sha256sum "cryptomator-${SEMVER}.tar.gz")
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Update release body with checksums
run: |
CURRENT_BODY=$(gh release view "${TAG}" --json body --jq .body)
RELEASE_BODY=$(printf '%s\n' "${CURRENT_BODY}" | sed '/<!-- HEADER -->/,/<!-- \/HEADER -->/c\
<!-- HEADER -->\
> [!NOTE]\
> Release artifacts finished building successfully.\
>\
> SHA-256 checksums have been updated below.\
<!-- /HEADER -->')
export TARBALL="${SRC_SHA} cryptomator-${SEMVER}.tar.gz"
export MSI="${MSI_SHA} Cryptomator-${SEMVER}-x64.msi"
export EXE="${EXE_SHA} Cryptomator-${SEMVER}-x64.exe"
export DMG_arm64="${DMG_ARM64_SHA} Cryptomator-${SEMVER}-arm64.dmg"
export DMG_x64="${DMG_X64_SHA} Cryptomator-${SEMVER}-x64.dmg"
export APPIMAGE_x86_64="${APPIMAGE_X64_SHA} cryptomator-${SEMVER}-x86_64.AppImage"
export APPIMAGE_aarch64="${APPIMAGE_AARCH64_SHA} cryptomator-${SEMVER}-aarch64.AppImage"
envsubst '$VERSION $TARBALL $EXE $MSI $DMG_x64 $DMG_arm64 $APPIMAGE_x86_64 $APPIMAGE_aarch64' \
<<< "${RELEASE_BODY}" \
> release-body.md
gh release edit "${TAG}" --draft --notes-file release-body.md
env:
VERSION: ${{ needs.get-version.outputs.semVerStr }}
SRC_SHA: ${{ steps.src-sha256.outputs.value }}
MSI_SHA: ${{ needs.build-exe-and-msi.outputs.sha256-msi }}
EXE_SHA: ${{ needs.build-exe-and-msi.outputs.sha256-exe }}
DMG_ARM64_SHA: ${{ needs.build-dmg-arm64.outputs.sha256-dmg }}
DMG_X64_SHA: ${{ needs.build-dmg-x64.outputs.sha256-dmg }}
APPIMAGE_X64_SHA: ${{ needs.build-appimages.outputs.sha256-appimage-x64 }}
APPIMAGE_AARCH64_SHA: ${{ needs.build-appimages.outputs.sha256-appimage-aarch64 }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Query Discussion Data - name: Query Discussion Data
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted' if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
id: query-data id: query-data
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
script: | script: |
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) { const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
+19 -10
View File
@@ -14,6 +14,9 @@ on:
semVerNum: semVerNum:
description: "The numerical part of the version string" description: "The numerical part of the version string"
value: ${{ jobs.determine-version.outputs.semVerNum}} value: ${{ jobs.determine-version.outputs.semVerNum}}
semVerSuffix:
description: "The suffix of the version string"
value: ${{ jobs.determine-version.outputs.semVerSuffix}}
revNum: revNum:
description: "The revision number" description: "The revision number"
value: ${{ jobs.determine-version.outputs.revNum}} value: ${{ jobs.determine-version.outputs.revNum}}
@@ -23,7 +26,7 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: 25 JAVA_VERSION: 26
jobs: jobs:
determine-version: determine-version:
@@ -32,14 +35,15 @@ jobs:
outputs: outputs:
semVerNum: ${{ steps.versions.outputs.semVerNum }} semVerNum: ${{ steps.versions.outputs.semVerNum }}
semVerStr: ${{ steps.versions.outputs.semVerStr }} semVerStr: ${{ steps.versions.outputs.semVerStr }}
semVerSuffix: ${{ steps.versions.outputs.semVerSuffix }}
revNum: ${{ steps.versions.outputs.revNum }} revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}} type: ${{ steps.versions.outputs.type}}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -52,27 +56,32 @@ jobs:
elif [[ "${VERSION_STRING}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then elif [[ "${VERSION_STRING}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${VERSION_STRING}" SEM_VER_STR="${VERSION_STRING}"
else else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` SEM_VER_STR=`./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout`
fi fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
SEM_VER_NUM=$(echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
SEM_VER_SUFFIX="${SEM_VER_STR#"$SEM_VER_NUM"}"
REVCOUNT=`git rev-list --count HEAD` REVCOUNT=`git rev-list --count HEAD`
TYPE="unknown" TYPE="unknown"
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then if [[ -z $SEM_VER_SUFFIX ]]; then
TYPE="stable" TYPE="stable"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then elif [[ $SEM_VER_SUFFIX =~ -alpha[1-9]+$ ]]; then
TYPE="alpha" TYPE="alpha"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then elif [[ $SEM_VER_SUFFIX =~ -beta[1-9]+$ ]]; then
TYPE="beta" TYPE="beta"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then elif [[ $SEM_VER_SUFFIX =~ -rc[1-9]+$ ]]; then
TYPE="rc" TYPE="rc"
fi fi
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
echo "semVerSuffix=${SEM_VER_SUFFIX}" >> $GITHUB_OUTPUT
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
echo "type=${TYPE}" >> $GITHUB_OUTPUT echo "type=${TYPE}" >> $GITHUB_OUTPUT
env: env:
VERSION_STRING: ${{ inputs.version }} VERSION_STRING: ${{ inputs.version }}
- name: Validate Version - name: Validate Version
uses: skymatic/semver-validation-action@7a6ae1c9e121540d11c9c7e4e667c83d583aa153 # v3.0.0 uses: skymatic/semver-validation-action@7c80b6b03a18b42884761daa9862ff5683ec8c8a # v4.0.0
with: with:
version: ${{ steps.versions.outputs.semVerStr }} version: ${{ steps.versions.outputs.semVerStr }}
+14 -14
View File
@@ -49,16 +49,16 @@ jobs:
env: env:
SRC_GIT_SHA: ${{ inputs.src-tag || github.sha}} SRC_GIT_SHA: ${{ inputs.src-tag || github.sha}}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
repository: flathub/org.cryptomator.Cryptomator repository: flathub/org.cryptomator.Cryptomator
submodules: true submodules: true
- name: Checkout build script - name: Checkout build script
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
path: build-scripts path: build-scripts
- name: Checkout app source - name: Checkout app source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
path: cryptomator path: cryptomator
ref: ${{ env.SRC_GIT_SHA }} ref: ${{ env.SRC_GIT_SHA }}
@@ -77,7 +77,7 @@ jobs:
commit: ${{ env.SRC_GIT_SHA }} commit: ${{ env.SRC_GIT_SHA }}
- name: Copy build script for upload - name: Copy build script for upload
run: cp org.cryptomator.Cryptomator.yaml org.cryptomator.Cryptomator.${{matrix.variant.arch}}.yaml run: cp org.cryptomator.Cryptomator.yaml org.cryptomator.Cryptomator.${{matrix.variant.arch}}.yaml
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
archive: false archive: false
if-no-files-found: error if-no-files-found: error
@@ -129,7 +129,7 @@ jobs:
EOF EOF
done < maven-dependency-files-javafx.txt done < maven-dependency-files-javafx.txt
) > javafx-maven-dependencies-${{ matrix.variant.arch }}.yaml ) > javafx-maven-dependencies-${{ matrix.variant.arch }}.yaml
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: maven-sources-${{ matrix.variant.arch }} name: maven-sources-${{ matrix.variant.arch }}
if-no-files-found: error if-no-files-found: error
@@ -145,12 +145,12 @@ jobs:
contents: none contents: none
steps: steps:
- name: Download updated maven aarch64 dependencies - name: Download updated maven aarch64 dependencies
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: maven-sources-aarch64 name: maven-sources-aarch64
path: mvn-src-aarch64 path: mvn-src-aarch64
- name: Download updated maven x86_64 dependencies - name: Download updated maven x86_64 dependencies
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: maven-sources-x86_64 name: maven-sources-x86_64
path: mvn-src-x64 path: mvn-src-x64
@@ -176,7 +176,7 @@ jobs:
fi fi
env: env:
SRC_TAG: ${{ inputs.src-tag }} SRC_TAG: ${{ inputs.src-tag }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
repository: flathub/org.cryptomator.Cryptomator repository: flathub/org.cryptomator.Cryptomator
submodules: true #TODO: Update submodule! submodules: true #TODO: Update submodule!
@@ -184,7 +184,7 @@ jobs:
- name: Checkout release branch - name: Checkout release branch
run: | run: |
git checkout -b release/${{ needs.get-version.outputs.semVerStr }} git checkout -b release/${{ needs.get-version.outputs.semVerStr }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
path: cryptomator path: cryptomator
- name: Download source tarball and compute checksum - name: Download source tarball and compute checksum
@@ -194,12 +194,12 @@ jobs:
TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1) TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1)
echo "value=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT" echo "value=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT"
- name: Download updated maven aarch64 dependencies - name: Download updated maven aarch64 dependencies
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: maven-sources-aarch64 name: maven-sources-aarch64
path: mvn-src-aarch64 path: mvn-src-aarch64
- name: Download updated maven x86_64 dependencies - name: Download updated maven x86_64 dependencies
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: maven-sources-x86_64 name: maven-sources-x86_64
path: mvn-src-x64 path: mvn-src-x64
@@ -250,15 +250,15 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification - name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
if: github.event_name == 'release' if: github.event_name == 'release'
env: env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot' SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:' SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop' SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created." SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.FLATHUB_PR_URL }}|PR> on how to proceed." SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.FLATHUB_PR_URL }}|PR> on how to proceed."
SLACK_FOOTER: false SLACK_FOOTER: ''
MSG_MINIMAL: true MSG_MINIMAL: true
+10 -11
View File
@@ -44,7 +44,7 @@ jobs:
pacman-key --init pacman-key --init
pacman-key --populate archlinux pacman-key --populate archlinux
pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
path: cryptomator path: cryptomator
- name: Create build user - name: Create build user
@@ -69,13 +69,13 @@ jobs:
sudo -u builder sudo -u builder
env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST" env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST"
makepkg --syncdeps --cleanbuild --noconfirm --log makepkg --syncdeps --cleanbuild --noconfirm --log
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: arch-package name: arch-package
if-no-files-found: error if-no-files-found: error
path: | path: |
${{ env.PKGDEST }}/*.pkg.tar.zst ${{ env.PKGDEST }}/*.pkg.tar.zst
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: pkgbuild-file name: pkgbuild-file
if-no-files-found: error if-no-files-found: error
@@ -108,7 +108,7 @@ jobs:
env: env:
TAG: ${{ needs.get-version.outputs.semVerStr || github.event.release.tag_name }} TAG: ${{ needs.get-version.outputs.semVerStr || github.event.release.tag_name }}
- name: Checkout cryptomator/aur repo - name: Checkout cryptomator/aur repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
repository: 'cryptomator/aur' repository: 'cryptomator/aur'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
@@ -132,7 +132,6 @@ jobs:
- name: Determine pkgrel - name: Determine pkgrel
id: pkgrel id: pkgrel
run: | run: |
TARGET_VERSION='${{ needs.get-version.outputs.semVerStr }}'
CURRENT_VERSION="$(sed -nE 's/^pkgver=(.*)$/\1/p' PKGBUILD | head -n1)" CURRENT_VERSION="$(sed -nE 's/^pkgver=(.*)$/\1/p' PKGBUILD | head -n1)"
CURRENT_REL="$(sed -nE 's/^pkgrel=([0-9]+).*$/\1/p' PKGBUILD | head -n1)" CURRENT_REL="$(sed -nE 's/^pkgrel=([0-9]+).*$/\1/p' PKGBUILD | head -n1)"
@@ -143,11 +142,11 @@ jobs:
fi fi
echo "value=${NEXT_REL}" >> "$GITHUB_OUTPUT" echo "value=${NEXT_REL}" >> "$GITHUB_OUTPUT"
echo "dist-version=${VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT" echo "dist-version=${TARGET_VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT"
env: env:
VERSION: ${{ needs.get-version.outputs.semVerStr }} TARGET_VERSION: ${{ needs.get-version.outputs.semVerStr }}
- name: Download PKGBUILD template - name: Download PKGBUILD template
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: pkgbuild-file name: pkgbuild-file
- name: Prepare PKGBUILD - name: Prepare PKGBUILD
@@ -189,14 +188,14 @@ jobs:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification - name: Slack Notification
if: github.event_name == 'release' if: github.event_name == 'release'
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
env: env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot' SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:' SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop' SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR release PR created for ${{ github.event.repository.name }} ${{ steps.pkgrel.outputs.dist-version }} ." SLACK_TITLE: "AUR release PR created for ${{ github.event.repository.name }} ${{ steps.pkgrel.outputs.dist-version }} ."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed." SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: false SLACK_FOOTER: ''
MSG_MINIMAL: true MSG_MINIMAL: true
+68 -36
View File
@@ -9,15 +9,45 @@ name: Build macOS .dmg for x64
####################################### #######################################
on: on:
release:
types: [published]
schedule: schedule:
- cron: '0 20 20 * *' - cron: '0 20 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
notarize:
description: 'Notarize'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-dmg:
description: "SHA256 sum of the x64 dmg"
value: ${{ jobs.build.outputs.sha256sum}}
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: semVerNum:
description: 'Version' description: 'The Major.Minor.Patch part of the version'
required: false required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
notarize: notarize:
description: 'Notarize' description: 'Notarize'
required: true required: true
@@ -26,18 +56,18 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS' JAVA_VERSION: '26.0.1+8'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs: jobs:
get-version: build:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-arm:
name: Build Cryptomator.app for ${{ matrix.output-suffix }} name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [get-version] outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -46,12 +76,12 @@ jobs:
architecture: x64 architecture: x64
output-suffix: x64 output-suffix: x64
fuse-lib: macFUSE fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_osx-x64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_osx-x64_bin-jmods.zip'
openjfx-sha: '0b4d8463f03901b7425d94628e4116b7078abb8dd540fbec415266fac20bda5c' openjfx-sha: '3512fabe43aee467538d329cfbbaab3c53dff2a810f0d54e381f461d5e0fac43'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -70,7 +100,7 @@ jobs:
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(./mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -79,9 +109,9 @@ jobs:
exit 1 exit 1
fi fi
- name: Set version - name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} run : ./mvnw versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
- name: Run maven - name: Run maven
run: mvn -B clean package -Pmac -DskipTests run: ./mvnw -B clean package -Pmac -DskipTests
- name: Patch target dir - name: Patch target dir
run: | run: |
cp LICENSE.txt target cp LICENSE.txt target
@@ -119,16 +149,17 @@ jobs:
--dest appdir --dest appdir
--name Cryptomator --name Cryptomator
--vendor "Skymatic GmbH" --vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2025 Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}" --app-version "${VERSION_NUM}"
--java-options "--enable-preview" --java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true" --java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\"" --java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\"" --java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
@@ -138,7 +169,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism" --java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NUM}\""
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" --java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--mac-package-identifier org.cryptomator --mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources --resource-dir dist/mac/resources
@@ -147,16 +178,14 @@ jobs:
mv appdir/Cryptomator.app Cryptomator.app mv appdir/Cryptomator.app Cryptomator.app
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/ cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NUM}|g" Cryptomator.app/Contents/Info.plist
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
env: env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }} PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
- name: Generate license for dmg - name: Generate license for dmg
run: > run: >
mvn -B license:add-third-party ./mvnw -B license:add-third-party
-Dlicense.thirdPartyFilename=license.rtf -Dlicense.thirdPartyFilename=license.rtf
-Dlicense.outputDirectory=dist/mac/dmg/resources -Dlicense.outputDirectory=dist/mac/dmg/resources
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl -Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
@@ -241,16 +270,14 @@ jobs:
--eula "dist/mac/dmg/resources/license.rtf" --eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758 --icon ".background" 128 758
--icon ".VolumeIcon.icns" 512 758 --icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg Cryptomator-${VERSION_NUM}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Codesign .dmg - name: Codesign .dmg
run: | run: |
codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg
env: env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg - name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize || github.event_name == 'schedule' if: inputs.notarize || github.event_name == 'schedule'
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3 uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with: with:
app-path: 'Cryptomator-*.dmg' app-path: 'Cryptomator-*.dmg'
@@ -258,8 +285,12 @@ jobs:
password: ${{ secrets.MACOS_NOTARIZATION_PW }} password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: '/Applications/Xcode_16.app' xcode-path: '/Applications/Xcode_16.app'
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(shasum -a256 Cryptomator-*.dmg)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags to installer name - name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg run: mv Cryptomator-*.dmg "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.output-suffix }}.dmg"
- name: Create detached GPG signature with key 615D449FE6E6A235 - name: Create detached GPG signature with key 615D449FE6E6A235
run: | run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -272,7 +303,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true continue-on-error: true
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: dmg-${{ matrix.output-suffix }} name: dmg-${{ matrix.output-suffix }}
path: | path: |
@@ -280,9 +311,10 @@ jobs:
Cryptomator-*.asc Cryptomator-*.asc
if-no-files-found: error if-no-files-found: error
- name: Publish dmg on GitHub Releases - name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' if: inputs.upload-to-draft
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with: with:
draft: true
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: | files: |
+67 -35
View File
@@ -1,15 +1,45 @@
name: Build macOS .dmg for arm64 name: Build macOS .dmg for arm64
on: on:
release:
types: [published]
schedule: schedule:
- cron: '0 20 20 * *' - cron: '0 20 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
notarize:
description: 'Notarize'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-dmg:
description: "SHA256 sum of the arm64 dmg"
value: ${{ jobs.build.outputs.sha256sum}}
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: semVerNum:
description: 'Version' description: 'The Major.Minor.Patch part of the version'
required: false required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
notarize: notarize:
description: 'Notarize' description: 'Notarize'
required: true required: true
@@ -24,18 +54,18 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS' JAVA_VERSION: '26.0.1+8'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs: jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build: build:
name: Build Cryptomator.app for ${{ matrix.output-suffix }} name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [get-version] outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -44,12 +74,12 @@ jobs:
architecture: aarch64 architecture: aarch64
output-suffix: arm64 output-suffix: arm64
fuse-lib: FUSE-T fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_osx-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_osx-aarch64_bin-jmods.zip'
openjfx-sha: '4cd258001c75af7047005c5c891e2400ed11d24fbb09412324c0cbaf8b503c5a' openjfx-sha: 'a52014d625b8b04e57fd71650f881c1397542b4018e1b04f1b4e66c8800a1f34'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -68,7 +98,7 @@ jobs:
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(./mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -77,9 +107,9 @@ jobs:
exit 1 exit 1
fi fi
- name: Set version - name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} run : ./mvnw versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
- name: Run maven - name: Run maven
run: mvn -B clean package -Pmac -DskipTests run: ./mvnw -B clean package -Pmac -DskipTests
- name: Patch target dir - name: Patch target dir
run: | run: |
cp LICENSE.txt target cp LICENSE.txt target
@@ -117,16 +147,17 @@ jobs:
--dest appdir --dest appdir
--name Cryptomator --name Cryptomator
--vendor "Skymatic GmbH" --vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2025 Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}" --app-version "${VERSION_NUM}"
--java-options "--enable-preview" --java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true" --java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\"" --java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\"" --java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
@@ -136,7 +167,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism" --java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NUM}\""
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" --java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--mac-package-identifier org.cryptomator --mac-package-identifier org.cryptomator
@@ -146,16 +177,14 @@ jobs:
mv appdir/Cryptomator.app Cryptomator.app mv appdir/Cryptomator.app Cryptomator.app
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/ cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NUM}|g" Cryptomator.app/Contents/Info.plist
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
env: env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }} PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
- name: Generate license for dmg - name: Generate license for dmg
run: > run: >
mvn -B license:add-third-party ./mvnw -B license:add-third-party
-Dlicense.thirdPartyFilename=license.rtf -Dlicense.thirdPartyFilename=license.rtf
-Dlicense.outputDirectory=dist/mac/dmg/resources -Dlicense.outputDirectory=dist/mac/dmg/resources
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl -Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
@@ -240,16 +269,14 @@ jobs:
--eula "dist/mac/dmg/resources/license.rtf" --eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758 --icon ".background" 128 758
--icon ".VolumeIcon.icns" 512 758 --icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg Cryptomator-${VERSION_NUM}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Codesign .dmg - name: Codesign .dmg
run: | run: |
codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg
env: env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg - name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize || github.event_name == 'schedule' if: inputs.notarize || github.event_name == 'schedule'
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3 uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with: with:
app-path: 'Cryptomator-*.dmg' app-path: 'Cryptomator-*.dmg'
@@ -257,8 +284,12 @@ jobs:
password: ${{ secrets.MACOS_NOTARIZATION_PW }} password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: '/Applications/Xcode_16.app' xcode-path: '/Applications/Xcode_16.app'
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(shasum -a256 Cryptomator-*.dmg)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags to installer name - name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg run: mv Cryptomator-*.dmg "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.output-suffix }}.dmg"
- name: Create detached GPG signature with key 615D449FE6E6A235 - name: Create detached GPG signature with key 615D449FE6E6A235
run: | run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -271,7 +302,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true continue-on-error: true
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: dmg-${{ matrix.output-suffix }} name: dmg-${{ matrix.output-suffix }}
path: | path: |
@@ -279,9 +310,10 @@ jobs:
Cryptomator-*.asc Cryptomator-*.asc
if-no-files-found: error if-no-files-found: error
- name: Publish dmg on GitHub Releases - name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' if: inputs.upload-to-draft
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with: with:
draft: true
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: | files: |
+2 -2
View File
@@ -7,12 +7,12 @@ on:
jobs: jobs:
no-response: no-response:
runs-on: ubuntu-latest runs-on: ubuntu-slim
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
days-before-stale: 14 days-before-stale: 14
days-before-close: 0 days-before-close: 0
+129 -23
View File
@@ -5,35 +5,141 @@ on:
types: [published] types: [published]
jobs: jobs:
get-version: notify:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download source tarball - name: Notify about DEB build
run: | uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
- name: Sign source tarball with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env: env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot' SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:' SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop' SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published." SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>." SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
SLACK_FOOTER: false SLACK_FOOTER: ''
MSG_MINIMAL: true MSG_MINIMAL: true
- name: Notify about latest-version update
uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Requiring version check source update for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}."
SLACK_MESSAGE: 'Check S3 bucket for <https://static.cryptomator.org/desktop/latest-version.json|latest-version.json>.'
SLACK_FOOTER: ''
MSG_MINIMAL: true
get-asset-urls:
name: Get release asset URLs
runs-on: ubuntu-slim
outputs:
is-windows-release: ${{ steps.urls.outputs.urls-present }}
msi-url: ${{ steps.urls.outputs.msi }}
exe-url: ${{ steps.urls.outputs.exe }}
steps:
- name: Extract MSI and EXE download URLs
id: urls
run: |
MSI_URL=$(jq -r '[.[] | select(.name | endswith("-x64.msi"))][0].browser_download_url // "null"' <<< "$RELEASE_ASSETS")
EXE_URL=$(jq -r '[.[] | select(.name | endswith("-x64.exe"))][0].browser_download_url // "null"' <<< "$RELEASE_ASSETS")
if [[ "$MSI_URL" == "null" || -z "$MSI_URL" || "$EXE_URL" == "null" || -z "$EXE_URL" ]]; then
echo "urls-present=false" >> $GITHUB_OUTPUT
else
echo "urls-present=true" >> $GITHUB_OUTPUT
echo "msi=${MSI_URL}" >> $GITHUB_OUTPUT
echo "exe=${EXE_URL}" >> $GITHUB_OUTPUT
fi
env:
RELEASE_ASSETS: ${{ toJson(github.event.release.assets) }}
allowlist-msi-x64:
needs: [get-asset-urls]
if: needs.get-asset-urls.outputs.is-windows-release == 'true'
uses: ./.github/workflows/av-whitelist.yml
with:
url: ${{ needs.get-asset-urls.outputs.msi-url }}
secrets: inherit
allowlist-exe-x64:
needs: [get-asset-urls, allowlist-msi-x64]
if: needs.get-asset-urls.outputs.is-windows-release == 'true'
uses: ./.github/workflows/av-whitelist.yml
with:
url: ${{ needs.get-asset-urls.outputs.exe-url }}
secrets: inherit
check-release:
name: Analyzes the release for certain properties
runs-on: ubuntu-slim
outputs:
release-kind: ${{steps.determine-kind.outputs.value}} # Possible values are [alpha, beta, rc, stable, unknown]
steps:
- id: determine-kind
run: |
SEM_VER_NUM=$(echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
SEM_VER_SUFFIX="${SEM_VER_STR#"$SEM_VER_NUM"}"
TYPE="unknown"
if [[ -z $SEM_VER_SUFFIX ]]; then
TYPE="stable"
elif [[ $SEM_VER_SUFFIX =~ -alpha[1-9]+$ ]]; then
TYPE="alpha"
elif [[ $SEM_VER_SUFFIX =~ -beta[1-9]+$ ]]; then
TYPE="beta"
elif [[ $SEM_VER_SUFFIX =~ -rc[1-9]+$ ]]; then
TYPE="rc"
fi
echo "value=${TYPE}" >> $GITHUB_OUTPUT
env:
SEM_VER_STR: ${{ github.event.release.tag_name }}
notify-winget:
name: Notify for winget-release
if: needs.get-asset-urls.outputs.is-windows-release == 'true' && needs.check-release.outputs.release-kind == 'stable'
needs: [check-release, get-asset-urls]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml|release to winget>."
SLACK_FOOTER: ''
MSG_MINIMAL: true
trigger-website-update:
needs: [check-release]
runs-on: ubuntu-slim
if: needs.check-release.outputs.release-kind == 'stable'
steps:
- name: Start website update workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
event-type: desktop-release
token: ${{ secrets.CRYPTOBOT_WORKFLOW_DISPATCH_TOKEN }}
repository: cryptomator/cryptomator.github.io
client-payload: '{ "version": "${{ github.event.release.tag_name }}", "release": ${{ toJson(github.event.release.assets) }} }'
trigger-docs-update:
needs: [check-release, get-asset-urls]
runs-on: ubuntu-slim
if: needs.get-asset-urls.outputs.is-windows-release == 'true' && needs.check-release.outputs.release-kind == 'stable'
steps:
- name: Start docs update workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
event-type: desktop-release
token: ${{ secrets.CRYPTOBOT_WORKFLOW_DISPATCH_TOKEN }}
repository: cryptomator/docs
client-payload: '{ "version": "${{ github.event.release.tag_name }}", "release": ${{ toJson(github.event.release.assets) }} }'
+4 -4
View File
@@ -5,7 +5,7 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: 25 JAVA_VERSION: 26
defaults: defaults:
run: run:
@@ -16,11 +16,11 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
cache: 'maven' cache: 'maven'
- name: Build and Test - name: Build and Test
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage run: xvfb-run ./mvnw -B clean install jacoco:report -Pcoverage
+5 -20
View File
@@ -12,16 +12,16 @@ defaults:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_VERSION: 25 JAVA_VERSION: 26
jobs: jobs:
check-preconditions: check-preconditions:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -36,7 +36,7 @@ jobs:
exit 1 exit 1
fi fi
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then if [[ ${SEM_VER_STR} == `./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
else else
echo "Version not set in POM" echo "Version not set in POM"
@@ -48,19 +48,4 @@ jobs:
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\">" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\">" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml" echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
exit 1 exit 1
fi fi
- name: Cache NVD DB
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.m2/repository/org/owasp/dependency-check-data/
key: dependency-check-${{ github.run_id }}
restore-keys: |
dependency-check
env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5
- name: Run org.owasp:dependency-check plugin
id: dependency-check
continue-on-error: true
run: mvn -B verify -Pdependency-check -DskipTests
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
+2 -2
View File
@@ -7,12 +7,12 @@ on:
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-slim
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
days-before-stale: 365 days-before-stale: 365
days-before-close: 90 days-before-close: 90
+129 -99
View File
@@ -1,15 +1,48 @@
name: Build Windows Installer name: Build Windows Installer
on: on:
release:
types: [published]
schedule: schedule:
- cron: '0 19 20 * *' - cron: '0 19 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
sign:
description: 'Sign binaries'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-msi:
description: "SHA256 sum of the x64 msi"
value: ${{ jobs.build-msi.outputs.sha256sum}}
sha256-exe:
description: "SHA256 sum of the x64 exe"
value: ${{ jobs.build-exe.outputs.sha256sum}}
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: semVerNum:
description: 'Version' description: 'The Major.Minor.Patch part of the version'
required: false required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
sign: sign:
description: 'Sign binaries' description: 'Sign binaries'
required: false required: false
@@ -24,8 +57,11 @@ on:
env: env:
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_windows-x64_bin-jmods.zip' VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
OPENJFX_JMODS_AMD64_HASH: '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3' REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '0bf9b83260b85607a9ba200124debabd9cdb013cbc0d659e62a20192a7137907'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a' WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
@@ -36,27 +72,23 @@ defaults:
shell: bash shell: bash
jobs: jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-msi: build-msi:
name: Build .msi Installer name: Build .msi Installer
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [ get-version ] outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
strategy: strategy:
matrix: matrix:
include: include:
- arch: x64 - arch: x64
os: windows-latest os: windows-latest
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427 java-dist: 'temurin'
java-version: '25.0.1+8' java-version: '26.0.1+8'
java-package: 'jdk' java-package: 'jdk'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ matrix.java-dist }} distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
@@ -87,7 +119,7 @@ jobs:
JMOD_VERSION_AMD64=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION_AMD64=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@} JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@}
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*} JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(./mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -96,9 +128,9 @@ jobs:
exit 1 exit 1
fi fi
- name: Set version - name: Set version
run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} run: ./mvnw versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
- name: Run maven - name: Run maven
run: mvn -B clean package -Pwin -DskipTests run: ./mvnw -B clean package -Pwin -DskipTests
- name: Patch target dir - name: Patch target dir
run: | run: |
cp LICENSE.txt target cp LICENSE.txt target
@@ -136,13 +168,13 @@ jobs:
--dest appdir --dest appdir
--name Cryptomator --name Cryptomator
--vendor "Skymatic GmbH" --vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2025 Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --app-version "${VERSION_NUM}.${REVISION_NUM}"
--java-options "--enable-preview" --java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\"" --java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
@@ -153,7 +185,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
--java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\"" --java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"msi-${REVISION_NUM}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\"" --java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json\"" --java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json\""
@@ -188,22 +220,32 @@ jobs:
} }
$jar.Dispose() $jar.Dispose()
} }
- name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130 - name: Get msi helper dll for code signing
shell: pwsh
run: | run: |
New-Item -Path appdir/jpackage-jmod -ItemType Directory ${JAVA_HOME}/bin/jpackage --type msi --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 --app-image appdir/Cryptomator --dest /tmp/ --name Test --vendor Test --copyright "None" --app-version "1.0" --temp msi-helper
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod" find ./msi-helper/ -type f -name msica.dll -exec mv {} ./appdir \;
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Sign DLLs with Azure Trusted Signing - name: Sign DLLs with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release' || github.event_name == 'schedule' if: inputs.sign || github.event_name == 'schedule'
uses: ./.github/actions/win-sign-action uses: ./.github/actions/win-sign-action
with: with:
base-dir: ${{ github.workspace }}\appdir base-dir: ${{ github.workspace }}\appdir
file-extensions: 'exe,dll'
recursive: true recursive: true
append-signature: true append-signature: true
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign Scripts with Azure Trusted Signing
if: inputs.sign || github.event_name == 'schedule'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\appdir\Cryptomator
file-extensions: 'ps1'
recursive: false
append-signature: false # Powershell scripts cannot be signed in append mode, see #4260
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Replace DLLs inside jars with signed ones - name: Replace DLLs inside jars with signed ones
shell: pwsh shell: pwsh
run: | run: |
@@ -220,7 +262,7 @@ jobs:
} }
- name: Generate license for MSI - name: Generate license for MSI
run: > run: >
mvn -B license:add-third-party ./mvnw -B license:add-third-party
"-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.thirdPartyFilename=license.rtf"
"-Dlicense.outputDirectory=dist/win/resources" "-Dlicense.outputDirectory=dist/win/resources"
"-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
@@ -229,6 +271,16 @@ jobs:
"-Dlicense.failOnMissing=true" "-Dlicense.failOnMissing=true"
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges" "-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
shell: pwsh shell: pwsh
- name: Create file association file from template
working-directory: dist/win
run: |
$Env:JP_WIXWIZARD_RESOURCES_PROPERTIES_FORMAT = "${Env:JP_WIXWIZARD_RESOURCES}".Replace('\', '\\');
Get-Content .\resources\FAvaultFile.template.properties `
| ForEach-Object { $ExecutionContext.InvokeCommand.ExpandString($_) } `
| Out-File -FilePath .\resources\FAvaultFile.properties
env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources/ # requires abs path, used in resources/main.wxs
shell: pwsh
- name: Create MSI - name: Create MSI
run: > run: >
${JAVA_HOME}/bin/jpackage ${JAVA_HOME}/bin/jpackage
@@ -239,21 +291,21 @@ jobs:
--dest installer --dest installer
--name Cryptomator --name Cryptomator
--vendor "Skymatic GmbH" --vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2025 Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}" --app-version "${VERSION_NUM}.${REVISION_NUM}"
--win-menu --win-menu
--win-dir-chooser --win-dir-chooser
--win-shortcut-prompt --win-shortcut-prompt
--win-update-url "https:\\cryptomator.org\downloads" --win-update-url "https://cryptomator.org/downloads"
--win-menu-group Cryptomator --win-menu-group Cryptomator
--resource-dir dist/win/resources --resource-dir dist/win/resources
--license-file dist/win/resources/license.rtf --license-file dist/win/resources/license.rtf
--file-associations dist/win/resources/FAvaultFile.properties --file-associations dist/win/resources/FAvaultFile.properties
env: env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources/ # requires abs path, used in resources/main.wxs
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir\
- name: Sign MSI with Azure Trusted Signing - name: Sign MSI with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release' || github.event_name == 'schedule' if: inputs.sign || github.event_name == 'schedule'
uses: ./.github/actions/win-sign-action uses: ./.github/actions/win-sign-action
with: with:
base-dir: ${{ github.workspace }}\installer base-dir: ${{ github.workspace }}\installer
@@ -262,8 +314,12 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.msi)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags and architecture to installer name - name: Add possible alpha/beta tags and architecture to installer name
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi run: mv installer/Cryptomator-*.msi "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.arch }}.msi"
- name: Create detached GPG signature with key 615D449FE6E6A235 - name: Create detached GPG signature with key 615D449FE6E6A235
run: | run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -272,7 +328,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: msi-${{ matrix.arch }} name: msi-${{ matrix.arch }}
path: | path: |
@@ -283,18 +339,20 @@ jobs:
build-exe: build-exe:
name: Build .exe installer name: Build .exe installer
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [ get-version, build-msi ] needs: [ build-msi ]
outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
strategy: strategy:
matrix: matrix:
include: include:
- arch: x64 - arch: x64
os: windows-latest os: windows-latest
executable-suffix: x64 executable-suffix: x64
java-dist: 'zulu' java-dist: 'temurin'
java-version: '24.0.1+9' java-version: '26.0.1+8'
java-package: 'jdk' java-package: 'jdk'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install wix and extensions - name: Install wix and extensions
run: | run: |
dotnet tool install --global wix --version ${WIX_VERSION} dotnet tool install --global wix --version ${WIX_VERSION}
@@ -303,14 +361,14 @@ jobs:
env: env:
WIX_VERSION: ${{ env.WIX_VERSION }} WIX_VERSION: ${{ env.WIX_VERSION }}
- name: Download .msi - name: Download .msi
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: msi-${{ matrix.arch }} name: msi-${{ matrix.arch }}
path: dist/win/bundle/resources path: dist/win/bundle/resources
- name: Strip version info from msi file name - name: Strip version info from msi file name
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
- name: Setup Java - name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with: with:
distribution: ${{ matrix.java-dist }} distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
@@ -319,7 +377,7 @@ jobs:
cache: 'maven' cache: 'maven'
- name: Generate license for exe - name: Generate license for exe
run: > run: >
mvn -B license:add-third-party ./mvnw -B license:add-third-party
"-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.thirdPartyFilename=license.rtf"
"-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
"-Dlicense.outputDirectory=dist/win/bundle/resources" "-Dlicense.outputDirectory=dist/win/bundle/resources"
@@ -330,10 +388,10 @@ jobs:
shell: pwsh shell: pwsh
- name: Download WinFsp - name: Download WinFsp
run: | run: |
curl --silent --fail-with-body --proto "=https" -L ${{ env.WINFSP_MSI }} --output $env:WINFSP_PATH curl --silent --fail-with-body --proto "=https" -L "$env:WINFSP_MSI" --output $env:WINFSP_PATH
$computedHash = (Get-FileHash -Path $env:WINFSP_PATH -Algorithm SHA256).Hash.ToLower() $computedHash = (Get-FileHash -Path "$env:WINFSP_PATH" -Algorithm SHA256).Hash.ToLower()
if ($computedHash -ne "${{ env.WINFSP_MSI_HASH }}") { if ($computedHash -ne "$env:WINFSP_MSI_HASH") {
throw "Checksum mismatch for $env:WINFSP_PATH (expected ${{ env.WINFSP_MSI_HASH }}, got $computedHash)." throw "Checksum mismatch for ${env:WINFSP_PATH} (expected ${env:WINFSP_MSI_HASH}, got $computedHash)."
} }
env: env:
WINFSP_PATH: 'dist/win/bundle/resources/winfsp.msi' WINFSP_PATH: 'dist/win/bundle/resources/winfsp.msi'
@@ -347,21 +405,22 @@ jobs:
run: > run: >
wix build wix build
-define BundleName="Cryptomator" -define BundleName="Cryptomator"
-define BundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}" -define BundleVersion="${VERSION_NUM}.${REVISION_NUM}"
-define BundleVendor="Skymatic GmbH" -define BundleVendor="Skymatic GmbH"
-define BundleCopyright="(C) 2016 - 2025 Skymatic GmbH" -define BundleCopyright="(C) 2016 - 2026 Skymatic GmbH"
-define AboutUrl="https://cryptomator.org" -define AboutUrl="https://cryptomator.org"
-define HelpUrl="https://cryptomator.org/contact" -define HelpUrl="https://cryptomator.org/contact"
-define UpdateUrl="https://cryptomator.org/downloads/" -define UpdateUrl="https://cryptomator.org/downloads/"
-ext "WixToolset.Util.wixext" -ext "WixToolset.Util.wixext"
-ext "WixToolset.BootstrapperApplications.wixext" -ext "WixToolset.BootstrapperApplications.wixext"
./bundle/bundleWithWinfsp.wxs ./bundle/bundleWithWinfsp.wxs
-out "../../installer/unsigned/Cryptomator-Installer.exe" -out "../../installer/Cryptomator-Installer.exe"
- name: Detach burn engine in preparation to sign - name: Detach burn engine in preparation to sign
if: inputs.sign || github.event_name == 'schedule'
run: > run: >
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe wix burn detach installer/Cryptomator-Installer.exe -engine tmp/engine.exe
- name: Sign WiX burn engine with Azure Trusted Signing - name: Sign WiX burn engine with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release' || github.event_name == 'schedule' if: inputs.sign || github.event_name == 'schedule'
uses: ./.github/actions/win-sign-action uses: ./.github/actions/win-sign-action
with: with:
base-dir: ${{ github.workspace }}\tmp base-dir: ${{ github.workspace }}\tmp
@@ -372,10 +431,13 @@ jobs:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Reattach signed burn engine to installer - name: Reattach signed burn engine to installer
run: > if: inputs.sign || github.event_name == 'schedule'
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe shell: pwsh
run: |
Move-Item -Path installer/Cryptomator-Installer.exe -Destination tmp/Cryptomator-Installer.exe
wix burn reattach tmp/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
- name: Sign EXE installer with Azure Trusted Signing - name: Sign EXE installer with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release' || github.event_name == 'schedule' if: inputs.sign || github.event_name == 'schedule'
uses: ./.github/actions/win-sign-action uses: ./.github/actions/win-sign-action
with: with:
base-dir: ${{ github.workspace }}\installer base-dir: ${{ github.workspace }}\installer
@@ -385,8 +447,12 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.exe)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags to installer name - name: Add possible alpha/beta tags to installer name
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.executable-suffix }}.exe run: mv installer/Cryptomator-Installer.exe "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.executable-suffix }}.exe"
- name: Create detached GPG signature with key 615D449FE6E6A235 - name: Create detached GPG signature with key 615D449FE6E6A235
run: | run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -395,7 +461,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: exe-${{ matrix.executable-suffix }} name: exe-${{ matrix.executable-suffix }}
path: | path: |
@@ -405,58 +471,22 @@ jobs:
publish: publish:
name: Publish installers to the github release name: Publish installers to the github release
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' if: inputs.upload-to-draft
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build-msi, build-exe ] needs: [ build-msi, build-exe ]
outputs:
download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
steps: steps:
- name: Download installers - name: Download installers
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
merge-multiple: true merge-multiple: true
- name: Publish installers on GitHub Releases - name: Publish installers on GitHub Releases
id: publish id: publish
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with: with:
draft: true
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
# do not change ordering of filelist, required for correct job output
files: | files: |
*x64.msi *x64.msi
*x64.exe *x64.exe
*.asc *.asc
allowlist-msi-x64:
uses: ./.github/workflows/av-whitelist.yml
needs: [ publish ]
with:
url: ${{ needs.publish.outputs.download-url-msi-x64 }}
secrets: inherit
allowlist-exe-x64:
uses: ./.github/workflows/av-whitelist.yml
needs: [ publish, allowlist-msi-x64 ]
with:
url: ${{ needs.publish.outputs.download-url-exe-x64 }}
secrets: inherit
notify-winget:
name: Notify for winget-release
if: needs.get-version.outputs.versionType == 'stable'
needs: [publish, get-version]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "MSI packages of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml| release them to winget>."
SLACK_FOOTER: false
MSG_MINIMAL: true
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Submit package - name: Submit package
uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # no_specific_version uses: vedantmgoyal2009/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5 # no_specific_version
with: with:
identifier: Cryptomator.Cryptomator identifier: Cryptomator.Cryptomator
version: ${{ inputs.tag }} version: ${{ inputs.tag }}
+1 -1
View File
@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="25" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="temurin-26" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" /> <option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" /> <option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+4
View File
@@ -0,0 +1,4 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
distributionSha256Sum=5af3b743dd8b876b5c45da33b676251e5f1687712644abb4ee519ca56e1d89ce
+31 -1
View File
@@ -8,11 +8,41 @@ The changelog starts with version 1.19.0.
Changes to prior versions can be found on the [Github release page](https://github.com/cryptomator/cryptomator/releases). Changes to prior versions can be found on the [Github release page](https://github.com/cryptomator/cryptomator/releases).
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.19.1...HEAD) ## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.19.3...HEAD)
No changes yet. No changes yet.
## [1.19.3](https://github.com/cryptomator/cryptomator/releases/1.19.3) - 2026-06-29
### Added
* New error dialog if importing a vault fails ([#4243](https://github.com/cryptomator/cryptomator/pull/4243))
### Fixed
* Fixed Cryptomator file extensions were not registered on Windows ([#4219](https://github.com/cryptomator/cryptomator/issues/4219))
* Fixed warning was displayed when accessing update tab in settings even though an update check did not ran ([#4199](https://github.com/cryptomator/cryptomator/pull/4199))
* Fixed several Decrypt Name dialogs could be opened on the same vault ([#4164](https://github.com/cryptomator/cryptomator/pull/4164))
* Fixed not all mount options in vault specific settings could be displayed ([#4227](https://github.com/cryptomator/cryptomator/pull/4227))
* Fixed localhost alias on Windows was not removed on uninstall ([#3993](https://github.com/cryptomator/cryptomator/issues/3993))
### Changed
* Refactored release pipeline to allow immutable releases ([#4205](https://github.com/cryptomator/cryptomator/pull/4205))
* Updated to JDK 26.0.1 ([#4244](https://github.com/cryptomator/cryptomator/pull/4244))
* Updated to JavaFX 25.0.3 ([#4255](https://github.com/cryptomator/cryptomator/pull/4255))
* Drop signing with Actalis issued certificate ([#4169](https://github.com/cryptomator/cryptomator/pull/4169), [#4262](https://github.com/cryptomator/cryptomator/pull/4262))
* Fix dagger binding graph issues ([#4147](https://github.com/cryptomator/cryptomator/pull/4147))
* Added flatpak build to CI ([#4199](https://github.com/cryptomator/cryptomator/pull/4199))
* Updated dependencies:
- `org.cryptomator:webdav-nio-adapter` from 3.0.1 to 3.0.2
- `org.cryptomator:integrations-api` from 1.8.0 to 1.9.0
- `org.slf4j:slf4j-api` from 2.0.17 to 2.0.18
- `ch.qos.logback:logback-core` from 1.5.32 to 1.5.35
- `ch.qos.logback:logback-classic` from 1.5.32 to 1.5.35
- `com.auth0:java-jwt` from 4.5.1 4.5.2
- `com.fasterxml.jackson.core:jackson-databind` from 2.21.1 to 2.21.4
- `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.21.1 to 2.21.4
- `com.github.ben-manes.caffeine:caffeine` from 3.2.3 to 3.2.4
## [1.19.2](https://github.com/cryptomator/cryptomator/releases/1.19.2) - 2026-03-20 ## [1.19.2](https://github.com/cryptomator/cryptomator/releases/1.19.2) - 2026-03-20
### Security ### Security
+3 -6
View File
@@ -26,6 +26,7 @@ Become our Gold Sponsor and showcase your brand to a targeted audience! Please c
<tr> <tr>
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="56"></a></td> <td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="56"></a></td>
<td><a href="https://www.route4me.com/"><img src="https://cryptomator.org/img/sponsors/route4me.svg" alt="Route4Me" height="56"></a></td> <td><a href="https://www.route4me.com/"><img src="https://cryptomator.org/img/sponsors/route4me.svg" alt="Route4Me" height="56"></a></td>
<td><a href="https://www.apivoid.com/"><img src="https://cryptomator.org/img/sponsors/apivoid.svg" alt="ApiVoid" height="56"></a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -78,16 +79,12 @@ For more information on the security details visit [cryptomator.org](https://doc
### Dependencies ### Dependencies
* JDK 25 (e.g. temurin, zulu) * JDK 26 (e.g. temurin, zulu)
* Maven 3
### Run Maven ### Run Maven
``` ```
mvn clean install ./mvnw clean install
# or mvn clean install -Pwin
# or mvn clean install -Pmac
# or mvn clean install -Plinux
``` ```
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages. This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
+9 -9
View File
@@ -6,29 +6,29 @@ REVISION_NO=`git rev-list --count HEAD`
# check preconditions # check preconditions
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } [ -x ../../../mvnw ] || { echo >&2 "mvnw not found at ../../../mvnw."; exit 1; }
command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; } command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) VERSION=$(../../../mvnw -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION} SEMVER_STR=${VERSION}
CPU_ARCH=$(uname -m) CPU_ARCH=$(uname -m)
if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not supported"; exit 1; fi if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not supported"; exit 1; fi
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} ../../../mvnw -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
# compile # compile
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests ../../../mvnw -B -f ../../../pom.xml clean package -DskipTests
cp ../../../LICENSE.txt ../../../target cp ../../../LICENSE.txt ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods cp ../../../target/cryptomator-*.jar ../../../target/mods
JAVAFX_VERSION=25.0.2 JAVAFX_VERSION=25.0.3
JAVAFX_ARCH="x64" JAVAFX_ARCH="x64"
JAVAFX_JMODS_SHA256='e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b' JAVAFX_JMODS_SHA256='47035c653863a8e4be3dc6f142b8dbd84b4bb1efc9a8cbc68413e6a5ff5e9f50'
if [ "${CPU_ARCH}" = "aarch64" ]; then if [ "${CPU_ARCH}" = "aarch64" ]; then
JAVAFX_ARCH="aarch64" JAVAFX_ARCH="aarch64"
JAVAFX_JMODS_SHA256='c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646' JAVAFX_JMODS_SHA256='e3fd682354346845d2944a2da2b1ff2b6cb9259d92027f2f9c121b9b93c5e42f'
fi fi
# download javaFX jmods # download javaFX jmods
@@ -42,7 +42,7 @@ unzip -o -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/jav
JMOD_VERSION=$(jmod describe ./openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe ./openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout -B -f ../../../pom.xml) POM_JFX_VERSION=$(../../../mvnw help:evaluate "-Dexpression=javafx.version" -q -DforceStdout -B -f ../../../pom.xml)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then
@@ -82,7 +82,7 @@ ${JAVA_HOME}/bin/jpackage \
--vendor "Skymatic GmbH" \ --vendor "Skymatic GmbH" \
--java-options "--enable-preview" \ --java-options "--enable-preview" \
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2025 Skymatic GmbH" \ --copyright "(C) 2016 - 2026 Skymatic GmbH" \
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \ --app-version "${VERSION}.${REVISION_NO}" \
+2 -2
View File
@@ -1,11 +1,11 @@
[Desktop Entry] [Desktop Entry]
Name=Cryptomator Name=Cryptomator
Comment=Cloud Storage Encryption Utility Comment=Cloud Storage Encryption Utility
Exec=cryptomator %F Exec=cryptomator %U
Icon=org.cryptomator.Cryptomator Icon=org.cryptomator.Cryptomator
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Utility;Security;FileTools; Categories=Utility;Security;FileTools;
StartupNotify=true StartupNotify=true
StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp
MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault; MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault;x-scheme-handler/org.cryptomator;
@@ -84,6 +84,9 @@
</content_rating> </content_rating>
<releases> <releases>
<release date="2026-06-29" version="1.19.3">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.3</url>
</release>
<release date="2026-03-20" version="1.19.2"> <release date="2026-03-20" version="1.19.2">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.2</url> <url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.2</url>
</release> </release>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org> Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils Section: utils
Priority: optional Priority: optional
Build-Depends: debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1 Build-Depends: debhelper (>=10), coffeelibs-jdk-26 (>= 26.0.1+8-0ppa1), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1
Standards-Version: 4.5.0 Standards-Version: 4.5.0
Homepage: https://cryptomator.org Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git Vcs-Git: https://github.com/cryptomator/cryptomator.git
+2 -3
View File
@@ -4,12 +4,11 @@
# Uncomment this to turn on verbose mode. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-26-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64) ifeq ($(DEB_BUILD_ARCH),amd64)
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-amd64
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
else ifeq ($(DEB_BUILD_ARCH),arm64) else ifeq ($(DEB_BUILD_ARCH),arm64)
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-arm64
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
endif endif
@@ -46,7 +45,7 @@ override_dh_auto_build:
--vendor "Skymatic GmbH" \ --vendor "Skymatic GmbH" \
--java-options "--enable-preview" \ --java-options "--enable-preview" \
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2025 Skymatic GmbH" \ --copyright "(C) 2016 - 2026 Skymatic GmbH" \
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \ --java-options "-Dfile.encoding=\"utf-8\"" \
+10 -10
View File
@@ -94,7 +94,7 @@ modules:
- mkdir maven - mkdir maven
- tar xf maven.tar.gz --strip-components=1 --exclude=jansi-native --directory=maven - tar xf maven.tar.gz --strip-components=1 --exclude=jansi-native --directory=maven
# Build project # Build project
- maven/bin/mvn clean package -DskipTests -P"linux-$(uname -m)" - maven/bin/mvn clean package -DskipTests
- cp target/cryptomator-*.jar target/mods - cp target/cryptomator-*.jar target/mods
- cd target - cd target
- $JAVA_HOME/bin/jlink - $JAVA_HOME/bin/jlink
@@ -114,7 +114,7 @@ modules:
--dest . --dest .
--name Cryptomator --name Cryptomator
--vendor 'Skymatic GmbH' --vendor 'Skymatic GmbH'
--copyright '(C) 2016 - 2025 Skymatic GmbH' --copyright '(C) 2016 - 2026 Skymatic GmbH'
--java-options '--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator' --java-options '--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator'
--java-options "--sun-misc-unsafe-memory-access=allow" --java-options "--sun-misc-unsafe-memory-access=allow"
--java-options '-Xss5m' --java-options '-Xss5m'
@@ -150,26 +150,26 @@ modules:
dest-filename: jdk.tar.gz dest-filename: jdk.tar.gz
only-arches: only-arches:
- x86_64 - x86_64
url: https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_x64_linux_hotspot_25.0.2_10.tar.gz url: https://github.com/adoptium/temurin26-binaries/releases/download/jdk-26.0.1%2B8/OpenJDK26U-jdk_x64_linux_hotspot_26.0.1_8.tar.gz
sha512: 29043fde119a031c2ca8d57aed445fedd9e7f74608fcdc7a809076ba84cfd1c31f08de2ecccf352e159fdcd1cae172395ed46363007552ff242057826c81ab3a sha512: eb46cda97ffd46e2e0c1f6f977dc204d9cc969b958946287b7e7d0bfe859fd92faccb2f6ef79995421a963c6de140c436af559403e0a2cd27c90b06c20260d5c
- type: file - type: file
dest-filename: jdk.tar.gz dest-filename: jdk.tar.gz
only-arches: only-arches:
- aarch64 - aarch64
url: https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_aarch64_linux_hotspot_25.0.2_10.tar.gz url: https://github.com/adoptium/temurin26-binaries/releases/download/jdk-26.0.1%2B8/OpenJDK26U-jdk_aarch64_linux_hotspot_26.0.1_8.tar.gz
sha512: f1d3ccec3e1f1bed9d632f14b9223709d6e5c2e0d922125d068870dd3016492a2ca8f08924d4a9d0dc5eb2159fa09efee366a748fd0093475baf29e5c70c781a sha512: 3c31671552712a8f0df96df2eeae7e9ad5156c0e98ebd5bf4fa04c14bba58bd5e19ff567ddcdc7aa478f6f4b0a852762a09bd88d3132acb121e667cfe0397034
- type: file - type: file
dest-filename: openjfx.zip dest-filename: openjfx.zip
only-arches: only-arches:
- x86_64 - x86_64
url: https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip url: https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-x64_bin-jmods.zip
sha512: 21f550217101c513f9eb1d7947eba30cb79618238e6539ce770e54e84b01574cdaeba40af602391145f163dd8e43e3794395467413152f13ffffeff948b0ca1b sha512: 75605440f13d0337e70ada1220f77d0d9780fb4602e676f1f07674d8b2e296b25080549d07edcca8f66733e15388860e3472d4ff6d51fd72b0452ed8bbe78022
- type: file - type: file
dest-filename: openjfx.zip dest-filename: openjfx.zip
only-arches: only-arches:
- aarch64 - aarch64
url: https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip url: https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-aarch64_bin-jmods.zip
sha512: a9268409b3803e386490bf1319d0f0a14173cebe862c12254cd51b430ee0a297437d9e38d5ebeae0da8899be898b312b103330d09dcfd3e63c1e7d15f2f14311 sha512: 30c509b880ced1b29e0fafa41073d2350a16296d9439124b9a0e3bb391bf7f27e34d5abcfb338df11e5e898175699bceac40574bc9ad123ff071a5964f8fb14d
- type: file - type: file
dest-filename: maven.tar.gz dest-filename: maven.tar.gz
url: https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.13/apache-maven-3.9.13-bin.tar.gz url: https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.13/apache-maven-3.9.13-bin.tar.gz
+8 -8
View File
@@ -11,11 +11,11 @@ pkgdesc="Multiplatform transparent client-side encryption of your files in the c
arch=('any') arch=('any')
url="https://cryptomator.org/" url="https://cryptomator.org/"
license=('GPL3') license=('GPL3')
depends=('fuse3' 'alsa-lib' 'hicolor-icon-theme' 'libxtst' 'libnet' 'libxrender') depends=('fuse3' 'alsa-lib' 'hicolor-icon-theme' 'libxtst' 'libnet' 'libxrender' 'desktop-file-utils')
makedepends=('maven' 'unzip') makedepends=('maven' 'unzip')
optdepends=('keepassxc-cryptomator: Use KeePassXC to store vault passwords' 'ttf-hanazono: Install this font when using Japanese system language') optdepends=('keepassxc-cryptomator: Use KeePassXC to store vault passwords' 'ttf-hanazono: Install this font when using Japanese system language')
_jdkver=25.0.2+10 _jdkver=26.0.1+8
_jfxver=25.0.2 _jfxver=25.0.3
_src_app_dir=cryptomator-${pkgver//_/-} _src_app_dir=cryptomator-${pkgver//_/-}
source=($SOURCES); source=($SOURCES);
source_x86_64=("jdk-${_jdkver}.tar.gz::https://github.com/adoptium/temurin${_jdkver:0:2}-binaries/releases/download/jdk-${_jdkver//\+/%2B}/OpenJDK${_jdkver:0:2}U-jdk_x64_linux_hotspot_${_jdkver//\+/_}.tar.gz" source_x86_64=("jdk-${_jdkver}.tar.gz::https://github.com/adoptium/temurin${_jdkver:0:2}-binaries/releases/download/jdk-${_jdkver//\+/%2B}/OpenJDK${_jdkver:0:2}U-jdk_x64_linux_hotspot_${_jdkver//\+/_}.tar.gz"
@@ -24,10 +24,10 @@ source_aarch64=("jdk-${_jdkver}.tar.gz::https://github.com/adoptium/temurin${_jd
"openjfx-${_jfxver}.zip::https://download2.gluonhq.com/openjfx/${_jfxver}/openjfx-${_jfxver}_linux-aarch64_bin-jmods.zip") "openjfx-${_jfxver}.zip::https://download2.gluonhq.com/openjfx/${_jfxver}/openjfx-${_jfxver}_linux-aarch64_bin-jmods.zip")
noextract=("jdk-${_jdkver}.tar.gz" "openjfx-${_jfxver}.zip") noextract=("jdk-${_jdkver}.tar.gz" "openjfx-${_jfxver}.zip")
sha256sums=($SOURCES_SHA) sha256sums=($SOURCES_SHA)
sha256sums_x86_64=('987387933b64b9833846dee373b640440d3e1fd48a04804ec01a6dbf718e8ab8' sha256sums_x86_64=('8e512f13e575a43655fc92319436c94890c137b9035cc6bd6f9cf24239704d3a'
'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b') '47035c653863a8e4be3dc6f142b8dbd84b4bb1efc9a8cbc68413e6a5ff5e9f50')
sha256sums_aarch64=('a9d73e711d967dc44896d4f430f73a68fd33590dabc29a7f2fb9f593425b854c' sha256sums_aarch64=('613f9b2861dea937b24d5eca745ef8567733b377d0bb612195acaad0e3f61360'
'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646') 'e3fd682354346845d2944a2da2b1ff2b6cb9259d92027f2f9c121b9b93c5e42f')
options=('!strip') options=('!strip')
validpgpkeys=('58117AFA1F85B3EEC154677D615D449FE6E6A235') validpgpkeys=('58117AFA1F85B3EEC154677D615D449FE6E6A235')
@@ -47,7 +47,7 @@ build() {
cd "${srcdir}/${_src_app_dir}" cd "${srcdir}/${_src_app_dir}"
mvn -B clean package -DskipTests -Plinux mvn -B clean package -DskipTests
cp LICENSE.txt target cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods cp target/cryptomator-*.jar target/mods
+10 -9
View File
@@ -24,29 +24,29 @@ rm -rf runtime dmg *.app *.dmg
# set variables # set variables
APP_NAME="Cryptomator" APP_NAME="Cryptomator"
VENDOR="Skymatic GmbH" VENDOR="Skymatic GmbH"
COPYRIGHT_YEARS="2016 - 2025" COPYRIGHT_YEARS="2016 - 2026"
PACKAGE_IDENTIFIER="org.cryptomator" PACKAGE_IDENTIFIER="org.cryptomator"
MAIN_JAR_GLOB="cryptomator-*.jar" MAIN_JAR_GLOB="cryptomator-*.jar"
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
REVISION_NO=`git rev-list --count HEAD` REVISION_NO=`git rev-list --count HEAD`
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'` VERSION_NO=`../../../mvnw -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
FUSE_LIB="FUSE-T" FUSE_LIB="FUSE-T"
JAVAFX_VERSION=25.0.2 JAVAFX_VERSION=25.0.3
JAVAFX_ARCH="undefined" JAVAFX_ARCH="undefined"
JAVAFX_JMODS_SHA256="undefined" JAVAFX_JMODS_SHA256="undefined"
if [ "$(machine)" = "arm64e" ]; then if [ "$(machine)" = "arm64e" ]; then
JAVAFX_ARCH="aarch64" JAVAFX_ARCH="aarch64"
JAVAFX_JMODS_SHA256="4cd258001c75af7047005c5c891e2400ed11d24fbb09412324c0cbaf8b503c5a" JAVAFX_JMODS_SHA256="a52014d625b8b04e57fd71650f881c1397542b4018e1b04f1b4e66c8800a1f34"
else else
JAVAFX_ARCH="x64" JAVAFX_ARCH="x64"
JAVAFX_JMODS_SHA256="0b4d8463f03901b7425d94628e4116b7078abb8dd540fbec415266fac20bda5c" JAVAFX_JMODS_SHA256="3512fabe43aee467538d329cfbbaab3c53dff2a810f0d54e381f461d5e0fac43"
fi fi
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip" JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
# check preconditions # check preconditions
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found. Fix by 'brew install maven'."; exit 1; } [ -x ../../../mvnw ] || { echo >&2 "mvnw not found at ../../../mvnw."; exit 1; }
command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found. Fix by 'brew install create-dmg'."; exit 1; } command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found. Fix by 'brew install create-dmg'."; exit 1; }
if [ -n "${CODESIGN_IDENTITY}" ]; then if [ -n "${CODESIGN_IDENTITY}" ]; then
command -v codesign >/dev/null 2>&1 || { echo >&2 "codesign not found. Fix by 'xcode-select --install'."; exit 1; } command -v codesign >/dev/null 2>&1 || { echo >&2 "codesign not found. Fix by 'xcode-select --install'."; exit 1; }
@@ -61,7 +61,7 @@ unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javaf
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(../../../mvnw -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
@@ -71,7 +71,7 @@ if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
fi fi
# compile # compile
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac ../../../mvnw -B -f../../../pom.xml clean package -DskipTests -Pmac
cp ../../../LICENSE.txt ../../../target cp ../../../LICENSE.txt ../../../target
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
@@ -111,6 +111,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \ --java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \ --java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \ --java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \ --java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \
@@ -136,7 +137,7 @@ sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Inf
cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/ cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
# generate license # generate license
mvn -B -f../../../pom.xml license:add-third-party \ ../../../mvnw -B -f../../../pom.xml license:add-third-party \
-Dlicense.thirdPartyFilename=license.rtf \ -Dlicense.thirdPartyFilename=license.rtf \
-Dlicense.outputDirectory=dist/mac/dmg/resources \ -Dlicense.outputDirectory=dist/mac/dmg/resources \
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \ -Dlicense.fileTemplate=resources/licenseTemplate.ftl \
+14
View File
@@ -46,6 +46,20 @@
<string>Any</string> <string>Any</string>
</dict> </dict>
</dict> </dict>
<!-- register org.cryptomator:// URL scheme -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org.cryptomator.deeplink</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>org.cryptomator</string>
</array>
</dict>
</array>
<!-- register .cryptomator extension --> <!-- register .cryptomator extension -->
<key>CFBundleDocumentTypes</key> <key>CFBundleDocumentTypes</key>
<array> <array>
+1
View File
@@ -9,3 +9,4 @@ installer
*.jmod *.jmod
resources/jfxJmods.zip resources/jfxJmods.zip
license.rtf license.rtf
**/FAvaultFile.properties
+90 -66
View File
@@ -16,6 +16,19 @@ Param(
# Function Definitions Section # Function Definitions Section
# ============================ # ============================
function Invoke-CommandWithExitCheck {
param (
[string]$Command,
[string[]]$Arguments
)
& $Command @Arguments
if ($LASTEXITCODE -ne 0) {
Write-Error "Command '$Command' failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
}
function Main { function Main {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
@@ -65,7 +78,8 @@ Write-Host "`$Env:JAVA_HOME=$Env:JAVA_HOME"
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor" $copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
# compile # compile
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin Invoke-CommandWithExitCheck -Command `
"mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "clean", "package", "-DskipTests", "-Pwin")
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods" Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
# add runtime # add runtime
@@ -93,9 +107,9 @@ switch ($archName) {
$jmodPaths = "$Env:JAVA_HOME/jmods" $jmodPaths = "$Env:JAVA_HOME/jmods"
} }
'x64' { 'x64' {
$javaFxVersion='25.0.2' $javaFxVersion='25.0.3'
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" $javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
$javaFxJmodsSHA256 = '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3' $javaFxJmodsSHA256 = '0bf9b83260b85607a9ba200124debabd9cdb013cbc0d659e62a20192a7137907'
$javaFxJmods = '.\resources\jfxJmods.zip' $javaFxJmods = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $javaFxJmods) ) { if( !(Test-Path -Path $javaFxJmods) ) {
@@ -129,16 +143,18 @@ if ((& "$Env:JAVA_HOME\bin\jlink" --help | Select-String -Pattern "Linking from
} }
### create runtime ### create runtime
& "$Env:JAVA_HOME\bin\jlink" ` Invoke-CommandWithExitCheck -Command `
--verbose ` "$Env:JAVA_HOME\bin\jlink" -Arguments @(
--output runtime ` "--verbose",
--module-path $jmodPaths ` "--output", "runtime",
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` "--module-path", $jmodPaths,
--strip-native-commands ` "--add-modules", "java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml",
--no-header-files ` "--strip-native-commands",
--no-man-pages ` "--no-header-files",
--strip-debug ` "--no-man-pages",
--compress "zip-0" #do not compress and use msi compression "--strip-debug",
"--compress", "zip-0" #do not compress and use msi compression
)
$appPath = ".\$AppName" $appPath = ".\$AppName"
if ($clean -and (Test-Path -Path $appPath)) { if ($clean -and (Test-Path -Path $appPath)) {
@@ -195,14 +211,15 @@ if ($LASTEXITCODE -ne 0) {
} }
#Create RTF license for msi #Create RTF license for msi
&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` Invoke-CommandWithExitCheck -Command `
"-Dlicense.thirdPartyFilename=license.rtf" ` "mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" ` "-Dlicense.thirdPartyFilename=license.rtf", `
"-Dlicense.outputDirectory=$buildDir\resources\" ` "-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl", `
"-Dlicense.includedScopes=compile" ` "-Dlicense.outputDirectory=$buildDir\resources\", `
"-Dlicense.excludedGroups=^org\.cryptomator" ` "-Dlicense.includedScopes=compile", `
"-Dlicense.failOnMissing=true" ` "-Dlicense.excludedGroups=^org\.cryptomator", `
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" "-Dlicense.failOnMissing=true", `
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges")
# patch app dir # patch app dir
Copy-Item "contrib\*" -Destination "$AppName" Copy-Item "contrib\*" -Destination "$AppName"
@@ -210,42 +227,46 @@ attrib -r "$AppName\$AppName.exe"
attrib -r "$AppName\${AppName} (Debug).exe" attrib -r "$AppName\${AppName} (Debug).exe"
# create .msi # create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources\"
$Env:JP_WIXHELPER_DIR = "." $Env:JP_WIXWIZARD_RESOURCES_PROPERTIES_FORMAT = "${Env:JP_WIXWIZARD_RESOURCES}".Replace('\', '\\');
& "$Env:JAVA_HOME\bin\jpackage" ` $Env:JP_WIXHELPER_DIR = ""
--verbose `
--type msi `
--win-upgrade-uuid $UpgradeUUID `
--app-image $AppName `
--dest installer `
--name $AppName `
--vendor $Vendor `
--copyright $copyright `
--app-version "$semVerNo.$revisionNo" `
--win-menu `
--win-dir-chooser `
--win-shortcut-prompt `
--win-menu-group $AppName `
--resource-dir resources `
--license-file resources/license.rtf `
--win-update-url $UpdateUrl `
--about-url $AboutUrl `
--file-associations resources/FAvaultFile.properties
if ($LASTEXITCODE -ne 0) { Get-Content .\resources\FAvaultFile.template.properties ` # Similar to envsubst
Write-Error "jpackage MSI failed with exit code $LASTEXITCODE" | ForEach-Object { $ExecutionContext.InvokeCommand.ExpandString($_) } `
return 1; | Out-File -FilePath .\resources\FAvaultFile.properties
}
Invoke-CommandWithExitCheck -Command `
"$Env:JAVA_HOME\bin\jpackage" -Arguments @(
"--verbose",
"--type", "msi",
"--win-upgrade-uuid", $UpgradeUUID,
"--app-image", $AppName,
"--dest", "installer",
"--name", $AppName,
"--vendor", $Vendor,
"--copyright", $copyright,
"--app-version", "$semVerNo.$revisionNo",
"--win-menu",
"--win-dir-chooser",
"--win-shortcut-prompt",
"--win-menu-group", $AppName,
"--resource-dir", "resources",
"--license-file", "resources/license.rtf",
"--win-update-url", $UpdateUrl,
"--about-url", $AboutUrl,
"--file-associations", "resources/FAvaultFile.properties"
)
#Create RTF license for bundle #Create RTF license for bundle
&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` Invoke-CommandWithExitCheck -Command `
"-Dlicense.thirdPartyFilename=license.rtf" ` "mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" ` "-Dlicense.thirdPartyFilename=license.rtf", `
"-Dlicense.outputDirectory=$buildDir\bundle\resources\" ` "-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl", `
"-Dlicense.includedScopes=compile" ` "-Dlicense.outputDirectory=$buildDir\bundle\resources\", `
"-Dlicense.excludedGroups=^org\.cryptomator" ` "-Dlicense.includedScopes=compile", `
"-Dlicense.failOnMissing=true" ` "-Dlicense.excludedGroups=^org\.cryptomator", `
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" "-Dlicense.failOnMissing=true", `
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges")
# download Winfsp # download Winfsp
$winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi' $winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
@@ -271,18 +292,21 @@ Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninsta
Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" -Force Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" -Force
# create bundle including winfsp # create bundle including winfsp
& wix build ` Invoke-CommandWithExitCheck -Command `
-define BundleName="$AppName" ` "wix" -Arguments @(
-define BundleVersion="$semVerNo.$revisionNo" ` "build",
-define BundleVendor="$Vendor" ` "-define", "BundleName=$AppName",
-define BundleCopyright="$copyright" ` "-define", "BundleVersion=$semVerNo.$revisionNo",
-define AboutUrl="$AboutUrl" ` "-define", "BundleVendor=$Vendor",
-define HelpUrl="$HelpUrl" ` "-define", "BundleCopyright=$copyright",
-define UpdateUrl="$UpdateUrl" ` "-define", "AboutUrl=$AboutUrl",
-ext "WixToolset.Util.wixext" ` "-define", "HelpUrl=$HelpUrl",
-ext "WixToolset.BootstrapperApplications.wixext" ` "-define", "UpdateUrl=$UpdateUrl",
.\bundle\bundleWithWinfsp.wxs ` "-ext", "WixToolset.Util.wixext",
-out "installer\$AppName-Installer.exe" "-ext", "WixToolset.BootstrapperApplications.wixext",
".\bundle\bundleWithWinfsp.wxs",
"-out", ".\installer\$AppName-Installer.exe"
)
Write-Host "Created EXE installer .\installer\$AppName-Installer.exe" Write-Host "Created EXE installer .\installer\$AppName-Installer.exe"
return 0; return 0;
+4 -1
View File
@@ -4,15 +4,18 @@
:: This file must be located in the INSTALLDIR :: This file must be located in the INSTALLDIR
set "LOOPBACK_ALIAS=%1" set "LOOPBACK_ALIAS=%1"
set "ACTION=%2"
if "%ACTION%"=="" set "ACTION=install"
:: Log for debugging :: Log for debugging
echo LOOPBACK_ALIAS=%LOOPBACK_ALIAS% echo LOOPBACK_ALIAS=%LOOPBACK_ALIAS%
echo ACTION=%ACTION%
:: Change to INSTALLDIR :: Change to INSTALLDIR
cd %~dp0 cd %~dp0
:: Execute the PowerShell script :: Execute the PowerShell script
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\patchWebDAV.ps1^ powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\patchWebDAV.ps1^
-LoopbackAlias %LOOPBACK_ALIAS% -LoopbackAlias %LOOPBACK_ALIAS% -Action %ACTION%
:: Return the exit code from PowerShell :: Return the exit code from PowerShell
exit /b %ERRORLEVEL% exit /b %ERRORLEVEL%
+36 -11
View File
@@ -1,15 +1,17 @@
#Requires -RunAsAdministrator #Requires -RunAsAdministrator
Param( Param(
[Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias [Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias,
[string] $Action = "install"
) )
New-Variable -Name "sysdir" -Value ([Environment]::SystemDirectory) -Option Constant -Scope Global
New-Variable -Name "hostsFile" -Value "$sysdir\drivers\etc\hosts" -Option Constant -Scope Global
# Adds an alias for 127.0.0.1 to the hosts file # Adds an alias for 127.0.0.1 to the hosts file
function Add-AliasToHost { function Add-AliasToHost {
param ( param (
[string]$LoopbackAlias [string]$LoopbackAlias
) )
$sysdir = [Environment]::SystemDirectory
$hostsFile = "$sysdir\drivers\etc\hosts"
$aliasLine = "127.0.0.1 $LoopbackAlias" $aliasLine = "127.0.0.1 $LoopbackAlias"
foreach ($line in Get-Content $hostsFile) { foreach ($line in Get-Content $hostsFile) {
@@ -18,9 +20,26 @@ function Add-AliasToHost {
} }
} }
Add-Content -Path $hostsFile -Encoding ascii -Value "`r`n$aliasLine" $content = Get-Content $hostsFile
$content += "`r`n$aliasLine"
$content | Set-Content "$hostsFile.tmp" -Encoding ascii
Move-Item "$hostsFile.tmp" $hostsFile -Force
} }
# Removes an alias for 127.0.0.1 from the hosts file
function Remove-AliasFromHost {
param (
[string]$LoopbackAlias
)
$aliasLine = "127.0.0.1 $LoopbackAlias"
$content = Get-Content $hostsFile
$newContent = $content | Where-Object { $_ -ne $aliasLine }
$newContent | Set-Content "$hostsFile.tmp" -Encoding ascii
Move-Item "$hostsFile.tmp" $hostsFile -Force
}
# Sets in the registry the webclient file size limit to the maximum value # Sets in the registry the webclient file size limit to the maximum value
function Set-WebDAVFileSizeLimit { function Set-WebDAVFileSizeLimit {
@@ -54,14 +73,20 @@ function Edit-ProviderOrder {
New-ItemProperty -Path $RegistryPath -Name $Name -Value $UpdatedOrder -PropertyType String -Force | Out-Null New-ItemProperty -Path $RegistryPath -Name $Name -Value $UpdatedOrder -PropertyType String -Force | Out-Null
} }
if ($Action -eq "install") {
Add-AliasToHost $LoopbackAlias
Write-Output 'Ensured alias exists in hosts file'
Add-AliasToHost $LoopbackAlias Set-WebDAVFileSizeLimit
Write-Output 'Ensured alias exists in hosts file' Write-Output 'Set WebDAV file size limit'
Set-WebDAVFileSizeLimit Edit-ProviderOrder
Write-Output 'Set WebDAV file size limit' Write-Output 'Ensured correct provider order'
} elseif ($Action -eq "uninstall") {
Edit-ProviderOrder Remove-AliasFromHost $LoopbackAlias
Write-Output 'Ensured correct provider order' Write-Output 'Ensured alias removed from hosts file'
} else {
Write-Error "Invalid action: $Action. Only 'install' or 'uninstall' are valid."
}
exit 0 exit 0
@@ -1,4 +1,4 @@
mime-type=application/vnd.cryptomator.vault mime-type=application/vnd.cryptomator.vault
extension=cryptomator extension=cryptomator
description=Cryptomator Vault File description=Cryptomator Vault File
icon=resources/Cryptomator-Vault.ico icon=${env:JP_WIXWIZARD_RESOURCES_PROPERTIES_FORMAT}Cryptomator-Vault.ico
+25 -7
View File
@@ -27,6 +27,7 @@
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?> <?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
<?define CloseApplicationTarget= "cryptomator.exe" ?> <?define CloseApplicationTarget= "cryptomator.exe" ?>
<?define LoopbackAlias= "cryptomator-vault" ?> <?define LoopbackAlias= "cryptomator-vault" ?>
<?define UrlProtocolScheme= "org.cryptomator" ?>
<?include $(var.JpConfigDir)/overrides.wxi ?> <?include $(var.JpConfigDir)/overrides.wxi ?>
@@ -68,7 +69,7 @@
<?endif?> <?endif?>
<!-- TODO: how does this work again? --> <!-- TODO: how does this work again? -->
<ns0:Binary Id="JpCaDll" SourceFile="$(env.JP_WIXHELPER_DIR)\wixhelper.dll" /> <ns0:Binary Id="JpCaDll" SourceFile="$(env.JP_WIXHELPER_DIR)msica.dll"></ns0:Binary>
<ns0:CustomAction Id="JpFindRelatedProducts" BinaryRef="JpCaDll" DllEntry="FindRelatedProductsEx" /> <ns0:CustomAction Id="JpFindRelatedProducts" BinaryRef="JpCaDll" DllEntry="FindRelatedProductsEx" />
<?ifndef SkipCryptomatorLegacyCheck ?> <?ifndef SkipCryptomatorLegacyCheck ?>
@@ -97,6 +98,19 @@
<ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/> <ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
</ns0:ProgId> </ns0:ProgId>
</ns0:Component> </ns0:Component>
<!-- Register "org.cryptomator://" URL protocol handler -->
<ns0:Component Bitness="always64" Id="UrlProtocolHandler" Guid="*">
<ns0:RegistryKey Root="HKMU" Key="Software\Classes\$(var.UrlProtocolScheme)">
<ns0:RegistryValue Type="string" Value="URL:$(var.JpAppName) Protocol" KeyPath="yes"/>
<ns0:RegistryValue Name="URL Protocol" Type="string" Value=""/>
<ns0:RegistryKey Key="DefaultIcon">
<ns0:RegistryValue Type="string" Value="[INSTALLDIR]$(var.JpAppName).exe,0"/>
</ns0:RegistryKey>
<ns0:RegistryKey Key="shell\open\command">
<ns0:RegistryValue Type="string" Value="&quot;[INSTALLDIR]$(var.JpAppName).exe&quot; &quot;%1&quot;"/>
</ns0:RegistryKey>
</ns0:RegistryKey>
</ns0:Component>
</ns0:DirectoryRef> </ns0:DirectoryRef>
<ns0:StandardDirectory Id="CommonAppDataFolder"> <ns0:StandardDirectory Id="CommonAppDataFolder">
@@ -126,6 +140,7 @@
<ns0:ComponentGroupRef Id="FileAssociations"/> <ns0:ComponentGroupRef Id="FileAssociations"/>
<!-- Ref to additional ProgIDs --> <!-- Ref to additional ProgIDs -->
<ns0:ComponentRef Id="nonStartingProgID"/> <ns0:ComponentRef Id="nonStartingProgID"/>
<ns0:ComponentRef Id="UrlProtocolHandler"/>
<ns0:ComponentRef Id="AdminConfigDir"/> <ns0:ComponentRef Id="AdminConfigDir"/>
<ns0:ComponentRef Id="AdminConfigFile"/> <ns0:ComponentRef Id="AdminConfigFile"/>
</ns0:Feature> </ns0:Feature>
@@ -158,9 +173,13 @@
<ns0:CustomAction Id="DisableUserConfig" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/> <ns0:CustomAction Id="DisableUserConfig" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- WebDAV patches --> <!-- WebDAV patches -->
<ns0:SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot;" Sequence="execute" Before="PatchWebDAV" /> <ns0:SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot; install" Sequence="execute" Before="PatchWebDAV" />
<ns0:CustomAction Id="PatchWebDAV" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/> <ns0:CustomAction Id="PatchWebDAV" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- WebDAV patches (Uninstall) -->
<ns0:SetProperty Id="PatchWebDAVUninstall" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot; uninstall" Sequence="execute" Before="PatchWebDAVUninstall" />
<ns0:CustomAction Id="PatchWebDAVUninstall" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- Update check configuration --> <!-- Update check configuration -->
<ns0:SetProperty Id="PatchUpdateCheck" Value="&quot;[INSTALLDIR]patchUpdateCheck.bat&quot; &quot;[DISABLEUPDATECHECK]&quot;" Sequence="execute" Before="PatchUpdateCheck" /> <ns0:SetProperty Id="PatchUpdateCheck" Value="&quot;[INSTALLDIR]patchUpdateCheck.bat&quot; &quot;[DISABLEUPDATECHECK]&quot;" Sequence="execute" Before="PatchUpdateCheck" />
<ns0:CustomAction Id="PatchUpdateCheck" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/> <ns0:CustomAction Id="PatchUpdateCheck" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
@@ -214,9 +233,8 @@
<ns0:RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to Wix4CloseApplications_* --> <ns0:RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to Wix4CloseApplications_* -->
<ns0:Custom Action="DisableUserConfig" After="InstallFiles" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/> <ns0:Custom Action="DisableUserConfig" After="InstallFiles" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<!-- Skip action on uninstall -->
<!-- TODO: don't skip action, but remove cryptomator alias from hosts file -->
<ns0:Custom Action="PatchWebDAV" After="DisableUserConfig" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/> <ns0:Custom Action="PatchWebDAV" After="DisableUserConfig" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<ns0:Custom Action="PatchWebDAVUninstall" Before="RemoveFiles" Condition="Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE" />
<!-- Configure update check setting if property is provided --> <!-- Configure update check setting if property is provided -->
<ns0:Custom Action="PatchUpdateCheck" After="PatchWebDAV" Condition="DISABLEUPDATECHECK AND NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/> <ns0:Custom Action="PatchUpdateCheck" After="PatchWebDAV" Condition="DISABLEUPDATECHECK AND NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
</ns0:InstallExecuteSequence> </ns0:InstallExecuteSequence>
@@ -225,7 +243,7 @@
<ns0:Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/> <ns0:Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
</ns0:InstallUISequence> </ns0:InstallUISequence>
<ns0:WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" /> <ns0:WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)banner.bmp" />
<ns0:WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\background.bmp" /> <ns0:WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)background.bmp" />
</ns0:Package> </ns0:Package>
</ns0:Wix> </ns0:Wix>
Vendored Executable
+295
View File
@@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"
Vendored
+189
View File
@@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+20 -43
View File
@@ -26,52 +26,51 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>25</project.jdk.version> <project.jdk.version>26</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now --> <!-- Group IDs of jars that need to stay on the class path for now -->
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) --> <!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds> <nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.10.0</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.11.0-SNAPSHOT</cryptomator.cryptofs.version>
<cryptomator.cryptolib.version>2.2.2</cryptomator.cryptolib.version> <cryptomator.cryptolib.version>2.2.2</cryptomator.cryptolib.version>
<cryptomator.integrations.version>1.8.0</cryptomator.integrations.version> <cryptomator.integrations.version>1.9.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.6.0</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.6.1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.5.0-SNAPSHOT</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.5.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.7.0</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>6.0.1</cryptomator.fuse.version> <cryptomator.fuse.version>6.0.1</cryptomator.fuse.version>
<cryptomator.webdav.version>3.0.1</cryptomator.webdav.version> <cryptomator.webdav.version>3.0.2</cryptomator.webdav.version>
<cryptomator.webdav-servlet.version>1.2.12</cryptomator.webdav-servlet.version> <cryptomator.webdav-servlet.version>1.2.12</cryptomator.webdav-servlet.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<caffeine.version>3.2.3</caffeine.version> <caffeine.version>3.2.4</caffeine.version>
<commons-lang3.version>3.20.0</commons-lang3.version> <commons-lang3.version>3.20.0</commons-lang3.version>
<dagger.version>2.59.2</dagger.version> <dagger.version>2.59.2</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<jackson.version>2.21.1</jackson.version> <jackson.version>2.21.4</jackson.version>
<javafx.version>25.0.2</javafx.version> <javafx.version>25.0.3</javafx.version>
<jwt.version>4.5.1</jwt.version> <jwt.version>4.5.2</jwt.version>
<nimbus-jose.version>10.5</nimbus-jose.version> <nimbus-jose.version>10.5</nimbus-jose.version>
<logback.version>1.5.32</logback.version> <logback.version>1.5.35</logback.version>
<slf4j.version>2.0.17</slf4j.version> <slf4j.version>2.0.18</slf4j.version>
<tinyoauth2.version>0.8.1</tinyoauth2.version> <tinyoauth2.version>0.8.1</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version> <zxcvbn.version>1.9.0</zxcvbn.version>
<!-- test dependencies --> <!-- test dependencies -->
<junit.jupiter.version>6.0.3</junit.jupiter.version> <junit.jupiter.version>6.1.0</junit.jupiter.version>
<mockito.version>5.22.0</mockito.version> <mockito.version>5.23.0</mockito.version>
<hamcrest.version>3.0</hamcrest.version> <hamcrest.version>3.0</hamcrest.version>
<!-- build-time dependencies --> <!-- build-time dependencies -->
<jetbrains.annotations.version>26.1.0</jetbrains.annotations.version> <jetbrains.annotations.version>26.1.0</jetbrains.annotations.version>
<dependency-check.version>12.2.0</dependency-check.version> <dependency-check.version>12.2.2</dependency-check.version>
<jacoco.version>0.8.14</jacoco.version> <jacoco.version>0.8.15</jacoco.version>
<license-generator.version>2.7.1</license-generator.version> <license-generator.version>2.7.1</license-generator.version>
<junit-tree-reporter.version>1.5.1</junit-tree-reporter.version>
<mvn-compiler.version>3.15.0</mvn-compiler.version> <mvn-compiler.version>3.15.0</mvn-compiler.version>
<mvn-resources.version>3.5.0</mvn-resources.version> <mvn-resources.version>3.5.0</mvn-resources.version>
<mvn-dependency.version>3.10.0</mvn-dependency.version> <mvn-dependency.version>3.11.0</mvn-dependency.version>
<mvn-surefire.version>3.5.3</mvn-surefire.version> <mvn-surefire.version>3.5.6</mvn-surefire.version>
<mvn-jar.version>3.5.0</mvn-jar.version> <mvn-jar.version>3.5.0</mvn-jar.version>
<!-- Property used by surefire to determine jacoco engine --> <!-- Property used by surefire to determine jacoco engine -->
@@ -356,22 +355,12 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<dependencies>
<dependency>
<groupId>me.fabriciorby</groupId>
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
<version>${junit-tree-reporter.version}</version>
</dependency>
</dependencies>
<configuration> <configuration>
<reportFormat>plain</reportFormat> <reportFormat>plain</reportFormat>
<consoleOutputReporter> <consoleOutputReporter>
<disable>true</disable> <disable>true</disable>
</consoleOutputReporter> </consoleOutputReporter>
<argLine>@{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar} --enable-native-access=javafx.graphics</argLine> <argLine>@{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar} --enable-native-access=javafx.graphics</argLine>
<statelessTestsetInfoReporter
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
</statelessTestsetInfoReporter>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -513,9 +502,6 @@
<os> <os>
<family>mac</family> <family>mac</family>
</os> </os>
<property>
<name>idea.version</name>
</property>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -531,12 +517,9 @@
<activation> <activation>
<os> <os>
<family>unix</family> <family>unix</family>
<name>Linux</name> <name>linux</name>
<arch>aarch64</arch> <arch>aarch64</arch>
</os> </os>
<property>
<name>idea.version</name>
</property>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -576,12 +559,9 @@
<activation> <activation>
<os> <os>
<family>unix</family> <family>unix</family>
<name>Linux</name> <name>linux</name>
<arch>amd64</arch> <arch>amd64</arch>
</os> </os>
<property>
<name>idea.version</name>
</property>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -598,9 +578,6 @@
<os> <os>
<family>windows</family> <family>windows</family>
</os> </os>
<property>
<name>idea.version</name>
</property>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -0,0 +1,32 @@
package org.cryptomator.common.vaults;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
public class NotAVaultDirectoryException extends NoSuchFileException {
public enum Reason {
MISSING_DATA_DIR,
DATA_NOT_A_DIRECTORY,
MISSING_VAULT_CONFIG,
VAULT_CONFIG_ACCESS_DENIED,
UNSUPPORTED_STRUCTURE
}
private final transient Path path;
private final Reason reason;
public NotAVaultDirectoryException(Path path, Reason reason) {
super(path.toString(), null, "Not a vault directory: " + reason);
this.path = path;
this.reason = reason;
}
public Path path() {
return path;
}
public Reason notAVaultReason() {
return reason;
}
}
@@ -22,20 +22,31 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javafx.application.Platform;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.*; import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING;
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
@Singleton @Singleton
public class VaultListManager { public class VaultListManager {
@@ -72,18 +83,57 @@ public class VaultListManager {
return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath())); return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
} }
/**
* Safe to call from any thread: the IO work runs on the calling thread, but the
* {@code ObservableList} mutation is marshaled to the JavaFX application thread.
*/
public Vault add(Path pathToVault) throws IOException { public Vault add(Path pathToVault) throws IOException {
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath(); Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) { assertIsVaultDirectory(normalizedPathToVault);
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
}
return get(normalizedPathToVault) // return get(normalizedPathToVault).orElseGet(() -> {
.orElseGet(() -> { Vault newVault = create(newVaultSettings(normalizedPathToVault));
Vault newVault = create(newVaultSettings(normalizedPathToVault)); if (Platform.isFxApplicationThread()) {
vaultList.add(newVault); addVault(newVault);
return newVault; } else {
}); Platform.runLater(() -> addVault(newVault));
}
return newVault;
});
}
public static void assertIsVaultDirectory(Path pathToVault) throws IOException {
if (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
checkDataDir(pathToVault);
checkConfigFile(pathToVault);
//if vault is legacy _and_ not readable, just say unsupported
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.UNSUPPORTED_STRUCTURE);
}
}
static void checkDataDir(Path pathToVault) throws NotAVaultDirectoryException {
Path dataDir = pathToVault.resolve(DATA_DIR_NAME);
if (!Files.exists(dataDir)) {
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_DATA_DIR);
}
if (!Files.isDirectory(dataDir)) {
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.DATA_NOT_A_DIRECTORY);
}
}
static void checkConfigFile(Path pathToVault) throws NotAVaultDirectoryException {
Path vaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME);
try (var ch = Files.newByteChannel(vaultConfig, StandardOpenOption.READ)) {
ch.read(ByteBuffer.allocate(1));
} catch (AccessDeniedException e) {
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.VAULT_CONFIG_ACCESS_DENIED);
} catch (NoSuchFileException e) {
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG);
} catch (IOException e) {
LOG.warn("Failed to read vault config: {}", e.getMessage());
throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.UNSUPPORTED_STRUCTURE);
}
} }
private VaultSettings newVaultSettings(Path path) { private VaultSettings newVaultSettings(Path path) {
@@ -153,7 +203,7 @@ public class VaultListManager {
//for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME); vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
} }
case VAULT_CONFIG_MISSING -> { case VAULT_CONFIG_MISSING -> {
//Nothing to do here, since there is no config to read //Nothing to do here, since there is no config to read
} }
case MISSING, ALL_MISSING, ERROR, PROCESSING -> { case MISSING, ALL_MISSING, ERROR, PROCESSING -> {
@@ -1,13 +1,16 @@
package org.cryptomator.launcher; package org.cryptomator.launcher;
import java.nio.file.Path; /**
import java.util.Collection; * An event triggering an action in the running application instance.
* <p>
public record AppLaunchEvent(AppLaunchEvent.EventType type, Collection<Path> pathsToOpen) { * Produced by the launch-argument handling (see {@link LaunchArgsParser} and the {@code *RequestHandler}s) and consumed
* by the UI's {@code AppLaunchEventHandler}. Each permitted subtype represents one supported action:
public enum EventType { * <ul>
REVEAL_APP, * <li>{@link RevealRunningEvent} - reveal the already-running app,</li>
OPEN_FILE * <li>{@link OpenFileEvent} - open one or more paths,</li>
} * <li>{@link OpenHubVaultEvent} - open a Hub vault from a deeplink.</li>
* </ul>
*/
public sealed interface AppLaunchEvent permits RevealRunningEvent, OpenFileEvent, OpenHubVaultEvent {
} }
@@ -25,7 +25,7 @@ class CryptomatorModule {
@Provides @Provides
@Singleton @Singleton
@Named("launchEventQueue") @Named("launchEventQueue")
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() { static BlockingQueue<AppLaunchEvent> provideLaunchEventQueue() {
return new ArrayBlockingQueue<>(10); return new ArrayBlockingQueue<>(10);
} }
@@ -41,7 +41,7 @@ class FileOpenRequestHandler {
private void openFiles(OpenFilesEvent evt) { private void openFiles(OpenFilesEvent evt) {
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList(); Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList();
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen); AppLaunchEvent launchEvent = new OpenFileEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent); tryToEnqueueFileOpenRequest(launchEvent);
} }
@@ -60,7 +60,7 @@ class FileOpenRequestHandler {
} }
}).filter(Objects::nonNull).toList(); }).filter(Objects::nonNull).toList();
if (!pathsToOpen.isEmpty()) { if (!pathsToOpen.isEmpty()) {
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen); AppLaunchEvent launchEvent = new OpenFileEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent); tryToEnqueueFileOpenRequest(launchEvent);
} }
} }
@@ -68,7 +68,7 @@ class FileOpenRequestHandler {
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) { private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
if (!launchEventQueue.offer(launchEvent)) { if (!launchEventQueue.offer(launchEvent)) {
LOG.warn("Could not enqueue application launch event.", launchEvent); LOG.warn("Could not enqueue application launch event {}.", launchEvent);
} }
} }
@@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
@@ -16,24 +15,28 @@ class IpcMessageHandler implements IpcMessageListener {
private static final Logger LOG = LoggerFactory.getLogger(IpcMessageHandler.class); private static final Logger LOG = LoggerFactory.getLogger(IpcMessageHandler.class);
private final FileOpenRequestHandler fileOpenRequestHandler; private final LaunchArgsParser launchArgsParser;
private final BlockingQueue<AppLaunchEvent> launchEventQueue; private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject @Inject
public IpcMessageHandler(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) { public IpcMessageHandler(LaunchArgsParser launchArgsParser, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.fileOpenRequestHandler = fileOpenRequestHandler; this.launchArgsParser = launchArgsParser;
this.launchEventQueue = launchEventQueue; this.launchEventQueue = launchEventQueue;
} }
@Override @Override
public void revealRunningApp() { public void revealRunningApp() {
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Collections.emptyList())); launchEventQueue.add(new RevealRunningEvent());
} }
@Override @Override
public void handleLaunchArgs(List<String> args) { public void handleLaunchArgs(List<String> args) {
LOG.debug("Received launch args: {}", args.stream().reduce((a, b) -> a + ", " + b).orElse("")); LOG.debug("Received launch args: {}", args);
fileOpenRequestHandler.handleLaunchArgs(args); try {
launchArgsParser.process(args);
} catch (IllegalArgumentException e) {
LOG.warn("Ignoring malformed launch args: {}", e.getMessage());
}
} }
} }
@@ -0,0 +1,88 @@
package org.cryptomator.launcher;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.URI;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
/**
* Preprocesses the launch arguments and delegates them to the matching handler.
* <p>
* An argument is treated as a URI if it starts with a (non-{@code file}) scheme of at least two characters, e.g.
* {@code cryptomator://…}. Everything else - including plain paths and {@code file://…} URIs - is treated as a file path
* and forwarded to the {@link FileOpenRequestHandler}. The two-character minimum prevents Windows drive letters
* (e.g. {@code C:\…}) from being misinterpreted as URIs.
* <p>
* URIs and file paths must not be mixed and at most a single URI is accepted, which has to be the first argument.
*/
@Singleton
class LaunchArgsParser {
private static final Pattern SCHEME_PATTERN = Pattern.compile("^([a-zA-Z][a-zA-Z0-9+.-]+):.*");
private static final String FILE_SCHEME = "file";
private final FileOpenRequestHandler fileOpenRequestHandler;
private final URIOpenRequestHandler uriOpenRequestHandler;
private final NoopRequestHandler noopRequestHandler;
@Inject
public LaunchArgsParser(FileOpenRequestHandler fileOpenRequestHandler, URIOpenRequestHandler uriOpenRequestHandler, NoopRequestHandler noopRequestHandler) {
this.fileOpenRequestHandler = fileOpenRequestHandler;
this.uriOpenRequestHandler = uriOpenRequestHandler;
this.noopRequestHandler = noopRequestHandler;
}
/**
* Classifies the given launch arguments and delegates them to the responsible handler.
*
* @param args the raw launch arguments
* @throws IllegalArgumentException if URIs and file paths are mixed, if more than one URI is given, if a URI is not
* the first argument, or if a URI argument is malformed
*/
public void process(List<String> args) {
if(args.isEmpty()) {
noopRequestHandler.revealApp();
return;
}
var classified = args.stream().map(LaunchArgsParser::classify).toList();
var uris = classified.stream().filter(arg -> arg.kind() == Kind.URI).toList();
if (uris.isEmpty()) {
var paths = classified.stream().map(Arg::value).toList();
fileOpenRequestHandler.handleLaunchArgs(paths);
return;
}
if (uris.size() > 1) {
throw new IllegalArgumentException("Only a single URI argument is accepted, but got " + uris.size() + ".");
}
if (classified.getFirst().kind() != Kind.URI) {
throw new IllegalArgumentException("URI argument must be the first parameter.");
}
if (classified.size() > 1) {
throw new IllegalArgumentException("Mixing a URI with file paths is not supported.");
}
uriOpenRequestHandler.handleLaunchArgs(URI.create(classified.getFirst().value()));
}
private static Arg classify(String arg) {
var matcher = SCHEME_PATTERN.matcher(arg);
if (!matcher.matches()) {
return new Arg(Kind.PATH, arg);
}
var scheme = matcher.group(1);
if (FILE_SCHEME.equalsIgnoreCase(scheme)) {
// file:// URIs (e.g. passed by Linux file managers) are file paths in disguise
return new Arg(Kind.PATH, Path.of(URI.create(arg)).toString());
}
return new Arg(Kind.URI, arg);
}
private enum Kind {PATH, URI}
private record Arg(Kind kind, String value) {}
}
@@ -0,0 +1,29 @@
package org.cryptomator.launcher;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
import java.util.concurrent.BlockingQueue;
@Singleton
public class NoopRequestHandler {
private static final Logger LOG = LoggerFactory.getLogger(NoopRequestHandler.class);
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject
public NoopRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.launchEventQueue = launchEventQueue;
}
public void revealApp() {
AppLaunchEvent launchEvent = new RevealRunningEvent();
if (!launchEventQueue.offer(launchEvent)) {
LOG.warn("Could not enqueue application launch event {}.", launchEvent);
}
}
}
@@ -0,0 +1,13 @@
package org.cryptomator.launcher;
import java.nio.file.Path;
import java.util.Collection;
/**
* Requests that the given paths (e.g. {@code .cryptomator} vault files) are opened.
*
* @param pathsToOpen the paths to open
*/
public record OpenFileEvent(Collection<Path> pathsToOpen) implements AppLaunchEvent {
}
@@ -0,0 +1,182 @@
package org.cryptomator.launcher;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.ui.keyloading.hub.HubConfig;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Requests opening a Hub vault from an {@code org.cryptomator://vault/open#vaultConfig=…} deeplink.
* <p>
* The single parameter is the vault's {@code vault.cryptomator}, a compact JWS embedded verbatim, carried in the fragment part.
* <p>
* Notes:
* <ul>
* <li> The config is read <em>unverified</em>, since its signature is keyed on the masterkey, which is only obtainable from Hu later on. </li>
* <li> The deeplink parsing makes a strict validation due to untrusted input</li>
* </ul>
*
* @param vaultConfig the decoded, unverified vault config
* @param vaultId the vault's id within its Hub instance, taken from the config's {@code jti} claim
*/
public record OpenHubVaultEvent(VaultConfig.UnverifiedVaultConfig vaultConfig, UUID vaultId) implements AppLaunchEvent {
private static final Logger LOG = LoggerFactory.getLogger(OpenHubVaultEvent.class);
private static final String SCHEME = "org.cryptomator";
private static final String HOST = "vault";
private static final String PATH = "/open";
private static final String PARAM_VAULT_CONFIG = "vaultConfig";
private static final String HUB_HEADER = "hub";
private static final int MAX_CONFIG_LENGTH = 8192; //real Hub vault config is ~1KB leaving some room for extensions
/**
* Attempts to interpret the given URI as an {@code org.cryptomator://vault/open#vaultConfig=…} deeplink.
*
* @param uri the deeplink URI
* @return the parsed event, or an empty optional if the URI's scheme, host or path do not identify a vault-open
* deeplink
* @throws IllegalArgumentException if the URI identifies a vault-open deeplink, but the config is missing, too
* large, not decodable, or does not describe a Hub vault
*/
public static Optional<OpenHubVaultEvent> tryParse(URI uri) {
if (!SCHEME.equalsIgnoreCase(uri.getScheme()) || !HOST.equalsIgnoreCase(uri.getHost()) || !PATH.equals(uri.getPath())) {
return Optional.empty();
}
var params = parseParams(uri.getRawFragment());
var token = params.get(PARAM_VAULT_CONFIG);
if (token == null || token.isBlank()) {
throw new IllegalArgumentException("Missing required fragment parameter '" + PARAM_VAULT_CONFIG + "'.");
}
var vaultConfig = decode(token);
requireHubVault(vaultConfig);
var vaultId = extractVaultId(vaultConfig);
var leftoverParams = params.keySet().stream().filter(k -> !k.equals(PARAM_VAULT_CONFIG)).toList();
if (!leftoverParams.isEmpty()) {
LOG.debug("Ignoring unknown parameters {}", leftoverParams);
}
return Optional.of(new OpenHubVaultEvent(vaultConfig, vaultId));
}
private static VaultConfig.UnverifiedVaultConfig decode(String token) {
// a compact JWS is ASCII, so its character count is its byte count
if (token.length() > MAX_CONFIG_LENGTH) {
throw new IllegalArgumentException("Fragment parameter '%s' must not exceed %d bytes.".formatted(PARAM_VAULT_CONFIG, MAX_CONFIG_LENGTH));
}
try {
return VaultConfig.decode(token);
} catch (VaultConfigLoadException e) {
throw new IllegalArgumentException("Fragment parameter '" + PARAM_VAULT_CONFIG + "' is not a decodable vault config.", e);
}
}
/**
* Ensures the config describes a Hub vault and that the endpoints the app will talk to are usable.
*/
private static void requireHubVault(VaultConfig.UnverifiedVaultConfig vaultConfig) {
var keyIdScheme = vaultConfig.getKeyId().getScheme();
if (keyIdScheme == null || !keyIdScheme.startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX)) {
throw new IllegalArgumentException("Vault config does not describe a Hub vault, but had key id scheme '" + keyIdScheme + "'.");
}
HubConfig hubConfig;
try {
hubConfig = vaultConfig.getHeader(HUB_HEADER, HubConfig.class);
} catch (RuntimeException e) {
throw new IllegalArgumentException("Vault config contains an unreadable '" + HUB_HEADER + "' header.", e);
}
if (hubConfig == null) {
throw new IllegalArgumentException("Vault config contains no '" + HUB_HEADER + "' header.");
}
URI apiBaseUrl;
try {
apiBaseUrl = hubConfig.getApiBaseUrl();
} catch (RuntimeException e) {
throw new IllegalArgumentException("Vault config declares no usable hub api base url.", e);
}
requireUsableEndpoint("apiBaseUrl", apiBaseUrl);
requireUsableEndpoint("authEndpoint", toUri("authEndpoint", hubConfig.authEndpoint));
}
private static URI toUri(String field, String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Vault config declares no hub " + field + ".");
}
try {
return URI.create(value);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Vault config's hub " + field + " is not a valid url, but was '" + value + "'.", e);
}
}
private static void requireUsableEndpoint(String field, URI uri) {
if (!uri.isAbsolute() || uri.getHost() == null) {
throw new IllegalArgumentException("Vault config's hub " + field + " is not an absolute url with a host, but was '" + uri + "'.");
}
// Whether an http host is acceptable (it is, for local development) is decided by CheckHostTrustController
// Here we only ensure the endpoint is shaped like something that decision can be made on.
var scheme = uri.getScheme();
if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) {
throw new IllegalArgumentException("Vault config's hub " + field + " is neither http nor https, but was '" + uri + "'.");
}
}
/**
* Reads the vault id from the config's {@code jti} claim.
* <p>
* Requiring a UUID matters beyond well-formedness: the id is interpolated into the {@code api/vaults/{vaultId}/…}
* request path, so it must not be able to introduce a path segment. A {@code jti} is an arbitrary string, so parsing
* it as a {@link UUID} and passing that on - rather than the raw claim - is what keeps that guarantee.
* <p>
* Hub writes the same id into the key id's trailing path segment, and the two have always agreed, so a config where
* they differ is forged or broken and is rejected.
*/
private static UUID extractVaultId(VaultConfig.UnverifiedVaultConfig vaultConfig) {
var allegedVaultId = vaultConfig.allegedVaultId();
if (allegedVaultId == null || allegedVaultId.isBlank()) {
throw new IllegalArgumentException("Vault config declares no vault id.");
}
UUID vaultId;
try {
vaultId = UUID.fromString(allegedVaultId);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Vault config's vault id is not a uuid, but was '" + allegedVaultId + "'.", e);
}
var keyId = vaultConfig.getKeyId();
var path = keyId.getPath();
var lastSegment = path == null ? "" : path.substring(path.lastIndexOf('/') + 1);
if (!vaultId.toString().equalsIgnoreCase(lastSegment)) {
throw new IllegalArgumentException("Vault config's vault id '" + vaultId + "' does not match its key id '" + keyId + "'.");
}
return vaultId;
}
private static Map<String, String> parseParams(String rawParams) {
var params = new HashMap<String, String>();
if (rawParams == null || rawParams.isEmpty()) {
return params;
}
for (var pair : rawParams.split("&")) {
var idx = pair.indexOf('=');
if (idx < 0) {
continue;
}
var key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
var value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
params.put(key, value);
}
return params;
}
}
@@ -0,0 +1,8 @@
package org.cryptomator.launcher;
/**
* Requests that the already-running application instance reveals itself (brings its main window to the front).
*/
public record RevealRunningEvent() implements AppLaunchEvent {
}
@@ -0,0 +1,58 @@
package org.cryptomator.launcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.function.Function;
@Singleton
public class URIOpenRequestHandler {
private static final Logger LOG = LoggerFactory.getLogger(URIOpenRequestHandler.class);
/**
* The registered deeplink parsers, tried in order. Each returns a matching event, an empty optional if the URI is
* not its concern, or throws {@link IllegalArgumentException} if the URI is its concern but malformed.
*/
private static final List<Function<URI, Optional<? extends AppLaunchEvent>>> DEEPLINK_PARSERS = List.of( //
OpenHubVaultEvent::tryParse //
);
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject
public URIOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.launchEventQueue = launchEventQueue;
}
public void handleLaunchArgs(URI uri) {
AppLaunchEvent launchEvent = toLaunchEvent(uri);
if (!launchEventQueue.offer(launchEvent)) {
LOG.warn("Could not enqueue application launch event {}.", launchEvent);
}
}
private AppLaunchEvent toLaunchEvent(URI uri) {
try {
for (var parser : DEEPLINK_PARSERS) {
var event = parser.apply(uri);
if (event.isPresent()) {
return event.get();
}
}
} catch (IllegalArgumentException e) {
LOG.warn("Received malformed deeplink {}: {}. Revealing running app instead.", uri, e.getMessage());
return new RevealRunningEvent();
}
LOG.warn("Received unsupported deeplink {}, revealing running app instead.", uri);
return new RevealRunningEvent();
}
}
@@ -2,12 +2,14 @@ package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy; import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.NotAVaultDirectoryException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.integrations.uiappearance.Theme; import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationStyle; import org.cryptomator.ui.fxapp.FxApplicationStyle;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -41,6 +43,7 @@ public class ChooseExistingVaultController implements FxController {
private final ObjectProperty<Vault> vault; private final ObjectProperty<Vault> vault;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final Dialogs dialogs;
private final ObservableValue<Image> screenshot; private final ObservableValue<Image> screenshot;
@Inject @Inject
@@ -51,6 +54,7 @@ public class ChooseExistingVaultController implements FxController {
@AddVaultWizardWindow ObjectProperty<Vault> vault, // @AddVaultWizardWindow ObjectProperty<Vault> vault, //
VaultListManager vaultListManager, // VaultListManager vaultListManager, //
ResourceBundle resourceBundle, // ResourceBundle resourceBundle, //
Dialogs dialogs, //
FxApplicationStyle applicationStyle) { FxApplicationStyle applicationStyle) {
this.window = window; this.window = window;
this.successScene = successScene; this.successScene = successScene;
@@ -59,6 +63,7 @@ public class ChooseExistingVaultController implements FxController {
this.vault = vault; this.vault = vault;
this.vaultListManager = vaultListManager; this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.dialogs = dialogs;
this.screenshot = applicationStyle.appliedAppThemeProperty().map(this::selectScreenshot); this.screenshot = applicationStyle.appliedAppThemeProperty().map(this::selectScreenshot);
} }
@@ -87,6 +92,9 @@ public class ChooseExistingVaultController implements FxController {
Vault newVault = vaultListManager.add(vaultPath.get()); Vault newVault = vaultListManager.add(vaultPath.get());
vault.set(newVault); vault.set(newVault);
window.setScene(successScene.get()); window.setScene(successScene.get());
} catch (NotAVaultDirectoryException e) {
LOG.warn("Selected folder is not a vault directory: {}", e.getMessage());
dialogs.prepareNotAVaultDirectoryDialog(window, e).build().showAndWait();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to open existing vault.", e); LOG.error("Failed to open existing vault.", e);
appWindows.showErrorWindow(e, window, window.getScene()); appWindows.showErrorWindow(e, window, window.getScene());
@@ -58,8 +58,6 @@ public class DecryptFileNamesViewController implements FxController {
private final Stage window; private final Stage window;
private final Vault vault; private final Vault vault;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final List<Path> initialList;
@FXML @FXML
public TableColumn<CipherAndCleartext, String> ciphertextColumn; public TableColumn<CipherAndCleartext, String> ciphertextColumn;
@FXML @FXML
@@ -68,12 +66,11 @@ public class DecryptFileNamesViewController implements FxController {
public TableView<CipherAndCleartext> cipherToCleartextTable; public TableView<CipherAndCleartext> cipherToCleartextTable;
@Inject @Inject
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) { public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList()); this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
this.initialList = pathsToDecrypt;
} }
@FXML @FXML
@@ -97,8 +94,7 @@ public class DecryptFileNamesViewController implements FxController {
}); });
cipherToCleartextTable.setOnDragDropped(event -> { cipherToCleartextTable.setOnDragDropped(event -> {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) { if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList()); decrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
cipherToCleartextTable.setItems(mapping);
} }
}); });
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping)); cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
@@ -124,9 +120,7 @@ public class DecryptFileNamesViewController implements FxController {
}); });
} }
}); });
if (!initialList.isEmpty()) { window.setOnHidden(_ -> mapping.clear());
checkAndDecrypt(initialList);
}
} }
private void copySingleCelltoClipboard() { private void copySingleCelltoClipboard() {
@@ -149,10 +143,18 @@ public class DecryptFileNamesViewController implements FxController {
fileChooser.setInitialDirectory(vault.getPath().toFile()); fileChooser.setInitialDirectory(vault.getPath().toFile());
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window); var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
if (ciphertextNodes != null) { if (ciphertextNodes != null) {
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList()); decrypt(ciphertextNodes.stream().map(File::toPath).toList());
} }
} }
public void decrypt(List<Path> pathsToDecrypt) {
if (pathsToDecrypt.isEmpty()) {
return;
}
checkAndDecrypt(pathsToDecrypt);
cipherToCleartextTable.setItems(mapping);
}
private void checkAndDecrypt(List<Path> pathsToDecrypt) { private void checkAndDecrypt(List<Path> pathsToDecrypt) {
mapping.clear(); mapping.clear();
//Assumption: All files are in the same directory //Assumption: All files are in the same directory
@@ -28,23 +28,28 @@ public interface DecryptNameComponent {
@FxmlScene(FxmlFile.DECRYPTNAMES) @FxmlScene(FxmlFile.DECRYPTNAMES)
Lazy<Scene> decryptNamesView(); Lazy<Scene> decryptNamesView();
DecryptFileNamesViewController controller();
@DecryptNameWindow @DecryptNameWindow
Vault vault(); Vault vault();
default void showDecryptFileNameWindow() { default void showDecryptFileNameWindow(List<Path> pathsToDecrypt) {
Stage s = window(); Stage s = window();
s.setScene(decryptNamesView().get()); s.setScene(decryptNamesView().get());
s.sizeToScene(); s.sizeToScene();
if (vault().isUnlocked()) { if (vault().isUnlocked()) {
controller().decrypt(pathsToDecrypt);
s.show(); s.show();
s.requestFocus();
} else { } else {
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState()); LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
s.close();
} }
} }
@Subcomponent.Factory @Subcomponent.Factory
interface Factory { interface Factory {
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt); DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner);
} }
} }
@@ -1,6 +1,7 @@
package org.cryptomator.ui.dialogs; package org.cryptomator.ui.dialogs;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.NotAVaultDirectoryException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
@@ -61,9 +62,9 @@ public class Dialogs {
.setOkButtonKey(BUTTON_KEY_CLOSE); .setOkButtonKey(BUTTON_KEY_CLOSE);
} }
public SimpleDialog.Builder prepareHubVaultArchived(Stage window, Vault vault) { public SimpleDialog.Builder prepareHubVaultArchived(Stage window, String vaultDisplayName) {
return createDialogBuilder().setOwner(window) // return createDialogBuilder().setOwner(window) //
.setTitleKey("unlock.title", vault.getDisplayName()) // .setTitleKey("unlock.title", vaultDisplayName) //
.setMessageKey("hub.archived.message") // .setMessageKey("hub.archived.message") //
.setDescriptionKey("hub.archived.description") // .setDescriptionKey("hub.archived.description") //
.setIcon(FontAwesome5Icon.BAN)// .setIcon(FontAwesome5Icon.BAN)//
@@ -139,6 +140,24 @@ public class Dialogs {
.setCancelAction(Stage::close); .setCancelAction(Stage::close);
} }
public SimpleDialog.Builder prepareNotAVaultDirectoryDialog(Stage window, NotAVaultDirectoryException e) {
String descriptionKey = switch (e.notAVaultReason()) {
case MISSING_DATA_DIR -> "addvaultwizard.existing.notAVault.description.missingDataDir";
case DATA_NOT_A_DIRECTORY -> "addvaultwizard.existing.notAVault.description.dataNotADirectory";
case MISSING_VAULT_CONFIG -> "addvaultwizard.existing.notAVault.description.missingVaultConfig";
case VAULT_CONFIG_ACCESS_DENIED -> "addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied";
case UNSUPPORTED_STRUCTURE -> "addvaultwizard.existing.notAVault.description.unsupportedStructure";
};
return createDialogBuilder() //
.setOwner(window) //
.setTitleKey("addvaultwizard.existing.notAVault.title") //
.setMessageKey("addvaultwizard.existing.notAVault.message") //
.setDescriptionKey(descriptionKey, e.path().getFileName() != null ? e.path().getFileName().toString() : e.path().toString()) //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey(BUTTON_KEY_CLOSE) //
.setOkAction(Stage::close);
}
public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) { public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
return createDialogBuilder() // return createDialogBuilder() //
.setOwner(window) // .setOwner(window) //
@@ -1,15 +1,23 @@
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import org.cryptomator.common.vaults.NotAVaultDirectoryException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.launcher.AppLaunchEvent; import org.cryptomator.launcher.AppLaunchEvent;
import org.cryptomator.launcher.OpenFileEvent;
import org.cryptomator.launcher.OpenHubVaultEvent;
import org.cryptomator.launcher.RevealRunningEvent;
import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.keyloading.hub.HubVaults;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
@@ -28,15 +36,21 @@ class AppLaunchEventHandler {
private final ExecutorService executorService; private final ExecutorService executorService;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final ObservableList<Vault> vaults;
private final VaultService vaultService; private final VaultService vaultService;
private final Stage primaryStage;
private final Dialogs dialogs;
@Inject @Inject
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) { public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, ObservableList<Vault> vaults, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) {
this.launchEventQueue = launchEventQueue; this.launchEventQueue = launchEventQueue;
this.executorService = executorService; this.executorService = executorService;
this.appWindows = appWindows; this.appWindows = appWindows;
this.vaultListManager = vaultListManager; this.vaultListManager = vaultListManager;
this.vaults = vaults;
this.vaultService = vaultService; this.vaultService = vaultService;
this.primaryStage = primaryStage;
this.dialogs = dialogs;
} }
public void startHandlingLaunchEvents() { public void startHandlingLaunchEvents() {
@@ -56,33 +70,57 @@ class AppLaunchEventHandler {
} }
private void handleLaunchEvent(AppLaunchEvent event) { private void handleLaunchEvent(AppLaunchEvent event) {
switch (event.type()) { switch (event) {
case REVEAL_APP -> appWindows.showMainWindow(); case RevealRunningEvent _ -> appWindows.showMainWindow();
case OPEN_FILE -> Platform.runLater(() -> { case OpenFileEvent openFileEvent -> openFileEvent.pathsToOpen().forEach(this::openPotentialVault);
event.pathsToOpen().forEach(this::openPotentialVault); case OpenHubVaultEvent openHubVaultEvent -> openHubVault(openHubVaultEvent);
}
}
/**
* Whether a hub vault is set up on this machine is a purely local question - hub manages the vault's key, not where
* it lives. Only if it is not set up here do we need to ask hub about it.
*/
private void openHubVault(OpenHubVaultEvent event) {
var existing = HubVaults.findByVaultId(vaults, event.vaultId());
if (existing.isPresent()) {
var vault = existing.get();
Platform.runLater(() -> {
if (vault.isUnlocked()) {
vaultService.reveal(vault);
} else if (vault.isLocked()) {
appWindows.startUnlockWorkflow(vault, null);
}
}); });
default -> LOG.warn("Unsupported event type: {}", event.type()); } else {
//TODO: authenticate, ask hub for the vault's details and offer to add it, see docs/hub-vault-open-deeplink-plan.md
LOG.info("Hub vault {} is not set up on this machine.", event.vaultId());
appWindows.showMainWindow();
} }
} }
// TODO deduplicate MainWindowController... // TODO deduplicate MainWindowController...
private void openPotentialVault(Path path) { private void openPotentialVault(Path path) {
assert Platform.isFxApplicationThread(); Path potentialVaultPath = path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? path.getParent() : path;
try { Optional<Vault> existing = vaultListManager.get(potentialVaultPath.normalize().toAbsolutePath());
Path potentialVaultPath = path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? path.getParent() : path; if (existing.isPresent()) {
final Optional<Vault> v = vaultListManager.get(potentialVaultPath); Platform.runLater(() -> {
if (v.isPresent()) { if (existing.get().isUnlocked()) {
if (v.get().isUnlocked()) { vaultService.reveal(existing.get());
vaultService.reveal(v.get()); } else if (existing.get().isLocked()) {
} else if (v.get().isLocked()) { appWindows.startUnlockWorkflow(existing.get(), null);
appWindows.startUnlockWorkflow(v.get(), null);
} }
} else { });
vaultListManager.add(potentialVaultPath); return;
LOG.debug("Added vault {}", potentialVaultPath); }
} try {
vaultListManager.add(potentialVaultPath);
LOG.debug("Added vault {}", potentialVaultPath);
} catch (NotAVaultDirectoryException e) {
LOG.warn("Cannot add {}: {}", potentialVaultPath, e.getMessage());
Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(primaryStage, e).build().showAndWait());
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to add vault " + path, e); LOG.error("Failed to add vault {}", potentialVaultPath, e);
} }
} }
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingRef;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javax.inject.Named; import javax.inject.Named;
@@ -64,7 +65,11 @@ abstract class HealthCheckModule {
@HealthCheckWindow @HealthCheckWindow
@HealthCheckScoped @HealthCheckScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) { static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
return compFactory.create(vault, window).keyloadingStrategy(); try {
return compFactory.create(KeyLoadingRef.forVault(vault), vault, window).keyloadingStrategy();
} catch (IOException e) {
return KeyLoadingStrategy.failed(e);
}
} }
@Provides @Provides
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading;
import dagger.BindsInstance; import dagger.BindsInstance;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -16,7 +17,14 @@ public interface KeyLoadingComponent {
@Subcomponent.Factory @Subcomponent.Factory
interface Factory { interface Factory {
KeyLoadingComponent create(@BindsInstance @KeyLoading Vault vault, @KeyLoading @BindsInstance Stage window); /**
* @param vaultRef the {@link KeyLoadingRef} containing the info to load the key
* @param vault the local vault, or {@code null} if it is not set up on this machine.
* @param window the window to show the key loading scenes in
*/
KeyLoadingComponent create(@BindsInstance @KeyLoading KeyLoadingRef vaultRef, //
@BindsInstance @KeyLoading @Nullable Vault vault, //
@BindsInstance @KeyLoading Stage window);
} }
} }
@@ -2,7 +2,6 @@ package org.cryptomator.ui.keyloading;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
@@ -10,7 +9,6 @@ import org.cryptomator.ui.keyloading.hub.HubKeyLoadingModule;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule; import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
import javax.inject.Provider; import javax.inject.Provider;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -27,14 +25,10 @@ abstract class KeyLoadingModule {
@Provides @Provides
@KeyLoading @KeyLoading
@KeyLoadingScoped @KeyLoadingScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) { static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading KeyLoadingRef vaultRef, Map<String, Provider<KeyLoadingStrategy>> strategies) {
try { String scheme = vaultRef.keyId().getScheme();
String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme(); var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme)); return strategies.getOrDefault(scheme, () -> fallback).get();
return strategies.getOrDefault(scheme, () -> fallback).get();
} catch (IOException e) {
return KeyLoadingStrategy.failed(e);
}
} }
} }
@@ -0,0 +1,50 @@
package org.cryptomator.ui.keyloading;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import java.io.IOException;
import java.net.URI;
/**
* Identifies the vault a key is being loaded for, independently of whether that vault exists on this machine.
* <p>
* Key loading needs the (unverified) vault config selecting the strategy and addresses
* the vault within the strategy, and the display name titles the windows.
* <p>
* The config is <em>unverified</em>: its signature is keyed on the masterkey, which is exactly what key loading is
* about to obtain.
*
* @param vaultConfig the vault's unverified config
* @param displayName the vault's name, as shown to the user
*/
public record KeyLoadingRef(VaultConfig.UnverifiedVaultConfig vaultConfig, String displayName) {
/**
* Describes a vault that is already set up on this machine.
*
* @param vault the vault to load a key for
* @throws IOException if the vault's config cannot be read
*/
public static KeyLoadingRef forVault(Vault vault) throws IOException {
return new KeyLoadingRef(vault.getVaultConfigCache().get(), vault.getDisplayName());
}
/**
* The key id, whose scheme selects the key loading strategy.
*/
public URI keyId() {
return vaultConfig.getKeyId();
}
/**
* The vault's id, i.e. how the vault is addressed within its Hub instance.
* <p>
* Read from the config's {@code jti} claim, which is the authoritative source: the key id carries the same id in its
* trailing path segment, but only its <em>scheme</em> is a source of truth here.
*/
public String vaultId() {
return vaultConfig.allegedVaultId();
}
}
@@ -7,7 +7,6 @@ import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey; import dagger.multibindings.StringKey;
import org.cryptomator.common.settings.DeviceKey; import org.cryptomator.common.settings.DeviceKey;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.common.MessageDigestSupplier; import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
@@ -15,13 +14,12 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingRef;
import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javax.inject.Named; import javax.inject.Named;
import javafx.scene.Scene; import javafx.scene.Scene;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects; import java.util.Objects;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -32,19 +30,15 @@ public abstract class HubKeyLoadingModule {
@Provides @Provides
@KeyLoadingScoped @KeyLoadingScoped
static HubConfig provideHubConfig(@KeyLoading Vault vault) { static HubConfig provideHubConfig(@KeyLoading KeyLoadingRef vaultRef) {
try { return vaultRef.vaultConfig().getHeader("hub", HubConfig.class);
return vault.getVaultConfigCache().get().getHeader("hub", HubConfig.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} }
@Provides @Provides
@KeyLoadingScoped @KeyLoadingScoped
@Named("windowTitle") @Named("windowTitle")
static String provideWindowTitle(@KeyLoading Vault vault, ResourceBundle resourceBundle) { static String provideWindowTitle(@KeyLoading KeyLoadingRef vaultRef, ResourceBundle resourceBundle) {
return String.format(resourceBundle.getString("unlock.title"), vault.getDisplayName()); return String.format(resourceBundle.getString("unlock.title"), vaultRef.displayName());
} }
@@ -0,0 +1,59 @@
package org.cryptomator.ui.keyloading.hub;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
/**
* Locates Hub vaults among the vaults set up on this machine.
* <p>
* Hub manages a vault's key, not where it lives or how it is laid out, so the local vault list is the only place that
* can answer whether a given Hub vault is already set up here.
*/
public final class HubVaults {
private static final Logger LOG = LoggerFactory.getLogger(HubVaults.class);
private HubVaults() {
}
/**
* Finds the Hub vault with the given id.
* <p>
* A vault whose config cannot be read - e.g. because it sits on storage that is currently unavailable - is skipped
* rather than failing the lookup: one unreachable vault must not prevent finding a different one.
*
* @param vaults the vaults set up on this machine
* @param hubVaultId the vault's id within its Hub instance
* @return the local vault, or empty if none of them is that Hub vault
*/
public static Optional<Vault> findByVaultId(Collection<Vault> vaults, UUID hubVaultId) {
return vaults.stream() //
.filter(vault -> hasVaultId(vault, hubVaultId)) //
.findAny();
}
private static boolean hasVaultId(Vault vault, UUID hubVaultId) {
try {
return hasVaultId(vault.getVaultConfigCache().get(), hubVaultId);
} catch (IOException e) {
LOG.debug("Skipping vault {} while looking for hub vault {}, its config is not readable.", vault.getPath(), hubVaultId);
return false;
}
}
private static boolean hasVaultId(VaultConfig.UnverifiedVaultConfig config, UUID hubVaultId) {
var keyIdScheme = config.getKeyId().getScheme();
if (keyIdScheme == null || !keyIdScheme.startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX)) {
return false; //not a hub vault, so it cannot be the one we are looking for
}
return hubVaultId.toString().equalsIgnoreCase(config.allegedVaultId());
}
}
@@ -7,12 +7,12 @@ import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.Constants; import org.cryptomator.common.Constants;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingRef;
import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -60,10 +60,10 @@ public class ReceiveKeyController implements FxController {
private final Lazy<Scene> invalidLicenseScene; private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient; private final HttpClient httpClient;
private final Dialogs dialogs; private final Dialogs dialogs;
private final Vault vault; private final KeyLoadingRef vaultRef;
@Inject @Inject
public ReceiveKeyController(@KeyLoading Vault vault, // public ReceiveKeyController(@KeyLoading KeyLoadingRef vaultRef, //
ExecutorService executor, // ExecutorService executor, //
@KeyLoading Stage window, // @KeyLoading Stage window, //
HubConfig hubConfig, // HubConfig hubConfig, //
@@ -79,7 +79,7 @@ public class ReceiveKeyController implements FxController {
Dialogs dialogs) { Dialogs dialogs) {
this.window = window; this.window = window;
this.hubConfig = hubConfig; this.hubConfig = hubConfig;
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs) this.vaultId = vaultRef.vaultId();
this.deviceId = deviceId; this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.fsOwnerId = fsOwnerId; this.fsOwnerId = fsOwnerId;
@@ -92,7 +92,7 @@ public class ReceiveKeyController implements FxController {
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
this.dialogs = dialogs; this.dialogs = dialogs;
this.vault = vault; this.vaultRef = vaultRef;
} }
@FXML @FXML
@@ -313,7 +313,7 @@ public class ReceiveKeyController implements FxController {
private void accessGoneVaultArchived() { private void accessGoneVaultArchived() {
window.close(); window.close();
dialogs.prepareHubVaultArchived((Stage)window.getOwner(), vault).build().showAndWait(); dialogs.prepareHubVaultArchived((Stage)window.getOwner(), vaultRef.displayName()).build().showAndWait();
} }
private void accountInitializationRequired() { private void accountInitializationRequired() {
@@ -343,12 +343,6 @@ public class ReceiveKeyController implements FxController {
} }
} }
private static String extractVaultId(URI vaultKeyUri) {
assert vaultKeyUri.getScheme().startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX);
var path = vaultKeyUri.getPath();
return path.substring(path.lastIndexOf('/') + 1);
}
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
private record UserDto(@JsonProperty(value = "name", required = true) String name) {} private record UserDto(@JsonProperty(value = "name", required = true) String name) {}
@@ -1,5 +1,6 @@
package org.cryptomator.ui.keyloading.masterkeyfile; package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -18,6 +19,7 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -41,12 +43,12 @@ public class ChooseMasterkeyFileController implements FxController {
@Inject @Inject
public ChooseMasterkeyFileController(@KeyLoading Stage window, // public ChooseMasterkeyFileController(@KeyLoading Stage window, //
@KeyLoading Vault vault, // @KeyLoading @Nullable Vault vault, //
CompletableFuture<Path> result, // CompletableFuture<Path> result, //
RecoveryKeyComponent.Factory recoveryKeyWindow, // RecoveryKeyComponent.Factory recoveryKeyWindow, //
ResourceBundle resourceBundle) { ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.vault = vault; this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
this.result = result; this.result = result;
this.recoveryKeyWindow = recoveryKeyWindow; this.recoveryKeyWindow = recoveryKeyWindow;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
@@ -5,6 +5,7 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey; import dagger.multibindings.StringKey;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessException;
@@ -15,20 +16,27 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Named; import javax.inject.Named;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class}) @Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class})
public interface MasterkeyFileLoadingModule { public interface MasterkeyFileLoadingModule {
/**
* Key loading may run for a yet-to-setup vault (i.e. deeplink with only a config) - Masterkey loading requires in the current implementation
* an already setup vault.
*/
String NO_LOCAL_VAULT = "masterkey file loading requires a local vault";
@Provides @Provides
@Named("savedPassword") @Named("savedPassword")
@KeyLoadingScoped @KeyLoadingScoped
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading Vault vault) { static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading @Nullable Vault vault) {
if (!keychain.isSupported() || keychain.isLocked()) { if (!keychain.isSupported() || keychain.isLocked()) {
return Optional.empty(); return Optional.empty();
} else { } else {
try { try {
return Optional.ofNullable(keychain.loadPassphrase(vault.getId())); return Optional.ofNullable(keychain.loadPassphrase(Objects.requireNonNull(vault, NO_LOCAL_VAULT).getId()));
} catch (KeychainAccessException e) { } catch (KeychainAccessException e) {
LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e); LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e);
return Optional.empty(); return Optional.empty();
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading.masterkeyfile;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.cryptomator.common.Constants; import org.cryptomator.common.Constants;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.Passphrase; import org.cryptomator.common.Passphrase;
import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
@@ -25,6 +26,7 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
@@ -48,8 +50,8 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
private boolean wrongPassphrase; private boolean wrongPassphrase;
@Inject @Inject
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional<char[]> savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) { public MasterkeyFileLoadingStrategy(@KeyLoading @Nullable Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional<char[]> savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) {
this.vault = vault; this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
this.masterkeyFileAccess = masterkeyFileAccess; this.masterkeyFileAccess = masterkeyFileAccess;
this.window = window; this.window = window;
this.passphraseEntry = passphraseEntry; this.passphraseEntry = passphraseEntry;
@@ -35,6 +35,7 @@ import javafx.scene.transform.Translate;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import javafx.util.Duration; import javafx.util.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -66,9 +67,9 @@ public class PassphraseEntryController implements FxController {
public Animation unlockAnimation; public Animation unlockAnimation;
@Inject @Inject
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) { public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading @Nullable Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) {
this.window = window; this.window = window;
this.vault = vault; this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
this.result = result; this.result = result;
this.savedPassword = savedPassword; this.savedPassword = savedPassword;
this.forgetPassword = forgetPassword; this.forgetPassword = forgetPassword;
@@ -1,9 +1,8 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.EasyBind;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable; import org.cryptomator.common.Nullable;
@@ -58,6 +57,7 @@ public class VaultDetailUnlockedController implements FxController {
private final DecryptNameComponent.Factory decryptNameWindowFactory; private final DecryptNameComponent.Factory decryptNameWindowFactory;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats; private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
private final LoadingCache<Vault, DecryptNameComponent> decryptNameWindows;
private final VaultStatisticsComponent.Builder vaultStatsBuilder; private final VaultStatisticsComponent.Builder vaultStatsBuilder;
private final ObservableValue<Boolean> accessibleViaPath; private final ObservableValue<Boolean> accessibleViaPath;
private final ObservableValue<Boolean> accessibleViaUri; private final ObservableValue<Boolean> accessibleViaUri;
@@ -89,7 +89,8 @@ public class VaultDetailUnlockedController implements FxController {
this.revealPathService = revealPathService; this.revealPathService = revealPathService;
this.decryptNameWindowFactory = decryptNameWindowFactory; this.decryptNameWindowFactory = decryptNameWindowFactory;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats)); this.vaultStats = Caffeine.newBuilder().weakValues().build(this::buildVaultStats);
this.decryptNameWindows = Caffeine.newBuilder().weakValues().build(this::buildDecryptNameWindow);
this.vaultStatsBuilder = vaultStatsBuilder; this.vaultStatsBuilder = vaultStatsBuilder;
var mp = vault.flatMap(Vault::mountPointProperty); var mp = vault.flatMap(Vault::mountPointProperty);
this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false); this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
@@ -161,7 +162,7 @@ public class VaultDetailUnlockedController implements FxController {
} }
private void showDecryptNameWindow(List<Path> pathsToDecrypt) { private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow(); decryptNameWindows.get(vault.get()).showDecryptFileNameWindow(pathsToDecrypt);
} }
private boolean startsWithVaultAccessPoint(Path path) { private boolean startsWithVaultAccessPoint(Path path) {
@@ -198,6 +199,10 @@ public class VaultDetailUnlockedController implements FxController {
return vaultStatsBuilder.vault(vault).build(); return vaultStatsBuilder.vault(vault).build();
} }
private DecryptNameComponent buildDecryptNameWindow(Vault vault) {
return decryptNameWindowFactory.create(vault, mainWindow);
}
@FXML @FXML
public void revealAccessLocation() { public void revealAccessLocation() {
vaultService.reveal(vault.get()); vaultService.reveal(vault.get());
@@ -217,7 +222,7 @@ public class VaultDetailUnlockedController implements FxController {
@FXML @FXML
public void showVaultStatistics() { public void showVaultStatistics() {
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow(); vaultStats.get(vault.get()).showVaultStatisticsWindow();
} }
/* Getter/Setter */ /* Getter/Setter */
@@ -4,6 +4,7 @@ import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.recovery.VaultPreparator; import org.cryptomator.common.recovery.VaultPreparator;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.NotAVaultDirectoryException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent; import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
@@ -23,6 +24,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@@ -55,6 +57,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
@@ -90,6 +93,7 @@ public class VaultListController implements FxController {
private final VaultComponent.Factory vaultComponentFactory; private final VaultComponent.Factory vaultComponentFactory;
private final RecoveryKeyComponent.Factory recoveryKeyWindow; private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final List<MountService> mountServices; private final List<MountService> mountServices;
private final ExecutorService executor;
public ListView<Vault> vaultList; public ListView<Vault> vaultList;
public StackPane root; public StackPane root;
@@ -113,7 +117,8 @@ public class VaultListController implements FxController {
RecoveryKeyComponent.Factory recoveryKeyWindow, // RecoveryKeyComponent.Factory recoveryKeyWindow, //
VaultComponent.Factory vaultComponentFactory, // VaultComponent.Factory vaultComponentFactory, //
List<MountService> mountServices, // List<MountService> mountServices, //
FxFSEventList fxFSEventList) { FxFSEventList fxFSEventList, //
ExecutorService executor) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.vaults = vaults; this.vaults = vaults;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
@@ -127,6 +132,7 @@ public class VaultListController implements FxController {
this.recoveryKeyWindow = recoveryKeyWindow; this.recoveryKeyWindow = recoveryKeyWindow;
this.vaultComponentFactory = vaultComponentFactory; this.vaultComponentFactory = vaultComponentFactory;
this.mountServices = mountServices; this.mountServices = mountServices;
this.executor = executor;
this.emptyVaultList = Bindings.isEmpty(vaults); this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty(); this.unreadEvents = fxFSEventList.unreadEventsProperty();
@@ -324,15 +330,18 @@ public class VaultListController implements FxController {
} }
private void addVault(Path pathToVault) { private void addVault(Path pathToVault) {
try { Path target = pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? pathToVault.getParent() : pathToVault;
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) { executor.execute(() -> {
vaultListManager.add(pathToVault.getParent()); try {
} else { vaultListManager.add(target);
vaultListManager.add(pathToVault); } catch (NotAVaultDirectoryException e) {
LOG.warn("Cannot add {}: {}", target, e.getMessage());
Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(mainWindow, e).build().showAndWait());
} catch (IOException e) {
LOG.warn("Failed to add vault {}", target, e);
Platform.runLater(() -> appWindows.showErrorWindow(e, mainWindow, null));
} }
} catch (IOException e) { });
LOG.debug("Not a vault: {}", pathToVault);
}
} }
@FXML @FXML
@@ -18,7 +18,10 @@ import java.net.URISyntaxException;
public class ShareVaultController implements FxController { public class ShareVaultController implements FxController {
private static final String SCHEME_PREFIX = "hub+"; private static final String SCHEME_PREFIX = "hub+";
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/"; private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/" //
+ "?utm_source=cryptomator-desktop" //
+ "&utm_medium=app" //
+ "&utm_campaign=share-vault";
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/security/best-practices/#sharing-of-vaults"; private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/security/best-practices/#sharing-of-vaults";
private final Stage window; private final Stage window;
@@ -63,7 +63,6 @@ public class TrayMenuBuilder {
vaults.addListener(this::vaultListChanged); vaults.addListener(this::vaultListChanged);
vaults.forEach(v -> { vaults.forEach(v -> {
v.displayNameProperty().addListener(this::vaultListChanged); v.displayNameProperty().addListener(this::vaultListChanged);
v.stateProperty().addListener(this::vaultListChanged);
}); });
try { try {
@@ -77,9 +76,7 @@ public class TrayMenuBuilder {
for (Vault vault : vaults) { for (Vault vault : vaults) {
VaultListManager.redetermineVaultState(vault); VaultListManager.redetermineVaultState(vault);
} }
rebuildMenu();
}); });
vaults.forEach(VaultListManager::redetermineVaultState);
rebuildMenu(); rebuildMenu();
initialized = true; initialized = true;
} catch (TrayMenuException e) { } catch (TrayMenuException e) {
@@ -125,27 +122,18 @@ public class TrayMenuBuilder {
} }
private List<TrayMenuItem> buildSubmenu(Vault vault) { private List<TrayMenuItem> buildSubmenu(Vault vault) {
return switch (vault.getState()) { if (vault.isLocked()) {
case LOCKED -> List.of( // return List.of( //
new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault)) // new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault)) //
); );
case UNLOCKED -> List.of( // } else if (vault.isUnlocked()) {
return List.of( //
new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)), // new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)), //
new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault)) // new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault)) //
); );
case PROCESSING -> List.of( // } else {
new ActionItem(resourceBundle.getString("vault.state.processing"), () -> {}, false) // return List.of();
); }
case MISSING, VAULT_CONFIG_MISSING, ALL_MISSING -> List.of( //
new ActionItem(resourceBundle.getString("vault.state.missing"), () -> {}, false) //
);
case NEEDS_MIGRATION -> List.of( //
new ActionItem(resourceBundle.getString("vault.state.migrationNeeded"), () -> {}, false) //
);
case ERROR -> List.of( //
new ActionItem(resourceBundle.getString("vault.state.error"), () -> {}, false) //
);
};
} }
/* action listeners: */ /* action listeners: */
@@ -14,6 +14,7 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingRef;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -25,6 +26,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -58,7 +60,11 @@ abstract class UnlockModule {
@UnlockWindow @UnlockWindow
@UnlockScoped @UnlockScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) { static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
return compFactory.create(vault, window).keyloadingStrategy(); try {
return compFactory.create(KeyLoadingRef.forVault(vault), vault, window).keyloadingStrategy();
} catch (IOException e) {
return KeyLoadingStrategy.failed(e);
}
} }
@Provides @Provides
@@ -22,7 +22,7 @@
</ImageView> </ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT"> <VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/> <FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
<Label text="© 2016 2025 Skymatic GmbH"/> <Label text="© 2016 2026 Skymatic GmbH"/>
</VBox> </VBox>
</HBox> </HBox>
@@ -9,90 +9,94 @@
<?import javafx.scene.control.Hyperlink?> <?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?> <?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?> <?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?> <?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml" <ScrollPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController" fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
spacing="6"> fitToWidth="true"
<fx:define> hbarPolicy="NEVER">
<ToggleGroup fx:id="mountPointToggleGroup"/> <VBox spacing="6">
</fx:define> <fx:define>
<padding> <ToggleGroup fx:id="mountPointToggleGroup"/>
<Insets topRightBottomLeft="12"/> </fx:define>
</padding> <padding>
<children> <Insets topRightBottomLeft="12"/>
<HBox spacing="12" alignment="CENTER_LEFT"> </padding>
<Label text="%vaultOptions.mount.volume.type" labelFor="$vaultVolumeTypeChoiceBox"/> <children>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/> <HBox spacing="12" alignment="CENTER_LEFT">
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}" accessibleText="%vaultOptions.mount.info"> <Label text="%vaultOptions.mount.volume.type" labelFor="$vaultVolumeTypeChoiceBox"/>
<graphic> <ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}" accessibleText="%vaultOptions.mount.info">
</graphic>
<tooltip>
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip>
</Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}" accessibleText="%preferences.volume.docsTooltip">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</HBox>
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port" labelFor="$vaultLoopbackPortField"/>
<NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlyOptionAllowed}" managed="${controller.readOnlyOptionAllowed}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity" accessibleText="%vaultOptions.mount.customMountFlags">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
<Label text="%vaultOptions.mount.mountPoint">
<VBox.margin>
<Insets top="9"/>
</VBox.margin>
</Label>
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointAutoBtn" text="%vaultOptions.mount.mountPoint.auto"/>
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" accessibleText="%vaultOptions.mount.mountPoint.driveLetter"/>
</HBox>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<HBox spacing="6" alignment="CENTER_LEFT">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic> <graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/> <FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
</Button> <tooltip>
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip>
</Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}" accessibleText="%preferences.volume.docsTooltip">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</HBox> </HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" accessibleText="%vaultOptions.mount.mountPoint.custom">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
</children>
</VBox> <Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port" labelFor="$vaultLoopbackPortField"/>
<NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlyOptionAllowed}" managed="${controller.readOnlyOptionAllowed}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity" accessibleText="%vaultOptions.mount.customMountFlags">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
<Label text="%vaultOptions.mount.mountPoint">
<VBox.margin>
<Insets top="9"/>
</VBox.margin>
</Label>
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointAutoBtn" text="%vaultOptions.mount.mountPoint.auto"/>
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" accessibleText="%vaultOptions.mount.mountPoint.driveLetter"/>
</HBox>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<HBox spacing="6" alignment="CENTER_LEFT">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" accessibleText="%vaultOptions.mount.mountPoint.custom">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
</children>
</VBox>
</ScrollPane>
@@ -110,6 +110,13 @@ addvaultwizard.existing.restore=Restore…
addvaultwizard.existing.chooseBtn=Choose… addvaultwizard.existing.chooseBtn=Choose…
addvaultwizard.existing.filePickerTitle=Select Vault File addvaultwizard.existing.filePickerTitle=Select Vault File
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
addvaultwizard.existing.notAVault.title=Not a Vault
addvaultwizard.existing.notAVault.message=The selected folder is not a Cryptomator vault
addvaultwizard.existing.notAVault.description.missingDataDir=The required "d" subdirectory is missing inside "%s".
addvaultwizard.existing.notAVault.description.dataNotADirectory=The "d" entry inside "%s" is not a directory.
addvaultwizard.existing.notAVault.description.missingVaultConfig=The required "vault.cryptomator" file is missing inside "%s".
addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied=File "vault.cryptomator" inside "%s" cannot be read due to insufficient access rights.
addvaultwizard.existing.notAVault.description.unsupportedStructure=The directory structure of "%s" is not supported.
## Success ## Success
addvaultwizard.success.nextStepsInstructions=Added vault "%s".\nYou need to unlock this vault to access or add contents. Alternatively you can unlock it at any later point in time. addvaultwizard.success.nextStepsInstructions=Added vault "%s".\nYou need to unlock this vault to access or add contents. Alternatively you can unlock it at any later point in time.
addvaultwizard.success.unlockNow=Unlock Now addvaultwizard.success.unlockNow=Unlock Now
@@ -32,6 +32,7 @@
## Success ## Success
## Failure ## Failure
## Hub ## Hub
### Check Host Authenticity
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
@@ -40,6 +41,7 @@
### Registration Failed ### Registration Failed
### Archived ### Archived
### Unauthorized ### Unauthorized
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
### License Exceeded ### License Exceeded
@@ -154,4 +156,4 @@
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -154,6 +154,8 @@ unlock.error.title=فشل فتح "%s"
hub.noKeychain.message=غير قادر على الوصول إلى مفتاح الجهاز hub.noKeychain.message=غير قادر على الوصول إلى مفتاح الجهاز
hub.noKeychain.description=من أجل فتح مركز مخازن، مطلوب مفتاح جهاز، يتم تأمينه باستخدام سلسلة مفاتيح. للمضي قدما، قم بتمكين "%s" واختر سلسلة مفاتيح في التفضيلات. hub.noKeychain.description=من أجل فتح مركز مخازن، مطلوب مفتاح جهاز، يتم تأمينه باستخدام سلسلة مفاتيح. للمضي قدما، قم بتمكين "%s" واختر سلسلة مفاتيح في التفضيلات.
hub.noKeychain.openBtn=فتح التفضيلات hub.noKeychain.openBtn=فتح التفضيلات
### Check Host Authenticity
hub.checkHostTrust.trustBtn=ثق
### Waiting ### Waiting
hub.auth.message=جاري الانتظار للمصادقة… hub.auth.message=جاري الانتظار للمصادقة…
hub.auth.description=يجب أن يتم إعادة توجيهك تلقائياً إلى صفحة تسجيل الدخول. hub.auth.description=يجب أن يتم إعادة توجيهك تلقائياً إلى صفحة تسجيل الدخول.
@@ -183,6 +185,7 @@ hub.registerFailed.description.deviceAlreadyExists=هذا الجهاز مسجل
### Unauthorized ### Unauthorized
hub.unauthorized.message=تم رفض الوصول hub.unauthorized.message=تم رفض الوصول
hub.unauthorized.description=غير مسموح لك بفتح هذا المستودع. اتصل بمالك المستودع لطلب الوصول. hub.unauthorized.description=غير مسموح لك بفتح هذا المستودع. اتصل بمالك المستودع لطلب الوصول.
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=مطلوب اتخاذ إجراء hub.requireAccountInit.message=مطلوب اتخاذ إجراء
hub.requireAccountInit.description.0=للمتابعة، يرجى إكمال الخطوات المطلوبة في hub.requireAccountInit.description.0=للمتابعة، يرجى إكمال الخطوات المطلوبة في
@@ -645,4 +648,4 @@ eventView.entry.inUse.showEncrypted=عرض ملَف المشفر
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -151,6 +151,8 @@ unlock.error.title="%s" биге асылманы
hub.noKeychain.message=Йыһаз асҡысына инеү рөхсәте юҡ hub.noKeychain.message=Йыһаз асҡысына инеү рөхсәте юҡ
hub.noKeychain.description=Хаб һаҡлағыстарын асыу өсөн йыһаз асҡысы кәрәк, ул иһә асҡыс сылбырында һаҡлана. Артабан “%s” мөмкинлеге бирегеҙ һәм көйләүҙәрҙә асҡыс сылбырын һайлағыҙ. hub.noKeychain.description=Хаб һаҡлағыстарын асыу өсөн йыһаз асҡысы кәрәк, ул иһә асҡыс сылбырында һаҡлана. Артабан “%s” мөмкинлеге бирегеҙ һәм көйләүҙәрҙә асҡыс сылбырын һайлағыҙ.
hub.noKeychain.openBtn=Көйләүҙәрҙе ас hub.noKeychain.openBtn=Көйләүҙәрҙе ас
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Ышан
### Waiting ### Waiting
hub.auth.message=Аутентиклау көтөлә… hub.auth.message=Аутентиклау көтөлә…
hub.auth.description=Автоматик рәүештә инеү битенә йүнәлтелергә тейешһегеҙ. hub.auth.description=Автоматик рәүештә инеү битенә йүнәлтелергә тейешһегеҙ.
@@ -170,6 +172,7 @@ hub.registerSuccess.unlockBtn=Биген ас
### Archived ### Archived
### Unauthorized ### Unauthorized
hub.unauthorized.message=Инеү кире ҡағылды hub.unauthorized.message=Инеү кире ҡағылды
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Эш-хәрәкәт кәрәкле hub.requireAccountInit.message=Эш-хәрәкәт кәрәкле
hub.requireAccountInit.description.0=Дауам итер өсөн, кәрәкле аҙымдар тамамланырға тейеш урын: hub.requireAccountInit.description.0=Дауам итер өсөн, кәрәкле аҙымдар тамамланырға тейеш урын:
@@ -555,4 +558,4 @@ dokanySupportEnd.preferencesBtn=Көйләүҙәрҙе ас
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -145,6 +145,7 @@ unlock.error.customPath.description.inUse=Дыскавая літара або
hub.noKeychain.message=Няма доступу да ключа прылады hub.noKeychain.message=Няма доступу да ключа прылады
hub.noKeychain.description=Каб разамкнуць скарбніцы Hub, патрэбны ключ прылады, які захаваны ў звязку ключоў. Каб працягнуць, уключы "%s" ды абяры звязак ключоў у наладах. hub.noKeychain.description=Каб разамкнуць скарбніцы Hub, патрэбны ключ прылады, які захаваны ў звязку ключоў. Каб працягнуць, уключы "%s" ды абяры звязак ключоў у наладах.
hub.noKeychain.openBtn=Адчыніць налады hub.noKeychain.openBtn=Адчыніць налады
### Check Host Authenticity
### Waiting ### Waiting
hub.auth.message=Чаканне спраўджання… hub.auth.message=Чаканне спраўджання…
hub.auth.description=Ты мусіш аўтаматычна перанакіравацца на старонку ўваходу. hub.auth.description=Ты мусіш аўтаматычна перанакіравацца на старонку ўваходу.
@@ -162,6 +163,7 @@ hub.registerSuccess.unlockBtn=Адамкнуць
### Archived ### Archived
### Unauthorized ### Unauthorized
hub.unauthorized.message=Адмова ў доступе hub.unauthorized.message=Адмова ў доступе
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Несапраўдная ліцэнзія Hub hub.invalidLicense.message=Несапраўдная ліцэнзія Hub
@@ -532,4 +534,4 @@ dokanySupportEnd.preferencesBtn=Адчыніць налады
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -158,6 +158,7 @@ unlock.error.title=Неуспешно отключване на „%s“
hub.noKeychain.message=Няма достъп до ключа на устройството hub.noKeychain.message=Няма достъп до ключа на устройството
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките. hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
hub.noKeychain.openBtn=Към настройките hub.noKeychain.openBtn=Към настройките
### Check Host Authenticity
### Waiting ### Waiting
hub.auth.message=Изчакване на удостоверяване… hub.auth.message=Изчакване на удостоверяване…
hub.auth.description=Автоматично ще бъдете пренасочени към страницата за вход. hub.auth.description=Автоматично ще бъдете пренасочени към страницата за вход.
@@ -177,6 +178,7 @@ hub.registerSuccess.unlockBtn=Отключване
### Archived ### Archived
### Unauthorized ### Unauthorized
hub.unauthorized.message=Отказан достъп hub.unauthorized.message=Отказан достъп
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Необходимо е действие hub.requireAccountInit.message=Необходимо е действие
hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в
@@ -563,4 +565,4 @@ dokanySupportEnd.preferencesBtn=Към настройките
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -92,6 +92,7 @@ unlock.unlockBtn=আনলক করুন
## Success ## Success
## Failure ## Failure
## Hub ## Hub
### Check Host Authenticity
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
@@ -101,6 +102,7 @@ hub.registerSuccess.unlockBtn=আনলক করুন
### Registration Failed ### Registration Failed
### Archived ### Archived
### Unauthorized ### Unauthorized
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
### License Exceeded ### License Exceeded
@@ -221,4 +223,4 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -104,6 +104,8 @@ unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo
unlock.success.revealBtn=Otkrij pogon unlock.success.revealBtn=Otkrij pogon
## Failure ## Failure
## Hub ## Hub
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Povjerenje
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
@@ -113,6 +115,7 @@ hub.registerSuccess.unlockBtn=Otključaj
### Registration Failed ### Registration Failed
### Archived ### Archived
### Unauthorized ### Unauthorized
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
### License Exceeded ### License Exceeded
@@ -356,4 +359,4 @@ quit.lockAndQuitBtn=Zaključaj i zatvori
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -155,6 +155,8 @@ unlock.error.title=El desblocatge de "%s" ha fallat
hub.noKeychain.message=No es pot accedir a la clau del dispositiu hub.noKeychain.message=No es pot accedir a la clau del dispositiu
hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències. hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències.
hub.noKeychain.openBtn=Obrir les Preferències hub.noKeychain.openBtn=Obrir les Preferències
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Confia-hi
### Waiting ### Waiting
hub.auth.message=Sestà esperant lautenticació… hub.auth.message=Sestà esperant lautenticació…
hub.auth.description=Hauríeu de ser redirigits a la pàgina d'accés. hub.auth.description=Hauríeu de ser redirigits a la pàgina d'accés.
@@ -184,6 +186,7 @@ hub.registerFailed.description.deviceAlreadyExists=El dispositiu ja ha estat reg
### Unauthorized ### Unauthorized
hub.unauthorized.message=Accés denegat hub.unauthorized.message=Accés denegat
hub.unauthorized.description=No estàs autoritzat a obrir aquesta caixa forta. Contacta amb el seu propietari per obtenir accés. hub.unauthorized.description=No estàs autoritzat a obrir aquesta caixa forta. Contacta amb el seu propietari per obtenir accés.
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Acció necessària hub.requireAccountInit.message=Acció necessària
hub.requireAccountInit.description.0=Per a continuar, si us plau, seguiu els passos necessaris en el vostre hub.requireAccountInit.description.0=Per a continuar, si us plau, seguiu els passos necessaris en el vostre
@@ -611,4 +614,4 @@ shareVault.hub.openHub=Obre Cryptomator Hub
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
@@ -150,6 +150,8 @@ unlock.error.title=Odemknutí "%s" selhalo
hub.noKeychain.message=Nelze získat přístup ke klíči zařízení hub.noKeychain.message=Nelze získat přístup ke klíči zařízení
hub.noKeychain.description=Chcete-li odemknout Hub trezory, je potřeba použít klíč zařízení, který je zabezpečen pomocí klíčenky. Chcete-li pokračovat, povolte „%s“ a vyberte klíčenku v nastavení. hub.noKeychain.description=Chcete-li odemknout Hub trezory, je potřeba použít klíč zařízení, který je zabezpečen pomocí klíčenky. Chcete-li pokračovat, povolte „%s“ a vyberte klíčenku v nastavení.
hub.noKeychain.openBtn=Otevřít předvolby hub.noKeychain.openBtn=Otevřít předvolby
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Důvěřovat
### Waiting ### Waiting
hub.auth.message=Čekání na ověření… hub.auth.message=Čekání na ověření…
hub.auth.description=Měli byste být automaticky přesměrováni na přihlašovací stránku. hub.auth.description=Měli byste být automaticky přesměrováni na přihlašovací stránku.
@@ -178,6 +180,7 @@ hub.registerFailed.description.deviceAlreadyExists=Toto zařízení je již regi
### Archived ### Archived
### Unauthorized ### Unauthorized
hub.unauthorized.message=Přístup odepřen hub.unauthorized.message=Přístup odepřen
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Požadována akce hub.requireAccountInit.message=Požadována akce
hub.requireAccountInit.description.0=Chcete-li pokračovat, vyplňte prosím požadované kroky ve vašem hub.requireAccountInit.description.0=Chcete-li pokračovat, vyplňte prosím požadované kroky ve vašem
@@ -572,4 +575,4 @@ shareVault.hub.openHub=Otevřít Cryptomator Hub
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
+18 -1
View File
@@ -109,6 +109,13 @@ addvaultwizard.existing.restore=Gendan…
addvaultwizard.existing.chooseBtn=Vælg… addvaultwizard.existing.chooseBtn=Vælg…
addvaultwizard.existing.filePickerTitle=Vælg boks-fil addvaultwizard.existing.filePickerTitle=Vælg boks-fil
addvaultwizard.existing.filePickerMimeDesc=Cryptomator boks addvaultwizard.existing.filePickerMimeDesc=Cryptomator boks
addvaultwizard.existing.notAVault.title=Ikke en boks
addvaultwizard.existing.notAVault.message=Den valgte mappe skal være en gyldig Cryptomator boks
addvaultwizard.existing.notAVault.description.missingDataDir=Den krævede "d" undermappe mangler inde i "%s".
addvaultwizard.existing.notAVault.description.dataNotADirectory=Indgangen "d" inde i "%s" er ikke en mappe.
addvaultwizard.existing.notAVault.description.missingVaultConfig=Den påkrævede "vault.cryptomator"-fil mangler inde i "%s".
addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied=Filen "vault.cryptomator" inde i "%s" kan ikke læses på grund af utilstrækkelige adgangsrettigheder.
addvaultwizard.existing.notAVault.description.unsupportedStructure=Mappestrukturen for "%s" understøttes ikke.
## Success ## Success
addvaultwizard.success.nextStepsInstructions=Tilføjede boks "%s".\nDu skal låse op for denne boks for at tilgå indholdet. Alternativt kan du låse den op på et senere tidspunkt. addvaultwizard.success.nextStepsInstructions=Tilføjede boks "%s".\nDu skal låse op for denne boks for at tilgå indholdet. Alternativt kan du låse den op på et senere tidspunkt.
addvaultwizard.success.unlockNow=Lås op nu addvaultwizard.success.unlockNow=Lås op nu
@@ -161,6 +168,12 @@ unlock.error.title=Oplåsning af "%s" mislykkedes
hub.noKeychain.message=Kan ikke tilgå enhedsnøgle hub.noKeychain.message=Kan ikke tilgå enhedsnøgle
hub.noKeychain.description=En enhedsnøgle er påkrævet for at låse Hub bokse op. Enhedsnøglen er sikret i en nøglering. For at fortsætte, aktivér “%s” og vælg en nøglering i indstillingerne. hub.noKeychain.description=En enhedsnøgle er påkrævet for at låse Hub bokse op. Enhedsnøglen er sikret i en nøglering. For at fortsætte, aktivér “%s” og vælg en nøglering i indstillingerne.
hub.noKeychain.openBtn=Åbn Indstillinger hub.noKeychain.openBtn=Åbn Indstillinger
### Check Host Authenticity
hub.checkHostTrust.message.check=Kontrollerer konfiguration…
hub.checkHostTrust.message.ask=Stol på denne vært?
hub.checkHostTrust.message.ask.plural=Stol på disse værter?
hub.checkHostTrust.trustBtn=Hav tillid
hub.checkHostTrust.denyBtn=Afvis
### Waiting ### Waiting
hub.auth.message=Afventer godkendelse… hub.auth.message=Afventer godkendelse…
hub.auth.description=Du burde automatisk blive omdirigeret til login-siden. hub.auth.description=Du burde automatisk blive omdirigeret til login-siden.
@@ -192,6 +205,9 @@ hub.archived.description=Denne boks er blevet arkiveret og er ikke længere tilg
### Unauthorized ### Unauthorized
hub.unauthorized.message=Adgang nægtet hub.unauthorized.message=Adgang nægtet
hub.unauthorized.description=Du har ikke tilladelse til at åbne denne boks. Kontant ejeren af boksen for at anmode om adgang. hub.unauthorized.description=Du har ikke tilladelse til at åbne denne boks. Kontant ejeren af boksen for at anmode om adgang.
### Untrusted Host
hub.untrustedHost.message=Vært Ikke pålidelig
hub.untrustedHost.description=Forbindelsen til Hub blev blokeret for din sikkerhed. Hvis du mener, at Hub-værten er sikker, så kontakt din Hub-administrator eller prøv igen.
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Handling påkrævet hub.requireAccountInit.message=Handling påkrævet
hub.requireAccountInit.description.0=For at fortsætte, skal du fuldføre de nødvendige trin i din hub.requireAccountInit.description.0=For at fortsætte, skal du fuldføre de nødvendige trin i din
@@ -305,6 +321,7 @@ preferences.general.debugDirectory=Vis logfiler
preferences.general.autoStart=Start Cryptomator automatisk ved opstart preferences.general.autoStart=Start Cryptomator automatisk ved opstart
preferences.general.keychainBackend=Gem adgangskoder med preferences.general.keychainBackend=Gem adgangskoder med
preferences.general.quickAccessService=Tilføj oplåste bokse til området hurtig adgang preferences.general.quickAccessService=Tilføj oplåste bokse til området hurtig adgang
preferences.general.resetTrustedHosts=Nulstil betroede værter
## Interface ## Interface
preferences.interface=Brugerflade preferences.interface=Brugerflade
preferences.interface.theme=Udseende preferences.interface.theme=Udseende
@@ -716,4 +733,4 @@ eventView.entry.inUse.ignoreLock=Ignorér anvendelsesstatus
## FileIsInUse Notification ## FileIsInUse Notification
notification.inUse.message=Filen er i brug på en anden enhed notification.inUse.message=Filen er i brug på en anden enhed
notification.inUse.description=Filen er åbnet af %s på %s. Bed dem om at lukke filen og lade synkroniseringen afslutte. Du kan ignorere status for at åbne den nu, men det kan forårsage konflikter eller overskrive nyere ændringer. notification.inUse.description=Filen er åbnet af %s på %s. Bed dem om at lukke filen og lade synkroniseringen afslutte. Du kan ignorere status for at åbne den nu, men det kan forårsage konflikter eller overskrive nyere ændringer.
notification.inUse.action=Ignorér anvendelsesstatus notification.inUse.action=Ignorér anvendelsesstatus
@@ -104,6 +104,7 @@ addvaultwizard.existing.restore=Wiederherstellen…
addvaultwizard.existing.chooseBtn=Durchsuchen … addvaultwizard.existing.chooseBtn=Durchsuchen …
addvaultwizard.existing.filePickerTitle=Tresordatei auswählen addvaultwizard.existing.filePickerTitle=Tresordatei auswählen
addvaultwizard.existing.filePickerMimeDesc=Cryptomator-Tresor addvaultwizard.existing.filePickerMimeDesc=Cryptomator-Tresor
addvaultwizard.existing.notAVault.description.unsupportedStructure=Die Verzeichnisstruktur von "%s" wird nicht unterstützt.
## Success ## Success
addvaultwizard.success.nextStepsInstructions=Tresor „%s“ wurde hinzugefügt.\nUm auf Inhalte zuzugreifen oder welche hinzuzufügen, musst du den Tresor entsperren. Du kannst ihn aber auch zu jedem späteren Zeitpunkt entsperren. addvaultwizard.success.nextStepsInstructions=Tresor „%s“ wurde hinzugefügt.\nUm auf Inhalte zuzugreifen oder welche hinzuzufügen, musst du den Tresor entsperren. Du kannst ihn aber auch zu jedem späteren Zeitpunkt entsperren.
addvaultwizard.success.unlockNow=Jetzt entsperren addvaultwizard.success.unlockNow=Jetzt entsperren
@@ -156,6 +157,8 @@ unlock.error.title=„%s“ konnte nicht entsperrt werden
hub.noKeychain.message=Zugriff auf Geräteschlüssel nicht möglich hub.noKeychain.message=Zugriff auf Geräteschlüssel nicht möglich
hub.noKeychain.description=Zum Entsperren von Hub-Tresoren wird ein Geräteschlüssel benötigt, der in einem Schlüsselbund gesichert ist. Um fortzufahren, aktiviere „%s“ und wähle in den Einstellungen einen Schlüsselbund. hub.noKeychain.description=Zum Entsperren von Hub-Tresoren wird ein Geräteschlüssel benötigt, der in einem Schlüsselbund gesichert ist. Um fortzufahren, aktiviere „%s“ und wähle in den Einstellungen einen Schlüsselbund.
hub.noKeychain.openBtn=Einstellungen öffnen hub.noKeychain.openBtn=Einstellungen öffnen
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Vertrauen
### Waiting ### Waiting
hub.auth.message=Warten auf Authentifizierung … hub.auth.message=Warten auf Authentifizierung …
hub.auth.description=Du solltest automatisch zur Anmeldeseite weitergeleitet werden. hub.auth.description=Du solltest automatisch zur Anmeldeseite weitergeleitet werden.
@@ -187,6 +190,7 @@ hub.archived.description=Dieser Tresor wurde archiviert, daher ist ein Zugriff d
### Unauthorized ### Unauthorized
hub.unauthorized.message=Zugriff verweigert hub.unauthorized.message=Zugriff verweigert
hub.unauthorized.description=Du bist nicht berechtigt, diesen Tresor zu öffnen. Wende dich an den Tresoreigentümer, um Zugriff zu erhalten. hub.unauthorized.description=Du bist nicht berechtigt, diesen Tresor zu öffnen. Wende dich an den Tresoreigentümer, um Zugriff zu erhalten.
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Aktion erforderlich hub.requireAccountInit.message=Aktion erforderlich
hub.requireAccountInit.description.0=Um fortzufahren, führe bitte die erforderlichen Schritte in deinem hub.requireAccountInit.description.0=Um fortzufahren, führe bitte die erforderlichen Schritte in deinem
@@ -298,6 +302,7 @@ preferences.general.debugDirectory=Protokolldateien anzeigen
preferences.general.autoStart=Cryptomator beim Systemstart starten preferences.general.autoStart=Cryptomator beim Systemstart starten
preferences.general.keychainBackend=Passwörter speichern mit preferences.general.keychainBackend=Passwörter speichern mit
preferences.general.quickAccessService=Entsperrte Tresore zum Schnellzugriff hinzufügen preferences.general.quickAccessService=Entsperrte Tresore zum Schnellzugriff hinzufügen
preferences.general.resetTrustedHosts=Vertrauenswürdige Hosts zurücksetzen
## Interface ## Interface
preferences.interface=Benutzeroberfläche preferences.interface=Benutzeroberfläche
preferences.interface.theme=Erscheinungsbild preferences.interface.theme=Erscheinungsbild
@@ -701,4 +706,4 @@ eventView.entry.inUse.copyUserAndDevice=Benutzer- und Gerätenamen der Sperre ko
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
notification.inUse.action=Verwendungsstatus ignorieren notification.inUse.action=Verwendungsstatus ignorieren
@@ -154,6 +154,8 @@ unlock.error.title=Ξεκλείδωμα "%s" απέτυχε
hub.noKeychain.message=Δεν είναι δυνατή η πρόσβαση στο κλειδί της συσκευής hub.noKeychain.message=Δεν είναι δυνατή η πρόσβαση στο κλειδί της συσκευής
hub.noKeychain.description=Για να ξεκλειδώσετε τις κρύπτες Hub, απαιτείται ένα κλειδί συσκευής, το οποίο ασφαλίζεται με χρήση μπρελόκ. Για να συνεχίσετε, ενεργοποιήστε το "%s" και επιλέξτε ένα keychain στις προτιμήσεις. hub.noKeychain.description=Για να ξεκλειδώσετε τις κρύπτες Hub, απαιτείται ένα κλειδί συσκευής, το οποίο ασφαλίζεται με χρήση μπρελόκ. Για να συνεχίσετε, ενεργοποιήστε το "%s" και επιλέξτε ένα keychain στις προτιμήσεις.
hub.noKeychain.openBtn=Άνοιγμα Προτιμήσεων hub.noKeychain.openBtn=Άνοιγμα Προτιμήσεων
### Check Host Authenticity
hub.checkHostTrust.trustBtn=Εμπιστευτείτε
### Waiting ### Waiting
hub.auth.message=Αναμονή για επαλήθευση… hub.auth.message=Αναμονή για επαλήθευση…
hub.auth.description=Θα πρέπει να ανακατευθυνθείτε αυτόματα στη σελίδα σύνδεσης. hub.auth.description=Θα πρέπει να ανακατευθυνθείτε αυτόματα στη σελίδα σύνδεσης.
@@ -183,6 +185,7 @@ hub.registerFailed.description.deviceAlreadyExists=Αυτή η συσκευή ε
### Unauthorized ### Unauthorized
hub.unauthorized.message=Δεν επιτρέπεται η πρόσβαση hub.unauthorized.message=Δεν επιτρέπεται η πρόσβαση
hub.unauthorized.description=Δεν είστε εξουσιοδοτημένοι να ανοίξετε αυτή την κρύπτη. Επικοινωνήστε με τον ιδιοκτήτη τς κρύπτης για να ζητήσετε πρόσβαση. hub.unauthorized.description=Δεν είστε εξουσιοδοτημένοι να ανοίξετε αυτή την κρύπτη. Επικοινωνήστε με τον ιδιοκτήτη τς κρύπτης για να ζητήσετε πρόσβαση.
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Απαιτείται ενέργεια hub.requireAccountInit.message=Απαιτείται ενέργεια
hub.requireAccountInit.description.0=Για να συνεχίσετε, παρακαλούμε ολοκληρώστε τα βήματα που απαιτούνται στο δικό σας hub.requireAccountInit.description.0=Για να συνεχίσετε, παρακαλούμε ολοκληρώστε τα βήματα που απαιτούνται στο δικό σας
@@ -645,4 +648,4 @@ eventView.entry.inUse.showEncrypted=Εμφάνιση κρυπτογραφημέ
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification
+30 -1
View File
@@ -18,6 +18,11 @@ generic.button.print=Imprimir
generic.button.remove=Eliminar generic.button.remove=Eliminar
## Vault state ## Vault state
vault.state.locked=Bloqueada
vault.state.unlocked=Desbloqueada
vault.state.missing=Faltante
vault.state.migrationNeeded=Migración obligatoria
vault.state.processing=Procesando
vault.state.error=Error vault.state.error=Error
# Error # Error
@@ -104,6 +109,13 @@ addvaultwizard.existing.restore=…
addvaultwizard.existing.chooseBtn=Elegir… addvaultwizard.existing.chooseBtn=Elegir…
addvaultwizard.existing.filePickerTitle=Seleccionar archivo de bóveda addvaultwizard.existing.filePickerTitle=Seleccionar archivo de bóveda
addvaultwizard.existing.filePickerMimeDesc=Bóveda de Cryptomator addvaultwizard.existing.filePickerMimeDesc=Bóveda de Cryptomator
addvaultwizard.existing.notAVault.title=No es una bóveda
addvaultwizard.existing.notAVault.message=La carpeta seleccionada no es una bóveda de Cryptomator
addvaultwizard.existing.notAVault.description.missingDataDir=Falta el subdirectorio "d" necesario dentro de "%s".
addvaultwizard.existing.notAVault.description.dataNotADirectory=La entrada "d" dentro de "%s" no es un directorio.
addvaultwizard.existing.notAVault.description.missingVaultConfig=Falta el archivo "vault.cryptomator" necesario dentro de "%s".
addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied=El archivo "vault.cryptomator" dentro de "%s" no se puede leer debido a la falta de permisos de acceso.
addvaultwizard.existing.notAVault.description.unsupportedStructure=La estructura de directorios de "%s" no está soportada.
## Success ## Success
addvaultwizard.success.nextStepsInstructions=Bóveda "%s" añadida.\nSe necesita desbloquear esta bóveda para acceder o añadir contenido. Alternativamente se \n puede desbloquear en otro momento. addvaultwizard.success.nextStepsInstructions=Bóveda "%s" añadida.\nSe necesita desbloquear esta bóveda para acceder o añadir contenido. Alternativamente se \n puede desbloquear en otro momento.
addvaultwizard.success.unlockNow=Desbloquear ahora addvaultwizard.success.unlockNow=Desbloquear ahora
@@ -156,6 +168,12 @@ unlock.error.title=Error al desbloquear "%s"
hub.noKeychain.message=No se puede acceder a la clave del dispositivo hub.noKeychain.message=No se puede acceder a la clave del dispositivo
hub.noKeychain.description=Para desbloquear las bóvedas de Hub, se requiere una clave de dispositivo que se asegura con un llavero. Para continuar, habilite "%s" y seleccione un llavero en las preferencias. hub.noKeychain.description=Para desbloquear las bóvedas de Hub, se requiere una clave de dispositivo que se asegura con un llavero. Para continuar, habilite "%s" y seleccione un llavero en las preferencias.
hub.noKeychain.openBtn=Abrir preferencias hub.noKeychain.openBtn=Abrir preferencias
### Check Host Authenticity
hub.checkHostTrust.message.check=Comprobando configuración…
hub.checkHostTrust.message.ask=¿Confiar en este host?
hub.checkHostTrust.message.ask.plural=¿Confiar en estos hosts?
hub.checkHostTrust.trustBtn=Confiar
hub.checkHostTrust.denyBtn=Denegar
### Waiting ### Waiting
hub.auth.message=Esperando la autenticación… hub.auth.message=Esperando la autenticación…
hub.auth.description=Debería ser redirigido automáticamente a la página de inicio de sesión. hub.auth.description=Debería ser redirigido automáticamente a la página de inicio de sesión.
@@ -182,9 +200,14 @@ hub.registerFailed.message=El registro del dispositivo ha fallado
hub.registerFailed.description.generic=Ha ocurrido un error al intentar registrarse. Para más detalles, consulte el registro de la aplicación. hub.registerFailed.description.generic=Ha ocurrido un error al intentar registrarse. Para más detalles, consulte el registro de la aplicación.
hub.registerFailed.description.deviceAlreadyExists=Este dispositivo ya se ha registrado para otro usuario. Intente cambiar la cuenta de usuario o utilice un dispositivo diferente. hub.registerFailed.description.deviceAlreadyExists=Este dispositivo ya se ha registrado para otro usuario. Intente cambiar la cuenta de usuario o utilice un dispositivo diferente.
### Archived ### Archived
hub.archived.message=Bóveda archivada
hub.archived.description=Esta bóveda ha sido archivada y ya no es accesible. Póngase en contacto con el propietario de la bóveda.
### Unauthorized ### Unauthorized
hub.unauthorized.message=Acceso denegado hub.unauthorized.message=Acceso denegado
hub.unauthorized.description=No tiene autorización para abrir esta bóveda. Contacta al propietario de la bóveda para solicitar acceso. hub.unauthorized.description=No tiene autorización para abrir esta bóveda. Contacta al propietario de la bóveda para solicitar acceso.
### Untrusted Host
hub.untrustedHost.message=Host no confiable
hub.untrustedHost.description=La conexión al Hub fue bloqueada por su seguridad. Si cree que el Hub es seguro, póngase en contacto con la administración de Hub o vuelva a intentarlo.
### Requires Account Initialization ### Requires Account Initialization
hub.requireAccountInit.message=Acción requerida hub.requireAccountInit.message=Acción requerida
hub.requireAccountInit.description.0=Para continuar, por favor complete los pasos necesarios en su hub.requireAccountInit.description.0=Para continuar, por favor complete los pasos necesarios en su
@@ -298,6 +321,7 @@ preferences.general.debugDirectory=Revelar archivos de registro
preferences.general.autoStart=Cargar Cryptomator al iniciar el sistema preferences.general.autoStart=Cargar Cryptomator al iniciar el sistema
preferences.general.keychainBackend=Guardar contraseñas con preferences.general.keychainBackend=Guardar contraseñas con
preferences.general.quickAccessService=Añadir bóvedas desbloqueadas al área de acceso rápido preferences.general.quickAccessService=Añadir bóvedas desbloqueadas al área de acceso rápido
preferences.general.resetTrustedHosts=Restablecer hosts de confianza
## Interface ## Interface
preferences.interface=Interfaz preferences.interface=Interfaz
preferences.interface.theme=Apariencia preferences.interface.theme=Apariencia
@@ -403,6 +427,7 @@ stats.access.total=Accesos totales: %d
# Main Window # Main Window
## Vault List ## Vault List
main.vaultlist=Bóvedas main.vaultlist=Bóvedas
main.vaultlist.listEntry=Bóveda %s (%s)
main.vaultlist.emptyList.onboardingInstruction=Haga clic aquí para añadir una bóveda main.vaultlist.emptyList.onboardingInstruction=Haga clic aquí para añadir una bóveda
main.vaultlist.contextMenu.remove=Eliminar… main.vaultlist.contextMenu.remove=Eliminar…
main.vaultlist.contextMenu.lock=Bloquear main.vaultlist.contextMenu.lock=Bloquear
@@ -420,9 +445,11 @@ main.vaultlist.showPreferencesButton.tooltip=Mostrar preferencias
##Notification ##Notification
main.notification.updateAvailable=Existen actualizaciones disponibles. main.notification.updateAvailable=Existen actualizaciones disponibles.
main.notification.support=Soporte de Cryptomator. main.notification.support=Soporte de Cryptomator.
main.notification.closeButton.tooltip=Cerrar barra de información
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger sus archivos. En caso de necesitar ayuda, revise nuestras guías: main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger sus archivos. En caso de necesitar ayuda, revise nuestras guías:
main.vaultDetail.storageLocation=Ubicación de almacenamiento en bóveda
### Locked ### Locked
main.vaultDetail.lockedStatus=BLOQUEADA main.vaultDetail.lockedStatus=BLOQUEADA
main.vaultDetail.unlockBtn=Desbloquear… main.vaultDetail.unlockBtn=Desbloquear…
@@ -511,6 +538,7 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Olvidar contraseña guardada
vaultOptions.masterkey.recoveryKeyExplanation=Una clave de recuperación es el único medio para restaurar el acceso a una bóveda si pierde su contraseña. vaultOptions.masterkey.recoveryKeyExplanation=Una clave de recuperación es el único medio para restaurar el acceso a una bóveda si pierde su contraseña.
vaultOptions.masterkey.showRecoveryKeyBtn=Mostrar clave de recuperación vaultOptions.masterkey.showRecoveryKeyBtn=Mostrar clave de recuperación
vaultOptions.masterkey.recoverPasswordBtn=Restablecer contraseña vaultOptions.masterkey.recoverPasswordBtn=Restablecer contraseña
vaultOptions.masterkey.missingMasterkeyFile=Estas opciones sólo están disponibles si el archivo masterkey está presente en el directorio de la bóveda.
## Hub ## Hub
vaultOptions.hub=Recuperación vaultOptions.hub=Recuperación
vaultOptions.hub.convertInfo=Puede utilizar la clave de recuperación para convertir esta bóveda de Hub en una bóveda con contraseña en una emergencia. vaultOptions.hub.convertInfo=Puede utilizar la clave de recuperación para convertir esta bóveda de Hub en una bóveda con contraseña en una emergencia.
@@ -697,6 +725,7 @@ eventView.entry.brokenFileNode.copyDecrypted=Copiar ruta descifrada
eventView.entry.inUse.message=Fichero en uso eventView.entry.inUse.message=Fichero en uso
eventView.entry.inUse.showDecrypted=Mostrar archivo descifrado eventView.entry.inUse.showDecrypted=Mostrar archivo descifrado
eventView.entry.inUse.showEncrypted=Mostrar archivo cifrado eventView.entry.inUse.showEncrypted=Mostrar archivo cifrado
eventView.entry.inUse.copyUserAndDevice=Copiar nombre de usuario y dispositivo de bloqueo
eventView.entry.inUse.ignoreLock=Ignorar el estado de uso eventView.entry.inUse.ignoreLock=Ignorar el estado de uso
@@ -704,4 +733,4 @@ eventView.entry.inUse.ignoreLock=Ignorar el estado de uso
## FileIsInUse Notification ## FileIsInUse Notification
notification.inUse.message=El archivo está en uso en otro dispositivo notification.inUse.message=El archivo está en uso en otro dispositivo
notification.inUse.description=El archivo está abierto por %s en %s. Solicítale que cierre el archivo y deje que finalice la sincronización. Puede ignorar el estado para abrirlo ahora, pero esto puede causar conflictos o sobrescribir cambios más recientes. notification.inUse.description=El archivo está abierto por %s en %s. Solicítale que cierre el archivo y deje que finalice la sincronización. Puede ignorar el estado para abrirlo ahora, pero esto puede causar conflictos o sobrescribir cambios más recientes.
notification.inUse.action=Ignorar el Estado de Uso notification.inUse.action=Ignorar el Estado de Uso
@@ -114,6 +114,7 @@ unlock.success.revealBtn=نمایش درایو
## Failure ## Failure
## Hub ## Hub
hub.noKeychain.openBtn=باز کردن تنظیمات hub.noKeychain.openBtn=باز کردن تنظیمات
### Check Host Authenticity
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
@@ -124,6 +125,7 @@ hub.registerSuccess.unlockBtn=بازکردن قفل
### Registration Failed ### Registration Failed
### Archived ### Archived
### Unauthorized ### Unauthorized
### Untrusted Host
### Requires Account Initialization ### Requires Account Initialization
### License Exceeded ### License Exceeded
@@ -256,4 +258,4 @@ dokanySupportEnd.preferencesBtn=باز کردن تنظیمات
# Notifications # Notifications
## FileIsInUse Notification ## FileIsInUse Notification

Some files were not shown because too many files have changed in this diff Show More