mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	fix(pipeline): correct paths for pipeline & support default WebUI pipeline config extraction (#4051)
# Description of Changes - **What was changed:** - Updated `.github/labeler-config-srvaroa.yml` to include `app/core/src/main/resources/static/pipeline/defaultWebUIConfigs/**` under the labeler paths. - Removed `COPY pipeline /pipeline` from all three Dockerfiles to slim down images. - Added a new `PIPELINE_PATH` constant and `getPipelinePath()` method in `InstallationPathConfig.java`. - Implemented `GeneralUtils.extractPipeline()` to copy default pipeline JSON configs (`OCR images.json`, `Prepare-pdfs-for-email.json`, `split-rotate-auto-rename.json`) from classpath into the installation directory. - Invoked `GeneralUtils.extractPipeline()` during initial setup in `InitialSetup.java`. - Updated `.gitignore` to treat `./pipeline/` as ignored. - **Why the change was made:** Ensures that default WebUI pipeline configurations are automatically extracted at runtime rather than baked into the image, improving flexibility and reducing image size. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] 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) - [x] I have performed a self-review of my own code - [x] 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) ### 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. --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									9bb08eed5a
								
							
						
					
					
						commit
						1274dc9279
					
				
							
								
								
									
										1
									
								
								.github/labeler-config-srvaroa.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/labeler-config-srvaroa.yml
									
									
									
									
										vendored
									
									
								
							| @ -78,6 +78,7 @@ labels: | ||||
|       - 'app/core/src/main/resources/banner.txt' | ||||
|       - 'app/core/src/main/resources/static/python/png_to_webp.py' | ||||
|       - 'app/core/src/main/resources/static/python/split_photos.py' | ||||
|       - 'app/core/src/main/resources/static/pipeline/defaultWebUIConfigs/**' | ||||
|       - 'application.properties' | ||||
| 
 | ||||
|   - label: 'Security' | ||||
|  | ||||
| @ -3,7 +3,6 @@ FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8 | ||||
| 
 | ||||
| # Copy necessary files | ||||
| COPY scripts /scripts | ||||
| COPY pipeline /pipeline | ||||
| COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ | ||||
| COPY app/core/build/libs/*.jar app.jar | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,6 @@ FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8 | ||||
| 
 | ||||
| # Copy necessary files | ||||
| COPY scripts /scripts | ||||
| COPY pipeline /pipeline | ||||
| COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ | ||||
| # first /app directory is for the build stage, second is for the final image | ||||
| COPY --from=build /app/app/core/build/libs/*.jar app.jar | ||||
|  | ||||
| @ -21,7 +21,6 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \ | ||||
| COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh | ||||
| COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh | ||||
| COPY scripts/installFonts.sh /scripts/installFonts.sh | ||||
| COPY pipeline /pipeline | ||||
| COPY app/core/build/libs/*.jar app.jar | ||||
| 
 | ||||
| # Set up necessary directories and permissions | ||||
|  | ||||
| @ -15,6 +15,7 @@ public class InstallationPathConfig { | ||||
|     private static final String CUSTOM_FILES_PATH; | ||||
|     private static final String CLIENT_WEBUI_PATH; | ||||
|     private static final String SCRIPTS_PATH; | ||||
|     private static final String PIPELINE_PATH; | ||||
| 
 | ||||
|     // Config paths | ||||
|     private static final String SETTINGS_PATH; | ||||
| @ -33,6 +34,7 @@ public class InstallationPathConfig { | ||||
|         CONFIG_PATH = BASE_PATH + "configs" + File.separator; | ||||
|         CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator; | ||||
|         CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator; | ||||
|         PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator; | ||||
| 
 | ||||
|         // Initialize config paths | ||||
|         SETTINGS_PATH = CONFIG_PATH + "settings.yml"; | ||||
| @ -95,6 +97,10 @@ public class InstallationPathConfig { | ||||
|         return SCRIPTS_PATH; | ||||
|     } | ||||
| 
 | ||||
|     public static String getPipelinePath() { | ||||
|         return PIPELINE_PATH; | ||||
|     } | ||||
| 
 | ||||
|     public static String getSettingsPath() { | ||||
|         return SETTINGS_PATH; | ||||
|     } | ||||
|  | ||||
| @ -14,6 +14,7 @@ import java.util.Arrays; | ||||
| import java.util.Enumeration; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Set; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.springframework.core.io.ClassPathResource; | ||||
| @ -34,8 +35,16 @@ import stirling.software.common.configuration.InstallationPathConfig; | ||||
| @Slf4j | ||||
| public class GeneralUtils { | ||||
| 
 | ||||
|     private static final List<String> DEFAULT_VALID_SCRIPTS = | ||||
|             List.of("png_to_webp.py", "split_photos.py"); | ||||
|     private static final Set<String> DEFAULT_VALID_SCRIPTS = | ||||
|             Set.of("png_to_webp.py", "split_photos.py"); | ||||
|     private static final Set<String> DEFAULT_VALID_PIPELINE = | ||||
|             Set.of( | ||||
|                     "OCR images.json", | ||||
|                     "Prepare-pdfs-for-email.json", | ||||
|                     "split-rotate-auto-rename.json"); | ||||
| 
 | ||||
|     private static final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs"; | ||||
|     private static final String PYTHON_SCRIPTS_DIR = "python"; | ||||
| 
 | ||||
|     public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { | ||||
|         String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY"); | ||||
| @ -446,6 +455,48 @@ public class GeneralUtils { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Extracts the default pipeline configurations from the classpath to the installation path. | ||||
|      * Creates directories if needed and copies default JSON files. | ||||
|      * | ||||
|      * <p>Existing files will be overwritten atomically (when supported). In case of unsupported | ||||
|      * atomic moves, falls back to non-atomic replace. | ||||
|      * | ||||
|      * @throws IOException if an I/O error occurs during file operations | ||||
|      */ | ||||
|     public static void extractPipeline() throws IOException { | ||||
|         Path pipelineDir = | ||||
|                 Paths.get(InstallationPathConfig.getPipelinePath(), DEFAULT_WEBUI_CONFIGS_DIR); | ||||
|         Files.createDirectories(pipelineDir); | ||||
| 
 | ||||
|         for (String name : DEFAULT_VALID_PIPELINE) { | ||||
|             if (!Paths.get(name).getFileName().toString().equals(name)) { | ||||
|                 log.error("Invalid pipeline file name: {}", name); | ||||
|                 throw new IllegalArgumentException("Invalid pipeline file name: " + name); | ||||
|             } | ||||
|             Path target = pipelineDir.resolve(name); | ||||
|             ClassPathResource res = | ||||
|                     new ClassPathResource( | ||||
|                             "static/pipeline/" + DEFAULT_WEBUI_CONFIGS_DIR + "/" + name); | ||||
|             if (!res.exists()) { | ||||
|                 log.error("Resource not found: {}", res.getPath()); | ||||
|                 throw new IOException("Resource not found: " + res.getPath()); | ||||
|             } | ||||
|             copyResourceToFile(res, target); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Extracts the specified Python script from the classpath to the installation path. Validates | ||||
|      * name and copies file atomically when possible, overwriting existing. | ||||
|      * | ||||
|      * <p>Existing files will be overwritten atomically (when supported). | ||||
|      * | ||||
|      * @param scriptName the name of the script to extract | ||||
|      * @return the path to the extracted script | ||||
|      * @throws IllegalArgumentException if the script name is invalid or not allowed | ||||
|      * @throws IOException if an I/O error occurs | ||||
|      */ | ||||
|     public static Path extractScript(String scriptName) throws IOException { | ||||
|         // Validate input | ||||
|         if (scriptName == null || scriptName.trim().isEmpty()) { | ||||
| @ -455,26 +506,71 @@ public class GeneralUtils { | ||||
|             throw new IllegalArgumentException( | ||||
|                     "scriptName must not contain path traversal characters"); | ||||
|         } | ||||
|         if (!Paths.get(scriptName).getFileName().toString().equals(scriptName)) { | ||||
|             throw new IllegalArgumentException( | ||||
|                     "scriptName must not contain path traversal characters"); | ||||
|         } | ||||
| 
 | ||||
|         if (!DEFAULT_VALID_SCRIPTS.contains(scriptName)) { | ||||
|             throw new IllegalArgumentException( | ||||
|                     "scriptName must be either 'png_to_webp.py' or 'split_photos.py'"); | ||||
|         } | ||||
| 
 | ||||
|         Path scriptsDir = Paths.get(InstallationPathConfig.getScriptsPath(), "python"); | ||||
|         Path scriptsDir = Paths.get(InstallationPathConfig.getScriptsPath(), PYTHON_SCRIPTS_DIR); | ||||
|         Files.createDirectories(scriptsDir); | ||||
| 
 | ||||
|         Path scriptFile = scriptsDir.resolve(scriptName); | ||||
|         if (!Files.exists(scriptFile)) { | ||||
|             ClassPathResource resource = new ClassPathResource("static/python/" + scriptName); | ||||
|         Path target = scriptsDir.resolve(scriptName); | ||||
|         ClassPathResource res = | ||||
|                 new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName); | ||||
|         if (!res.exists()) { | ||||
|             log.error("Resource not found: {}", res.getPath()); | ||||
|             throw new IOException("Resource not found: " + res.getPath()); | ||||
|         } | ||||
|         copyResourceToFile(res, target); | ||||
|         return target; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copies a resource from the classpath to a specified target file. | ||||
|      * | ||||
|      * @param resource the ClassPathResource to copy | ||||
|      * @param target the target Path where the resource will be copied | ||||
|      * @throws IOException if an I/O error occurs during the copy operation | ||||
|      */ | ||||
|     private static void copyResourceToFile(ClassPathResource resource, Path target) | ||||
|             throws IOException { | ||||
|         Path dir = target.getParent(); | ||||
|         Path tmp = Files.createTempFile(dir, target.getFileName().toString(), ".tmp"); | ||||
|         try (InputStream in = resource.getInputStream()) { | ||||
|                 Files.copy(in, scriptFile, StandardCopyOption.REPLACE_EXISTING); | ||||
|             } catch (IOException e) { | ||||
|                 log.error("Failed to extract Python script", e); | ||||
|             Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING); | ||||
|             try { | ||||
|                 Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE); | ||||
|             } catch (AtomicMoveNotSupportedException e) { | ||||
|                 log.warn( | ||||
|                         "Atomic move not supported, falling back to non-atomic move for {}", | ||||
|                         target, | ||||
|                         e); | ||||
|                 Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING); | ||||
|             } | ||||
|         } catch (FileAlreadyExistsException e) { | ||||
|             log.debug("File already exists at {}, attempting to replace it.", target); | ||||
|             Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING); | ||||
|         } catch (AccessDeniedException e) { | ||||
|             log.error("Access denied while attempting to copy resource to {}", target, e); | ||||
|             throw e; | ||||
|         } catch (FileSystemException e) { | ||||
|             log.error("File system error occurred while copying resource to {}", target, e); | ||||
|             throw e; | ||||
|         } catch (IOException e) { | ||||
|             log.error("Failed to copy resource to {}", target, e); | ||||
|             throw e; | ||||
|         } finally { | ||||
|             try { | ||||
|                 Files.deleteIfExists(tmp); | ||||
|             } catch (IOException e) { | ||||
|                 log.warn("Failed to delete temporary file {}", tmp, e); | ||||
|             } | ||||
|         } | ||||
|         return scriptFile; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isVersionHigher(String currentVersion, String compareVersion) { | ||||
|  | ||||
							
								
								
									
										3
									
								
								app/core/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								app/core/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -16,8 +16,7 @@ local.properties | ||||
| version.properties | ||||
| 
 | ||||
| #### Stirling-PDF Files ### | ||||
| pipeline/watchedFolders/ | ||||
| pipeline/finishedFolders/ | ||||
| pipeline/* | ||||
| customFiles/ | ||||
| configs/ | ||||
| watchedFolders/ | ||||
|  | ||||
| @ -35,6 +35,7 @@ public class InitialSetup { | ||||
|         initEnableCSRFSecurity(); | ||||
|         initLegalUrls(); | ||||
|         initSetAppVersion(); | ||||
|         GeneralUtils.extractPipeline(); | ||||
|     } | ||||
| 
 | ||||
|     public void initUUIDKey() throws IOException { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user