From 9ddbf4c9b4a647a895820445255c39963b3ca1a0 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 29 Mar 2026 23:21:13 +0100 Subject: [PATCH] feat(distro): add multi-OS release workflow, WinGet and AppImage manifests Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/multiOSReleases.yml | 2 + .github/workflows/winget-publish.yml | 24 +++ docs/distribution/APPIMAGE.md | 87 ++++++++ docs/distribution/WINGET.md | 186 ++++++++++++++++++ frontend/src-tauri/tauri.conf.json | 1 + .../StirlingTools.StirlingPDF.installer.yaml | 22 +++ ...tirlingTools.StirlingPDF.locale.en-US.yaml | 36 ++++ .../winget/StirlingTools.StirlingPDF.yaml | 7 + 8 files changed, 365 insertions(+) create mode 100644 .github/workflows/winget-publish.yml create mode 100644 docs/distribution/APPIMAGE.md create mode 100644 docs/distribution/WINGET.md create mode 100644 manifests/winget/StirlingTools.StirlingPDF.installer.yaml create mode 100644 manifests/winget/StirlingTools.StirlingPDF.locale.en-US.yaml create mode 100644 manifests/winget/StirlingTools.StirlingPDF.yaml diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index b1ee6c8c88..c65cd118b6 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -541,6 +541,7 @@ jobs: find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; else find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; + find . -name "*.rpm" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.rpm" \; find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; fi @@ -600,6 +601,7 @@ jobs: ./artifacts/**/*.msi ./artifacts/**/*.dmg ./artifacts/**/*.deb + ./artifacts/**/*.rpm ./artifacts/**/*.AppImage draft: false prerelease: false diff --git a/.github/workflows/winget-publish.yml b/.github/workflows/winget-publish.yml new file mode 100644 index 0000000000..02d27c1eaa --- /dev/null +++ b/.github/workflows/winget-publish.yml @@ -0,0 +1,24 @@ +name: Publish to winget + +on: + release: + types: [released] + +permissions: + contents: read + +jobs: + publish: + runs-on: windows-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Submit to winget + uses: vedantmgoyal9/winget-releaser@v2 + with: + identifier: StirlingTools.StirlingPDF + installers-regex: 'Stirling-PDF-windows-x86_64\.msi$' + token: ${{ secrets.WINGET_TOKEN }} diff --git a/docs/distribution/APPIMAGE.md b/docs/distribution/APPIMAGE.md new file mode 100644 index 0000000000..47c83d9cd6 --- /dev/null +++ b/docs/distribution/APPIMAGE.md @@ -0,0 +1,87 @@ +# AppImage — local testing + +The AppImage build is produced automatically by Tauri when `"appimage"` is listed in the +`targets` array of `frontend/src-tauri/tauri.conf.json`. It is uploaded to GitHub Releases +alongside the `.deb` artifact. + +--- + +## Prerequisites + +- A Linux machine or WSL2 instance (Ubuntu 22.04+ recommended). +- `libfuse2` — required by all AppImages built with the current AppImage runtime: + + ```bash + sudo apt-get install libfuse2 # Debian / Ubuntu + sudo dnf install fuse-libs # Fedora / RHEL + ``` + + On Ubuntu 22.04+ `libfuse2` is no longer installed by default; this is the single most + common reason an AppImage fails to launch. + +--- + +## Running the AppImage + +```bash +# 1. Download (or build) the AppImage +# From a GitHub Release: +curl -L -O https://github.com/Stirling-Tools/Stirling-PDF/releases/download/vX.Y.Z/Stirling-PDF_X.Y.Z_amd64.AppImage + +# 2. Make it executable (only needed once) +chmod +x Stirling-PDF_*.AppImage + +# 3. Launch +./Stirling-PDF_*.AppImage +``` + +The app should open a browser window (or system tray icon) pointing at `http://localhost:8080`. + +--- + +## Smoke-test checklist + +| Check | Expected result | +|-------|-----------------| +| Launch without arguments | App starts, UI loads at `http://localhost:8080` | +| Upload a PDF and convert | Operation completes successfully | +| Close the window / tray | Process exits cleanly (no zombie) | +| Re-launch immediately | Port is free, app starts again without errors | + +--- + +## Local build (without a full release) + +If you want to test an AppImage from a local Tauri build rather than a published release: + +```bash +cd frontend +npm run tauri build +# Output is at: +# src-tauri/target/release/bundle/appimage/Stirling-PDF_*.AppImage +chmod +x src-tauri/target/release/bundle/appimage/Stirling-PDF_*.AppImage +./src-tauri/target/release/bundle/appimage/Stirling-PDF_*.AppImage +``` + +--- + +## Common gotchas + +| Symptom | Likely cause | Fix | +|---------|-------------|-----| +| `fuse: device not found` or `FUSE library not found` | `libfuse2` missing | `sudo apt-get install libfuse2` | +| AppImage exits immediately with no output | Missing shared library | Run with `./Stirling-PDF_*.AppImage --appimage-extract-and-run` to bypass FUSE and surface the real error | +| Port 8080 already in use | Another process bound to the port | `lsof -i :8080` to identify and kill it, then re-launch | +| Blank window / white screen | Webview renderer issue | Ensure `libwebkit2gtk-4.0` or `libwebkit2gtk-4.1` is installed | +| Works in terminal but not double-click from file manager | Executable bit lost on download | Re-run `chmod +x` on the file | + +--- + +## Extracting the AppImage for inspection + +To inspect the contents without running the app (useful for debugging or verifying bundled files): + +```bash +./Stirling-PDF_*.AppImage --appimage-extract +# Creates a ./squashfs-root/ directory with the full app tree +``` diff --git a/docs/distribution/WINGET.md b/docs/distribution/WINGET.md new file mode 100644 index 0000000000..def77916f1 --- /dev/null +++ b/docs/distribution/WINGET.md @@ -0,0 +1,186 @@ +# Publishing Stirling-PDF to winget + +This document explains how to get Stirling-PDF listed in the Windows Package Manager (winget) +and how to keep releases automatically in sync. + +--- + +## One-time setup + +### 1. Submit the initial PR to microsoft/winget-pkgs + +The very first submission must be done manually so the winget team can review the package. + +1. Fork [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs). +2. Copy the three manifest files from `manifests/winget/` into the correct path inside the fork: + + ``` + manifests/s/StirlingTools/StirlingPDF/2.7.3/ + ├── StirlingTools.StirlingPDF.yaml + ├── StirlingTools.StirlingPDF.locale.en-US.yaml + └── StirlingTools.StirlingPDF.installer.yaml + ``` + +3. Before opening the PR, update `InstallerSha256` in the installer manifest with the real + SHA-256 of the published `.msi` file: + + ```powershell + Get-FileHash .\Stirling-PDF-windows-x86_64.msi -Algorithm SHA256 + ``` + + Or on Linux/macOS: + + ```bash + sha256sum Stirling-PDF-windows-x86_64.msi + ``` + +4. Open a pull request against `microsoft/winget-pkgs`. The title should follow the required + format: `New package: StirlingTools.StirlingPDF version 2.7.3`. + +5. The winget validation bot will run automated checks. Fix any reported issues, then wait for + human review. Initial reviews usually take a few days to a couple of weeks. + +6. Once the PR is merged, `winget install StirlingTools.StirlingPDF` will work for all Windows + users. + +--- + +### 2. Create the WINGET_TOKEN secret + +After the initial package is accepted, subsequent releases are submitted automatically by the +`winget-publish.yml` workflow using `vedantmgoyal9/winget-releaser`. + +The workflow needs a GitHub Personal Access Token (PAT) with permission to open pull requests +against `microsoft/winget-pkgs`. + +**Steps:** + +1. Go to and generate a **Classic** PAT with the + `public_repo` scope (or a fine-grained token scoped to `microsoft/winget-pkgs` with + *Contents: read & write* and *Pull requests: read & write*). + +2. In the Stirling-PDF repository, go to + **Settings → Secrets and variables → Actions → New repository secret**. + +3. Name the secret `WINGET_TOKEN` and paste the PAT as the value. + +4. Save. + +From this point on, every time a GitHub Release is published the workflow will: +- Download the release's `.msi` asset. +- Compute its SHA-256. +- Open a PR to `microsoft/winget-pkgs` with an updated manifest. + +--- + +## How the automation works + +The workflow file is at `.github/workflows/winget-publish.yml`. It triggers on the +`release: published` event and calls the +[vedantmgoyal9/winget-releaser](https://github.com/vedantmgoyal9/winget-releaser) action, which: + +1. Finds the `.msi` asset in the release that matches `Stirling-PDF-windows-x86_64.msi`. +2. Builds a new set of manifests with the correct version and SHA-256. +3. Opens a PR against `microsoft/winget-pkgs` using the `WINGET_TOKEN`. + +No manual steps are needed after the initial submission. + +--- + +## License notes + +Stirling-PDF uses an **Open Core** model. The canonical license URL for all manifests is: + +``` +https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LICENSE +``` + +Never use `stirlingpdf.com/terms` as the license URL in any manifest. + +For the license identifier, use whichever option the platform supports best: + +| Platform supports | Use | +|-------------------|-----| +| Free-text / custom field | `Open Core` | +| SPDX identifiers only | `LicenseRef-OpenCore` | +| Known identifiers only (e.g. winget fallback) | `Proprietary` for desktop, `MIT` for server JAR | + +The desktop app (`frontend/src/desktop/`) is proprietary. The server JAR (`app/core/`) is +MIT-licensed. Winget's `License` field is free-text, so the manifest uses `Open Core`. + +--- + +## Local testing + +Validate manifests locally before opening a PR to `microsoft/winget-pkgs` to catch errors +that would otherwise stall the review. + +### Prerequisites + +```powershell +# Install the winget client (already present on Windows 11; on Windows 10 install from the Store) +# Install wingetcreate +winget install Microsoft.WingetCreate +``` + +### 1. Validate manifest structure + +`winget validate` checks syntax, required fields, and schema conformance: + +```powershell +# From the repo root — point at the folder containing the three YAML files +winget validate manifests\winget\ +``` + +A clean run prints `Manifest validation succeeded.` with no errors. + +**Common gotchas:** + +- `PackageVersion` must be a pure semver string (`2.7.3`, not `v2.7.3`). +- `InstallerUrl` must be HTTPS and publicly reachable. If you are testing against a pre-release + or a local build, temporarily host the `.msi` somewhere public (e.g. a GitHub release draft). +- `InstallerSha256` must match the file at `InstallerUrl` exactly. Recompute it whenever the + `.msi` changes. +- Trailing whitespace in YAML causes schema errors that can be hard to spot — run your editor's + "trim trailing whitespace" pass before validating. + +### 2. Dry-run a PR submission + +`wingetcreate submit --test` generates the updated manifests and opens a **test** PR against a +sandbox fork instead of the real `microsoft/winget-pkgs`: + +```powershell +wingetcreate submit ` + --token "$env:WINGET_TOKEN" ` + --test ` + StirlingTools.StirlingPDF +``` + +This lets you see exactly what the PR will look like, including the diff, without touching the +real repository. + +**Note:** `--test` requires the `WINGET_TOKEN` PAT (same one used by the CI workflow). If the +token is not set as an environment variable, pass it directly with `--token `. + +### 3. Install from a local manifest (end-to-end smoke test) + +Once `winget validate` passes, install the package directly from the local manifest files to +confirm the installer actually runs: + +```powershell +# Requires running as Administrator or in a dev environment with Developer Mode enabled +winget install --manifest manifests\winget\ +``` + +This downloads the `.msi` from `InstallerUrl`, verifies the hash, and runs the installer — the +same path a real user would follow. + +--- + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|-------------|-----| +| Workflow fails with `403` | `WINGET_TOKEN` expired or missing | Regenerate and update the secret | +| winget-pkgs PR blocked by validation | Manifest field formatting | Check the PR comments from the `winget-bot` and update the manifests in `manifests/winget/` | +| `InstallerSha256` mismatch | Stale placeholder in manifests | The automation recalculates the hash; the placeholder in-repo only matters for the initial manual PR | diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 44ef827c3f..d83bccd34c 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -27,6 +27,7 @@ "targets": [ "deb", "rpm", + "appimage", "dmg", "msi" ], diff --git a/manifests/winget/StirlingTools.StirlingPDF.installer.yaml b/manifests/winget/StirlingTools.StirlingPDF.installer.yaml new file mode 100644 index 0000000000..8a2538381f --- /dev/null +++ b/manifests/winget/StirlingTools.StirlingPDF.installer.yaml @@ -0,0 +1,22 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json + +PackageIdentifier: StirlingTools.StirlingPDF +PackageVersion: 2.8.0 +InstallerLocale: en-US +InstallerType: msi +Scope: machine +InstallerSwitches: + Silent: /quiet /norestart ALLUSERS=1 + SilentWithProgress: /passive /norestart ALLUSERS=1 +UpgradeBehavior: install +Protocols: + - stirlingpdf +FileExtensions: + - pdf +ReleaseDate: PLACEHOLDER_RELEASE_DATE +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v2.8.0/Stirling-PDF-windows-x86_64.msi + InstallerSha256: PLACEHOLDER_UPDATE_ON_RELEASE +ManifestType: installer +ManifestVersion: 1.10.0 diff --git a/manifests/winget/StirlingTools.StirlingPDF.locale.en-US.yaml b/manifests/winget/StirlingTools.StirlingPDF.locale.en-US.yaml new file mode 100644 index 0000000000..6c37aa884b --- /dev/null +++ b/manifests/winget/StirlingTools.StirlingPDF.locale.en-US.yaml @@ -0,0 +1,36 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json + +PackageIdentifier: StirlingTools.StirlingPDF +PackageVersion: 2.8.0 +PackageLocale: en-US +Publisher: Stirling PDF Inc +PublisherUrl: https://stirlingpdf.com +PublisherSupportUrl: https://github.com/Stirling-Tools/Stirling-PDF/issues +PublisherEmail: contact@stirlingpdf.com +PrivacyUrl: https://stirlingpdf.com/privacy-policy +Author: Anthony Stirling +PackageName: Stirling-PDF +PackageUrl: https://github.com/Stirling-Tools/Stirling-PDF +License: Open Core +LicenseUrl: https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LICENSE +Copyright: Copyright (c) 2025 Stirling PDF Inc +CopyrightUrl: https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LICENSE +ShortDescription: Locally hosted, feature-rich PDF manipulation tool +Description: |- + Stirling-PDF is a robust, locally hosted web-based PDF manipulation tool. + It enables you to carry out various operations on PDF files, including + splitting, merging, converting, reorganizing, adding images, rotating, + compressing, and more. The desktop app bundles a full Java backend and + runs entirely on your machine — no data ever leaves your device. +Moniker: stirling-pdf +Tags: + - pdf + - converter + - editor + - merge + - split + - compress + - ocr +ReleaseNotesUrl: https://github.com/Stirling-Tools/Stirling-PDF/releases/tag/v2.8.0 +ManifestType: defaultLocale +ManifestVersion: 1.10.0 diff --git a/manifests/winget/StirlingTools.StirlingPDF.yaml b/manifests/winget/StirlingTools.StirlingPDF.yaml new file mode 100644 index 0000000000..a22ba022e8 --- /dev/null +++ b/manifests/winget/StirlingTools.StirlingPDF.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json + +PackageIdentifier: StirlingTools.StirlingPDF +PackageVersion: 2.8.0 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.10.0