Skip to content
JDesk
Guides

Signing & distributing

Sign, notarize, verify, and publish native JDesk application packages.

Build an OS-native installer from your packaged app image and understand what signing requires. This guide continues from Package your app: you should already have a working jdeskPackage app image before building an installer.

Build a native installer#

./gradlew jdeskInstaller

jdeskInstaller runs jpackage on the jdeskPackage app image to build the OS's native installer into build/jdesk/installer. It runs on the target OS only — the same cross-packaging rule as jdeskPackage.

The installer type defaults to the OS default; override it with -PjdeskInstallerType:

OSFormatsExample
macOSdmg, pkg./gradlew jdeskInstaller -PjdeskInstallerType=pkg
Windowsmsi, exe./gradlew jdeskInstaller -PjdeskInstallerType=msi
Linuxdeb, rpm./gradlew jdeskInstaller -PjdeskInstallerType=deb

The macOS DMG path is verified end-to-end locally (a real 34 MB DMG); Windows MSI and Linux DEB are built in the CI package jobs. See packaging and signing for the current per-platform status.

Application name and the macOS menu bar#

The name a user sees comes from jdesk.applicationName:

jdesk {
    applicationId.set("dev.example.dragon7")
    applicationName.set("Dragon 7")   // optional; defaults to the last applicationId segment
}

jdeskPackage passes it to jpackage --name, which becomes the app's CFBundleName — the bold application name in the macOS menu bar (just right of the menu). The task also embeds it as a -Djdesk.applicationName launch option so the runtime's Quit <Name> item matches the bold title exactly. Set it once and both agree; leave it unset and both derive from the last applicationId segment (e.g. dragon7).

Only a packaged app can change the bold menu-bar name. On a bundle-less dev launch (./gradlew run) macOS forces that title to the executable name — it shows java, and no runtime call can override it. It reads correctly the moment the app is packaged, because CFBundleName is then present. This is purely cosmetic — permissions and identity read the code signature and bundle id, never the menu-bar text.

Installers are UNSIGNED without an identity#

jdeskInstaller produces a real but UNSIGNED installer unless a signing identity is configured for the current OS. Unsigned packages are fine for development and internal verification, but they are labeled UNSIGNED and do not satisfy a signed-release gate. Signing itself is delegated to the OS toolchains (signtool, codesign + notarytool, dpkg-sig/rpmsign); the plugin only invokes them from the values you set. On macOS, when both macSigningIdentity and macNotarizationProfile are set, jdeskInstaller code-signs the image (Hardened Runtime + secure timestamp), then automatically submits the installer to Apple notarization and staples the ticket so it launches without a Gatekeeper prompt.

Configure the signing hooks#

The signing surface is the jdesk { signing { } } block. Set the identities for the platforms you distribute:

jdesk {
    signing {
        // Windows — Authenticode
        windowsCertificate.set("<certificate subject or thumbprint>")
        windowsTimestampUrl.set("http://timestamp.example/rfc3161")

        // macOS — Developer ID + notarization
        macSigningIdentity.set("Developer ID Application: Acme Inc (TEAMID)")
        macNotarizationProfile.set("<notarytool keychain profile name>")

        // Linux — package signing
        linuxSigningKey.set("<GPG key id>")
    }
}
PropertyToolchainPurpose
windowsCertificatesigntoolAuthenticode identity (certificate subject or thumbprint).
windowsTimestampUrlsigntoolRFC 3161 timestamp URL.
macSigningIdentitycodesignDeveloper ID Application identity.
macNotarizationProfilenotarytoolNotarization keychain profile (--keychain-profile).
linuxSigningKeydpkg-sig / rpmsignGPG package-signing key id.

When at least one identity is set for the current OS, the installer step signs using it; otherwise the artifact is UNSIGNED.

A signed pipeline needs your credentials#

The signing hooks are a configuration surface, not a turnkey signed pipeline. A fully wired, signed-and-notarized end-to-end release is Phase-7 work and is not yet demonstrated. It also requires credentials that only you can supply and that are never checked in:

  • Windows — an Authenticode code-signing certificate.
  • macOS — a Developer ID Application certificate plus an Apple notarization profile; the flow is codesignnotarytool submitstapler.
  • Linux — a GPG key for package/repository signing.

CI packages are UNSIGNED because these credentials are not present in CI. Do not treat any current artifact as signed. See the verification report and packaging and signing for exactly what is proven, and implementation status for the Phase-7 gate list.

Publish the framework and npm packages#

Distributing your app is the installer above. If you are also publishing JDesk itself or its JS packages, the flows are:

# Framework artifacts — local verification, no credentials:
./gradlew publishToMavenLocal

# Real publish (credentials supplied at invocation, never checked in):
./gradlew publish \
  -PjdeskPublishUrl=https://maven.pkg.github.com/tuanworlddev/jdesk \
  -PjdeskPublishUser=<user> -PjdeskPublishToken=<token>

# The two npm packages:
npm --prefix js/create-jdesk-app publish --access public   # the scaffolder
npm --prefix js/jdesk-client publish --access public       # jdesk-client runtime

A signed Maven release adds an in-memory PGP key (-PsigningKey=... -PsigningPassword=...). Publishing to a public repository requires repository credentials and an npm token that only the account owner can supply. See scaffolding and publishing for the full publish → consume chain.

Next steps#

  • Package your app — build the app image these installers wrap.
  • Packaging and signing — the pipeline and SBOM details.
  • Scaffolding and publishing — framework and npm publishing.