mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-22 23:08:53 +02:00
Builds custom Jar (#5029)
# Description of Changes Change jar files to contain frontend if provided with param, else doesnt... add release artifact -server version which wont have frontend --- ## 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/devGuide/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 tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
41
.github/workflows/multiOSReleases.yml
vendored
41
.github/workflows/multiOSReleases.yml
vendored
@@ -90,12 +90,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
disable_security: [true, false]
|
variant:
|
||||||
include:
|
- name: "default"
|
||||||
- disable_security: false
|
disable_security: true
|
||||||
file_suffix: "-with-login"
|
build_frontend: true
|
||||||
- disable_security: true
|
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
|
- name: "with-login"
|
||||||
|
disable_security: false
|
||||||
|
build_frontend: true
|
||||||
|
file_suffix: "-with-login"
|
||||||
|
- name: "server-only"
|
||||||
|
disable_security: true
|
||||||
|
build_frontend: false
|
||||||
|
file_suffix: "-server"
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
@@ -114,10 +121,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: matrix.variant.build_frontend == true
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
- name: Build JAR
|
- name: Build JAR
|
||||||
run: ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
run: ./gradlew clean build ${{ matrix.variant.build_frontend && '-PbuildWithFrontend=true' || '' }} -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
env:
|
env:
|
||||||
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }}
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.variant.disable_security }}
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Rename JAR
|
- name: Rename JAR
|
||||||
@@ -126,12 +141,12 @@ jobs:
|
|||||||
echo "Looking for: app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar"
|
echo "Looking for: app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar"
|
||||||
ls -la app/core/build/libs/
|
ls -la app/core/build/libs/
|
||||||
mkdir -p ./jar-dist
|
mkdir -p ./jar-dist
|
||||||
cp app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar ./jar-dist/Stirling-PDF${{ matrix.file_suffix }}.jar
|
cp app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar ./jar-dist/Stirling-PDF${{ matrix.variant.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload JAR artifacts
|
- name: Upload JAR artifacts
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: jar${{ matrix.file_suffix }}
|
name: jar${{ matrix.variant.file_suffix }}
|
||||||
path: ./jar-dist/*.jar
|
path: ./jar-dist/*.jar
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
@@ -524,7 +539,7 @@ jobs:
|
|||||||
pattern: Stirling-PDF-*
|
pattern: Stirling-PDF-*
|
||||||
path: ./artifacts/tauri
|
path: ./artifacts/tauri
|
||||||
|
|
||||||
- name: Download JAR artifact (no login)
|
- name: Download JAR artifact (default)
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
@@ -536,6 +551,12 @@ jobs:
|
|||||||
name: jar-with-login
|
name: jar-with-login
|
||||||
path: ./artifacts/jars
|
path: ./artifacts/jars
|
||||||
|
|
||||||
|
- name: Download JAR artifact (server only)
|
||||||
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
|
with:
|
||||||
|
name: jar-server
|
||||||
|
path: ./artifacts/jars
|
||||||
|
|
||||||
- name: Display structure of downloaded files
|
- name: Display structure of downloaded files
|
||||||
run: ls -R ./artifacts
|
run: ls -R ./artifacts
|
||||||
|
|
||||||
|
|||||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -17,8 +17,8 @@ local.properties
|
|||||||
version.properties
|
version.properties
|
||||||
|
|
||||||
#### Stirling-PDF Files ###
|
#### Stirling-PDF Files ###
|
||||||
pipeline/watchedFolders/
|
pipeline/
|
||||||
pipeline/finishedFolders/
|
!pipeline/.gitkeep
|
||||||
customFiles/
|
customFiles/
|
||||||
configs/
|
configs/
|
||||||
watchedFolders/
|
watchedFolders/
|
||||||
@@ -31,6 +31,20 @@ exampleYmlFiles/stirling/
|
|||||||
/testing/file_snapshots
|
/testing/file_snapshots
|
||||||
SwaggerDoc.json
|
SwaggerDoc.json
|
||||||
|
|
||||||
|
# Frontend build artifacts copied to backend static resources
|
||||||
|
# These are generated by npm build and should not be committed
|
||||||
|
app/core/src/main/resources/static/assets/
|
||||||
|
app/core/src/main/resources/static/index.html
|
||||||
|
app/core/src/main/resources/static/locales/
|
||||||
|
app/core/src/main/resources/static/Login/
|
||||||
|
app/core/src/main/resources/static/classic-logo/
|
||||||
|
app/core/src/main/resources/static/modern-logo/
|
||||||
|
app/core/src/main/resources/static/og_images/
|
||||||
|
app/core/src/main/resources/static/samples/
|
||||||
|
app/core/src/main/resources/static/manifest-classic.json
|
||||||
|
app/core/src/main/resources/static/robots.txt
|
||||||
|
# Note: Keep backend-managed files like fonts/, css/, js/, pdfjs/, etc.
|
||||||
|
|
||||||
# Gradle
|
# Gradle
|
||||||
.gradle
|
.gradle
|
||||||
.gradle-home
|
.gradle-home
|
||||||
|
|||||||
@@ -7,23 +7,103 @@ public class RequestUriUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStaticResource(String contextPath, String requestURI) {
|
public static boolean isStaticResource(String contextPath, String requestURI) {
|
||||||
return requestURI.startsWith(contextPath + "/css/")
|
if (requestURI == null) {
|
||||||
|| requestURI.startsWith(contextPath + "/fonts/")
|
return false;
|
||||||
|| requestURI.startsWith(contextPath + "/js/")
|
}
|
||||||
|| requestURI.endsWith(contextPath + "robots.txt")
|
|
||||||
|| requestURI.startsWith(contextPath + "/images/")
|
String normalizedUri = stripContextPath(contextPath, requestURI);
|
||||||
|| requestURI.startsWith(contextPath + "/public/")
|
|
||||||
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
// API routes are never static except for the public status endpoint
|
||||||
|| requestURI.startsWith(contextPath + "/pdfjs-legacy/")
|
if (normalizedUri.startsWith("/api/")) {
|
||||||
|| requestURI.startsWith(contextPath + "/login")
|
return normalizedUri.startsWith("/api/v1/info/status");
|
||||||
|| requestURI.startsWith(contextPath + "/error")
|
}
|
||||||
|| requestURI.startsWith(contextPath + "/favicon")
|
|
||||||
|| requestURI.endsWith(".svg")
|
// Well-known static asset directories (backend + React build artifacts)
|
||||||
|| requestURI.endsWith(".png")
|
if (normalizedUri.startsWith("/css/")
|
||||||
|| requestURI.endsWith(".ico")
|
|| normalizedUri.startsWith("/fonts/")
|
||||||
|| requestURI.endsWith(".txt")
|
|| normalizedUri.startsWith("/js/")
|
||||||
|| requestURI.endsWith(".webmanifest")
|
|| normalizedUri.startsWith("/images/")
|
||||||
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
|
|| normalizedUri.startsWith("/public/")
|
||||||
|
|| normalizedUri.startsWith("/pdfjs/")
|
||||||
|
|| normalizedUri.startsWith("/pdfjs-legacy/")
|
||||||
|
|| normalizedUri.startsWith("/assets/")
|
||||||
|
|| normalizedUri.startsWith("/locales/")
|
||||||
|
|| normalizedUri.startsWith("/Login/")
|
||||||
|
|| normalizedUri.startsWith("/samples/")
|
||||||
|
|| normalizedUri.startsWith("/classic-logo/")
|
||||||
|
|| normalizedUri.startsWith("/modern-logo/")
|
||||||
|
|| normalizedUri.startsWith("/og_images/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific static files bundled with the frontend
|
||||||
|
if (normalizedUri.equals("/robots.txt")
|
||||||
|
|| normalizedUri.equals("/favicon.ico")
|
||||||
|
|| normalizedUri.equals("/site.webmanifest")
|
||||||
|
|| normalizedUri.equals("/manifest-classic.json")
|
||||||
|
|| normalizedUri.equals("/index.html")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login/error pages remain public
|
||||||
|
if (normalizedUri.startsWith("/login") || normalizedUri.startsWith("/error")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat common static file extensions as static resources
|
||||||
|
return normalizedUri.endsWith(".svg")
|
||||||
|
|| normalizedUri.endsWith(".png")
|
||||||
|
|| normalizedUri.endsWith(".ico")
|
||||||
|
|| normalizedUri.endsWith(".txt")
|
||||||
|
|| normalizedUri.endsWith(".webmanifest")
|
||||||
|
|| normalizedUri.endsWith(".js")
|
||||||
|
|| normalizedUri.endsWith(".css")
|
||||||
|
|| normalizedUri.endsWith(".mjs")
|
||||||
|
|| normalizedUri.endsWith(".html")
|
||||||
|
|| normalizedUri.endsWith(".toml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFrontendRoute(String contextPath, String requestURI) {
|
||||||
|
if (requestURI == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedUri = stripContextPath(contextPath, requestURI);
|
||||||
|
|
||||||
|
// APIs are never treated as frontend routes
|
||||||
|
if (normalizedUri.startsWith("/api/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocklist of backend/non-frontend paths that should still go through filters
|
||||||
|
String[] backendOnlyPrefixes = {
|
||||||
|
"/register",
|
||||||
|
"/invite",
|
||||||
|
"/pipeline",
|
||||||
|
"/pdfjs",
|
||||||
|
"/pdfjs-legacy",
|
||||||
|
"/fonts",
|
||||||
|
"/images",
|
||||||
|
"/files",
|
||||||
|
"/css",
|
||||||
|
"/js",
|
||||||
|
"/swagger",
|
||||||
|
"/v1/api-docs",
|
||||||
|
"/actuator"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String prefix : backendOnlyPrefixes) {
|
||||||
|
if (normalizedUri.equals(prefix) || normalizedUri.startsWith(prefix + "/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedUri.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow root and any extensionless path (React Router will handle these)
|
||||||
|
return !normalizedUri.contains(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTrackableResource(String requestURI) {
|
public static boolean isTrackableResource(String requestURI) {
|
||||||
@@ -43,6 +123,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.endsWith(".svg")
|
|| requestURI.endsWith(".svg")
|
||||||
|| requestURI.endsWith("popularity.txt")
|
|| requestURI.endsWith("popularity.txt")
|
||||||
|| requestURI.endsWith(".js")
|
|| requestURI.endsWith(".js")
|
||||||
|
|| requestURI.endsWith(".toml")
|
||||||
|| requestURI.contains("swagger")
|
|| requestURI.contains("swagger")
|
||||||
|| requestURI.startsWith("/api/v1/info")
|
|| requestURI.startsWith("/api/v1/info")
|
||||||
|| requestURI.startsWith("/site.webmanifest")
|
|| requestURI.startsWith("/site.webmanifest")
|
||||||
@@ -83,4 +164,11 @@ public class RequestUriUtils {
|
|||||||
|| trimmedUri.startsWith("/api/v1/invite/accept")
|
|| trimmedUri.startsWith("/api/v1/invite/accept")
|
||||||
|| trimmedUri.contains("/v1/api-docs");
|
|| trimmedUri.contains("/v1/api-docs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String stripContextPath(String contextPath, String requestURI) {
|
||||||
|
if (contextPath != null && !contextPath.isBlank() && requestURI.startsWith(contextPath)) {
|
||||||
|
return requestURI.substring(contextPath.length());
|
||||||
|
}
|
||||||
|
return requestURI;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,26 @@ public class RequestUriUtilsTest {
|
|||||||
"API products should not be static");
|
"API products should not be static");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIsFrontendRoute() {
|
||||||
|
assertTrue(RequestUriUtils.isFrontendRoute("", "/"), "Root path should be a frontend route");
|
||||||
|
assertTrue(
|
||||||
|
RequestUriUtils.isFrontendRoute("", "/app/dashboard"),
|
||||||
|
"React routes without extensions should be frontend routes");
|
||||||
|
assertFalse(
|
||||||
|
RequestUriUtils.isFrontendRoute("", "/api/v1/users"),
|
||||||
|
"API routes should not be frontend routes");
|
||||||
|
assertFalse(
|
||||||
|
RequestUriUtils.isFrontendRoute("", "/register"),
|
||||||
|
"Register should not be treated as a frontend route");
|
||||||
|
assertFalse(
|
||||||
|
RequestUriUtils.isFrontendRoute("", "/pipeline/jobs"),
|
||||||
|
"Pipeline should not be treated as a frontend route");
|
||||||
|
assertFalse(
|
||||||
|
RequestUriUtils.isFrontendRoute("", "/files/download"),
|
||||||
|
"Files path should not be treated as a frontend route");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsStaticResourceWithContextPath() {
|
void testIsStaticResourceWithContextPath() {
|
||||||
String contextPath = "/myapp";
|
String contextPath = "/myapp";
|
||||||
@@ -83,6 +103,7 @@ public class RequestUriUtilsTest {
|
|||||||
"/favicon.ico",
|
"/favicon.ico",
|
||||||
"/icon.svg",
|
"/icon.svg",
|
||||||
"/image.png",
|
"/image.png",
|
||||||
|
"/locales/en/translation.toml",
|
||||||
"/site.webmanifest",
|
"/site.webmanifest",
|
||||||
"/app/logo.svg",
|
"/app/logo.svg",
|
||||||
"/downloads/document.png",
|
"/downloads/document.png",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
|
|
||||||
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url = 'https://build.shibboleth.net/maven/releases' }
|
maven { url = 'https://build.shibboleth.net/maven/releases' }
|
||||||
maven { url = 'https://maven.pkg.github.com/jcefmaven/jcefmaven' }
|
maven { url = 'https://maven.pkg.github.com/jcefmaven/jcefmaven' }
|
||||||
@@ -15,6 +17,7 @@ configurations {
|
|||||||
spotless {
|
spotless {
|
||||||
java {
|
java {
|
||||||
target 'src/**/java/**/*.java'
|
target 'src/**/java/**/*.java'
|
||||||
|
targetExclude 'src/main/resources/static/**'
|
||||||
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||||
|
|
||||||
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
||||||
@@ -25,12 +28,14 @@ spotless {
|
|||||||
}
|
}
|
||||||
yaml {
|
yaml {
|
||||||
target '**/*.yml', '**/*.yaml'
|
target '**/*.yml', '**/*.yaml'
|
||||||
|
targetExclude 'src/main/resources/static/**'
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
leadingTabsToSpaces()
|
leadingTabsToSpaces()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
format 'gradle', {
|
format 'gradle', {
|
||||||
target '**/gradle/*.gradle', '**/*.gradle'
|
target '**/gradle/*.gradle', '**/*.gradle'
|
||||||
|
targetExclude 'src/main/resources/static/**'
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
leadingTabsToSpaces()
|
leadingTabsToSpaces()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
@@ -157,5 +162,125 @@ springBoot {
|
|||||||
mainClass = 'stirling.software.SPDF.SPDFApplication'
|
mainClass = 'stirling.software.SPDF.SPDFApplication'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frontend build tasks - only enabled with -PbuildWithFrontend=true
|
||||||
|
def buildWithFrontend = project.hasProperty('buildWithFrontend') && project.property('buildWithFrontend') == 'true'
|
||||||
|
def frontendDir = file('../../frontend')
|
||||||
|
def frontendDistDir = file('../../frontend/dist')
|
||||||
|
def resourcesStaticDir = file('src/main/resources/static')
|
||||||
|
def generatedFrontendPaths = [
|
||||||
|
'assets',
|
||||||
|
'index.html',
|
||||||
|
'locales',
|
||||||
|
'Login',
|
||||||
|
'classic-logo',
|
||||||
|
'modern-logo',
|
||||||
|
'og_images',
|
||||||
|
'samples',
|
||||||
|
'manifest-classic.json'
|
||||||
|
]
|
||||||
|
|
||||||
|
tasks.register('npmInstall', Exec) {
|
||||||
|
enabled = buildWithFrontend
|
||||||
|
group = 'frontend'
|
||||||
|
description = 'Install frontend dependencies'
|
||||||
|
workingDir frontendDir
|
||||||
|
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'npm', 'ci', '--prefer-offline'] : ['npm', 'ci', '--prefer-offline']
|
||||||
|
inputs.file(new File(frontendDir, 'package.json'))
|
||||||
|
inputs.file(new File(frontendDir, 'package-lock.json'))
|
||||||
|
outputs.dir(new File(frontendDir, 'node_modules'))
|
||||||
|
|
||||||
|
// Show live output
|
||||||
|
standardOutput = System.out
|
||||||
|
errorOutput = System.err
|
||||||
|
|
||||||
|
// Skip if node_modules exists and is up-to-date
|
||||||
|
onlyIf {
|
||||||
|
def nodeModules = new File(frontendDir, 'node_modules')
|
||||||
|
if (!nodeModules.exists()) {
|
||||||
|
println "node_modules not found, will install..."
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
def packageJson = new File(frontendDir, 'package.json')
|
||||||
|
def packageLock = new File(frontendDir, 'package-lock.json')
|
||||||
|
def isOutdated = nodeModules.lastModified() < packageJson.lastModified() ||
|
||||||
|
nodeModules.lastModified() < packageLock.lastModified()
|
||||||
|
if (isOutdated) {
|
||||||
|
println "package.json or package-lock.json changed, will reinstall..."
|
||||||
|
} else {
|
||||||
|
println "node_modules is up-to-date, skipping npm install"
|
||||||
|
}
|
||||||
|
return isOutdated
|
||||||
|
}
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
println "Installing npm dependencies in ${frontendDir}..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('npmBuild', Exec) {
|
||||||
|
enabled = buildWithFrontend
|
||||||
|
group = 'frontend'
|
||||||
|
description = 'Build frontend application'
|
||||||
|
workingDir frontendDir
|
||||||
|
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'npm', 'run', 'build'] : ['npm', 'run', 'build']
|
||||||
|
dependsOn npmInstall
|
||||||
|
inputs.dir(new File(frontendDir, 'src'))
|
||||||
|
inputs.file(new File(frontendDir, 'package.json'))
|
||||||
|
outputs.dir(frontendDistDir)
|
||||||
|
|
||||||
|
// Show live output
|
||||||
|
standardOutput = System.out
|
||||||
|
errorOutput = System.err
|
||||||
|
|
||||||
|
// Override VITE_API_BASE_URL to use relative paths for production builds
|
||||||
|
// This ensures JARs work regardless of how they're deployed (direct, proxied, etc.)
|
||||||
|
environment 'VITE_API_BASE_URL', '/'
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
println "Building frontend application for production (VITE_API_BASE_URL=/)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyFrontendAssets', Copy) {
|
||||||
|
enabled = buildWithFrontend
|
||||||
|
group = 'frontend'
|
||||||
|
description = 'Copy frontend build to static resources'
|
||||||
|
dependsOn npmBuild
|
||||||
|
from(frontendDistDir) {
|
||||||
|
// Exclude files that conflict with backend static resources
|
||||||
|
exclude 'robots.txt' // Backend already has this
|
||||||
|
exclude 'favicon.ico' // Backend already has this
|
||||||
|
}
|
||||||
|
into resourcesStaticDir
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Let frontend overwrite when needed
|
||||||
|
doFirst {
|
||||||
|
println "Copying frontend build from ${frontendDistDir} to ${resourcesStaticDir}..."
|
||||||
|
println "Backend static resources will be preserved"
|
||||||
|
}
|
||||||
|
doLast {
|
||||||
|
println "Frontend assets copied successfully!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('cleanFrontendAssets', Delete) {
|
||||||
|
group = 'frontend'
|
||||||
|
description = 'Remove previously generated frontend assets from static resources'
|
||||||
|
delete generatedFrontendPaths.collect { new File(resourcesStaticDir, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure copyFrontendAssets runs after spotless tasks
|
||||||
|
tasks.named('copyFrontendAssets').configure {
|
||||||
|
mustRunAfter tasks.matching { it.name.startsWith('spotless') }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildWithFrontend) {
|
||||||
|
println "Frontend build enabled - JAR will include React frontend"
|
||||||
|
processResources.dependsOn copyFrontendAssets
|
||||||
|
} else {
|
||||||
|
println "Frontend build disabled - JAR will be backend-only"
|
||||||
|
// When not building the UI, ensure any stale frontend assets are removed
|
||||||
|
processResources.dependsOn cleanFrontendAssets
|
||||||
|
}
|
||||||
|
|
||||||
bootJar.dependsOn ':common:jar'
|
bootJar.dependsOn ':common:jar'
|
||||||
bootJar.dependsOn ':proprietary:jar'
|
bootJar.dependsOn ':proprietary:jar'
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
// @Controller // Disabled - Backend-only mode, no Thymeleaf UI
|
@Controller
|
||||||
public class ReactRoutingController {
|
public class ReactRoutingController {
|
||||||
|
|
||||||
@GetMapping("/{path:^(?!api|static|robots\\.txt|favicon\\.ico)[^\\.]*$}")
|
@GetMapping("/{path:^(?!api|static|robots\\.txt|favicon\\.ico|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*$}")
|
||||||
public String forwardRootPaths() {
|
public String forwardRootPaths() {
|
||||||
return "forward:/index.html";
|
return "forward:/index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{path:^(?!api|static)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
|
@GetMapping("/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
|
||||||
public String forwardNestedPaths() {
|
public String forwardNestedPaths() {
|
||||||
return "forward:/index.html";
|
return "forward:/index.html";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ server.servlet.session.timeout:30m
|
|||||||
springdoc.api-docs.path=/v1/api-docs
|
springdoc.api-docs.path=/v1/api-docs
|
||||||
# Set the URL of the OpenAPI JSON for the Swagger UI
|
# Set the URL of the OpenAPI JSON for the Swagger UI
|
||||||
springdoc.swagger-ui.url=/v1/api-docs
|
springdoc.swagger-ui.url=/v1/api-docs
|
||||||
springdoc.swagger-ui.path=/index.html
|
springdoc.swagger-ui.path=/swagger-ui.html
|
||||||
# Force OpenAPI 3.0 specification version
|
# Force OpenAPI 3.0 specification version
|
||||||
springdoc.api-docs.version=OPENAPI_3_0
|
springdoc.api-docs.version=OPENAPI_3_0
|
||||||
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
||||||
|
import stirling.software.common.util.RequestUriUtils;
|
||||||
import stirling.software.proprietary.security.model.ApiKeyAuthenticationToken;
|
import stirling.software.proprietary.security.model.ApiKeyAuthenticationToken;
|
||||||
import stirling.software.proprietary.security.model.User;
|
import stirling.software.proprietary.security.model.User;
|
||||||
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
@@ -110,7 +111,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// If we still don't have any authentication, check if it's a public endpoint. If not, deny
|
// If we still don't have any authentication, check if it's a public endpoint. If not, deny
|
||||||
// the request
|
// the request
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String method = request.getMethod();
|
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
|
|
||||||
// Allow public auth endpoints to pass through without authentication
|
// Allow public auth endpoints to pass through without authentication
|
||||||
@@ -119,18 +119,18 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("GET".equalsIgnoreCase(method) && !requestURI.startsWith(contextPath + "/login")) {
|
// For API requests, return 401 with JSON response (no redirects)
|
||||||
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
} else {
|
response.setContentType("application/json");
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.getWriter()
|
||||||
response.getWriter()
|
.write(
|
||||||
.write(
|
"""
|
||||||
"""
|
{
|
||||||
Authentication required. Please provide a X-API-KEY in request header.
|
"error": "Unauthorized",
|
||||||
This is found in Settings -> Account Settings -> API Key
|
"message": "Authentication required. Please provide valid credentials or X-API-KEY header.",
|
||||||
Alternatively you can disable authentication if this is unexpected.
|
"status": 401
|
||||||
""");
|
}
|
||||||
}
|
""");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +179,18 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Block user registration if not allowed by configuration
|
// Block user registration if not allowed by configuration
|
||||||
if (blockRegistration && !isUserExists) {
|
if (blockRegistration && !isUserExists) {
|
||||||
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
||||||
response.sendRedirect(
|
SecurityContextHolder.clearContext();
|
||||||
request.getContextPath() + "/logout?oAuth2AdminBlockedUser=true");
|
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter()
|
||||||
|
.write(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"error": "Forbidden",
|
||||||
|
"message": "User registration is blocked by administrator",
|
||||||
|
"status": 403
|
||||||
|
}
|
||||||
|
""");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,13 +204,35 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to logout if credentials are invalid
|
// Return 401 if credentials are invalid (no redirects)
|
||||||
if (!isUserExists && notSsoLogin) {
|
if (!isUserExists && notSsoLogin) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?badCredentials=true");
|
SecurityContextHolder.clearContext();
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter()
|
||||||
|
.write(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"error": "Unauthorized",
|
||||||
|
"message": "Invalid credentials",
|
||||||
|
"status": 401
|
||||||
|
}
|
||||||
|
""");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isUserDisabled) {
|
if (isUserDisabled) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?userIsDisabled=true");
|
SecurityContextHolder.clearContext();
|
||||||
|
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter()
|
||||||
|
.write(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"error": "Forbidden",
|
||||||
|
"message": "User account is disabled",
|
||||||
|
"status": 403
|
||||||
|
}
|
||||||
|
""");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,33 +282,28 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
String[] permitAllPatterns = {
|
|
||||||
contextPath + "/login",
|
// Allow unauthenticated access to static resources and SPA routes (GET/HEAD only)
|
||||||
contextPath + "/register",
|
if ("GET".equalsIgnoreCase(request.getMethod())
|
||||||
contextPath + "/invite",
|
|| "HEAD".equalsIgnoreCase(request.getMethod())) {
|
||||||
contextPath + "/error",
|
if (RequestUriUtils.isStaticResource(contextPath, uri)
|
||||||
contextPath + "/images/",
|
|| RequestUriUtils.isFrontendRoute(contextPath, uri)) {
|
||||||
contextPath + "/public/",
|
return true;
|
||||||
contextPath + "/css/",
|
}
|
||||||
contextPath + "/fonts/",
|
}
|
||||||
contextPath + "/js/",
|
|
||||||
contextPath + "/pdfjs/",
|
// For API routes, only skip filter for these public endpoints
|
||||||
contextPath + "/pdfjs-legacy/",
|
String[] publicApiPatterns = {
|
||||||
contextPath + "/api/v1/info/status",
|
contextPath + "/api/v1/info/status",
|
||||||
contextPath + "/api/v1/auth/login",
|
contextPath + "/api/v1/auth/login",
|
||||||
contextPath + "/api/v1/auth/refresh",
|
contextPath + "/api/v1/auth/refresh",
|
||||||
contextPath + "/api/v1/auth/me",
|
contextPath + "/api/v1/auth/me",
|
||||||
contextPath + "/api/v1/invite/validate",
|
contextPath + "/api/v1/invite/validate",
|
||||||
contextPath + "/api/v1/invite/accept",
|
contextPath + "/api/v1/invite/accept"
|
||||||
contextPath + "/site.webmanifest"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (String pattern : permitAllPatterns) {
|
for (String pattern : publicApiPatterns) {
|
||||||
if (uri.startsWith(pattern)
|
if (uri.startsWith(pattern)) {
|
||||||
|| uri.endsWith(".svg")
|
|
||||||
|| uri.endsWith(".mjs")
|
|
||||||
|| uri.endsWith(".png")
|
|
||||||
|| uri.endsWith(".ico")) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ repositories {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '2.0.0'
|
version = '2.0.1'
|
||||||
|
|
||||||
configurations.configureEach {
|
configurations.configureEach {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ COPY frontend/package.json frontend/package-lock.json ./
|
|||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY frontend .
|
COPY frontend .
|
||||||
RUN DISABLE_ADDITIONAL_FEATURES=false npm run build
|
# Override VITE_API_BASE_URL to use relative paths for production
|
||||||
|
# This ensures frontend works with nginx proxy setup
|
||||||
|
RUN DISABLE_ADDITIONAL_FEATURES=false VITE_API_BASE_URL=/ npm run build
|
||||||
|
|
||||||
# Stage 2: Build Backend
|
# Stage 2: Build Backend (server-only JAR - no UI)
|
||||||
FROM gradle:8.14-jdk21 AS backend-build
|
FROM gradle:8.14-jdk21 AS backend-build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
@@ -27,6 +29,7 @@ RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || re
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build server-only JAR (no frontend, includes security features controlled by DOCKER_ENABLE_SECURITY at runtime)
|
||||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
@@ -62,8 +65,7 @@ COPY docker/unified/nginx.conf /etc/nginx/nginx.conf
|
|||||||
COPY docker/unified/entrypoint.sh /entrypoint.sh
|
COPY docker/unified/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
ENV DISABLE_ADDITIONAL_FEATURES=false \
|
ENV VERSION_TAG=$VERSION_TAG \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ COPY frontend/package.json frontend/package-lock.json ./
|
|||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY frontend .
|
COPY frontend .
|
||||||
RUN DISABLE_ADDITIONAL_FEATURES=true npm run build
|
# Override VITE_API_BASE_URL to use relative paths for production
|
||||||
|
# This ensures frontend works with nginx proxy setup
|
||||||
|
RUN DISABLE_ADDITIONAL_FEATURES=true VITE_API_BASE_URL=/ npm run build
|
||||||
|
|
||||||
# Stage 2: Build Backend
|
# Stage 2: Build Backend
|
||||||
FROM gradle:8.14-jdk21 AS backend-build
|
FROM gradle:8.14-jdk21 AS backend-build
|
||||||
@@ -76,7 +78,6 @@ ENV DISABLE_ADDITIONAL_FEATURES=false \
|
|||||||
TMP=/tmp/stirling-pdf \
|
TMP=/tmp/stirling-pdf \
|
||||||
MODE=BOTH \
|
MODE=BOTH \
|
||||||
BACKEND_INTERNAL_PORT=8081 \
|
BACKEND_INTERNAL_PORT=8081 \
|
||||||
VITE_API_BASE_URL=http://localhost:8080 \
|
|
||||||
ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
|
|
||||||
# Install minimal dependencies
|
# Install minimal dependencies
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ WORKDIR /app
|
|||||||
# Copy the entire project to the working directory
|
# Copy the entire project to the working directory
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
# Build the application (server-only JAR - no UI, includes security features controlled at runtime)
|
||||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
@@ -44,8 +44,7 @@ LABEL org.opencontainers.image.version="${VERSION_TAG}"
|
|||||||
LABEL org.opencontainers.image.keywords="PDF, manipulation, backend, API, Spring Boot"
|
LABEL org.opencontainers.image.keywords="PDF, manipulation, backend, API, Spring Boot"
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DISABLE_ADDITIONAL_FEATURES=false \
|
ENV VERSION_TAG=$VERSION_TAG \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ WORKDIR /app
|
|||||||
# Copy the entire project to the working directory
|
# Copy the entire project to the working directory
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
# Build the application (server-only JAR - no UI, includes security features controlled at runtime)
|
||||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
@@ -35,8 +35,7 @@ COPY --from=build /app/build/libs/restart-helper.jar restart-helper.jar
|
|||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
ENV VERSION_TAG=$VERSION_TAG \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8
|
|||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
ENV HOME=/home/stirlingpdfuser \
|
||||||
HOME=/home/stirlingpdfuser \
|
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Get the base URL for API requests.
|
* Get the base URL for API requests.
|
||||||
* Core version uses simple environment variable.
|
*
|
||||||
|
* Priority:
|
||||||
|
* 1. window.STIRLING_PDF_API_BASE_URL (runtime override - fixes hardcoded localhost issues)
|
||||||
|
* 2. import.meta.env.VITE_API_BASE_URL (build-time env var)
|
||||||
|
* 3. '/' (relative path - works for same-origin deployments)
|
||||||
|
*
|
||||||
|
* Note: Runtime override is needed because VITE_API_BASE_URL gets baked into the build.
|
||||||
|
* If someone builds with VITE_API_BASE_URL=http://localhost:8080, it breaks production deployments.
|
||||||
*/
|
*/
|
||||||
export function getApiBaseUrl(): string {
|
export function getApiBaseUrl(): string {
|
||||||
|
// Runtime override to fix hardcoded localhost in builds
|
||||||
|
if (typeof window !== 'undefined' && (window as any).STIRLING_PDF_API_BASE_URL) {
|
||||||
|
return (window as any).STIRLING_PDF_API_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
return import.meta.env.VITE_API_BASE_URL || '/';
|
return import.meta.env.VITE_API_BASE_URL || '/';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,22 @@ import { isTauri } from '@tauri-apps/api/core';
|
|||||||
/**
|
/**
|
||||||
* Desktop override: Determine base URL depending on Tauri environment
|
* Desktop override: Determine base URL depending on Tauri environment
|
||||||
*
|
*
|
||||||
|
* Priority (non-Tauri mode):
|
||||||
|
* 1. window.STIRLING_PDF_API_BASE_URL (runtime override - fixes hardcoded localhost issues)
|
||||||
|
* 2. import.meta.env.VITE_API_BASE_URL (build-time env var)
|
||||||
|
* 3. '/' (relative path - works for same-origin deployments)
|
||||||
|
*
|
||||||
* Note: In Tauri mode, the actual URL is determined dynamically by operationRouter
|
* Note: In Tauri mode, the actual URL is determined dynamically by operationRouter
|
||||||
* based on connection mode and backend port. This initial baseURL is overridden
|
* based on connection mode and backend port. This initial baseURL is overridden
|
||||||
* by request interceptors in apiClientSetup.ts.
|
* by request interceptors in apiClientSetup.ts.
|
||||||
*/
|
*/
|
||||||
export function getApiBaseUrl(): string {
|
export function getApiBaseUrl(): string {
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
|
// Runtime override to fix hardcoded localhost in builds
|
||||||
|
if (typeof window !== 'undefined' && (window as any).STIRLING_PDF_API_BASE_URL) {
|
||||||
|
return (window as any).STIRLING_PDF_API_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
return import.meta.env.VITE_API_BASE_URL || '/';
|
return import.meta.env.VITE_API_BASE_URL || '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user