Skip to content

Commit 5be9a0e

Browse files
authored
fix desktop bundles (Stirling-Tools#6773)
# Description of Changes Changes - Use 127.0.0.1 instead of localhost for the local backend. The bundled backend starts on a random port and binds the IPv4 wildcard, but the frontend health-checked http://localhost:{port}. On macOS (and some Linux) localhost resolves to IPv6 ::1 first, so the connection is refused and every backend-dependent tool shows "backend offline" even though the backend started fine. Switched getBackendUrl() and the health-check URL to the 127.0.0.1 loopback literal (already in the Tauri HTTP capability allowlist, and what the OAuth loopback server already uses). Client-side tools were unaffected, which matches the reports. - Fail the desktop build when the bundled JRE is older than the app JAR. The app JAR is compiled for Java 25, but the bundle could ship an older runtime/jre (jlink:runtime short-circuits on an existing runtime, and nothing checked its version), producing UnsupportedClassVersionError at launch so the backend never starts. Added a jlink:verify task that reads the jlink release file and fails the build if the bundled JRE major is below REQUIRED_JAVA (25, kept in sync with build.gradle modernJavaVersion). It runs after the runtime is staged - including the short-circuit reuse path that lets a stale JRE slip through. Cross-platform Node script, no new dependencies. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
1 parent f7f7b87 commit 5be9a0e

4 files changed

Lines changed: 75 additions & 6 deletions

File tree

.taskfiles/desktop.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ vars:
55
# NoClassDefFoundError: jdk/dynalink/Namespace at runtime in get-info-on-pdf and verify-pdf
66
JLINK_MODULES: "java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.dynalink"
77

8+
# Minimum Java major the bundled JRE must be. Keep in sync with build.gradle
9+
# `modernJavaVersion` - the app JAR is compiled for this, so an older runtime
10+
# fails at launch with UnsupportedClassVersionError. Enforced by jlink:verify.
11+
REQUIRED_JAVA: "25"
12+
813
# Override via JPDFIUM_PLATFORMS env (csv of platform keys, or 'all').
914
JPDFIUM_PLATFORMS:
1015
sh: |
@@ -102,6 +107,20 @@ tasks:
102107
jlink:
103108
desc: "Build backend JAR and create JLink runtime for Tauri"
104109
deps: [jlink:jar, jlink:runtime]
110+
# Runs after the runtime is in place. Lives here (not in jlink:runtime's
111+
# cmds) so it still fires when jlink:runtime short-circuits on its `status:`
112+
# check and reuses an existing runtime/jre - that reuse path is exactly how
113+
# a stale, too-old JRE slips through.
114+
cmds:
115+
- task: jlink:verify
116+
117+
jlink:verify:
118+
desc: "Fail the build if the bundled JRE is older than the app JAR requires"
119+
dir: editor
120+
env:
121+
REQUIRED_JAVA: "{{.REQUIRED_JAVA}}"
122+
cmds:
123+
- node scripts/verify-bundled-jre.mjs src-tauri/runtime/jre/release
105124

106125
jlink:jar:
107126
desc: "Build backend JAR for Tauri bundling (host-OS natives only by default)"
@@ -127,9 +146,13 @@ tasks:
127146
cmds:
128147
- rm -rf runtime/jre
129148
- mkdir -p runtime
149+
# Pin jlink to JAVA_HOME so the bundled JRE matches the JDK the build
150+
# uses. Bare `jlink` on PATH can resolve to an older system Java (the
151+
# ubuntu runner ships Java 11), producing a runtime jlink:verify rejects.
130152
- |
131-
JLINK_COMPRESS="$(jlink --help 2>&1 | grep -q 'zip-\[0-9\]' && echo zip-6 || echo 2)"
132-
jlink \
153+
JLINK="${JAVA_HOME:+$JAVA_HOME/bin/}jlink"
154+
JLINK_COMPRESS="$("$JLINK" --help 2>&1 | grep -q 'zip-\[0-9\]' && echo zip-6 || echo 2)"
155+
"$JLINK" \
133156
--add-modules {{.JLINK_MODULES}} \
134157
--strip-debug \
135158
--compress="$JLINK_COMPRESS" \
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Fail the desktop build if the bundled jlink runtime is older than the Java
2+
// version the app JAR is compiled for. A too-old runtime ships happily today
3+
// (the jlink task short-circuits on a stale runtime/jre, and nothing checks the
4+
// version), then dies at launch with UnsupportedClassVersionError -> the
5+
// backend never starts and every tool shows "backend offline".
6+
//
7+
// Reads JAVA_VERSION from the jlink `release` file (always present in a jlink
8+
// output) so it needs no shell and behaves identically on Windows/macOS/Linux.
9+
//
10+
// Required major comes from REQUIRED_JAVA (wired from .taskfiles/desktop.yml,
11+
// which mirrors build.gradle `modernJavaVersion`). Keep them in sync.
12+
import { readFileSync } from "node:fs";
13+
14+
const required = Number(process.env.REQUIRED_JAVA ?? "25");
15+
const releasePath = process.argv[2] ?? "runtime/jre/release";
16+
17+
let raw;
18+
try {
19+
raw = readFileSync(releasePath, "utf8");
20+
} catch (err) {
21+
console.error(
22+
`FATAL: cannot read bundled JRE release file at "${releasePath}": ${err.message}. ` +
23+
`Is the runtime built? Run 'task desktop:jlink'.`,
24+
);
25+
process.exit(1);
26+
}
27+
28+
const match = raw.match(/JAVA_VERSION="?(\d+)/);
29+
const major = match ? Number(match[1]) : 0;
30+
31+
console.log(
32+
`Bundled JRE major: ${major || "unknown"} (required >= ${required})`,
33+
);
34+
35+
if (!major || major < required) {
36+
console.error(
37+
`FATAL: bundled runtime/jre is Java ${major || "unknown"} but the app JAR requires ` +
38+
`Java ${required}. Run 'task desktop:jlink:clean' and rebuild with JDK ${required} active ` +
39+
`(check 'java -version' / JAVA_HOME).`,
40+
);
41+
process.exit(1);
42+
}

frontend/editor/src-tauri/src/commands/backend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ fn monitor_backend_output(mut rx: tauri::async_runtime::Receiver<tauri_plugin_sh
308308
let mut port_guard = BACKEND_PORT.lock().unwrap();
309309
*port_guard = Some(port);
310310
add_log(format!("🎉 Backend started on port: {}", port));
311-
add_log(format!("🔌 Navigate to: http://localhost:{}/", port));
311+
add_log(format!("🔌 Navigate to: http://127.0.0.1:{}/", port));
312312
}
313313
}
314314

frontend/editor/src/desktop/services/tauriBackendService.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ export class TauriBackendService {
4343
}
4444

4545
getBackendUrl(): string | null {
46-
return this.backendPort ? `http://localhost:${this.backendPort}` : null;
46+
// Use the IPv4 loopback literal, not "localhost": on macOS (and some Linux)
47+
// "localhost" can resolve to IPv6 ::1 first, but the bundled backend binds
48+
// the IPv4 wildcard, so a ::1 connection is refused and every tool shows
49+
// "backend offline" even though the backend is up.
50+
return this.backendPort ? `http://127.0.0.1:${this.backendPort}` : null;
4751
}
4852

4953
subscribeToStatus(listener: (status: BackendStatus) => void): () => void {
@@ -227,7 +231,7 @@ export class TauriBackendService {
227231
});
228232
}
229233

230-
/** Always checks the local bundled backend at localhost:{port}. */
234+
/** Always checks the local bundled backend at 127.0.0.1:{port}. */
231235
async checkBackendHealth(): Promise<boolean> {
232236
if (!this.backendStarted) {
233237
console.debug("[TauriBackendService] Health check: backend not started");
@@ -241,7 +245,7 @@ export class TauriBackendService {
241245
return false;
242246
}
243247

244-
const configUrl = `http://localhost:${this.backendPort}/api/v1/config/app-config`;
248+
const configUrl = `http://127.0.0.1:${this.backendPort}/api/v1/config/app-config`;
245249
console.debug(
246250
`[TauriBackendService] Checking local backend health at: ${configUrl}`,
247251
);

0 commit comments

Comments
 (0)