diff --git a/.github/workflows/push-docker-base.yml b/.github/workflows/push-docker-base.yml index 59561ed0df..699eb4708c 100644 --- a/.github/workflows/push-docker-base.yml +++ b/.github/workflows/push-docker-base.yml @@ -4,6 +4,7 @@ on: push: branches: - baseDockerImage + - accessIssueFix workflow_dispatch: inputs: version: @@ -34,6 +35,8 @@ jobs: run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then VERSION="${{ github.event.inputs.version }}" + elif [ "${{ github.ref_name }}" == "accessIssueFix" ]; then + VERSION="1.0.3" else VERSION="1.0.0" fi diff --git a/.github/workflows/rollback-latest.yml b/.github/workflows/rollback-latest.yml new file mode 100644 index 0000000000..21027cc762 --- /dev/null +++ b/.github/workflows/rollback-latest.yml @@ -0,0 +1,93 @@ +name: Rollback Latest Tags to Version + +on: + workflow_dispatch: + inputs: + version: + description: "Version to rollback to (e.g. 2.8.0)" + required: true + type: string + +permissions: + contents: read + +jobs: + rollback: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Install crane + uses: imjasonh/setup-crane@31b88afe9de28ae0ffa220711af4b60be9435f6e # v0.4 + + - name: Login to Docker Hub + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_API }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Convert repository owner to lowercase + id: repoowner + run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT + + - name: Rollback all latest tags to v${{ inputs.version }} + env: + VERSION: ${{ inputs.version }} + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} + DOCKER_HUB_ORG_USERNAME: ${{ secrets.DOCKER_HUB_ORG_USERNAME }} + REPO_OWNER: ${{ steps.repoowner.outputs.lowercase }} + run: | + set -euo pipefail + + IMAGES=( + "${DOCKER_HUB_USERNAME}/s-pdf" + "ghcr.io/${REPO_OWNER}/s-pdf" + "ghcr.io/${REPO_OWNER}/stirling-pdf" + "${DOCKER_HUB_ORG_USERNAME}/stirling-pdf" + ) + + VARIANTS=( + "${VERSION}:latest" + "${VERSION}-fat:latest-fat" + "${VERSION}-ultra-lite:latest-ultra-lite" + ) + + FAILED=0 + + for image in "${IMAGES[@]}"; do + for variant in "${VARIANTS[@]}"; do + SOURCE_TAG="${variant%%:*}" + TARGET_TAG="${variant##*:}" + + echo "::group::${image} — ${SOURCE_TAG} → ${TARGET_TAG}" + + if crane manifest "${image}:${SOURCE_TAG}" > /dev/null 2>&1; then + crane cp "${image}:${SOURCE_TAG}" "${image}:${TARGET_TAG}" + echo "✅ ${image}:${TARGET_TAG} now points to ${SOURCE_TAG}" + else + echo "::warning::⚠️ ${image}:${SOURCE_TAG} not found, skipping" + FAILED=1 + fi + + echo "::endgroup::" + done + done + + if [ "$FAILED" -ne 0 ]; then + echo "::warning::Some source tags were not found. This is expected if not all variants exist for this version." + fi + + echo "" + echo "🎉 Rollback to ${VERSION} complete!" diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 69c9d261b2..456c5a7c34 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -293,7 +293,12 @@ public class SecurityConfiguration { http.addFilterBefore( userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(rateLimitingFilter, UsernamePasswordAuthenticationFilter.class) + // TODO: IPRateLimitingFilter disabled — limit is 1M (no-op) and raw Filter + // impl causes Spring Security async dispatch bug (response already committed + // errors on StreamingResponseBody endpoints). Re-enable once converted to + // OncePerRequestFilter with proper config-driven limits. + // .addFilterBefore(rateLimitingFilter, + // UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class); http.sessionManagement( diff --git a/build.gradle b/build.gradle index 4a319a315f..5d50620e57 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.9.0' + version = '2.9.1' configurations.configureEach { exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index d44e616f59..0d8bb176e8 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -91,6 +91,15 @@ ENV VERSION_TAG=$VERSION_TAG \ _JVM_OPTS_BALANCED="-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/configs/heap_dumps -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=4m -XX:G1PeriodicGCInterval=60000 -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -XX:+ExplicitGCInvokesConcurrent -Dspring.threads.virtual.enabled=true -Djava.awt.headless=true" \ _JVM_OPTS_PERFORMANCE="-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/configs/heap_dumps -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -XX:+UseCompactObjectHeaders -XX:+UseStringDeduplication -XX:+AlwaysPreTouch -XX:+ExplicitGCInvokesConcurrent -Dspring.threads.virtual.enabled=true -Djava.awt.headless=true" \ JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf \ + DBUS_SESSION_BUS_ADDRESS=/dev/null \ SAL_TMP=/tmp/stirling-pdf/libre # Metadata labels diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 6ad392d341..43d4da75a9 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -91,8 +91,17 @@ ENV VERSION_TAG=$VERSION_TAG \ _JVM_OPTS_BALANCED="-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/configs/heap_dumps -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=4m -XX:G1PeriodicGCInterval=60000 -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -XX:+ExplicitGCInvokesConcurrent -Dspring.threads.virtual.enabled=true -Djava.awt.headless=true" \ _JVM_OPTS_PERFORMANCE="-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/configs/heap_dumps -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -XX:+UseCompactObjectHeaders -XX:+UseStringDeduplication -XX:+AlwaysPreTouch -XX:+ExplicitGCInvokesConcurrent -Dspring.threads.virtual.enabled=true -Djava.awt.headless=true" \ JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ FAT_DOCKER=true \ INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf \ + DBUS_SESSION_BUS_ADDRESS=/dev/null \ SAL_TMP=/tmp/stirling-pdf/libre # Metadata labels diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 84acfeba8a..aa6580bdb5 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling-PDF", - "version": "2.9.0", + "version": "2.9.1", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", diff --git a/frontend/src/core/testing/serverExperienceSimulations.ts b/frontend/src/core/testing/serverExperienceSimulations.ts index 06bd8fdc76..4d40d7b7e8 100644 --- a/frontend/src/core/testing/serverExperienceSimulations.ts +++ b/frontend/src/core/testing/serverExperienceSimulations.ts @@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.9.0', + appVersion: '2.9.1', serverCertificateEnabled: false, enableAlphaFunctionality: false, serverPort: 8080, diff --git a/frontend/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/src/proprietary/testing/serverExperienceSimulations.ts index d7087533f4..69d4205506 100644 --- a/frontend/src/proprietary/testing/serverExperienceSimulations.ts +++ b/frontend/src/proprietary/testing/serverExperienceSimulations.ts @@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.9.0', + appVersion: '2.9.1', serverCertificateEnabled: false, enableAlphaFunctionality: false, enableDesktopInstallSlide: true, diff --git a/scripts/init-without-ocr.sh b/scripts/init-without-ocr.sh index f48955f780..0a406e9a58 100755 --- a/scripts/init-without-ocr.sh +++ b/scripts/init-without-ocr.sh @@ -192,7 +192,11 @@ run_as_runtime_user() { if [ "$CURRENT_USER" = "$RUNTIME_USER" ]; then "$@" elif [ "$CURRENT_UID" -eq 0 ] && command_exists setpriv; then - setpriv --reuid="$RUNTIME_USER" --regid="$(id -gn "$RUNTIME_USER")" --init-groups -- "$@" + # Set HOME/USER/LOGNAME to match gosu behavior (setpriv does not touch env vars) + env HOME="$(getent passwd "$RUNTIME_USER" | cut -d: -f6)" \ + USER="$RUNTIME_USER" \ + LOGNAME="$RUNTIME_USER" \ + setpriv --reuid="$RUNTIME_USER" --regid="$(id -gn "$RUNTIME_USER")" --init-groups -- "$@" else warn_switch_user_once "$@" @@ -868,6 +872,21 @@ for p in "${CHOWN_PATHS[@]}"; do fi done +# Verify write access to critical directories; repair if chown failed on bind mounts +CRITICAL_DIRS=("/configs" "/logs" "/customFiles" "/pipeline") +for dir in "${CRITICAL_DIRS[@]}"; do + if [ -d "$dir" ]; then + # Test write access as the runtime user + if ! run_as_runtime_user test -w "$dir" 2>/dev/null; then + log "WARNING: ${RUNTIME_USER} cannot write to $dir — attempting to fix permissions" + # Try adding group-write and world-write as fallbacks + chmod -R o+rwX "$dir" 2>/dev/null \ + || chmod -R a+rwX "$dir" 2>/dev/null \ + || log "ERROR: Could not grant ${RUNTIME_USER} write access to $dir. Check your volume mount permissions (e.g. set PUID/PGID or fix host directory ownership)." + fi + fi +done + # ---------- Xvfb ---------- # Start a virtual framebuffer for GUI-based LibreOffice interactions. if command_exists Xvfb; then @@ -920,7 +939,11 @@ fi if [ "$CURRENT_USER" = "$RUNTIME_USER" ]; then "${JAVA_CMD[@]}" & elif [ "$CURRENT_UID" -eq 0 ] && command_exists setpriv; then - setpriv --reuid="$RUNTIME_USER" --regid="$(id -gn "$RUNTIME_USER")" --init-groups -- "${JAVA_CMD[@]}" & + # Set HOME/USER/LOGNAME to match gosu behavior (setpriv does not touch env vars) + env HOME="$(getent passwd "$RUNTIME_USER" | cut -d: -f6)" \ + USER="$RUNTIME_USER" \ + LOGNAME="$RUNTIME_USER" \ + setpriv --reuid="$RUNTIME_USER" --regid="$(id -gn "$RUNTIME_USER")" --init-groups -- "${JAVA_CMD[@]}" & else warn_switch_user_once "${JAVA_CMD[@]}" & diff --git a/testing/test.sh b/testing/test.sh index 4dce6c0157..e168c660a1 100644 --- a/testing/test.sh +++ b/testing/test.sh @@ -340,6 +340,8 @@ capture_file_list() { -not -path '*/tmp/hsperfdata_stirlingpdfuser/*' \ -not -path '*/tmp/hsperfdata_root/*' \ -not -path '*/tmp/stirling-pdf/jetty-*/*' \ + -not -path '*/tmp/stirling-pdf/lu*' \ + -not -path '*/tmp/stirling-pdf/tmp*' \ -not -path '/tmp/lu*' \ -not -path '*/tmp/*/user/registrymodifications.xcu' \ -not -path '/app/stirling.aot' \ @@ -369,8 +371,10 @@ capture_file_list() { -not -path '*/tmp/hsperfdata_root/*' \ -not -path '*/tmp/stirling-pdf/hsperfdata_stirlingpdfuser/*' \ -not -path '*/tmp/stirling-pdf/jetty-*/*' \ - -not -path '/tmp/lu*' \ - -not -path '/tmp/tmp*' \ + -not -path '*/tmp/stirling-pdf/lu*' \ + -not -path '*/tmp/stirling-pdf/tmp*' \ + -not -path '*/tmp/lu*' \ + -not -path '*/tmp/tmp*' \ -not -path '/app/stirling.aot' \ -not -path '*/tmp/stirling.aotconf' \ -not -path '*/tmp/aot-*.log' \ @@ -898,6 +902,36 @@ main() { passed_tests+=("Stirling-PDF-Regression $CONTAINER_NAME") else echo "WARNING: Unexpected temporary files detected after behave tests!" + + # Save temp file failure details to a log for the test report + local tempfile_log="$REPORT_DIR/temp-files-failure.log" + { + echo "=== Temp File Regression Failure ===" + echo "Container: $CONTAINER_NAME" + echo "" + echo "=== Before snapshot ===" + cat "$BEFORE_FILE" 2>/dev/null || echo "(empty)" + echo "" + echo "=== After snapshot ===" + cat "$AFTER_FILE" 2>/dev/null || echo "(empty)" + echo "" + echo "=== Diff (new/changed files) ===" + cat "$DIFF_FILE" 2>/dev/null || echo "(empty)" + echo "" + echo "=== Leftover temp files ===" + cat "${DIFF_FILE}.tmp" 2>/dev/null || echo "(none found)" + echo "" + echo "=== Docker logs ===" + docker logs "$CONTAINER_NAME" 2>&1 | tail -200 + } > "$tempfile_log" 2>/dev/null || true + + # Copy snapshots to report dir for artifact upload + cp "$BEFORE_FILE" "$REPORT_DIR/" 2>/dev/null || true + cp "$AFTER_FILE" "$REPORT_DIR/" 2>/dev/null || true + cp "$DIFF_FILE" "$REPORT_DIR/" 2>/dev/null || true + cp "${DIFF_FILE}.tmp" "$REPORT_DIR/files_diff_tmp_matches.txt" 2>/dev/null || true + + test_failure_logs["Stirling-PDF-Regression-Temp-Files"]="$tempfile_log" failed_tests+=("Stirling-PDF-Regression-Temp-Files") fi passed_tests+=("Stirling-PDF-Regression $CONTAINER_NAME")