diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 62d9c08a6..fe19e2615 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -499,13 +499,15 @@ jobs:
uses: ./.github/actions/setup
# bsdtar (libarchive-tools) is fpm's mtree generator for the pacman target.
+ # rpmbuild (rpm) is what fpm shells out to for the rpm target; without it the
+ # build fails at packaging, not at config parse.
# patchelf is what build-linux-compositor-addon.mjs renames the ffmpeg symbols
# with, so the addon cannot bind to Chromium's bundled ffmpeg — an unconditional
# dependency that resolvePatchelf() throws on. It happens to be preinstalled on
# the ubuntu-24.04 image, which is why this has worked; declaring it means the
# build stops depending on the runner image's contents.
- name: Install Linux packaging and native build dependencies
- run: sudo apt-get update && sudo apt-get install -y libarchive-tools patchelf
+ run: sudo apt-get update && sudo apt-get install -y libarchive-tools rpm patchelf
- name: Stage whisper-stt binaries
shell: bash
@@ -516,6 +518,30 @@ jobs:
- name: Build Linux app
run: npm run build:linux -- --publish never
+ # The guard for the trap documented on the upload step below: `if-no-files-found`
+ # evaluates the union of the globs, so a format that stops being produced is
+ # invisible there. It is a real failure mode and not a hypothetical one — the rpm
+ # target was added to electron-builder.json5's `linux.target` alone, where the CLI
+ # list in `build:linux` overrides it, and the upload glob for it would have matched
+ # nothing on every release with the job still green. One assertion per format.
+ - name: Verify every Linux format was produced
+ run: |
+ if [[ ! -d release ]]; then
+ echo "::error::electron-builder produced no release/ directory"
+ exit 1
+ fi
+ MISSING=()
+ for ext in AppImage deb pacman rpm; do
+ COUNT="$(find release -type f -name "*.${ext}" -printf . | wc -c)"
+ echo "${ext}: ${COUNT}"
+ if [[ "$COUNT" -eq 0 ]]; then MISSING+=("$ext"); fi
+ done
+ if [[ "${#MISSING[@]}" -gt 0 ]]; then
+ echo "::error::No artifact produced for: ${MISSING[*]} — check the target list in package.json's build:linux, which overrides linux.target in electron-builder.json5"
+ find release -maxdepth 2 -type f -print
+ exit 1
+ fi
+
- name: Upload Linux packages
uses: actions/upload-artifact@v4
with:
@@ -524,6 +550,7 @@ jobs:
release/**/*.AppImage
release/**/*.deb
release/**/*.pacman
+ release/**/*.rpm
# No *.zsync: nothing produces one. zsync is electron-updater's delta format,
# this repo has no updater (no electron-updater, no autoUpdater, no
# latest-linux.yml), and app-builder-lib 26.x dropped zsync entirely in favour
diff --git a/README.md b/README.md
index 372662287..20c8dc4ea 100644
--- a/README.md
+++ b/README.md
@@ -83,16 +83,21 @@ Download the `.exe` installer directly from the [Releases page](https://github.c
### Linux
-Three packages are published to the [Releases page](https://github.com/getopenscreen/openscreen/releases) for each version. Pick the one that matches your distro:
+Four packages are published to the [Releases page](https://github.com/getopenscreen/openscreen/releases) for each version. Pick the one that matches your distro:
**Debian / Ubuntu / Pop!_OS (`.deb`)**
```bash
-sudo apt install ./Openscreen-Linux-latest.deb
+sudo apt install ./Openscreen-Linux-*.deb
+```
+
+**Fedora / RHEL / CentOS (`.rpm`)**
+```bash
+sudo dnf install ./Openscreen-Linux-*.rpm
```
**Arch / Manjaro (`.pacman`)**
```bash
-sudo pacman -U Openscreen-Linux-latest.pacman
+sudo pacman -U Openscreen-Linux-*.pacman
```
**Any distro (`.AppImage`)**
diff --git a/electron-builder.json5 b/electron-builder.json5
index 9879089be..c3e3b9d40 100644
--- a/electron-builder.json5
+++ b/electron-builder.json5
@@ -102,10 +102,16 @@
}
},
"linux": {
+ // Cette liste ne suffit PAS à produire un artefact en CI : `build:linux` passe
+ // ses cibles en ligne de commande (`electron-builder --linux AppImage deb pacman
+ // rpm`), et la CLI REMPLACE `linux.target` au lieu de le compléter. Toute cible
+ // ajoutée ici doit l'être aussi dans le script, sinon elle n'existe que pour un
+ // `electron-builder --linux` nu, que rien n'exécute.
"target": [
"AppImage",
"deb",
- "pacman"
+ "pacman",
+ "rpm"
],
"icon": "icons/icons/png",
"artifactName": "${productName}-Linux-${version}.${ext}",
@@ -176,6 +182,32 @@
"vulkan-swrast"
]
},
+ "rpm": {
+ // Même règle que ci-dessus : la liste REMPLACE le défaut d'electron-builder,
+ // donc les huit premières entrées SONT ce défaut (FpmTarget.getDefaultDepends,
+ // cas "rpm"), verbatim, suivies des deux qui nous sont propres.
+ //
+ // `mesa-vulkan-drivers` porte le même nom sur Fedora que sur Debian : c'est le
+ // paquet qui garantit lavapipe, sans quoi l'aperçu du compositeur est indisponible.
+ //
+ // `libsecret` n'est PAS dans le défaut rpm alors qu'il l'est dans le défaut deb
+ // (`libsecret-1-0`). `safeStorage` chiffre les clés d'API des fournisseurs LLM
+ // (electron/ai-edition/llm-config-store.ts) et passe par le Secret Service :
+ // sans lui, `isEncryptionAvailable()` répond faux et l'enregistrement d'une clé
+ // lève. Une omission d'electron-builder, pas un choix.
+ "depends": [
+ "gtk3",
+ "libnotify",
+ "nss",
+ "libXScrnSaver",
+ "(libXtst or libXtst6)",
+ "xdg-utils",
+ "at-spi2-core",
+ "(libuuid or libuuid1)",
+ "libsecret",
+ "mesa-vulkan-drivers"
+ ]
+ },
"win": {
"target": [
"nsis"
diff --git a/package.json b/package.json
index 8b2065643..287e8bb79 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"build:native:linux": "node scripts/build-linux-pipewire-helper.mjs",
"build:win": "npm run build:native:win && npm run fetch:ffmpeg && npm run build:native:compositor && tsc && vite build && electron-builder --win --config.npmRebuild=false",
"build:win:store": "npm run build:native:win && npm run fetch:ffmpeg && npm run build:native:compositor && tsc && vite build && electron-builder --win appx --config.npmRebuild=false",
- "build:linux": "npm run fetch:ffmpeg:sdk && npm run build:native:linux && npm run build:native:compositor:linux && tsc && vite build && electron-builder --linux AppImage deb pacman --config.npmRebuild=false",
+ "build:linux": "npm run fetch:ffmpeg:sdk && npm run build:native:linux && npm run build:native:compositor:linux && tsc && vite build && electron-builder --linux AppImage deb pacman rpm --config.npmRebuild=false",
"build:whisper-binaries": "bash scripts/build-whisper-stt.sh",
"test:whisper-stt": "node scripts/test-whisper-stt.mjs",
"test": "vitest --run",
diff --git a/technical-documentation/engineering/build-and-packaging.md b/technical-documentation/engineering/build-and-packaging.md
index 0aecb5996..9795be997 100644
--- a/technical-documentation/engineering/build-and-packaging.md
+++ b/technical-documentation/engineering/build-and-packaging.md
@@ -12,7 +12,7 @@ OpenScreen builds its renderer, Electron main process, preload bridge, native he
| `npm run build:mac` | Builds the ScreenCaptureKit and cursor helpers, checks TypeScript, runs Vite, and packages the macOS target. |
| `npm run build:win` | Builds WGC/cursor helpers and the D3D11 compositor addon, fetches FFmpeg, checks TypeScript, runs Vite, and packages the Windows NSIS target without npm rebuild. |
| `npm run build:win:store` | Performs the Windows native and renderer build, then asks electron-builder for the configured AppX Store package. |
-| `npm run build:linux` | Checks TypeScript, runs Vite, then packages AppImage, Debian, and pacman artifacts without npm rebuild. |
+| `npm run build:linux` | Checks TypeScript, runs Vite, then packages AppImage, Debian, pacman, and RPM artifacts without npm rebuild. Its explicit `--linux` target list overrides `linux.target` in `electron-builder.json5`, so a target added to the config alone is never built. |
| `npm run build:native:mac` | Uses SwiftPM to build requested single-architecture ScreenCaptureKit and macOS cursor helpers and stages them under `electron/native/bin/darwin-*`. |
| `npm run build:native:win` | Uses CMake/Ninja in an MSVC environment to build WGC capture and cursor-sampler executables and stage x64 binaries. |
| `npm run build:native:compositor` | Uses Cargo/MSVC and the pinned shared FFmpeg SDK to build `compositor_view.node`. |
@@ -124,7 +124,7 @@ Electron-builder targets DMG for both `arm64` and `x64`, enables hardened runtim
### Linux and Nix
-Electron-builder produces AppImage, `.deb`, and `.pacman` targets. The flake separately supports `x86_64-linux` and `aarch64-linux`, offers NixOS and Home Manager modules, and builds a wrapper around nixpkgs' system Electron. `nix/package.nix` runs Vite directly, installs `dist/`, `dist-electron/`, production npm dependencies, wallpapers, icons, and a desktop entry; it does not invoke electron-builder. The release workflow later opens a PR to update the Nix package version and npm dependency hash after stable releases.
+Electron-builder produces AppImage, `.deb`, `.pacman`, and `.rpm` targets. Each fpm target carries its own `depends` list, which *replaces* electron-builder's default rather than extending it; all three package formats therefore repeat that default verbatim before adding the Vulkan ICD (`mesa-vulkan-drivers`, `vulkan-swrast` on Arch) the native compositor needs. The RPM list also restores `libsecret`, which electron-builder includes in its `deb` default but omits from its `rpm` one, and which `safeStorage` needs to encrypt LLM credentials. The flake separately supports `x86_64-linux` and `aarch64-linux`, offers NixOS and Home Manager modules, and builds a wrapper around nixpkgs' system Electron. `nix/package.nix` runs Vite directly, installs `dist/`, `dist-electron/`, production npm dependencies, wallpapers, icons, and a desktop entry; it does not invoke electron-builder. The release workflow later opens a PR to update the Nix package version and npm dependency hash after stable releases.
## Node and toolchain versions
diff --git a/technical-documentation/engineering/ci-workflows.md b/technical-documentation/engineering/ci-workflows.md
index 7807c473b..011529447 100644
--- a/technical-documentation/engineering/ci-workflows.md
+++ b/technical-documentation/engineering/ci-workflows.md
@@ -21,7 +21,7 @@ flowchart TD
ReleaseBuild["build.yml
v* tag or dispatch"] --> Win[Windows NSIS]
ReleaseBuild --> Store[Windows AppX]
ReleaseBuild --> Mac["macOS arm64 and x64 DMGs"]
- ReleaseBuild --> Linux["AppImage, deb, pacman"]
+ ReleaseBuild --> Linux["AppImage, deb, pacman, rpm"]
Win --> Publish[GitHub release]
Mac --> Publish
Linux --> Publish
@@ -101,7 +101,7 @@ A `v*` tag or manual dispatch starts platform builds. Dispatch accepts `arch` (`
- `build-windows` runs `npm run build:win` and uploads `openscreen-windows` for 30 days.
- `build-windows-store` runs `npm run build:win:store` and uploads `openscreen-windows-store` for 30 days.
- `build-macos` is an `arm64`/`x64` matrix. It builds Vite/Electron and native helpers, packages and optionally signs the app, creates DMGs, notarizes every signed build including pre-releases, and uploads one artifact per architecture for 30 days.
-- `build-linux` produces AppImage, deb, and pacman files and uploads `openscreen-linux` for 30 days. No zsync: that is electron-updater's delta format, this repo ships no updater, and app-builder-lib 26.x embeds a block map in the AppImage instead.
+- `build-linux` produces AppImage, deb, pacman, and rpm files and uploads `openscreen-linux` for 30 days. It asserts one artifact per format before uploading, because `if-no-files-found: error` evaluates the union of the upload globs and so cannot catch a single format that stopped being produced. No zsync: that is electron-updater's delta format, this repo ships no updater, and app-builder-lib 26.x embeds a block map in the AppImage instead.
- `publish-release` waits for Windows NSIS, macOS, and Linux jobs; the Store job is not a dependency. It checks the tag against `package.json`, downloads the NSIS/macOS/Linux artifacts, and creates or updates a GitHub release with `OPENSCREEN_RELEASE_TOKEN`.
The build comments and package behavior refer to the local Whisper architecture documented in [transcription and captions](../architecture/transcription-and-captions.md). The STT model downloads to user data at runtime and is not a release-build asset.
diff --git a/website/docs/installation.md b/website/docs/installation.md
index d00941abb..141498332 100644
--- a/website/docs/installation.md
+++ b/website/docs/installation.md
@@ -2,13 +2,14 @@
id: installation
title: Installation
sidebar_position: 2
-description: "Install OpenScreen on macOS, Windows, or Linux — .dmg, .exe, .deb, .pacman, AppImage, and a Nix flake, including the macOS Gatekeeper step."
+description: "Install OpenScreen on macOS, Windows, or Linux — .dmg, .exe, .deb, .rpm, .pacman, AppImage, and a Nix flake, including the macOS Gatekeeper step."
keywords:
- install screen recorder
- download OpenScreen
- macOS dmg
- Windows installer
- Linux deb
+ - Fedora rpm
- AppImage
- Nix flake
---
@@ -41,13 +42,18 @@ Download and run the `.exe` installer from [Releases](https://github.com/getopen
## Linux
-Three packages are published per release — pick the one matching your distro.
+Four packages are published per release — pick the one matching your distro.
**Debian / Ubuntu / Pop!_OS**
```bash
sudo apt install ./Openscreen-Linux-*.deb
```
+**Fedora / RHEL / CentOS**
+```bash
+sudo dnf install ./Openscreen-Linux-*.rpm
+```
+
**Arch / Manjaro**
```bash
sudo pacman -U Openscreen-Linux-*.pacman
diff --git a/website/src/lib/release.ts b/website/src/lib/release.ts
index df0242877..bb48b148e 100644
--- a/website/src/lib/release.ts
+++ b/website/src/lib/release.ts
@@ -30,6 +30,7 @@ export const ASSET_PATTERNS = {
macIntel: /Mac.*x64.*\.dmg$/i,
windows: /\.exe$/i,
deb: /\.deb$/i,
+ rpm: /\.rpm$/i,
pacman: /\.pacman$/i,
appImage: /\.AppImage$/i,
} as const;
diff --git a/website/src/pages/download.tsx b/website/src/pages/download.tsx
index 3d19ac594..fec64fe17 100644
--- a/website/src/pages/download.tsx
+++ b/website/src/pages/download.tsx
@@ -25,7 +25,7 @@ const PAGE_URL = "https://getopenscreen.com/download/";
// structured-data description that contradicts the meta one is worse than none.
const PAGE_TITLE = "Download for Windows, macOS & Linux";
const PAGE_DESCRIPTION =
- "Download OpenScreen free for Windows, macOS, and Linux — .dmg, .exe, .deb, .pacman, AppImage, and a Nix flake. Open source, no account, no watermark.";
+ "Download OpenScreen free for Windows, macOS, and Linux — .dmg, .exe, .deb, .rpm, .pacman, AppImage, and a Nix flake. Open source, no account, no watermark.";
type PlatformSpec = {
id: string;
@@ -60,6 +60,7 @@ const PLATFORMS: PlatformSpec[] = [
icon: TerminalSquare,
options: [
{ kind: "deb", label: "Debian, Ubuntu, Pop!_OS", sublabel: "Package · .deb" },
+ { kind: "rpm", label: "Fedora, RHEL, CentOS", sublabel: "Package · .rpm" },
{ kind: "pacman", label: "Arch, Manjaro", sublabel: "Package · .pacman" },
{ kind: "appImage", label: "Any distribution", sublabel: "Portable · .AppImage" },
],
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index 93fbd1c8f..e7109c5d9 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -331,8 +331,9 @@ export default function Home() {
- The macOS line is only needed if Gatekeeper blocks the app. Linux also ships
- .pacman, an AppImage, and a Nix flake — every artifact is on the{" "}
+ The macOS line is only needed if Gatekeeper blocks the app. Linux also ships{" "}
+ .rpm, .pacman, an AppImage, and a Nix flake — every artifact
+ is on the{" "}
Releases page, and{" "}
Installation has the full steps.